blob: 179f8270747047f19a1970f1ea7e97d91d9cc6eb [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/sms/sms_service.h"
#include <string>
#include <utility>
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h"
#include "base/run_loop.h"
#include "base/single_thread_task_runner.h"
#include "base/task/post_task.h"
#include "base/test/bind_test_util.h"
#include "base/test/metrics/histogram_tester.h"
#include "content/browser/sms/sms_fetcher_impl.h"
#include "content/browser/sms/test/mock_sms_provider.h"
#include "content/browser/sms/test/mock_sms_web_contents_delegate.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/browser/sms_fetcher.h"
#include "content/public/common/service_manager_connection.h"
#include "content/public/test/navigation_simulator.h"
#include "content/public/test/test_browser_context.h"
#include "content/public/test/test_renderer_host.h"
#include "mojo/public/cpp/bindings/remote.h"
#include "mojo/public/cpp/test_support/test_utils.h"
#include "services/service_manager/public/cpp/bind_source_info.h"
#include "services/service_manager/public/cpp/connector.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "third_party/blink/public/common/sms/sms_receiver_destroyed_reason.h"
#include "third_party/blink/public/mojom/sms/sms_receiver.mojom.h"
using base::BindLambdaForTesting;
using base::Optional;
using blink::SmsReceiverDestroyedReason;
using blink::mojom::SmsReceiver;
using blink::mojom::SmsStatus;
using std::string;
using ::testing::_;
using ::testing::Invoke;
using ::testing::NiceMock;
using ::testing::Return;
using url::Origin;
namespace content {
class RenderFrameHost;
namespace {
const char kTestUrl[] = "https://www.google.com";
// Service encapsulates a SmsService endpoint, with all of its dependencies
// mocked out (and the common plumbing needed to inject them), and a
// mojo::Remote<SmsReceiver> endpoint that tests can use to make requests.
// It exposes some common methods, like MakeRequest and NotifyReceive, but it
// also exposes the low level mocks that enables tests to set expectations and
// control the testing environment.
class Service {
public:
Service(WebContents* web_contents, const Origin& origin) {
auto provider = std::make_unique<NiceMock<MockSmsProvider>>();
provider_ = provider.get();
fetcher_ = static_cast<SmsFetcherImpl*>(
SmsFetcher::Get(web_contents->GetBrowserContext()));
fetcher_->SetSmsProviderForTesting(std::move(provider));
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents);
web_contents_impl->SetDelegate(&delegate_);
service_ = std::make_unique<SmsService>(
fetcher_, origin, web_contents->GetMainFrame(),
service_remote_.BindNewPipeAndPassReceiver());
}
Service(WebContents* web_contents)
: Service(web_contents,
web_contents->GetMainFrame()->GetLastCommittedOrigin()) {}
NiceMock<MockSmsProvider>* provider() { return provider_; }
SmsFetcherImpl* fetcher() { return fetcher_; }
void CreateSmsPrompt(RenderFrameHost* rfh) {
EXPECT_CALL(delegate_, CreateSmsPrompt(rfh, _, _, _, _))
.WillOnce(Invoke([=](RenderFrameHost*, const Origin& origin,
const std::string&, base::OnceClosure on_confirm,
base::OnceClosure on_cancel) {
confirm_callback_ = std::move(on_confirm);
dismiss_callback_ = std::move(on_cancel);
}));
}
void ConfirmPrompt() {
if (!confirm_callback_.is_null()) {
std::move(confirm_callback_).Run();
dismiss_callback_.Reset();
return;
}
FAIL() << "SmsInfobar not available";
}
void DismissPrompt() {
if (dismiss_callback_.is_null()) {
FAIL() << "SmsInfobar not available";
return;
}
std::move(dismiss_callback_).Run();
confirm_callback_.Reset();
}
void MakeRequest(SmsReceiver::ReceiveCallback callback) {
service_remote_->Receive(std::move(callback));
}
void AbortRequest() { service_remote_->Abort(); }
void NotifyReceive(const GURL& url, const string& otp) {
provider_->NotifyReceive(Origin::Create(url), otp, "");
}
private:
NiceMock<MockSmsWebContentsDelegate> delegate_;
NiceMock<MockSmsProvider>* provider_;
SmsFetcherImpl* fetcher_ = nullptr;
mojo::Remote<blink::mojom::SmsReceiver> service_remote_;
std::unique_ptr<SmsService> service_;
base::OnceClosure confirm_callback_;
base::OnceClosure dismiss_callback_;
};
class SmsServiceTest : public RenderViewHostTestHarness {
protected:
SmsServiceTest() {}
~SmsServiceTest() override {}
std::unique_ptr<base::HistogramSamples> GetHistogramSamplesSinceTestStart(
const std::string& name) {
return histogram_tester_.GetHistogramSamplesSinceCreation(name);
}
void ExpectDestroyedReasonCount(SmsReceiverDestroyedReason bucket,
int32_t count) {
histogram_tester_.ExpectBucketCount("Blink.Sms.Receive.DestroyedReason",
bucket, count);
}
private:
base::HistogramTester histogram_tester_;
DISALLOW_COPY_AND_ASSIGN(SmsServiceTest);
};
} // namespace
TEST_F(SmsServiceTest, Basic) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi");
service.ConfirmPrompt();
}));
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kSuccess, status);
EXPECT_EQ("hi", otp.value());
loop.Quit();
}));
loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers());
}
TEST_F(SmsServiceTest, HandlesMultipleCalls) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
{
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "first");
service.ConfirmPrompt();
}));
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ("first", otp.value());
EXPECT_EQ(SmsStatus::kSuccess, status);
loop.Quit();
}));
loop.Run();
}
{
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second");
service.ConfirmPrompt();
}));
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ("second", otp.value());
EXPECT_EQ(SmsStatus::kSuccess, status);
loop.Quit();
}));
loop.Run();
}
}
TEST_F(SmsServiceTest, IgnoreFromOtherOrigins) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
SmsStatus sms_status;
Optional<string> response;
base::RunLoop sms_loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
// Delivers an SMS from an unrelated origin first and expect the
// receiver to ignore it.
service.NotifyReceive(GURL("http://b.com"), "wrong");
service.NotifyReceive(GURL(kTestUrl), "right");
service.ConfirmPrompt();
}));
service.MakeRequest(
BindLambdaForTesting([&sms_status, &response, &sms_loop](
SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
sms_status = status;
response = otp;
sms_loop.Quit();
}));
sms_loop.Run();
EXPECT_EQ("right", response.value());
EXPECT_EQ(SmsStatus::kSuccess, sms_status);
}
TEST_F(SmsServiceTest, ExpectOneReceiveTwo) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
SmsStatus sms_status;
Optional<string> response;
base::RunLoop sms_loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
// Delivers two SMSes for the same origin, even if only one was being
// expected.
ASSERT_TRUE(service.fetcher()->HasSubscribers());
service.NotifyReceive(GURL(kTestUrl), "first");
service.ConfirmPrompt();
ASSERT_FALSE(service.fetcher()->HasSubscribers());
service.NotifyReceive(GURL(kTestUrl), "second");
}));
service.MakeRequest(
BindLambdaForTesting([&sms_status, &response, &sms_loop](
SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
sms_status = status;
response = otp;
sms_loop.Quit();
}));
sms_loop.Run();
EXPECT_EQ("first", response.value());
EXPECT_EQ(SmsStatus::kSuccess, sms_status);
}
TEST_F(SmsServiceTest, AtMostOneSmsRequestPerOrigin) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
SmsStatus sms_status1;
Optional<string> response1;
SmsStatus sms_status2;
Optional<string> response2;
base::RunLoop sms1_loop, sms2_loop;
// Expect SMS Prompt to be created once.
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve())
.WillOnce(Return())
.WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second");
service.ConfirmPrompt();
}));
service.MakeRequest(
BindLambdaForTesting([&sms_status1, &response1, &sms1_loop](
SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
sms_status1 = status;
response1 = otp;
sms1_loop.Quit();
}));
// Make the 2nd SMS request which will cancel the 1st request because only
// one request can be pending per origin per tab.
service.MakeRequest(
BindLambdaForTesting([&sms_status2, &response2, &sms2_loop](
SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
sms_status2 = status;
response2 = otp;
sms2_loop.Quit();
}));
sms1_loop.Run();
sms2_loop.Run();
EXPECT_EQ(base::nullopt, response1);
EXPECT_EQ(SmsStatus::kCancelled, sms_status1);
EXPECT_EQ("second", response2.value());
EXPECT_EQ(SmsStatus::kSuccess, sms_status2);
}
TEST_F(SmsServiceTest, SecondRequestDuringPrompt) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
SmsStatus sms_status1;
Optional<string> response1;
SmsStatus sms_status2;
Optional<string> response2;
base::RunLoop sms_loop;
// Expect SMS Prompt to be created once.
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second");
}));
// First request.
service.MakeRequest(
BindLambdaForTesting([&sms_status1, &response1, &service](
SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
sms_status1 = status;
response1 = otp;
service.ConfirmPrompt();
}));
// Make second request before confirming prompt.
service.MakeRequest(
BindLambdaForTesting([&sms_status2, &response2, &sms_loop](
SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
sms_status2 = status;
response2 = otp;
sms_loop.Quit();
}));
sms_loop.Run();
EXPECT_EQ(base::nullopt, response1);
EXPECT_EQ(SmsStatus::kCancelled, sms_status1);
EXPECT_EQ("second", response2.value());
EXPECT_EQ(SmsStatus::kSuccess, sms_status2);
}
TEST_F(SmsServiceTest, CleansUp) {
NavigateAndCommit(GURL(kTestUrl));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
auto provider = std::make_unique<MockSmsProvider>();
MockSmsProvider* mock_provider_ptr = provider.get();
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(),
std::move(provider));
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop navigate;
EXPECT_CALL(*mock_provider_ptr, Retrieve()).WillOnce(Invoke([&navigate]() {
navigate.Quit();
}));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting(
[&reload](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, sms);
reload.Quit();
}));
navigate.Run();
// Simulates the user reloading the page and navigating away, which
// destructs the service.
NavigateAndCommit(GURL(kTestUrl));
reload.Run();
ASSERT_FALSE(fetcher.HasSubscribers());
}
TEST_F(SmsServiceTest, PromptsDialog) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi");
service.ConfirmPrompt();
}));
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ("hi", otp.value());
EXPECT_EQ(SmsStatus::kSuccess, status);
loop.Quit();
}));
loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers());
}
TEST_F(SmsServiceTest, Cancel) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kCancelled, status);
EXPECT_EQ(base::nullopt, otp);
loop.Quit();
}));
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi");
service.DismissPrompt();
}));
loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers());
}
TEST_F(SmsServiceTest, Abort) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop loop;
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kAborted, status);
EXPECT_EQ(base::nullopt, otp);
loop.Quit();
}));
service.AbortRequest();
loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers());
}
TEST_F(SmsServiceTest, AbortWhilePrompt) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kAborted, status);
EXPECT_EQ(base::nullopt, otp);
loop.Quit();
}));
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "ABC");
}));
service.AbortRequest();
loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers());
service.ConfirmPrompt();
}
TEST_F(SmsServiceTest, SecondRequestWhilePrompt) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop callback_loop1, callback_loop2, req_loop;
service.CreateSmsPrompt(main_rfh());
service.MakeRequest(BindLambdaForTesting(
[&callback_loop1](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kAborted, status);
EXPECT_EQ(base::nullopt, otp);
callback_loop1.Quit();
}));
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi");
service.AbortRequest();
}));
callback_loop1.Run();
base::ThreadTaskRunnerHandle::Get()->PostTaskAndReply(
FROM_HERE, BindLambdaForTesting([&]() {
service.MakeRequest(BindLambdaForTesting(
[&callback_loop2](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kSuccess, status);
EXPECT_EQ("hi", otp.value());
callback_loop2.Quit();
}));
}),
req_loop.QuitClosure());
req_loop.Run();
// Simulate pressing 'Verify' on Infobar.
service.ConfirmPrompt();
callback_loop2.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers());
}
TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "ABC");
service.ConfirmPrompt();
}));
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) { loop.Quit(); }));
loop.Run();
std::unique_ptr<base::HistogramSamples> continue_samples(
GetHistogramSamplesSinceTestStart(
"Blink.Sms.Receive.TimeContinueOnSuccess"));
EXPECT_EQ(1, continue_samples->TotalCount());
std::unique_ptr<base::HistogramSamples> receive_samples(
GetHistogramSamplesSinceTestStart("Blink.Sms.Receive.TimeSmsReceive"));
EXPECT_EQ(1, receive_samples->TotalCount());
}
TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
// Histogram will be recorded if the SMS has already arrived.
base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi");
service.DismissPrompt();
}));
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) { loop.Quit(); }));
loop.Run();
std::unique_ptr<base::HistogramSamples> samples(
GetHistogramSamplesSinceTestStart(
"Blink.Sms.Receive.TimeCancelOnSuccess"));
EXPECT_EQ(1, samples->TotalCount());
std::unique_ptr<base::HistogramSamples> receive_samples(
GetHistogramSamplesSinceTestStart("Blink.Sms.Receive.TimeSmsReceive"));
EXPECT_EQ(1, receive_samples->TotalCount());
}
TEST_F(SmsServiceTest, RecordMetricsForNewPage) {
// This test depends on the page being destroyed on navigation.
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
NavigateAndCommit(GURL(kTestUrl));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
auto provider = std::make_unique<MockSmsProvider>();
MockSmsProvider* mock_provider_ptr = provider.get();
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(),
std::move(provider));
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop navigate;
EXPECT_CALL(*mock_provider_ptr, Retrieve()).WillOnce(Invoke([&navigate]() {
navigate.Quit();
}));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting(
[&reload](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, sms);
reload.Quit();
}));
navigate.Run();
// Simulates the user navigating to a new page.
NavigateAndCommit(GURL("https://www.example.com"));
reload.Run();
ExpectDestroyedReasonCount(SmsReceiverDestroyedReason::kNavigateNewPage, 1);
}
TEST_F(SmsServiceTest, RecordMetricsForSamePage) {
NavigateAndCommit(GURL(kTestUrl));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
auto provider = std::make_unique<MockSmsProvider>();
MockSmsProvider* mock_provider_ptr = provider.get();
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(),
std::move(provider));
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop navigate;
EXPECT_CALL(*mock_provider_ptr, Retrieve()).WillOnce(Invoke([&navigate]() {
navigate.Quit();
}));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting(
[&reload](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, sms);
reload.Quit();
}));
navigate.Run();
// Simulates the user re-navigating to the same page through the omni-box.
NavigateAndCommit(GURL(kTestUrl));
reload.Run();
ExpectDestroyedReasonCount(SmsReceiverDestroyedReason::kNavigateSamePage, 1);
}
TEST_F(SmsServiceTest, RecordMetricsForExistingPage) {
// This test depends on the page being destroyed on navigation.
web_contents()->GetController().GetBackForwardCache().DisableForTesting(
content::BackForwardCache::TEST_ASSUMES_NO_CACHING);
NavigateAndCommit(GURL(kTestUrl)); // Add to history.
NavigateAndCommit(GURL("https://example.com"));
NiceMock<MockSmsWebContentsDelegate> delegate;
WebContentsImpl* web_contents_impl =
reinterpret_cast<WebContentsImpl*>(web_contents());
web_contents_impl->SetDelegate(&delegate);
auto provider = std::make_unique<MockSmsProvider>();
MockSmsProvider* mock_provider_ptr = provider.get();
SmsFetcherImpl fetcher(web_contents()->GetBrowserContext(),
std::move(provider));
mojo::Remote<blink::mojom::SmsReceiver> service;
SmsService::Create(&fetcher, main_rfh(),
service.BindNewPipeAndPassReceiver());
base::RunLoop navigate;
EXPECT_CALL(*mock_provider_ptr, Retrieve()).WillOnce(Invoke([&navigate]() {
navigate.Quit();
}));
base::RunLoop reload;
service->Receive(base::BindLambdaForTesting(
[&reload](SmsStatus status, const Optional<string>& otp,
const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kTimeout, status);
EXPECT_EQ(base::nullopt, sms);
reload.Quit();
}));
navigate.Run();
// Simulates the user re-navigating to an existing history page.
NavigationSimulator::GoBack(web_contents());
reload.Run();
ExpectDestroyedReasonCount(SmsReceiverDestroyedReason::kNavigateExistingPage,
1);
}
} // namespace content