| // 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 "content/browser/devtools/devtools_background_services_context_impl.h" |
| |
| #include <string> |
| #include <vector> |
| |
| #include "base/containers/flat_map.h" |
| #include "base/memory/scoped_refptr.h" |
| #include "base/run_loop.h" |
| #include "content/browser/devtools/devtools_background_services.pb.h" |
| #include "content/browser/service_worker/embedded_worker_test_helper.h" |
| #include "content/public/browser/content_browser_client.h" |
| #include "content/public/test/test_browser_context.h" |
| #include "content/public/test/test_browser_thread_bundle.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| #include "url/origin.h" |
| |
| namespace content { |
| namespace { |
| |
| using testing::_; |
| |
| const std::string kEventName = "Test Event"; |
| const std::string kInstanceId = "my-instance"; |
| |
| class TestBrowserClient : public ContentBrowserClient { |
| public: |
| TestBrowserClient() {} |
| ~TestBrowserClient() override {} |
| |
| void UpdateDevToolsBackgroundServiceExpiration( |
| BrowserContext* browser_context, |
| int service, |
| base::Time expiration_time) override { |
| exp_dict_[service] = expiration_time; |
| } |
| |
| base::flat_map<int, base::Time> GetDevToolsBackgroundServiceExpirations( |
| BrowserContext* browser_context) override { |
| return exp_dict_; |
| } |
| |
| private: |
| base::flat_map<int, base::Time> exp_dict_; |
| }; |
| |
| void DidRegisterServiceWorker(int64_t* out_service_worker_registration_id, |
| base::OnceClosure quit_closure, |
| blink::ServiceWorkerStatusCode status, |
| const std::string& status_message, |
| int64_t service_worker_registration_id) { |
| DCHECK(out_service_worker_registration_id); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status) << status_message; |
| |
| *out_service_worker_registration_id = service_worker_registration_id; |
| |
| std::move(quit_closure).Run(); |
| } |
| |
| void DidFindServiceWorkerRegistration( |
| scoped_refptr<ServiceWorkerRegistration>* out_service_worker_registration, |
| base::OnceClosure quit_closure, |
| blink::ServiceWorkerStatusCode status, |
| scoped_refptr<ServiceWorkerRegistration> service_worker_registration) { |
| DCHECK(out_service_worker_registration); |
| EXPECT_EQ(blink::ServiceWorkerStatusCode::kOk, status) |
| << blink::ServiceWorkerStatusToString(status); |
| |
| *out_service_worker_registration = service_worker_registration; |
| |
| std::move(quit_closure).Run(); |
| } |
| |
| void DidGetLoggedBackgroundServiceEvents( |
| base::OnceClosure quit_closure, |
| std::vector<devtools::proto::BackgroundServiceEvent>* out_feature_states, |
| std::vector<devtools::proto::BackgroundServiceEvent> feature_states) { |
| *out_feature_states = std::move(feature_states); |
| std::move(quit_closure).Run(); |
| } |
| |
| } // namespace |
| |
| class DevToolsBackgroundServicesContextTest |
| : public ::testing::Test, |
| DevToolsBackgroundServicesContextImpl::EventObserver { |
| public: |
| DevToolsBackgroundServicesContextTest() |
| : thread_bundle_(TestBrowserThreadBundle::IO_MAINLOOP), |
| embedded_worker_test_helper_(base::FilePath() /* in memory */) {} |
| |
| ~DevToolsBackgroundServicesContextTest() override = default; |
| |
| void SetUp() override { |
| // Register Service Worker. |
| service_worker_registration_id_ = RegisterServiceWorker(); |
| ASSERT_NE(service_worker_registration_id_, |
| blink::mojom::kInvalidServiceWorkerRegistrationId); |
| |
| browser_client_ = std::make_unique<TestBrowserClient>(); |
| SetBrowserClientForTesting(browser_client_.get()); |
| |
| SimulateBrowserRestart(); |
| } |
| |
| void TearDown() override { context_->RemoveObserver(this); } |
| |
| protected: |
| MOCK_METHOD1(OnEventReceived, |
| void(const devtools::proto::BackgroundServiceEvent& event)); |
| MOCK_METHOD2(OnRecordingStateChanged, |
| void(bool shoul_record, |
| devtools::proto::BackgroundService service)); |
| |
| void SimulateBrowserRestart() { |
| if (context_) |
| context_->RemoveObserver(this); |
| // Create |context_|. |
| context_ = base::MakeRefCounted<DevToolsBackgroundServicesContextImpl>( |
| &browser_context_, embedded_worker_test_helper_.context_wrapper()); |
| context_->AddObserver(this); |
| ASSERT_TRUE(context_); |
| } |
| |
| void SimulateOneWeekPassing() { |
| base::Time one_week_ago = base::Time::Now() - base::TimeDelta::FromDays(7); |
| context_->expiration_times_ |
| [devtools::proto::BackgroundService::BACKGROUND_FETCH] = one_week_ago; |
| } |
| |
| bool IsRecording() { |
| return context_->IsRecording( |
| devtools::proto::BackgroundService::BACKGROUND_FETCH); |
| } |
| |
| base::Time GetExpirationTime() { |
| return context_->expiration_times_ |
| [devtools::proto::BackgroundService::BACKGROUND_FETCH]; |
| } |
| |
| std::vector<devtools::proto::BackgroundServiceEvent> |
| GetLoggedBackgroundServiceEvents() { |
| std::vector<devtools::proto::BackgroundServiceEvent> feature_states; |
| |
| base::RunLoop run_loop; |
| context_->GetLoggedBackgroundServiceEvents( |
| devtools::proto::BackgroundService::BACKGROUND_FETCH, |
| base::BindOnce(&DidGetLoggedBackgroundServiceEvents, |
| run_loop.QuitClosure(), &feature_states)); |
| run_loop.Run(); |
| |
| return feature_states; |
| } |
| |
| void LogTestBackgroundServiceEvent(const std::string& log_message) { |
| context_->LogBackgroundServiceEventOnIO( |
| service_worker_registration_id_, origin_, |
| DevToolsBackgroundService::kBackgroundFetch, kEventName, kInstanceId, |
| {{"key", log_message}}); |
| } |
| |
| void StartRecording() { |
| EXPECT_CALL( |
| *this, OnRecordingStateChanged( |
| true, devtools::proto::BackgroundService::BACKGROUND_FETCH)); |
| context_->StartRecording( |
| devtools::proto::BackgroundService::BACKGROUND_FETCH); |
| |
| // Wait for the messages to propagate to the browser client. |
| thread_bundle_.RunUntilIdle(); |
| } |
| |
| void StopRecording() { |
| EXPECT_CALL( |
| *this, |
| OnRecordingStateChanged( |
| false, devtools::proto::BackgroundService::BACKGROUND_FETCH)); |
| context_->StopRecording( |
| devtools::proto::BackgroundService::BACKGROUND_FETCH); |
| |
| // Wait for the messages to propagate to the browser client. |
| thread_bundle_.RunUntilIdle(); |
| } |
| |
| void ClearLoggedBackgroundServiceEvents() { |
| context_->ClearLoggedBackgroundServiceEvents( |
| devtools::proto::BackgroundService::BACKGROUND_FETCH); |
| } |
| |
| TestBrowserThreadBundle thread_bundle_; // Must be first member. |
| url::Origin origin_ = url::Origin::Create(GURL("https://example.com")); |
| int64_t service_worker_registration_id_ = |
| blink::mojom::kInvalidServiceWorkerRegistrationId; |
| |
| private: |
| int64_t RegisterServiceWorker() { |
| GURL script_url(origin_.GetURL().spec() + "sw.js"); |
| int64_t service_worker_registration_id = |
| blink::mojom::kInvalidServiceWorkerRegistrationId; |
| |
| { |
| blink::mojom::ServiceWorkerRegistrationOptions options; |
| options.scope = origin_.GetURL(); |
| base::RunLoop run_loop; |
| embedded_worker_test_helper_.context()->RegisterServiceWorker( |
| script_url, options, |
| base::BindOnce(&DidRegisterServiceWorker, |
| &service_worker_registration_id, |
| run_loop.QuitClosure())); |
| |
| run_loop.Run(); |
| } |
| |
| if (service_worker_registration_id == |
| blink::mojom::kInvalidServiceWorkerRegistrationId) { |
| ADD_FAILURE() << "Could not obtain a valid Service Worker registration"; |
| return blink::mojom::kInvalidServiceWorkerRegistrationId; |
| } |
| |
| { |
| base::RunLoop run_loop; |
| embedded_worker_test_helper_.context()->storage()->FindRegistrationForId( |
| service_worker_registration_id, origin_.GetURL(), |
| base::BindOnce(&DidFindServiceWorkerRegistration, |
| &service_worker_registration_, |
| run_loop.QuitClosure())); |
| run_loop.Run(); |
| } |
| |
| // Wait for the worker to be activated. |
| base::RunLoop().RunUntilIdle(); |
| |
| if (!service_worker_registration_) { |
| ADD_FAILURE() << "Could not find the new Service Worker registration."; |
| return blink::mojom::kInvalidServiceWorkerRegistrationId; |
| } |
| |
| return service_worker_registration_id; |
| } |
| |
| EmbeddedWorkerTestHelper embedded_worker_test_helper_; |
| TestBrowserContext browser_context_; |
| scoped_refptr<DevToolsBackgroundServicesContextImpl> context_; |
| scoped_refptr<ServiceWorkerRegistration> service_worker_registration_; |
| std::unique_ptr<ContentBrowserClient> browser_client_; |
| |
| DISALLOW_COPY_AND_ASSIGN(DevToolsBackgroundServicesContextTest); |
| }; |
| |
| TEST_F(DevToolsBackgroundServicesContextTest, |
| NothingStoredWithRecordingModeOff) { |
| // Initially there are no entries. |
| EXPECT_TRUE(GetLoggedBackgroundServiceEvents().empty()); |
| |
| // "Log" some events and wait for them to finish. |
| LogTestBackgroundServiceEvent("f1"); |
| LogTestBackgroundServiceEvent("f2"); |
| |
| // There should still be nothing since recording mode is off. |
| EXPECT_TRUE(GetLoggedBackgroundServiceEvents().empty()); |
| } |
| |
| TEST_F(DevToolsBackgroundServicesContextTest, GetLoggedEvents) { |
| StartRecording(); |
| |
| // "Log" some events and wait for them to finish. |
| LogTestBackgroundServiceEvent("f1"); |
| LogTestBackgroundServiceEvent("f2"); |
| |
| // Check the values. |
| auto feature_events = GetLoggedBackgroundServiceEvents(); |
| ASSERT_EQ(feature_events.size(), 2u); |
| |
| for (const auto& feature_event : feature_events) { |
| EXPECT_EQ(feature_event.background_service(), |
| devtools::proto::BackgroundService::BACKGROUND_FETCH); |
| EXPECT_EQ(feature_event.origin(), origin_.GetURL().spec()); |
| EXPECT_EQ(feature_event.service_worker_registration_id(), |
| service_worker_registration_id_); |
| EXPECT_EQ(feature_event.event_name(), kEventName); |
| EXPECT_EQ(feature_event.instance_id(), kInstanceId); |
| ASSERT_EQ(feature_event.event_metadata().size(), 1u); |
| } |
| |
| EXPECT_EQ(feature_events[0].event_metadata().at("key"), "f1"); |
| EXPECT_EQ(feature_events[1].event_metadata().at("key"), "f2"); |
| |
| EXPECT_LE(feature_events[0].timestamp(), feature_events[1].timestamp()); |
| } |
| |
| TEST_F(DevToolsBackgroundServicesContextTest, StopRecording) { |
| StartRecording(); |
| // Initially there are no entries. |
| EXPECT_TRUE(GetLoggedBackgroundServiceEvents().empty()); |
| |
| // "Log" some events and wait for them to finish. |
| LogTestBackgroundServiceEvent("f1"); |
| StopRecording(); |
| LogTestBackgroundServiceEvent("f2"); |
| |
| // Check the values. |
| ASSERT_EQ(GetLoggedBackgroundServiceEvents().size(), 1u); |
| } |
| |
| TEST_F(DevToolsBackgroundServicesContextTest, DelegateExpirationTimes) { |
| // Initially expiration time is null. |
| EXPECT_TRUE(GetExpirationTime().is_null()); |
| EXPECT_FALSE(IsRecording()); |
| |
| // Toggle Recording mode, and now this should be non-null. |
| StartRecording(); |
| EXPECT_FALSE(GetExpirationTime().is_null()); |
| EXPECT_TRUE(IsRecording()); |
| |
| // The value should still be there on browser restarts. |
| SimulateBrowserRestart(); |
| EXPECT_FALSE(GetExpirationTime().is_null()); |
| EXPECT_TRUE(IsRecording()); |
| |
| // Stopping Recording mode should clear the value. |
| StopRecording(); |
| EXPECT_TRUE(GetExpirationTime().is_null()); |
| EXPECT_FALSE(IsRecording()); |
| SimulateBrowserRestart(); |
| EXPECT_TRUE(GetExpirationTime().is_null()); |
| EXPECT_FALSE(IsRecording()); |
| } |
| |
| TEST_F(DevToolsBackgroundServicesContextTest, RecordingExpiration) { |
| // Initially expiration time is null. |
| EXPECT_FALSE(IsRecording()); |
| |
| // Toggle Recording mode, and now this should be non-null. |
| StartRecording(); |
| EXPECT_TRUE(IsRecording()); |
| |
| SimulateOneWeekPassing(); |
| EXPECT_FALSE(GetExpirationTime().is_null()); |
| |
| // Recording should be true, with an expired value. |
| EXPECT_TRUE(IsRecording()); |
| |
| // Logging should not happen. |
| EXPECT_CALL(*this, OnEventReceived(_)).Times(0); |
| LogTestBackgroundServiceEvent("f1"); |
| |
| // Observers should be informed that recording stopped. |
| EXPECT_CALL(*this, |
| OnRecordingStateChanged( |
| false, devtools::proto::BackgroundService::BACKGROUND_FETCH)); |
| |
| thread_bundle_.RunUntilIdle(); |
| |
| // The expiration time entry should be cleared. |
| EXPECT_TRUE(GetExpirationTime().is_null()); |
| EXPECT_FALSE(IsRecording()); |
| } |
| |
| TEST_F(DevToolsBackgroundServicesContextTest, ClearLoggedEvents) { |
| StartRecording(); |
| |
| // "Log" some events and wait for them to finish. |
| LogTestBackgroundServiceEvent("f1"); |
| LogTestBackgroundServiceEvent("f2"); |
| |
| // Check the values. |
| auto feature_events = GetLoggedBackgroundServiceEvents(); |
| ASSERT_EQ(feature_events.size(), 2u); |
| |
| ClearLoggedBackgroundServiceEvents(); |
| |
| // Should be empty now. |
| feature_events = GetLoggedBackgroundServiceEvents(); |
| EXPECT_TRUE(feature_events.empty()); |
| } |
| |
| TEST_F(DevToolsBackgroundServicesContextTest, EventObserverCalled) { |
| { |
| EXPECT_CALL(*this, OnEventReceived(_)).Times(0); |
| LogTestBackgroundServiceEvent("f1"); |
| thread_bundle_.RunUntilIdle(); |
| } |
| |
| StartRecording(); |
| |
| { |
| EXPECT_CALL(*this, OnEventReceived(_)); |
| LogTestBackgroundServiceEvent("f2"); |
| thread_bundle_.RunUntilIdle(); |
| } |
| } |
| |
| } // namespace content |