| // Copyright 2020 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include <tuple> |
| |
| #include "base/run_loop.h" |
| #include "base/test/bind.h" |
| #include "base/threading/sequenced_task_runner_handle.h" |
| #include "build/build_config.h" |
| #include "components/services/storage/public/mojom/storage_service.mojom.h" |
| #include "components/services/storage/public/mojom/storage_usage_info.mojom.h" |
| #include "components/services/storage/public/mojom/test_api.test-mojom.h" |
| #include "content/browser/dom_storage/dom_storage_context_wrapper.h" |
| #include "content/browser/storage_partition_impl.h" |
| #include "content/public/browser/browser_context.h" |
| #include "content/public/browser/web_contents.h" |
| #include "content/public/test/browser_test.h" |
| #include "content/public/test/browser_test_utils.h" |
| #include "content/public/test/content_browser_test.h" |
| #include "content/public/test/content_browser_test_utils.h" |
| #include "content/shell/browser/shell.h" |
| |
| namespace content { |
| namespace { |
| |
| class StorageServiceRestartBrowserTest : public ContentBrowserTest { |
| public: |
| StorageServiceRestartBrowserTest() = default; |
| |
| DOMStorageContextWrapper* dom_storage() { |
| auto* partition = |
| static_cast<StoragePartitionImpl*>(shell() |
| ->web_contents() |
| ->GetBrowserContext() |
| ->GetDefaultStoragePartition()); |
| return partition->GetDOMStorageContext(); |
| } |
| |
| void WaitForAnyLocalStorageDataAsync(base::OnceClosure callback) { |
| dom_storage()->GetLocalStorageControl()->GetUsage(base::BindOnce( |
| [](StorageServiceRestartBrowserTest* test, base::OnceClosure callback, |
| std::vector<storage::mojom::StorageUsageInfoV2Ptr> usage) { |
| if (!usage.empty()) { |
| std::move(callback).Run(); |
| return; |
| } |
| |
| base::SequencedTaskRunnerHandle::Get()->PostDelayedTask( |
| FROM_HERE, |
| base::BindOnce(&StorageServiceRestartBrowserTest:: |
| WaitForAnyLocalStorageDataAsync, |
| base::Unretained(test), std::move(callback)), |
| base::Milliseconds(50)); |
| }, |
| this, std::move(callback))); |
| } |
| |
| void WaitForAnyLocalStorageData() { |
| base::RunLoop loop; |
| WaitForAnyLocalStorageDataAsync(loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| void FlushLocalStorage() { |
| base::RunLoop loop; |
| dom_storage()->GetLocalStorageControl()->Flush(loop.QuitClosure()); |
| loop.Run(); |
| } |
| |
| mojo::Remote<storage::mojom::TestApi>& GetTestApi() { |
| if (!test_api_) { |
| StoragePartitionImpl::GetStorageServiceForTesting()->BindTestApi( |
| test_api_.BindNewPipeAndPassReceiver().PassPipe()); |
| } |
| return test_api_; |
| } |
| |
| void CrashStorageServiceAndWaitForRestart() { |
| mojo::Remote<storage::mojom::StorageService>& service = |
| StoragePartitionImpl::GetStorageServiceForTesting(); |
| base::RunLoop loop; |
| service.set_disconnect_handler(base::BindLambdaForTesting([&] { |
| loop.Quit(); |
| service.reset(); |
| })); |
| GetTestApi()->CrashNow(); |
| loop.Run(); |
| test_api_.reset(); |
| } |
| |
| private: |
| mojo::Remote<storage::mojom::TestApi> test_api_; |
| }; |
| |
| IN_PROC_BROWSER_TEST_F(StorageServiceRestartBrowserTest, BasicReconnect) { |
| // Basic smoke test to ensure that we can force-crash the service and |
| // StoragePartitionImpl will internally re-establish a working connection to |
| // a new process. |
| GetTestApi().FlushForTesting(); |
| EXPECT_TRUE(GetTestApi().is_connected()); |
| CrashStorageServiceAndWaitForRestart(); |
| GetTestApi().FlushForTesting(); |
| EXPECT_TRUE(GetTestApi().is_connected()); |
| } |
| |
| IN_PROC_BROWSER_TEST_F(StorageServiceRestartBrowserTest, |
| SessionStorageRecovery) { |
| // Tests that the Session Storage API can recover and continue normal |
| // operation after a Storage Service crash. |
| EXPECT_TRUE( |
| NavigateToURL(shell(), GetTestUrl("dom_storage", "crash_recovery.html"))); |
| std::ignore = |
| EvalJs(shell()->web_contents(), R"(setSessionStorageValue("foo", 42))"); |
| |
| // Note that for Session Storage we don't need to wait for a commit. This is |
| // racy, but that's the point: whether or not a commit happens in time, the |
| // renderer should always retain its local cache of stored values. |
| |
| CrashStorageServiceAndWaitForRestart(); |
| EXPECT_EQ("42", EvalJs(shell()->web_contents(), |
| R"(getSessionStorageValue("foo"))")); |
| } |
| |
| // Flaky on Linux, Windows, and Mac. See crbug.com/1066138. |
| #if BUILDFLAG(IS_LINUX) || BUILDFLAG(IS_CHROMEOS) || BUILDFLAG(IS_WIN) || \ |
| BUILDFLAG(IS_MAC) |
| #define MAYBE_LocalStorageRecovery DISABLED_LocalStorageRecovery |
| #else |
| #define MAYBE_LocalStorageRecovery LocalStorageRecovery |
| #endif |
| IN_PROC_BROWSER_TEST_F(StorageServiceRestartBrowserTest, |
| MAYBE_LocalStorageRecovery) { |
| // Tests that the Local Storage API can recover and continue normal operation |
| // after a Storage Service crash. |
| EXPECT_TRUE( |
| NavigateToURL(shell(), GetTestUrl("dom_storage", "crash_recovery.html"))); |
| std::ignore = |
| EvalJs(shell()->web_contents(), R"(setLocalStorageValue("foo", 42))"); |
| |
| // We wait for the above storage request to be fully committed to disk. This |
| // ensures that renderer gets the correct value when recovering from the |
| // impending crash. |
| WaitForAnyLocalStorageData(); |
| FlushLocalStorage(); |
| |
| CrashStorageServiceAndWaitForRestart(); |
| EXPECT_EQ("42", |
| EvalJs(shell()->web_contents(), R"(getLocalStorageValue("foo"))")); |
| } |
| |
| } // namespace |
| } // namespace content |