blob: 58e9e54fe7efc19be4f9ab98e1e2bab94ebb2909 [file] [log] [blame]
// Copyright (c) 2012 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/profiles/profile.h"
#include <stddef.h>
#include <memory>
#include "base/bind.h"
#include "base/check.h"
#include "base/command_line.h"
#include "base/containers/contains.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/json/json_reader.h"
#include "base/macros.h"
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/sequenced_task_runner.h"
#include "base/synchronization/waitable_event.h"
#include "base/task/post_task.h"
#include "base/task/thread_pool/thread_pool_instance.h"
#include "base/test/bind.h"
#include "base/test/metrics/histogram_tester.h"
#include "base/test/scoped_feature_list.h"
#include "base/threading/thread_restrictions.h"
#include "base/values.h"
#include "base/version.h"
#include "build/build_config.h"
#include "build/chromeos_buildflags.h"
#include "chrome/browser/browser_process.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/profiles/chrome_version_service.h"
#include "chrome/browser/profiles/profile_destroyer.h"
#include "chrome/browser/profiles/profile_impl.h"
#include "chrome/browser/profiles/profile_manager.h"
#include "chrome/browser/profiles/profile_observer.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_list.h"
#include "chrome/browser/ui/tabs/tab_strip_model.h"
#include "chrome/common/chrome_constants.h"
#include "chrome/common/chrome_features.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/pref_names.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h"
#include "components/prefs/pref_service.h"
#include "components/version_info/version_info.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h"
#include "content/public/browser/notification_observer.h"
#include "content/public/browser/storage_partition.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/test_utils.h"
#include "extensions/buildflags/buildflags.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "net/base/load_flags.h"
#include "net/base/net_errors.h"
#include "net/dns/mock_host_resolver.h"
#include "net/net_buildflags.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/url_request/url_request_failed_job.h"
#include "net/traffic_annotation/network_traffic_annotation_test_helper.h"
#include "services/metrics/public/cpp/ukm_source_id.h"
#include "services/network/public/cpp/features.h"
#include "services/network/public/cpp/simple_url_loader.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "url/gurl.h"
#if BUILDFLAG(IS_CHROMEOS_ASH)
#include "chrome/browser/chromeos/profiles/profile_helper.h"
#include "chromeos/constants/chromeos_switches.h"
#endif
#if BUILDFLAG(ENABLE_EXTENSIONS)
#include "extensions/browser/extension_protocols.h"
#include "extensions/browser/extension_registry.h"
#include "extensions/common/extension.h"
#include "extensions/common/extension_builder.h"
#include "extensions/common/value_builder.h"
#endif
#if BUILDFLAG(IS_CHROMEOS_LACROS)
#include "base/strings/utf_string_conversions.h"
#include "chrome/browser/profiles/profile_attributes_entry.h"
#include "chrome/browser/profiles/profile_attributes_storage.h"
#include "chromeos/crosapi/mojom/crosapi.mojom.h"
#include "chromeos/lacros/lacros_chrome_service_impl.h"
#include "components/account_id/account_id.h"
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)
namespace {
// A helper class which creates a SimpleURLLoader with an expected final status
// and the ability to wait until a request completes. It's not considered a
// failure for the load to never complete.
class SimpleURLLoaderHelper {
public:
// Creating the SimpleURLLoaderHelper automatically creates and starts a
// SimpleURLLoader.
SimpleURLLoaderHelper(network::mojom::URLLoaderFactory* factory,
const GURL& url,
int expected_error_code,
int load_flags = net::LOAD_NORMAL)
: expected_error_code_(expected_error_code), is_complete_(false) {
auto request = std::make_unique<network::ResourceRequest>();
request->url = url;
request->load_flags = load_flags;
// Populate Network Isolation Key so that the request is cacheable.
url::Origin origin = url::Origin::Create(url);
request->trusted_params = network::ResourceRequest::TrustedParams();
request->trusted_params->isolation_info =
net::IsolationInfo::CreateForInternalRequest(origin);
request->site_for_cookies =
request->trusted_params->isolation_info.site_for_cookies();
loader_ = network::SimpleURLLoader::Create(std::move(request),
TRAFFIC_ANNOTATION_FOR_TESTS);
loader_->DownloadToStringOfUnboundedSizeUntilCrashAndDie(
factory, base::BindOnce(&SimpleURLLoaderHelper::OnSimpleLoaderComplete,
base::Unretained(this)));
}
void OnSimpleLoaderComplete(std::unique_ptr<std::string> response_body) {
EXPECT_EQ(expected_error_code_, loader_->NetError());
is_complete_ = true;
run_loop_.Quit();
}
void WaitForCompletion() { run_loop_.Run(); }
bool is_complete() const { return is_complete_; }
private:
const int expected_error_code_;
base::RunLoop run_loop_;
bool is_complete_;
std::unique_ptr<network::SimpleURLLoader> loader_;
DISALLOW_COPY_AND_ASSIGN(SimpleURLLoaderHelper);
};
class MockProfileDelegate : public Profile::Delegate {
public:
MOCK_METHOD1(OnPrefsLoaded, void(Profile*));
MOCK_METHOD3(OnProfileCreated, void(Profile*, bool, bool));
};
class ProfileDestructionWatcher : public ProfileObserver {
public:
ProfileDestructionWatcher() = default;
~ProfileDestructionWatcher() override = default;
void Watch(Profile* profile) { observed_profiles_.Add(profile); }
bool destroyed() const { return destroyed_; }
void WaitForDestruction() { run_loop_.Run(); }
private:
// ProfileObserver:
void OnProfileWillBeDestroyed(Profile* profile) override {
DCHECK(!destroyed_) << "Double profile destruction";
destroyed_ = true;
run_loop_.Quit();
observed_profiles_.Remove(profile);
}
bool destroyed_ = false;
base::RunLoop run_loop_;
ScopedObserver<Profile, ProfileObserver> observed_profiles_{this};
DISALLOW_COPY_AND_ASSIGN(ProfileDestructionWatcher);
};
// Creates a prefs file in the given directory.
void CreatePrefsFileInDirectory(const base::FilePath& directory_path) {
base::FilePath pref_path(directory_path.Append(chrome::kPreferencesFilename));
std::string data("{}");
ASSERT_TRUE(base::WriteFile(pref_path, data));
}
void CheckChromeVersion(Profile *profile, bool is_new) {
std::string created_by_version;
if (is_new) {
created_by_version = version_info::GetVersionNumber();
} else {
created_by_version = "1.0.0.0";
}
std::string pref_version =
ChromeVersionService::GetVersion(profile->GetPrefs());
// Assert that created_by_version pref gets set to current version.
EXPECT_EQ(created_by_version, pref_version);
}
void FlushTaskRunner(base::SequencedTaskRunner* runner) {
ASSERT_TRUE(runner);
base::WaitableEvent unblock(base::WaitableEvent::ResetPolicy::AUTOMATIC,
base::WaitableEvent::InitialState::NOT_SIGNALED);
runner->PostTask(FROM_HERE, base::BindOnce(&base::WaitableEvent::Signal,
base::Unretained(&unblock)));
unblock.Wait();
}
void SpinThreads() {
// Give threads a chance to do their stuff before shutting down (i.e.
// deleting scoped temp dir etc).
// Should not be necessary anymore once Profile deletion is fixed
// (see crbug.com/88586).
content::RunAllPendingInMessageLoop();
// This prevents HistoryBackend from accessing its databases after the
// directory that contains them has been deleted.
base::ThreadPoolInstance::Get()->FlushForTesting();
}
class BrowserCloseObserver : public BrowserListObserver {
public:
explicit BrowserCloseObserver(Browser* browser) : browser_(browser) {
BrowserList::AddObserver(this);
}
~BrowserCloseObserver() override { BrowserList::RemoveObserver(this); }
void Wait() { run_loop_.Run(); }
// BrowserListObserver implementation.
void OnBrowserRemoved(Browser* browser) override {
if (browser == browser_)
run_loop_.Quit();
}
private:
Browser* browser_;
base::RunLoop run_loop_;
DISALLOW_COPY_AND_ASSIGN(BrowserCloseObserver);
};
} // namespace
class ProfileBrowserTest : public InProcessBrowserTest {
protected:
void SetUpCommandLine(base::CommandLine* command_line) override {
#if BUILDFLAG(IS_CHROMEOS_ASH)
command_line->AppendSwitch(
chromeos::switches::kIgnoreUserProfileMappingForTests);
#endif
}
std::unique_ptr<Profile> CreateProfile(const base::FilePath& path,
Profile::Delegate* delegate,
Profile::CreateMode create_mode) {
std::unique_ptr<Profile> profile =
Profile::CreateProfile(path, delegate, create_mode);
EXPECT_TRUE(profile.get());
// Store the Profile's IO task runner so we can wind it down.
profile_io_task_runner_ = profile->GetIOTaskRunner();
return profile;
}
void FlushIoTaskRunnerAndSpinThreads() {
FlushTaskRunner(profile_io_task_runner_.get());
SpinThreads();
}
// Starts a test where a SimpleURLLoader is active during profile
// shutdown. The test completes during teardown of the test fixture. The
// request should be canceled by |context_getter| during profile shutdown,
// before the URLRequestContext is destroyed. If that doesn't happen, the
// Context's will still have outstanding requests during its destruction, and
// will trigger a CHECK failure.
void StartActiveLoaderDuringProfileShutdownTest(
network::mojom::URLLoaderFactory* factory) {
// This method should only be called once per test.
DCHECK(!simple_loader_helper_);
// Start a hanging request. This request may or may not completed before
// the end of the request.
simple_loader_helper_ = std::make_unique<SimpleURLLoaderHelper>(
factory, embedded_test_server()->GetURL("/hung"), net::ERR_FAILED);
// Start a second mock request that just fails, and wait for it to complete.
// This ensures the first request has reached the network stack.
SimpleURLLoaderHelper simple_loader_helper2(
factory, embedded_test_server()->GetURL("/echo?status=400"),
net::ERR_HTTP_RESPONSE_CODE_FAILURE);
simple_loader_helper2.WaitForCompletion();
// The first request should still be hung.
EXPECT_FALSE(simple_loader_helper_->is_complete());
}
// Runs a test where an incognito profile's SimpleURLLoader is active during
// teardown of the profile, and makes sure the request fails as expected.
// Also tries issuing a request after the incognito profile has been
// destroyed.
static void RunURLLoaderActiveDuringIncognitoTeardownTest(
net::EmbeddedTestServer* embedded_test_server,
Browser* incognito_browser,
network::mojom::URLLoaderFactory* factory) {
// Start a hanging request.
SimpleURLLoaderHelper simple_loader_helper1(
factory, embedded_test_server->GetURL("/hung"), net::ERR_FAILED);
// Start a second mock request that just fails, and wait for it to complete.
// This ensures the first request has reached the network stack.
SimpleURLLoaderHelper simple_loader_helper2(
factory, embedded_test_server->GetURL("/echo?status=400"),
net::ERR_HTTP_RESPONSE_CODE_FAILURE);
simple_loader_helper2.WaitForCompletion();
// The first request should still be hung.
EXPECT_FALSE(simple_loader_helper1.is_complete());
// Close all incognito tabs, starting profile shutdown.
incognito_browser->tab_strip_model()->CloseAllTabs();
// The request should have been canceled when the Profile shut down.
simple_loader_helper1.WaitForCompletion();
// Requests issued after Profile shutdown should fail in a similar manner.
SimpleURLLoaderHelper simple_loader_helper3(
factory, embedded_test_server->GetURL("/hung"), net::ERR_FAILED);
simple_loader_helper3.WaitForCompletion();
}
scoped_refptr<base::SequencedTaskRunner> profile_io_task_runner_;
// SimpleURLLoader that outlives the Profile, to test shutdown.
std::unique_ptr<SimpleURLLoaderHelper> simple_loader_helper_;
};
// Test OnProfileCreate is called with is_new_profile set to true when
// creating a new profile synchronously.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, CreateNewProfileSynchronous) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
MockProfileDelegate delegate;
EXPECT_CALL(delegate, OnProfileCreated(testing::NotNull(), true, true));
{
std::unique_ptr<Profile> profile(CreateProfile(
temp_dir.GetPath(), &delegate, Profile::CREATE_MODE_SYNCHRONOUS));
CheckChromeVersion(profile.get(), true);
// Creating a profile causes an implicit connection attempt to a Mojo
// service, which occurs as part of a new task. Before deleting |profile|,
// ensure this task runs to prevent a crash.
FlushIoTaskRunnerAndSpinThreads();
}
FlushIoTaskRunnerAndSpinThreads();
}
// Test OnProfileCreate is called with is_new_profile set to false when
// creating a profile synchronously with an existing prefs file.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, CreateOldProfileSynchronous) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
CreatePrefsFileInDirectory(temp_dir.GetPath());
MockProfileDelegate delegate;
EXPECT_CALL(delegate, OnProfileCreated(testing::NotNull(), true, false));
{
std::unique_ptr<Profile> profile(CreateProfile(
temp_dir.GetPath(), &delegate, Profile::CREATE_MODE_SYNCHRONOUS));
CheckChromeVersion(profile.get(), false);
// Creating a profile causes an implicit connection attempt to a Mojo
// service, which occurs as part of a new task. Before deleting |profile|,
// ensure this task runs to prevent a crash.
FlushIoTaskRunnerAndSpinThreads();
}
FlushIoTaskRunnerAndSpinThreads();
}
// Flaky: http://crbug.com/393177
// Test OnProfileCreate is called with is_new_profile set to true when
// creating a new profile asynchronously.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
DISABLED_CreateNewProfileAsynchronous) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
MockProfileDelegate delegate;
EXPECT_CALL(delegate, OnProfileCreated(testing::NotNull(), true, true));
{
content::WindowedNotificationObserver observer(
chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
std::unique_ptr<Profile> profile(CreateProfile(
temp_dir.GetPath(), &delegate, Profile::CREATE_MODE_ASYNCHRONOUS));
// Wait for the profile to be created.
observer.Wait();
CheckChromeVersion(profile.get(), true);
}
FlushIoTaskRunnerAndSpinThreads();
}
// Flaky: http://crbug.com/393177
// Test OnProfileCreate is called with is_new_profile set to false when
// creating a profile asynchronously with an existing prefs file.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
DISABLED_CreateOldProfileAsynchronous) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
CreatePrefsFileInDirectory(temp_dir.GetPath());
MockProfileDelegate delegate;
EXPECT_CALL(delegate, OnProfileCreated(testing::NotNull(), true, false));
{
content::WindowedNotificationObserver observer(
chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
std::unique_ptr<Profile> profile(CreateProfile(
temp_dir.GetPath(), &delegate, Profile::CREATE_MODE_ASYNCHRONOUS));
// Wait for the profile to be created.
observer.Wait();
CheckChromeVersion(profile.get(), false);
}
FlushIoTaskRunnerAndSpinThreads();
}
// Flaky: http://crbug.com/393177
// Test that a README file is created for profiles that didn't have it.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, DISABLED_ProfileReadmeCreated) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
MockProfileDelegate delegate;
EXPECT_CALL(delegate, OnProfileCreated(testing::NotNull(), true, true));
{
content::WindowedNotificationObserver observer(
chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
std::unique_ptr<Profile> profile(CreateProfile(
temp_dir.GetPath(), &delegate, Profile::CREATE_MODE_ASYNCHRONOUS));
// Wait for the profile to be created.
observer.Wait();
// Verify that README exists.
EXPECT_TRUE(
base::PathExists(temp_dir.GetPath().Append(chrome::kReadmeFilename)));
}
FlushIoTaskRunnerAndSpinThreads();
}
// Test that repeated setting of exit type is handled correctly.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, ExitType) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
MockProfileDelegate delegate;
EXPECT_CALL(delegate, OnProfileCreated(testing::NotNull(), true, true));
{
std::unique_ptr<Profile> profile(CreateProfile(
temp_dir.GetPath(), &delegate, Profile::CREATE_MODE_SYNCHRONOUS));
PrefService* prefs = profile->GetPrefs();
// The initial state is crashed; store for later reference.
std::string crash_value(prefs->GetString(prefs::kSessionExitType));
// The first call to a type other than crashed should change the value.
profile->SetExitType(Profile::EXIT_SESSION_ENDED);
std::string first_call_value(prefs->GetString(prefs::kSessionExitType));
EXPECT_NE(crash_value, first_call_value);
// Subsequent calls to a non-crash value should be ignored.
profile->SetExitType(Profile::EXIT_NORMAL);
std::string second_call_value(prefs->GetString(prefs::kSessionExitType));
EXPECT_EQ(first_call_value, second_call_value);
// Setting back to a crashed value should work.
profile->SetExitType(Profile::EXIT_CRASHED);
std::string final_value(prefs->GetString(prefs::kSessionExitType));
EXPECT_EQ(crash_value, final_value);
// Creating a profile causes an implicit connection attempt to a Mojo
// service, which occurs as part of a new task. Before deleting |profile|,
// ensure this task runs to prevent a crash.
FlushIoTaskRunnerAndSpinThreads();
}
FlushIoTaskRunnerAndSpinThreads();
}
// The EndSession IO synchronization is only critical on Windows, but also
// happens under the USE_X11 define. See BrowserProcessImpl::EndSession.
#if defined(USE_X11) || defined(OS_WIN) || defined(USE_OZONE)
namespace {
std::string GetExitTypePreferenceFromDisk(Profile* profile) {
base::FilePath prefs_path =
profile->GetPath().Append(chrome::kPreferencesFilename);
std::string prefs;
if (!base::ReadFileToString(prefs_path, &prefs))
return std::string();
std::unique_ptr<base::Value> value = base::JSONReader::ReadDeprecated(prefs);
if (!value)
return std::string();
base::DictionaryValue* dict = NULL;
if (!value->GetAsDictionary(&dict) || !dict)
return std::string();
std::string exit_type;
if (!dict->GetString("profile.exit_type", &exit_type))
return std::string();
return exit_type;
}
} // namespace
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
WritesProfilesSynchronouslyOnEndSession) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
ProfileManager* profile_manager = g_browser_process->profile_manager();
ASSERT_TRUE(profile_manager);
std::vector<Profile*> loaded_profiles = profile_manager->GetLoadedProfiles();
ASSERT_NE(loaded_profiles.size(), 0UL);
Profile* profile = loaded_profiles[0];
#if BUILDFLAG(IS_CHROMEOS_ASH)
for (auto* loaded_profile : loaded_profiles) {
if (!chromeos::ProfileHelper::IsSigninProfile(loaded_profile)) {
profile = loaded_profile;
break;
}
}
#endif
// It is important that the MessageLoop not pump extra messages during
// EndSession() as some of those may be tasks queued to attempt to revive
// services and processes that were just intentionally killed. This is a
// regression blocker for https://crbug.com/318527.
// Need to use this WeakPtr workaround as the browser test harness runs all
// tasks until idle when tearing down.
struct FailsIfCalledWhileOnStack
: public base::SupportsWeakPtr<FailsIfCalledWhileOnStack> {
void Fail() { ADD_FAILURE(); }
} fails_if_called_while_on_stack;
base::ThreadTaskRunnerHandle::Get()->PostTask(
FROM_HERE, base::BindOnce(&FailsIfCalledWhileOnStack::Fail,
fails_if_called_while_on_stack.AsWeakPtr()));
// This retry loop reduces flakiness due to the fact that this ultimately
// tests whether or not a code path hits a timed wait.
bool succeeded = false;
for (size_t retries = 0; !succeeded && retries < 3; ++retries) {
// Flush the profile data to disk for all loaded profiles.
profile->SetExitType(Profile::EXIT_CRASHED);
profile->GetPrefs()->CommitPendingWrite();
FlushTaskRunner(profile->GetIOTaskRunner().get());
// Make sure that the prefs file was written with the expected key/value.
ASSERT_EQ(GetExitTypePreferenceFromDisk(profile), "Crashed");
// The blocking wait in EndSession has a timeout.
base::Time start = base::Time::Now();
// This must not return until the profile data has been written to disk.
// If this test flakes, then logoff on Windows has broken again.
g_browser_process->EndSession();
base::Time end = base::Time::Now();
// The EndSession timeout is 10 seconds. If we take more than half that,
// go around again, as we may have timed out on the wait.
// This helps against flakes, and also ensures that if the IO thread starts
// blocking systemically for that length of time (e.g. deadlocking or such),
// we'll get a consistent test failure.
if (end - start > base::TimeDelta::FromSeconds(5))
continue;
// Make sure that the prefs file was written with the expected key/value.
ASSERT_EQ(GetExitTypePreferenceFromDisk(profile), "SessionEnded");
// Mark the success.
succeeded = true;
}
ASSERT_TRUE(succeeded) << "profile->EndSession() timed out too often.";
}
#endif // defined(USE_X11) || defined(OS_WIN) || defined(USE_OZONE)
// The following tests make sure that it's safe to shut down while one of the
// Profile's URLLoaderFactories is in use by a SimpleURLLoader.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
SimpleURLLoaderUsingMainContextDuringShutdown) {
ASSERT_TRUE(embedded_test_server()->Start());
StartActiveLoaderDuringProfileShutdownTest(
content::BrowserContext::GetDefaultStoragePartition(browser()->profile())
->GetURLLoaderFactoryForBrowserProcess()
.get());
}
// The following tests make sure that it's safe to destroy an incognito profile
// while one of the its URLLoaderFactory is in use by a SimpleURLLoader.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
SimpleURLLoaderUsingMainContextDuringIncognitoTeardown) {
ASSERT_TRUE(embedded_test_server()->Start());
Browser* incognito_browser =
OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
RunURLLoaderActiveDuringIncognitoTeardownTest(
embedded_test_server(), incognito_browser,
content::BrowserContext::GetDefaultStoragePartition(
incognito_browser->profile())
->GetURLLoaderFactoryForBrowserProcess()
.get());
}
#if BUILDFLAG(ENABLE_EXTENSIONS)
// Regression test for https://crbug.com/1136214 - verification that
// ExtensionURLLoaderFactory won't hit a use-after-free bug when used after
// a Profile has been torn down already.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
ExtensionURLLoaderFactoryAfterIncognitoTeardown) {
// Create a mojo::Remote to ExtensionURLLoaderFactory for the incognito
// profile.
Browser* incognito_browser =
OpenURLOffTheRecord(browser()->profile(), GURL("about:blank"));
Profile* incognito_profile = incognito_browser->profile();
mojo::Remote<network::mojom::URLLoaderFactory> url_loader_factory;
url_loader_factory.Bind(extensions::CreateExtensionNavigationURLLoaderFactory(
incognito_profile, ukm::kInvalidSourceIdObj,
false /* is_web_view_request */));
// Verify that the factory works fine while the profile is still alive.
// We don't need to test with a real extension URL - it is sufficient to
// verify that the factory responds with ERR_BLOCKED_BY_CLIENT that indicates
// a missing extension.
GURL missing_extension_url("chrome-extension://no-such-extension/blah");
{
SimpleURLLoaderHelper simple_loader_helper(url_loader_factory.get(),
missing_extension_url,
net::ERR_BLOCKED_BY_CLIENT);
simple_loader_helper.WaitForCompletion();
}
{
// Start monitoring |incognito_profile| for shutdown.
ProfileManager* profile_manager = g_browser_process->profile_manager();
EXPECT_TRUE(profile_manager->IsValidProfile(incognito_profile));
ProfileDestructionWatcher watcher;
watcher.Watch(incognito_profile);
// Close all incognito tabs, starting profile shutdown.
incognito_browser->tab_strip_model()->CloseAllTabs();
// ProfileDestructionWatcher waits for
// BrowserContext::NotifyWillBeDestroyed, but after the RunLoop unwinds, the
// profile should already be gone - let's assert this below (since this
// ensures that |simple_loader_helper2| really tests what needs to be
// tested).
watcher.WaitForDestruction();
EXPECT_FALSE(profile_manager->IsValidProfile(incognito_profile));
}
// Verify that the factory doesn't crash (https://crbug.com/1136214), but
// instead SimpleURLLoaderImpl::OnMojoDisconnect reports net::ERR_FAILED.
{
SimpleURLLoaderHelper simple_loader_helper2(
url_loader_factory.get(), missing_extension_url, net::ERR_FAILED);
simple_loader_helper2.WaitForCompletion();
}
}
#endif
// Verifies the cache directory supports multiple profiles when it's overridden
// by group policy or command line switches.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, DiskCacheDirOverride) {
base::ScopedAllowBlockingForTesting allow_blocking;
const base::FilePath::StringPieceType profile_name =
FILE_PATH_LITERAL("Profile 1");
base::ScopedTempDir mock_user_data_dir;
ASSERT_TRUE(mock_user_data_dir.CreateUniqueTempDir());
base::FilePath profile_path =
mock_user_data_dir.GetPath().Append(profile_name);
{
base::ScopedTempDir temp_disk_cache_dir;
ASSERT_TRUE(temp_disk_cache_dir.CreateUniqueTempDir());
g_browser_process->local_state()->SetFilePath(
prefs::kDiskCacheDir, temp_disk_cache_dir.GetPath());
}
}
// Verifies the last selected directory has a default value.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, LastSelectedDirectory) {
ProfileImpl* profile_impl = static_cast<ProfileImpl*>(browser()->profile());
base::FilePath home;
base::PathService::Get(base::DIR_HOME, &home);
ASSERT_EQ(profile_impl->last_selected_directory(), home);
}
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, Notifications) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
// Create the profile and check that a notification is received for it.
std::unique_ptr<Profile> profile;
{
content::WindowedNotificationObserver profile_created_observer(
chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
profile = CreateProfile(temp_dir.GetPath(), nullptr,
Profile::CREATE_MODE_SYNCHRONOUS);
profile_created_observer.Wait();
EXPECT_EQ(profile_created_observer.source(),
content::Source<Profile>(profile.get()));
}
// Now retrieve the off-the-record profile, which will be created because it
// doesn't exist yet.
Profile* otr_profile = nullptr;
{
content::WindowedNotificationObserver profile_created_observer(
chrome::NOTIFICATION_PROFILE_CREATED,
content::NotificationService::AllSources());
otr_profile = profile->GetPrimaryOTRProfile();
profile_created_observer.Wait();
EXPECT_EQ(profile_created_observer.source(),
content::Source<Profile>(otr_profile));
EXPECT_TRUE(profile->HasPrimaryOTRProfile());
EXPECT_TRUE(otr_profile->IsOffTheRecord());
EXPECT_TRUE(otr_profile->IsPrimaryOTRProfile());
EXPECT_TRUE(otr_profile->IsIncognitoProfile());
}
// We are about to destroy a profile. In production that will only happen
// as part of the destruction of BrowserProcess's ProfileManager. This
// happens in PostMainMessageLoopRun(). This means that to have this test
// represent production we have to make sure that no tasks are pending on the
// main thread before we destroy the profile. We also would need to prohibit
// the posting of new tasks on the main thread as in production the main
// thread's message loop will not be accepting them. We fallback on flushing
// as many runners as possible here to avoid the posts coming from any of
// them.
FlushIoTaskRunnerAndSpinThreads();
// Destroy the off-the-record profile.
{
ProfileDestructionWatcher watcher;
watcher.Watch(otr_profile);
if (profile->HasPrimaryOTRProfile()) {
profile->DestroyOffTheRecordProfile(profile->GetPrimaryOTRProfile());
watcher.WaitForDestruction();
}
EXPECT_FALSE(profile->HasPrimaryOTRProfile());
}
// Destroy the regular profile.
{
ProfileDestructionWatcher watcher;
watcher.Watch(profile.get());
profile.reset();
watcher.WaitForDestruction();
}
// Pending tasks related to |profile| could depend on |temp_dir|. We need to
// let them complete before |temp_dir| goes out of scope.
FlushIoTaskRunnerAndSpinThreads();
}
// Verifies creating an OTR with non-primary id results in a different profile
// from incognito profile.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, CreateNonPrimaryOTR) {
Profile::OTRProfileID otr_profile_id("profile::otr");
Profile* regular_profile = browser()->profile();
EXPECT_FALSE(regular_profile->HasAnyOffTheRecordProfile());
Profile* otr_profile =
regular_profile->GetOffTheRecordProfile(otr_profile_id);
EXPECT_TRUE(regular_profile->HasAnyOffTheRecordProfile());
EXPECT_TRUE(otr_profile->IsOffTheRecord());
EXPECT_EQ(otr_profile_id, otr_profile->GetOTRProfileID());
EXPECT_TRUE(regular_profile->HasOffTheRecordProfile(otr_profile_id));
EXPECT_NE(otr_profile, regular_profile->GetOffTheRecordProfile(
Profile::OTRProfileID::PrimaryID()));
regular_profile->DestroyOffTheRecordProfile(otr_profile);
EXPECT_FALSE(regular_profile->HasOffTheRecordProfile(otr_profile_id));
EXPECT_TRUE(regular_profile->HasOffTheRecordProfile(
Profile::OTRProfileID::PrimaryID()));
EXPECT_TRUE(regular_profile->HasAnyOffTheRecordProfile());
}
// Verifies creating two OTRs with different ids results in different profiles.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, CreateTwoNonPrimaryOTRs) {
Profile::OTRProfileID otr_profile_id1("profile::otr1");
Profile::OTRProfileID otr_profile_id2("profile::otr2");
Profile* regular_profile = browser()->profile();
Profile* otr_profile1 =
regular_profile->GetOffTheRecordProfile(otr_profile_id1);
Profile* otr_profile2 =
regular_profile->GetOffTheRecordProfile(otr_profile_id2);
EXPECT_NE(otr_profile1, otr_profile2);
EXPECT_TRUE(regular_profile->HasOffTheRecordProfile(otr_profile_id1));
EXPECT_TRUE(regular_profile->HasOffTheRecordProfile(otr_profile_id2));
regular_profile->DestroyOffTheRecordProfile(otr_profile1);
EXPECT_FALSE(regular_profile->HasOffTheRecordProfile(otr_profile_id1));
EXPECT_TRUE(regular_profile->HasOffTheRecordProfile(otr_profile_id2));
}
// Verifies destroying regular profile will result in destruction of OTR
// profiles.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, DestroyRegularProfileBeforeOTRs) {
Profile::OTRProfileID otr_profile_id1("profile::otr1");
Profile::OTRProfileID otr_profile_id2("profile::otr2");
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
MockProfileDelegate delegate;
std::unique_ptr<Profile> regular_profile(CreateProfile(
temp_dir.GetPath(), &delegate, Profile::CREATE_MODE_SYNCHRONOUS));
// Creating a profile causes an implicit connection attempt to a Mojo
// service, which occurs as part of a new task. Before deleting |profile|,
// ensure this task runs to prevent a crash.
FlushIoTaskRunnerAndSpinThreads();
Profile* otr_profile1 =
regular_profile->GetOffTheRecordProfile(otr_profile_id1);
Profile* otr_profile2 =
regular_profile->GetOffTheRecordProfile(otr_profile_id2);
ProfileDestructionWatcher watcher1;
ProfileDestructionWatcher watcher2;
watcher1.Watch(otr_profile1);
watcher2.Watch(otr_profile2);
ProfileDestroyer::DestroyProfileWhenAppropriate(regular_profile.release());
EXPECT_TRUE(watcher1.destroyed());
EXPECT_TRUE(watcher2.destroyed());
}
// Tests Profile::GetAllOffTheRecordProfiles
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, TestGetAllOffTheRecordProfiles) {
Profile::OTRProfileID otr_profile_id1("profile::otr1");
Profile::OTRProfileID otr_profile_id2("profile::otr2");
Profile* regular_profile = browser()->profile();
Profile* otr_profile1 =
regular_profile->GetOffTheRecordProfile(otr_profile_id1);
Profile* otr_profile2 =
regular_profile->GetOffTheRecordProfile(otr_profile_id2);
Profile* incognito_profile = regular_profile->GetOffTheRecordProfile(
Profile::OTRProfileID::PrimaryID());
std::vector<Profile*> all_otrs =
regular_profile->GetAllOffTheRecordProfiles();
EXPECT_EQ(3u, all_otrs.size());
EXPECT_TRUE(base::Contains(all_otrs, otr_profile1));
EXPECT_TRUE(base::Contains(all_otrs, otr_profile2));
EXPECT_TRUE(base::Contains(all_otrs, incognito_profile));
}
// Tests Profile::IsSameOrParent
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, TestIsSameOrParent) {
Profile::OTRProfileID otr_profile_id("profile::otr");
Profile* regular_profile = browser()->profile();
Profile* otr_profile =
regular_profile->GetOffTheRecordProfile(otr_profile_id);
Profile* incognito_profile = regular_profile->GetPrimaryOTRProfile();
EXPECT_TRUE(regular_profile->IsSameOrParent(otr_profile));
EXPECT_TRUE(otr_profile->IsSameOrParent(regular_profile));
EXPECT_TRUE(regular_profile->IsSameOrParent(incognito_profile));
EXPECT_TRUE(incognito_profile->IsSameOrParent(regular_profile));
EXPECT_FALSE(incognito_profile->IsSameOrParent(otr_profile));
EXPECT_FALSE(otr_profile->IsSameOrParent(incognito_profile));
}
// Tests if browser creation using non primary OTRs is blocked.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
TestCreatingBrowserUsingNonPrimaryOffTheRecordProfile) {
Profile::OTRProfileID otr_profile_id("profile::otr");
Profile* otr_profile =
browser()->profile()->GetOffTheRecordProfile(otr_profile_id);
EXPECT_EQ(Browser::BrowserCreationStatus::kErrorProfileUnsuitable,
Browser::GetBrowserCreationStatusForProfile(otr_profile));
}
#if !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
// TODO(https://crbug.com/1125474): Expand to cover ChromeOS.
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, ProfileLifetimeTestUnderOneMinute) {
base::HistogramTester tester;
Browser* browser = CreateGuestBrowser();
BrowserCloseObserver close_observer(browser);
BrowserList::CloseAllBrowsersWithProfile(browser->profile());
close_observer.Wait();
tester.ExpectUniqueSample("Profile.Guest.OTR.Lifetime", 0, 1);
}
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest, ProfileLifetimeTestOneHour) {
base::HistogramTester tester;
Browser* browser = CreateGuestBrowser();
BrowserCloseObserver close_observer(browser);
browser->profile()->SetCreationTimeForTesting(
base::Time::Now() - base::TimeDelta::FromSeconds(60) * 60);
BrowserList::CloseAllBrowsersWithProfile(browser->profile());
close_observer.Wait();
tester.ExpectUniqueSample("Profile.Guest.OTR.Lifetime", 60, 1);
}
class EphemeralGuestProfileBrowserTest : public ProfileBrowserTest {
public:
EphemeralGuestProfileBrowserTest() {
scoped_feature_list_.InitAndEnableFeature(
features::kEnableEphemeralGuestProfilesOnDesktop);
}
private:
base::test::ScopedFeatureList scoped_feature_list_;
};
// Tests profile type functions on an ephemeral Guest profile.
IN_PROC_BROWSER_TEST_F(EphemeralGuestProfileBrowserTest, TestProfileType) {
Profile* guest_profile = CreateGuestBrowser()->profile();
EXPECT_TRUE(guest_profile->IsRegularProfile());
EXPECT_FALSE(guest_profile->IsOffTheRecord());
EXPECT_FALSE(guest_profile->IsGuestSession());
EXPECT_TRUE(guest_profile->IsEphemeralGuestProfile());
}
IN_PROC_BROWSER_TEST_F(EphemeralGuestProfileBrowserTest,
ProfileLifetimeTestUnderOneMinute) {
base::HistogramTester tester;
Browser* browser = CreateGuestBrowser();
BrowserCloseObserver close_observer(browser);
BrowserList::CloseAllBrowsersWithProfile(browser->profile());
close_observer.Wait();
tester.ExpectUniqueSample("Profile.Guest.Ephemeral.Lifetime", 0, 1);
}
IN_PROC_BROWSER_TEST_F(EphemeralGuestProfileBrowserTest,
ProfileLifetimeTestOneHour) {
base::HistogramTester tester;
Browser* browser = CreateGuestBrowser();
BrowserCloseObserver close_observer(browser);
browser->profile()->SetCreationTimeForTesting(
base::Time::Now() - base::TimeDelta::FromSeconds(60) * 60);
BrowserList::CloseAllBrowsersWithProfile(browser->profile());
close_observer.Wait();
tester.ExpectUniqueSample("Profile.Guest.Ephemeral.Lifetime", 60, 1);
}
// Tests if ephemeral Guest profile paths are persistent as long as one does not
// close all Guest browsers.
IN_PROC_BROWSER_TEST_F(EphemeralGuestProfileBrowserTest,
TestProfilePathIsStableWhileNotClosed) {
Browser* guest1 = CreateGuestBrowser();
base::FilePath guest_path1 = guest1->profile()->GetPath();
Browser* guest2 = CreateGuestBrowser();
base::FilePath guest_path2 = guest2->profile()->GetPath();
EXPECT_EQ(guest_path1, guest_path2);
CloseBrowserSynchronously(guest1);
Browser* guest3 = CreateGuestBrowser();
base::FilePath guest_path3 = guest3->profile()->GetPath();
EXPECT_EQ(guest_path1, guest_path3);
}
// Tests if closing all ephemeral Guest profiles will result in a new path for
// the next ephemeral Guest profile.
IN_PROC_BROWSER_TEST_F(EphemeralGuestProfileBrowserTest,
TestGuestGetsNewPathAfterClosing) {
Browser* guest1 = CreateGuestBrowser();
base::FilePath guest_path1 = guest1->profile()->GetPath();
CloseBrowserSynchronously(guest1);
Browser* guest2 = CreateGuestBrowser();
base::FilePath guest_path2 = guest2->profile()->GetPath();
EXPECT_NE(guest_path1, guest_path2);
}
#endif // !defined(OS_ANDROID) && !BUILDFLAG(IS_CHROMEOS_ASH)
#if BUILDFLAG(IS_CHROMEOS_LACROS)
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
IsMainProfileReturnsFalseForNonDefaultPaths) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
{
base::FilePath profile_path = temp_dir.GetPath().Append("1Default");
std::unique_ptr<Profile> profile(
CreateProfile(profile_path, /* delegate= */ nullptr,
Profile::CREATE_MODE_SYNCHRONOUS));
EXPECT_FALSE(profile->IsMainProfile());
// Creating a profile causes an implicit connection attempt to a Mojo
// service, which occurs as part of a new task. Before deleting |profile|,
// ensure this task runs to prevent a crash.
FlushIoTaskRunnerAndSpinThreads();
}
FlushIoTaskRunnerAndSpinThreads();
}
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
IsMainProfileReturnsTrueForPublicSessions) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
{
base::FilePath profile_path =
temp_dir.GetPath().Append(chrome::kInitialProfile);
std::unique_ptr<Profile> profile(
CreateProfile(profile_path, /* delegate= */ nullptr,
Profile::CREATE_MODE_SYNCHRONOUS));
crosapi::mojom::LacrosInitParamsPtr init_params =
crosapi::mojom::LacrosInitParams::New();
init_params->session_type = crosapi::mojom::SessionType::kPublicSession;
chromeos::LacrosChromeServiceImpl::Get()->SetInitParamsForTests(
std::move(init_params));
EXPECT_TRUE(profile->IsMainProfile());
// Creating a profile causes an implicit connection attempt to a Mojo
// service, which occurs as part of a new task. Before deleting |profile|,
// ensure this task runs to prevent a crash.
FlushIoTaskRunnerAndSpinThreads();
}
FlushIoTaskRunnerAndSpinThreads();
}
IN_PROC_BROWSER_TEST_F(
ProfileBrowserTest,
IsMainProfileReturnsTrueForActiveDirectoryEnrolledDevices) {
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
{
base::FilePath profile_path =
temp_dir.GetPath().Append(chrome::kInitialProfile);
std::unique_ptr<Profile> profile(
CreateProfile(profile_path, /* delegate= */ nullptr,
Profile::CREATE_MODE_SYNCHRONOUS));
crosapi::mojom::LacrosInitParamsPtr init_params =
crosapi::mojom::LacrosInitParams::New();
init_params->session_type = crosapi::mojom::SessionType::kRegularSession;
init_params->device_mode =
crosapi::mojom::DeviceMode::kEnterpriseActiveDirectory;
chromeos::LacrosChromeServiceImpl::Get()->SetInitParamsForTests(
std::move(init_params));
EXPECT_TRUE(profile->IsMainProfile());
// Creating a profile causes an implicit connection attempt to a Mojo
// service, which occurs as part of a new task. Before deleting |profile|,
// ensure this task runs to prevent a crash.
FlushIoTaskRunnerAndSpinThreads();
}
FlushIoTaskRunnerAndSpinThreads();
}
// TODO(sinhak): Remove this test after launching go/cros-dent-1-lacros.
IN_PROC_BROWSER_TEST_F(
ProfileBrowserTest,
IsMainProfileReturnsTrueForMainProfileInRegularSessions) {
// Setup.
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const std::string kFakePrimaryUsername = "user@example.com";
const std::string kFakeGaiaId = "fake-gaia-id";
ProfileAttributesStorage& profile_attributes_storage =
g_browser_process->profile_manager()->GetProfileAttributesStorage();
const base::FilePath profile_path =
browser()->profile()->GetPath().DirName().Append(chrome::kInitialProfile);
// Creates a new Profile and (fake) signs in `kFakeGaiaId`.
profile_attributes_storage.AddProfile(
profile_path, base::UTF8ToUTF16(chrome::kInitialProfile), kFakeGaiaId,
base::UTF8ToUTF16(kFakePrimaryUsername),
/*is_consented_primary_account=*/false, /*icon_index=*/0,
/*supervised_user_id*/ std::string(), EmptyAccountId());
crosapi::mojom::LacrosInitParamsPtr init_params =
crosapi::mojom::LacrosInitParams::New();
init_params->session_type = crosapi::mojom::SessionType::kRegularSession;
init_params->device_mode = crosapi::mojom::DeviceMode::kConsumer;
init_params->device_account_gaia_id = kFakeGaiaId;
chromeos::LacrosChromeServiceImpl::Get()->SetInitParamsForTests(
std::move(init_params));
// Test.
Profile* profile =
g_browser_process->profile_manager()->GetProfileByPath(profile_path);
EXPECT_TRUE(profile->IsMainProfile());
}
IN_PROC_BROWSER_TEST_F(ProfileBrowserTest,
IsMainProfileReturnsTrueForOTRProfileInRegularSessions) {
// Setup.
base::ScopedAllowBlockingForTesting allow_blocking;
base::ScopedTempDir temp_dir;
ASSERT_TRUE(temp_dir.CreateUniqueTempDir());
const std::string kFakePrimaryUsername = "user@example.com";
const std::string kFakeGaiaId = "fake-gaia-id";
ProfileAttributesStorage& profile_attributes_storage =
g_browser_process->profile_manager()->GetProfileAttributesStorage();
const base::FilePath profile_path =
browser()->profile()->GetPath().DirName().Append(chrome::kInitialProfile);
// Creates a new Profile and (fake) signs in `kFakeGaiaId`.
profile_attributes_storage.AddProfile(
profile_path, base::UTF8ToUTF16(chrome::kInitialProfile), kFakeGaiaId,
base::UTF8ToUTF16(kFakePrimaryUsername),
/*is_consented_primary_account=*/false, /*icon_index=*/0,
/*supervised_user_id*/ std::string(), EmptyAccountId());
crosapi::mojom::LacrosInitParamsPtr init_params =
crosapi::mojom::LacrosInitParams::New();
init_params->session_type = crosapi::mojom::SessionType::kRegularSession;
init_params->device_mode = crosapi::mojom::DeviceMode::kConsumer;
init_params->device_account_gaia_id = kFakeGaiaId;
chromeos::LacrosChromeServiceImpl::Get()->SetInitParamsForTests(
std::move(init_params));
// Test.
Profile* profile =
g_browser_process->profile_manager()->GetProfileByPath(profile_path);
EXPECT_FALSE(profile->GetPrimaryOTRProfile()->IsMainProfile());
}
#endif // BUILDFLAG(IS_CHROMEOS_LACROS)