blob: 23373c89837704fa18510ca12bbc8461d48b6936 [file] [log] [blame]
// Copyright 2017 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "content/browser/geolocation/geolocation_service_impl.h"
#include <memory>
#include "base/functional/bind.h"
#include "base/functional/callback_helpers.h"
#include "base/run_loop.h"
#include "base/task/single_thread_task_runner.h"
#include "base/test/test_future.h"
#include "content/browser/permissions/permission_controller_impl.h"
#include "content/public/browser/device_service.h"
#include "content/public/browser/permission_controller.h"
#include "content/public/browser/permission_request_description.h"
#include "content/public/test/mock_permission_manager.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/test/test_render_frame_host.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "services/device/public/cpp/test/scoped_geolocation_overrider.h"
#include "services/device/public/mojom/geolocation.mojom.h"
#include "services/device/public/mojom/geolocation_context.mojom.h"
#include "services/device/public/mojom/geoposition.mojom.h"
#include "services/network/public/cpp/permissions_policy/origin_with_possible_wildcards.h"
#include "services/network/public/cpp/permissions_policy/permissions_policy_declaration.h"
#include "services/network/public/mojom/permissions_policy/permissions_policy_feature.mojom.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/permissions/permission_utils.h"
namespace content {
namespace {
using ::base::test::TestFuture;
using ::blink::mojom::GeolocationService;
using ::blink::mojom::PermissionStatus;
using ::device::mojom::Geolocation;
using ::device::mojom::GeopositionPtr;
using ::device::mojom::GeopositionResultPtr;
using PermissionCallback = base::OnceCallback<void(
const std::vector<blink::mojom::PermissionStatus>&)>;
double kMockLatitude = 1.0;
double kMockLongitude = 10.0;
class TestPermissionManager : public MockPermissionManager {
public:
TestPermissionManager() = default;
~TestPermissionManager() override = default;
void RequestPermissionsFromCurrentDocument(
RenderFrameHost* render_frame_host,
const PermissionRequestDescription& request_description,
base::OnceCallback<
void(const std::vector<blink::mojom::PermissionStatus>&)> callback)
override {
ASSERT_EQ(request_description.permissions.size(), 1u);
EXPECT_EQ(blink::PermissionDescriptorToPermissionType(
request_description.permissions[0]),
blink::PermissionType::GEOLOCATION);
EXPECT_TRUE(request_description.user_gesture);
request_callback_.Run(std::move(callback));
}
void SetRequestCallback(
base::RepeatingCallback<void(PermissionCallback)> request_callback) {
request_callback_ = std::move(request_callback);
}
private:
base::RepeatingCallback<void(PermissionCallback)> request_callback_;
};
class GeolocationServiceTest : public RenderViewHostImplTestHarness {
public:
GeolocationServiceTest(const GeolocationServiceTest&) = delete;
GeolocationServiceTest& operator=(const GeolocationServiceTest&) = delete;
protected:
GeolocationServiceTest() {}
~GeolocationServiceTest() override {}
void SetUp() override {
RenderViewHostImplTestHarness::SetUp();
NavigateAndCommit(GURL("https://www.google.com/maps"));
static_cast<TestBrowserContext*>(GetBrowserContext())
->SetPermissionControllerDelegate(
std::make_unique<TestPermissionManager>());
geolocation_overrider_ =
std::make_unique<device::ScopedGeolocationOverrider>(kMockLatitude,
kMockLongitude);
GetDeviceService().BindGeolocationContext(
context_.BindNewPipeAndPassReceiver());
}
void TearDown() override {
service_.reset();
context_.reset();
geolocation_overrider_.reset();
RenderViewHostImplTestHarness::TearDown();
}
void CreateEmbeddedFrameAndGeolocationService(
bool allow_via_permissions_policy) {
const GURL kEmbeddedUrl("https://embeddables.com/someframe");
network::ParsedPermissionsPolicy frame_policy = {};
if (allow_via_permissions_policy) {
frame_policy.push_back(
{network::mojom::PermissionsPolicyFeature::kGeolocation,
std::vector{*network::OriginWithPossibleWildcards::FromOrigin(
url::Origin::Create(kEmbeddedUrl))},
/*self_if_matches=*/std::nullopt,
/*matches_all_origins=*/false,
/*matches_opaque_src=*/false});
}
RenderFrameHost* embedded_rfh =
RenderFrameHostTester::For(main_rfh())
->AppendChildWithPolicy("", frame_policy);
RenderFrameHostTester::For(embedded_rfh)->InitializeRenderFrameIfNeeded();
auto navigation_simulator = NavigationSimulator::CreateRendererInitiated(
kEmbeddedUrl, embedded_rfh);
navigation_simulator->Commit();
embedded_rfh = navigation_simulator->GetFinalRenderFrameHost();
service_ =
std::make_unique<GeolocationServiceImpl>(context_.get(), embedded_rfh);
service_->Bind(service_remote_.BindNewPipeAndPassReceiver());
}
mojo::Remote<blink::mojom::GeolocationService>& service_remote() {
return service_remote_;
}
TestPermissionManager* permission_manager() {
return static_cast<TestPermissionManager*>(
GetBrowserContext()->GetPermissionControllerDelegate());
}
private:
std::unique_ptr<device::ScopedGeolocationOverrider> geolocation_overrider_;
std::unique_ptr<GeolocationServiceImpl> service_;
mojo::Remote<blink::mojom::GeolocationService> service_remote_;
mojo::Remote<device::mojom::GeolocationContext> context_;
};
} // namespace
TEST_F(GeolocationServiceTest, PermissionGrantedPolicyViolation) {
// The embedded frame is not allowed.
CreateEmbeddedFrameAndGeolocationService(
/*allow_via_permissions_policy=*/false);
permission_manager()->SetRequestCallback(
base::BindRepeating([](PermissionCallback callback) {
ADD_FAILURE() << "Permissions checked unexpectedly.";
}));
mojo::Remote<Geolocation> geolocation;
service_remote()->CreateGeolocation(
geolocation.BindNewPipeAndPassReceiver(), true,
base::BindOnce([](blink::mojom::PermissionStatus status) {
EXPECT_EQ(blink::mojom::PermissionStatus::DENIED, status);
}));
TestFuture<void> disconnect_future;
geolocation.set_disconnect_handler(disconnect_future.GetCallback());
geolocation->QueryNextPosition(
base::BindOnce([](GeopositionResultPtr result) {
ADD_FAILURE() << "Position updated unexpectedly";
}));
EXPECT_TRUE(disconnect_future.Wait());
}
TEST_F(GeolocationServiceTest, PermissionGrantedSync) {
CreateEmbeddedFrameAndGeolocationService(
/*allow_via_permissions_policy=*/true);
TestFuture<PermissionCallback> permission_request_future;
permission_manager()->SetRequestCallback(
base::BindRepeating([](PermissionCallback callback) {
std::move(callback).Run(std::vector{PermissionStatus::GRANTED});
}));
mojo::Remote<Geolocation> geolocation;
service_remote()->CreateGeolocation(
geolocation.BindNewPipeAndPassReceiver(), true,
base::BindOnce([](blink::mojom::PermissionStatus status) {
EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED, status);
}));
geolocation.set_disconnect_handler(base::BindOnce(
[] { ADD_FAILURE() << "Connection error handler called unexpectedly"; }));
TestFuture<GeopositionResultPtr> result_future;
geolocation->QueryNextPosition(result_future.GetCallback());
ASSERT_TRUE(result_future.Get()->is_position());
const auto& position = *result_future.Get()->get_position();
EXPECT_DOUBLE_EQ(kMockLatitude, position.latitude);
EXPECT_DOUBLE_EQ(kMockLongitude, position.longitude);
}
TEST_F(GeolocationServiceTest, PermissionDeniedSync) {
CreateEmbeddedFrameAndGeolocationService(
/*allow_via_permissions_policy=*/true);
permission_manager()->SetRequestCallback(
base::BindRepeating([](PermissionCallback callback) {
std::move(callback).Run(std::vector{PermissionStatus::DENIED});
}));
mojo::Remote<Geolocation> geolocation;
service_remote()->CreateGeolocation(
geolocation.BindNewPipeAndPassReceiver(), true,
base::BindOnce([](blink::mojom::PermissionStatus status) {
EXPECT_EQ(blink::mojom::PermissionStatus::DENIED, status);
}));
TestFuture<void> disconnect_future;
geolocation.set_disconnect_handler(disconnect_future.GetCallback());
geolocation->QueryNextPosition(
base::BindOnce([](GeopositionResultPtr result) {
ADD_FAILURE() << "Position updated unexpectedly";
}));
EXPECT_TRUE(disconnect_future.Wait());
}
TEST_F(GeolocationServiceTest, PermissionGrantedAsync) {
CreateEmbeddedFrameAndGeolocationService(
/*allow_via_permissions_policy=*/true);
permission_manager()->SetRequestCallback(
base::BindRepeating([](PermissionCallback permission_callback) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(permission_callback),
std::vector{PermissionStatus::GRANTED}));
}));
mojo::Remote<Geolocation> geolocation;
service_remote()->CreateGeolocation(
geolocation.BindNewPipeAndPassReceiver(), true,
base::BindOnce([](blink::mojom::PermissionStatus status) {
EXPECT_EQ(blink::mojom::PermissionStatus::GRANTED, status);
}));
geolocation.set_disconnect_handler(base::BindOnce(
[] { ADD_FAILURE() << "Connection error handler called unexpectedly"; }));
TestFuture<GeopositionResultPtr> result_future;
geolocation->QueryNextPosition(result_future.GetCallback());
ASSERT_TRUE(result_future.Get()->is_position());
const auto& position = *result_future.Get()->get_position();
EXPECT_DOUBLE_EQ(kMockLatitude, position.latitude);
EXPECT_DOUBLE_EQ(kMockLongitude, position.longitude);
}
TEST_F(GeolocationServiceTest, PermissionDeniedAsync) {
CreateEmbeddedFrameAndGeolocationService(
/*allow_via_permissions_policy=*/true);
permission_manager()->SetRequestCallback(
base::BindRepeating([](PermissionCallback permission_callback) {
base::SingleThreadTaskRunner::GetCurrentDefault()->PostTask(
FROM_HERE, base::BindOnce(std::move(permission_callback),
std::vector{PermissionStatus::DENIED}));
}));
mojo::Remote<Geolocation> geolocation;
service_remote()->CreateGeolocation(
geolocation.BindNewPipeAndPassReceiver(), true,
base::BindOnce([](blink::mojom::PermissionStatus status) {
EXPECT_EQ(blink::mojom::PermissionStatus::DENIED, status);
}));
TestFuture<void> disconnect_future;
geolocation.set_disconnect_handler(disconnect_future.GetCallback());
geolocation->QueryNextPosition(
base::BindOnce([](GeopositionResultPtr result) {
ADD_FAILURE() << "Position updated unexpectedly";
}));
EXPECT_TRUE(disconnect_future.Wait());
}
TEST_F(GeolocationServiceTest, ServiceClosedBeforePermissionResponse) {
CreateEmbeddedFrameAndGeolocationService(
/*allow_via_permissions_policy=*/true);
mojo::Remote<Geolocation> geolocation;
service_remote()->CreateGeolocation(
geolocation.BindNewPipeAndPassReceiver(), true,
base::BindOnce([](blink::mojom::PermissionStatus) {
ADD_FAILURE() << "PositionStatus received unexpectedly.";
}));
// Don't immediately respond to the request.
permission_manager()->SetRequestCallback(base::DoNothing());
base::RunLoop loop;
service_remote().reset();
geolocation->QueryNextPosition(
base::BindOnce([](GeopositionResultPtr result) {
ADD_FAILURE() << "Position updated unexpectedly";
}));
loop.RunUntilIdle();
}
} // namespace content