blob: b06c7bea32c441eeb10a415830033e398067850e [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 "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