Commit 3a50e491 authored by Ayu Ishii's avatar Ayu Ishii Committed by Commit Bot

Reland [sms] Implement AbortController for SMSReceiver API

This change allows developers to use the SMS Receiver API with the Abort
Controller to cancel once the API is called. This will help developers with
flows for retrying and sending a new code and they want to abort the previous
call they made to the API. If the InfoBar is up during abort, it will
leave the InfoBar open while returning an AbortError to the website. If
another request is made while the InfoBar for 1st request is still open (after
abort), upon clicking 'Verify' the SMS retrieved for the 1st request will
return to the 2nd request.

Original CL: https://crrev.com/c/1866914
Revert CL: https://crrev.com/c/1930275
Failure ticket: https://crbug.com/1027386

Failure locally reproducible with following test command:
python testing/scripts/run_isolated_script_test.py third_party/blink/tools/run_web_tests.py  -t LinuxTest third_party/blink/web_tests/external/wpt/sms/interceptor.https.html --isolated-script-test-output=output.json --isolated-script-test-perf-output=perftest-output.json --additional-expectations third_party/blink/web_tests/LeakExpectations --enable-leak-detection

Bug: 976401
Change-Id: I9b1208a6cf9c05185fa734809ab275235238cf4d
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/1929864Reviewed-by: default avatarReilly Grant <reillyg@chromium.org>
Reviewed-by: default avatarDaniel Cheng <dcheng@chromium.org>
Commit-Queue: Ayu Ishii <ayui@chromium.org>
Cr-Commit-Position: refs/heads/master@{#718954}
parent 98bbbda1
...@@ -70,6 +70,34 @@ class SmsBrowserTest : public ContentBrowserTest { ...@@ -70,6 +70,34 @@ class SmsBrowserTest : public ContentBrowserTest {
EXPECT_TRUE(ukm_recorder()->GetEntriesByName(Entry::kEntryName).empty()); EXPECT_TRUE(ukm_recorder()->GetEntriesByName(Entry::kEntryName).empty());
} }
void ExpectSmsPrompt() {
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _))
.WillOnce(Invoke([&](RenderFrameHost*, const url::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();
}
protected: protected:
void SetUpOnMainThread() override { void SetUpOnMainThread() override {
host_resolver()->AddRule("*", "127.0.0.1"); host_resolver()->AddRule("*", "127.0.0.1");
...@@ -97,6 +125,9 @@ class SmsBrowserTest : public ContentBrowserTest { ...@@ -97,6 +125,9 @@ class SmsBrowserTest : public ContentBrowserTest {
ContentMockCertVerifier cert_verifier_; ContentMockCertVerifier cert_verifier_;
std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_; std::unique_ptr<ukm::TestAutoSetUkmRecorder> ukm_recorder_;
base::OnceClosure confirm_callback_;
base::OnceClosure dismiss_callback_;
}; };
} // namespace } // namespace
...@@ -107,11 +138,7 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Receive) { ...@@ -107,11 +138,7 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Receive) {
shell()->web_contents()->SetDelegate(&delegate_); shell()->web_contents()->SetDelegate(&delegate_);
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(
Invoke([&](RenderFrameHost*, const url::Origin&, const std::string&,
base::OnceClosure on_confirm,
base::OnceClosure) { std::move(on_confirm).Run(); }));
auto* provider = new NiceMock<MockSmsProvider>(); auto* provider = new NiceMock<MockSmsProvider>();
BrowserMainLoop::GetInstance()->SetSmsProviderForTesting( BrowserMainLoop::GetInstance()->SetSmsProviderForTesting(
...@@ -125,11 +152,13 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Receive) { ...@@ -125,11 +152,13 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Receive) {
}) (); }) ();
)"; )";
EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&provider, &url]() { EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&]() {
provider->NotifyReceive(url::Origin::Create(url), "", "hello"); provider->NotifyReceive(url::Origin::Create(url), "", "hello");
ConfirmPrompt();
})); }));
// Wait for UKM to be recorded to avoid race condition. // Wait for UKM to be recorded to avoid race condition between outcome
// capture and evaluation.
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
...@@ -149,10 +178,7 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOrigin) { ...@@ -149,10 +178,7 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOrigin) {
shell()->web_contents()->SetDelegate(&delegate_); shell()->web_contents()->SetDelegate(&delegate_);
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(Invoke([](RenderFrameHost*, const url::Origin&,
const std::string&, base::OnceClosure on_confirm,
base::OnceClosure) { std::move(on_confirm).Run(); }));
auto* provider = new NiceMock<MockSmsProvider>(); auto* provider = new NiceMock<MockSmsProvider>();
BrowserMainLoop::GetInstance()->SetSmsProviderForTesting( BrowserMainLoop::GetInstance()->SetSmsProviderForTesting(
...@@ -170,13 +196,13 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOrigin) { ...@@ -170,13 +196,13 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOrigin) {
}) (); }) ();
)"; )";
EXPECT_CALL(*provider, Retrieve()) EXPECT_CALL(*provider, Retrieve()).WillOnce(Return()).WillOnce(Invoke([&]() {
.WillOnce(Return()) provider->NotifyReceive(url::Origin::Create(url), "", "hello");
.WillOnce(Invoke([&url, &provider]() { ConfirmPrompt();
provider->NotifyReceive(url::Origin::Create(url), "", "hello"); }));
}));
// Wait for UKM to be recorded to avoid race condition. // Wait for UKM to be recorded to avoid race condition between outcome
// capture and evaluation.
base::RunLoop ukm_loop1; base::RunLoop ukm_loop1;
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop1.QuitClosure()); ukm_loop1.QuitClosure());
...@@ -238,17 +264,15 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOriginPerTab) { ...@@ -238,17 +264,15 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOriginPerTab) {
{ {
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(
Invoke([](RenderFrameHost*, const url::Origin&, const std::string&,
base::OnceClosure on_confirm,
base::OnceClosure) { std::move(on_confirm).Run(); }));
// Wait for UKM to be recorded to avoid race condition. // Wait for UKM to be recorded to avoid race condition between outcome
// capture and evaluation.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
provider->NotifyReceive(url::Origin::Create(url), "", "hello1"); provider->NotifyReceive(url::Origin::Create(url), "", "hello1");
ConfirmPrompt();
ukm_loop.Run(); ukm_loop.Run();
} }
...@@ -264,17 +288,15 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOriginPerTab) { ...@@ -264,17 +288,15 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AtMostOneSmsRequestPerOriginPerTab) {
{ {
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(
Invoke([](RenderFrameHost*, const url::Origin&, const std::string&,
base::OnceClosure on_confirm,
base::OnceClosure) { std::move(on_confirm).Run(); }));
// Wait for UKM to be recorded to avoid race condition. // Wait for UKM to be recorded to avoid race condition between outcome
// capture and evaluation.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
provider->NotifyReceive(url::Origin::Create(url), "", "hello2"); provider->NotifyReceive(url::Origin::Create(url), "", "hello2");
ConfirmPrompt();
ukm_loop.Run(); ukm_loop.Run();
} }
...@@ -298,7 +320,6 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Reload) { ...@@ -298,7 +320,6 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Reload) {
// kicks off the sms receiver, adding the service // kicks off the sms receiver, adding the service
// to the observer's list. // to the observer's list.
navigator.sms.receive(); navigator.sms.receive();
true
)"; )";
base::RunLoop loop; base::RunLoop loop;
...@@ -309,13 +330,14 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Reload) { ...@@ -309,13 +330,14 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Reload) {
loop.Quit(); loop.Quit();
})); }));
EXPECT_EQ(true, EvalJs(shell(), script)); EXPECT_TRUE(ExecJs(shell(), script));
loop.Run(); loop.Run();
ASSERT_TRUE(GetSmsFetcher()->HasSubscribers()); ASSERT_TRUE(GetSmsFetcher()->HasSubscribers());
// Wait for UKM to be recorded to avoid race condition. // Wait for UKM to be recorded to avoid race condition between outcome
// capture and evaluation.
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
...@@ -338,18 +360,13 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Close) { ...@@ -338,18 +360,13 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Close) {
BrowserMainLoop::GetInstance()->SetSmsProviderForTesting( BrowserMainLoop::GetInstance()->SetSmsProviderForTesting(
base::WrapUnique(provider)); base::WrapUnique(provider));
std::string script = R"(
navigator.sms.receive();
true
)";
base::RunLoop loop; base::RunLoop loop;
EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&loop]() { EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&loop]() {
loop.Quit(); loop.Quit();
})); }));
EXPECT_EQ(true, EvalJs(shell(), script)); EXPECT_TRUE(ExecJs(shell(), R"( navigator.sms.receive(); )"));
loop.Run(); loop.Run();
...@@ -377,64 +394,38 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsSameOrigin) { ...@@ -377,64 +394,38 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsSameOrigin) {
EXPECT_TRUE(NavigateToURL(tab1, url)); EXPECT_TRUE(NavigateToURL(tab1, url));
EXPECT_TRUE(NavigateToURL(tab2, url)); EXPECT_TRUE(NavigateToURL(tab2, url));
tab1->web_contents()->SetDelegate(&delegate_);
tab2->web_contents()->SetDelegate(&delegate_);
EXPECT_CALL(*provider, Retrieve()).Times(2);
std::string script = R"( std::string script = R"(
navigator.sms.receive().then(({content}) => { var sms = navigator.sms.receive().then(({content}) => {
sms = content; return content;
}); });
true
)"; )";
{ // First tab registers an observer.
base::RunLoop loop; EXPECT_TRUE(ExecJs(tab1, script));
tab1->web_contents()->SetDelegate(&delegate_);
EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&loop]() {
loop.Quit();
}));
// First tab registers an observer.
EXPECT_EQ(true, EvalJs(tab1, script));
loop.Run();
}
{
base::RunLoop loop;
tab2->web_contents()->SetDelegate(&delegate_);
EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&loop]() { // Second tab registers an observer.
loop.Quit(); EXPECT_TRUE(ExecJs(tab2, script));
}));
// Second tab registers an observer.
EXPECT_EQ(true, EvalJs(tab2, script));
loop.Run();
}
ASSERT_TRUE(provider->HasObservers()); ASSERT_TRUE(provider->HasObservers());
{ {
base::RunLoop loop;
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(Invoke(
[&loop](RenderFrameHost*, const url::Origin&, const std::string&, // Wait for UKM to be recorded to avoid race condition between outcome
base::OnceClosure on_confirm, base::OnceClosure) { // capture and evaluation.
std::move(on_confirm).Run();
loop.Quit();
}));
// Wait for UKM to be recorded to avoid race condition.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
provider->NotifyReceive(url::Origin::Create(url), "", "hello1"); provider->NotifyReceive(url::Origin::Create(url), "", "hello1");
ConfirmPrompt();
loop.Run();
ukm_loop.Run(); ukm_loop.Run();
} }
...@@ -447,24 +438,18 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsSameOrigin) { ...@@ -447,24 +438,18 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsSameOrigin) {
ukm_recorder()->Purge(); ukm_recorder()->Purge();
{ {
base::RunLoop loop;
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(Invoke(
[&loop](RenderFrameHost*, const url::Origin&, const std::string&, // Wait for UKM to be recorded to avoid race condition between outcome
base::OnceClosure on_confirm, base::OnceClosure) { // capture and evaluation.
std::move(on_confirm).Run();
loop.Quit();
}));
// Wait for UKM to be recorded to avoid race condition.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
provider->NotifyReceive(url::Origin::Create(url), "", "hello2"); provider->NotifyReceive(url::Origin::Create(url), "", "hello2");
ConfirmPrompt();
loop.Run();
ukm_loop.Run(); ukm_loop.Run();
} }
...@@ -494,44 +479,32 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsDifferentOrigin) { ...@@ -494,44 +479,32 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsDifferentOrigin) {
EXPECT_TRUE(NavigateToURL(tab2, url2)); EXPECT_TRUE(NavigateToURL(tab2, url2));
std::string script = R"( std::string script = R"(
navigator.sms.receive().then(({content}) => { var sms = navigator.sms.receive().then(({content}) => {
sms = content; return content;
}); });
true;
)"; )";
base::RunLoop loop; base::RunLoop loop;
EXPECT_CALL(*provider, Retrieve()) EXPECT_CALL(*provider, Retrieve()).Times(2);
.WillOnce(testing::Return())
.WillOnce(Invoke([&loop]() { loop.Quit(); }));
tab1->web_contents()->SetDelegate(&delegate_); tab1->web_contents()->SetDelegate(&delegate_);
tab2->web_contents()->SetDelegate(&delegate_); tab2->web_contents()->SetDelegate(&delegate_);
EXPECT_TRUE(ExecJs(tab1, script));
EXPECT_EQ(true, EvalJs(tab1, script)); EXPECT_TRUE(ExecJs(tab2, script));
EXPECT_EQ(true, EvalJs(tab2, script));
loop.Run();
ASSERT_TRUE(provider->HasObservers()); ASSERT_TRUE(provider->HasObservers());
{ {
base::RunLoop loop;
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(Invoke( // Wait for UKM to be recorded to avoid race condition between outcome
[&loop](RenderFrameHost*, const url::Origin&, const std::string&, // capture and evaluation.
base::OnceClosure on_confirm, base::OnceClosure) {
std::move(on_confirm).Run();
loop.Quit();
}));
// Wait for UKM to be recorded to avoid race condition.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
provider->NotifyReceive(url::Origin::Create(url1), "", "hello1"); provider->NotifyReceive(url::Origin::Create(url1), "", "hello1");
loop.Run(); ConfirmPrompt();
ukm_loop.Run(); ukm_loop.Run();
} }
...@@ -540,20 +513,14 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsDifferentOrigin) { ...@@ -540,20 +513,14 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, TwoTabsDifferentOrigin) {
ASSERT_TRUE(provider->HasObservers()); ASSERT_TRUE(provider->HasObservers());
{ {
base::RunLoop loop;
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(Invoke( // Wait for UKM to be recorded to avoid race condition between outcome
[&loop](RenderFrameHost*, const url::Origin&, const std::string&, // capture and evaluation.
base::OnceClosure on_confirm, base::OnceClosure) {
std::move(on_confirm).Run();
loop.Quit();
}));
// Wait for UKM to be recorded to avoid race condition.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
provider->NotifyReceive(url::Origin::Create(url2), "", "hello2"); provider->NotifyReceive(url::Origin::Create(url2), "", "hello2");
loop.Run(); ConfirmPrompt();
ukm_loop.Run(); ukm_loop.Run();
} }
...@@ -573,19 +540,16 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, SmsReceivedAfterTabIsClosed) { ...@@ -573,19 +540,16 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, SmsReceivedAfterTabIsClosed) {
BrowserMainLoop::GetInstance()->SetSmsProviderForTesting( BrowserMainLoop::GetInstance()->SetSmsProviderForTesting(
base::WrapUnique(provider)); base::WrapUnique(provider));
std::string script = R"(
// kicks off an sms receiver call, but deliberately leaves it hanging.
navigator.sms.receive();
true
)";
base::RunLoop loop; base::RunLoop loop;
EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&loop]() { EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&loop]() {
loop.Quit(); loop.Quit();
})); }));
EXPECT_EQ(true, EvalJs(shell(), script)); EXPECT_TRUE(ExecJs(shell(), R"(
// kicks off an sms receiver call, but deliberately leaves it hanging.
navigator.sms.receive();
)"));
loop.Run(); loop.Run();
...@@ -606,36 +570,26 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Cancels) { ...@@ -606,36 +570,26 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Cancels) {
shell()->web_contents()->SetDelegate(&delegate_); shell()->web_contents()->SetDelegate(&delegate_);
base::RunLoop loop;
base::RunLoop ukm_loop; base::RunLoop ukm_loop;
EXPECT_CALL(delegate_, CreateSmsPrompt(_, _, _, _, _)) ExpectSmsPrompt();
.WillOnce(Invoke([&loop](RenderFrameHost*, const url::Origin&,
const std::string&, base::OnceClosure,
base::OnceClosure on_cancel) {
// Simulates the user pressing "cancel".
std::move(on_cancel).Run();
loop.Quit();
}));
EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&provider, &url]() { EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&]() {
provider->NotifyReceive(url::Origin::Create(url), "", "hello"); provider->NotifyReceive(url::Origin::Create(url), "", "hello");
DismissPrompt();
})); }));
// Wait for UKM to be recorded to avoid race condition. // Wait for UKM to be recorded to avoid race condition between outcome
// capture and evaluation.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName, ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure()); ukm_loop.QuitClosure());
std::string script = R"( EXPECT_TRUE(ExecJs(shell(), R"(
navigator.sms.receive().catch(({name}) => { var error = navigator.sms.receive().catch(({name}) => {
error = name; return name;
}); });
true; )"));
)";
EXPECT_EQ(true, EvalJs(shell(), script));
loop.Run();
ukm_loop.Run(); ukm_loop.Run();
EXPECT_EQ("AbortError", EvalJs(shell(), "error")); EXPECT_EQ("AbortError", EvalJs(shell(), "error"));
...@@ -643,4 +597,44 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Cancels) { ...@@ -643,4 +597,44 @@ IN_PROC_BROWSER_TEST_F(SmsBrowserTest, Cancels) {
ExpectOutcomeUKM(url, blink::SMSReceiverOutcome::kCancelled); ExpectOutcomeUKM(url, blink::SMSReceiverOutcome::kCancelled);
} }
IN_PROC_BROWSER_TEST_F(SmsBrowserTest, AbortAfterSmsRetrieval) {
GURL url = GetTestUrl(nullptr, "simple_page.html");
EXPECT_TRUE(NavigateToURL(shell(), url));
auto* provider = new NiceMock<MockSmsProvider>();
BrowserMainLoop::GetInstance()->SetSmsProviderForTesting(
base::WrapUnique(provider));
shell()->web_contents()->SetDelegate(&delegate_);
ExpectSmsPrompt();
EXPECT_CALL(*provider, Retrieve()).WillOnce(Invoke([&provider, &url]() {
provider->NotifyReceive(url::Origin::Create(url), "", "hello");
}));
EXPECT_TRUE(ExecJs(shell(), R"(
var controller = new AbortController();
var signal = controller.signal;
var request = navigator.sms.receive({signal}).catch((e) => {
return e.name;
});
)"));
base::RunLoop ukm_loop;
// Wait for UKM to be recorded to avoid race condition between outcome
// capture and evaluation.
ukm_recorder()->SetOnAddEntryCallback(Entry::kEntryName,
ukm_loop.QuitClosure());
EXPECT_TRUE(ExecJs(shell(), R"( controller.abort(); )"));
ukm_loop.Run();
EXPECT_EQ("AbortError", EvalJs(shell(), "request"));
ExpectOutcomeUKM(url, blink::SMSReceiverOutcome::kAborted);
}
} // namespace content } // namespace content
...@@ -69,6 +69,13 @@ void SmsService::Receive(ReceiveCallback callback) { ...@@ -69,6 +69,13 @@ void SmsService::Receive(ReceiveCallback callback) {
callback_ = std::move(callback); callback_ = std::move(callback);
// |sms_| and prompt are still present from the previous request so a new
// subscription is unnecessary.
if (prompt_open_) {
// TODO(crbug.com/1024598): Add UMA histogram.
return;
}
fetcher_->Subscribe(origin_, this); fetcher_->Subscribe(origin_, this);
} }
...@@ -87,10 +94,17 @@ void SmsService::OnReceive(const std::string& one_time_code, ...@@ -87,10 +94,17 @@ void SmsService::OnReceive(const std::string& one_time_code,
OpenInfoBar(one_time_code); OpenInfoBar(one_time_code);
} }
void SmsService::Abort() {
DCHECK(callback_);
Process(SmsStatus::kAborted, base::nullopt);
}
void SmsService::OpenInfoBar(const std::string& one_time_code) { void SmsService::OpenInfoBar(const std::string& one_time_code) {
WebContents* web_contents = WebContents* web_contents =
content::WebContents::FromRenderFrameHost(render_frame_host()); content::WebContents::FromRenderFrameHost(render_frame_host());
prompt_open_ = true;
web_contents->GetDelegate()->CreateSmsPrompt( web_contents->GetDelegate()->CreateSmsPrompt(
render_frame_host(), origin_, one_time_code, render_frame_host(), origin_, one_time_code,
base::BindOnce(&SmsService::OnConfirm, weak_ptr_factory_.GetWeakPtr()), base::BindOnce(&SmsService::OnConfirm, weak_ptr_factory_.GetWeakPtr()),
...@@ -115,7 +129,10 @@ void SmsService::OnConfirm() { ...@@ -115,7 +129,10 @@ void SmsService::OnConfirm() {
DCHECK(!receive_time_.is_null()); DCHECK(!receive_time_.is_null());
RecordContinueOnSuccessTime(base::TimeTicks::Now() - receive_time_); RecordContinueOnSuccessTime(base::TimeTicks::Now() - receive_time_);
Process(SmsStatus::kSuccess, sms_); prompt_open_ = false;
if (callback_)
Process(SmsStatus::kSuccess, sms_);
} }
void SmsService::OnCancel() { void SmsService::OnCancel() {
...@@ -125,14 +142,22 @@ void SmsService::OnCancel() { ...@@ -125,14 +142,22 @@ void SmsService::OnCancel() {
DCHECK(!receive_time_.is_null()); DCHECK(!receive_time_.is_null());
RecordCancelOnSuccessTime(base::TimeTicks::Now() - receive_time_); RecordCancelOnSuccessTime(base::TimeTicks::Now() - receive_time_);
Process(SmsStatus::kCancelled, base::nullopt); prompt_open_ = false;
if (callback_)
Process(SmsStatus::kCancelled, base::nullopt);
} }
void SmsService::CleanUp() { void SmsService::CleanUp() {
callback_.Reset(); // Skip resetting |sms_| and |receive_time_| while prompt is still open
sms_.reset(); // in case it needs to be returned to the next incoming request upon prompt
// confirmation.
if (!prompt_open_) {
sms_.reset();
receive_time_ = base::TimeTicks();
}
start_time_ = base::TimeTicks(); start_time_ = base::TimeTicks();
receive_time_ = base::TimeTicks(); callback_.Reset();
fetcher_->Unsubscribe(origin_, this); fetcher_->Unsubscribe(origin_, this);
} }
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
#define CONTENT_BROWSER_SMS_SMS_SERVICE_H_ #define CONTENT_BROWSER_SMS_SMS_SERVICE_H_
#include <memory> #include <memory>
#include <string>
#include "base/callback_forward.h" #include "base/callback_forward.h"
#include "base/macros.h" #include "base/macros.h"
...@@ -49,6 +50,7 @@ class CONTENT_EXPORT SmsService ...@@ -49,6 +50,7 @@ class CONTENT_EXPORT SmsService
// blink::mojom::SmsReceiver: // blink::mojom::SmsReceiver:
void Receive(ReceiveCallback) override; void Receive(ReceiveCallback) override;
void Abort() override;
// content::SmsQueue::Subscriber // content::SmsQueue::Subscriber
void OnReceive(const std::string& one_time_code, void OnReceive(const std::string& one_time_code,
...@@ -71,6 +73,8 @@ class CONTENT_EXPORT SmsService ...@@ -71,6 +73,8 @@ class CONTENT_EXPORT SmsService
const url::Origin origin_; const url::Origin origin_;
bool prompt_open_ = false;
ReceiveCallback callback_; ReceiveCallback callback_;
base::Optional<std::string> sms_; base::Optional<std::string> sms_;
base::TimeTicks start_time_; base::TimeTicks start_time_;
......
...@@ -12,6 +12,8 @@ ...@@ -12,6 +12,8 @@
#include "base/memory/ptr_util.h" #include "base/memory/ptr_util.h"
#include "base/memory/weak_ptr.h" #include "base/memory/weak_ptr.h"
#include "base/run_loop.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/bind_test_util.h"
#include "base/test/metrics/histogram_tester.h" #include "base/test/metrics/histogram_tester.h"
#include "content/browser/sms/sms_fetcher_impl.h" #include "content/browser/sms/sms_fetcher_impl.h"
...@@ -79,25 +81,40 @@ class Service { ...@@ -79,25 +81,40 @@ class Service {
NiceMock<MockSmsProvider>* provider() { return &provider_; } NiceMock<MockSmsProvider>* provider() { return &provider_; }
SmsFetcher* fetcher() { return &fetcher_; } SmsFetcher* fetcher() { return &fetcher_; }
void CreateSmsPrompt(RenderFrameHost* rfh, bool confirm) { void CreateSmsPrompt(RenderFrameHost* rfh) {
EXPECT_CALL(*delegate(), CreateSmsPrompt(rfh, _, _, _, _)) EXPECT_CALL(*delegate(), CreateSmsPrompt(rfh, _, _, _, _))
.WillOnce(Invoke([=](RenderFrameHost*, const Origin& origin, .WillOnce(Invoke([=](RenderFrameHost*, const Origin& origin,
const std::string&, base::OnceClosure on_confirm, const std::string&, base::OnceClosure on_confirm,
base::OnceClosure on_cancel) { base::OnceClosure on_cancel) {
if (confirm) { confirm_callback_ = std::move(on_confirm);
// Simulates user clicking the "Enter code" button to verify dismiss_callback_ = std::move(on_cancel);
// received sms.
std::move(on_confirm).Run();
} else {
std::move(on_cancel).Run();
}
})); }));
} }
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) { void MakeRequest(SmsReceiver::ReceiveCallback callback) {
service_remote_->Receive(std::move(callback)); service_remote_->Receive(std::move(callback));
} }
void AbortRequest() { service_remote_->Abort(); }
void NotifyReceive(const GURL& url, const string& message) { void NotifyReceive(const GURL& url, const string& message) {
provider_.NotifyReceive(Origin::Create(url), "", message); provider_.NotifyReceive(Origin::Create(url), "", message);
} }
...@@ -108,6 +125,8 @@ class Service { ...@@ -108,6 +125,8 @@ class Service {
SmsFetcherImpl fetcher_; SmsFetcherImpl fetcher_;
mojo::Remote<blink::mojom::SmsReceiver> service_remote_; mojo::Remote<blink::mojom::SmsReceiver> service_remote_;
std::unique_ptr<SmsService> service_; std::unique_ptr<SmsService> service_;
base::OnceClosure confirm_callback_;
base::OnceClosure dismiss_callback_;
}; };
class SmsServiceTest : public RenderViewHostTestHarness { class SmsServiceTest : public RenderViewHostTestHarness {
...@@ -135,10 +154,11 @@ TEST_F(SmsServiceTest, Basic) { ...@@ -135,10 +154,11 @@ TEST_F(SmsServiceTest, Basic) {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh(), true); service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi"); service.NotifyReceive(GURL(kTestUrl), "hi");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -162,10 +182,11 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) { ...@@ -162,10 +182,11 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) {
{ {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh(), true); service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "first"); service.NotifyReceive(GURL(kTestUrl), "first");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -182,10 +203,11 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) { ...@@ -182,10 +203,11 @@ TEST_F(SmsServiceTest, HandlesMultipleCalls) {
{ {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh(), true); service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "second"); service.NotifyReceive(GURL(kTestUrl), "second");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -210,13 +232,14 @@ TEST_F(SmsServiceTest, IgnoreFromOtherOrigins) { ...@@ -210,13 +232,14 @@ TEST_F(SmsServiceTest, IgnoreFromOtherOrigins) {
base::RunLoop sms_loop; base::RunLoop sms_loop;
service.CreateSmsPrompt(main_rfh(), true); service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
// Delivers an SMS from an unrelated origin first and expect the // Delivers an SMS from an unrelated origin first and expect the
// receiver to ignore it. // receiver to ignore it.
service.NotifyReceive(GURL("http://b.com"), "wrong"); service.NotifyReceive(GURL("http://b.com"), "wrong");
service.NotifyReceive(GURL(kTestUrl), "right"); service.NotifyReceive(GURL(kTestUrl), "right");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -243,13 +266,14 @@ TEST_F(SmsServiceTest, ExpectOneReceiveTwo) { ...@@ -243,13 +266,14 @@ TEST_F(SmsServiceTest, ExpectOneReceiveTwo) {
base::RunLoop sms_loop; base::RunLoop sms_loop;
service.CreateSmsPrompt(main_rfh(), true); service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
// Delivers two SMSes for the same origin, even if only one was being // Delivers two SMSes for the same origin, even if only one was being
// expected. // expected.
ASSERT_TRUE(service.fetcher()->HasSubscribers()); ASSERT_TRUE(service.fetcher()->HasSubscribers());
service.NotifyReceive(GURL(kTestUrl), "first"); service.NotifyReceive(GURL(kTestUrl), "first");
service.ConfirmPrompt();
ASSERT_FALSE(service.fetcher()->HasSubscribers()); ASSERT_FALSE(service.fetcher()->HasSubscribers());
service.NotifyReceive(GURL(kTestUrl), "second"); service.NotifyReceive(GURL(kTestUrl), "second");
})); }));
...@@ -281,17 +305,14 @@ TEST_F(SmsServiceTest, AtMostOneSmsRequestPerOrigin) { ...@@ -281,17 +305,14 @@ TEST_F(SmsServiceTest, AtMostOneSmsRequestPerOrigin) {
base::RunLoop sms1_loop, sms2_loop; base::RunLoop sms1_loop, sms2_loop;
// Expect SMS Prompt to be created once. // Expect SMS Prompt to be created once.
EXPECT_CALL(*service.delegate(), CreateSmsPrompt(main_rfh(), _, _, _, _)) service.CreateSmsPrompt(main_rfh());
.WillOnce(
Invoke([](RenderFrameHost*, const Origin& origin, const std::string&,
base::OnceClosure on_confirm, base::OnceClosure on_cancel) {
std::move(on_confirm).Run();
}));
EXPECT_CALL(*service.provider(), Retrieve()) EXPECT_CALL(*service.provider(), Retrieve())
.WillOnce(Return()) .WillOnce(Return())
.WillOnce(Invoke( .WillOnce(Invoke([&service]() {
[&service]() { service.NotifyReceive(GURL(kTestUrl), "second"); })); service.NotifyReceive(GURL(kTestUrl), "second");
service.ConfirmPrompt();
}));
service.MakeRequest( service.MakeRequest(
BindLambdaForTesting([&sms_status1, &response1, &sms1_loop]( BindLambdaForTesting([&sms_status1, &response1, &sms1_loop](
...@@ -330,44 +351,35 @@ TEST_F(SmsServiceTest, SecondRequestDuringPrompt) { ...@@ -330,44 +351,35 @@ TEST_F(SmsServiceTest, SecondRequestDuringPrompt) {
Optional<string> response1; Optional<string> response1;
SmsStatus sms_status2; SmsStatus sms_status2;
Optional<string> response2; Optional<string> response2;
base::OnceClosure prompt_confirm;
base::RunLoop sms1_loop, sms2_loop; base::RunLoop sms_loop;
// Expect SMS Prompt to be created once. // Expect SMS Prompt to be created once.
EXPECT_CALL(*service.delegate(), CreateSmsPrompt(main_rfh(), _, _, _, _)) service.CreateSmsPrompt(main_rfh());
.WillOnce(Invoke([&](RenderFrameHost*, const Origin& origin,
const std::string&, base::OnceClosure on_confirm,
base::OnceClosure on_cancel) {
prompt_confirm = std::move(on_confirm);
sms1_loop.Quit();
}));
EXPECT_CALL(*service.provider(), Retrieve()) EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
.WillOnce(Invoke( service.NotifyReceive(GURL(kTestUrl), "second");
[&service]() { service.NotifyReceive(GURL(kTestUrl), "second"); })) }));
.WillOnce(Return());
// First request. // First request.
service.MakeRequest( service.MakeRequest(
BindLambdaForTesting([&sms_status1, &response1, &prompt_confirm]( BindLambdaForTesting([&sms_status1, &response1, &service](
SmsStatus status, const Optional<string>& sms) { SmsStatus status, const Optional<string>& sms) {
sms_status1 = status; sms_status1 = status;
response1 = sms; response1 = sms;
std::move(prompt_confirm).Run(); service.ConfirmPrompt();
})); }));
// Make second request before confirming prompt. // Make second request before confirming prompt.
service.MakeRequest( service.MakeRequest(
BindLambdaForTesting([&sms_status2, &response2, &sms2_loop]( BindLambdaForTesting([&sms_status2, &response2, &sms_loop](
SmsStatus status, const Optional<string>& sms) { SmsStatus status, const Optional<string>& sms) {
sms_status2 = status; sms_status2 = status;
response2 = sms; response2 = sms;
sms2_loop.Quit(); sms_loop.Quit();
})); }));
sms1_loop.Run(); sms_loop.Run();
sms2_loop.Run();
EXPECT_EQ(base::nullopt, response1); EXPECT_EQ(base::nullopt, response1);
EXPECT_EQ(SmsStatus::kCancelled, sms_status1); EXPECT_EQ(SmsStatus::kCancelled, sms_status1);
...@@ -424,10 +436,11 @@ TEST_F(SmsServiceTest, PromptsDialog) { ...@@ -424,10 +436,11 @@ TEST_F(SmsServiceTest, PromptsDialog) {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh(), true); service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi"); service.NotifyReceive(GURL(kTestUrl), "hi");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -450,6 +463,8 @@ TEST_F(SmsServiceTest, Cancel) { ...@@ -450,6 +463,8 @@ TEST_F(SmsServiceTest, Cancel) {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
service.MakeRequest( service.MakeRequest(
BindLambdaForTesting( BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& sms) { [&loop](SmsStatus status, const Optional<string>& sms) {
...@@ -460,18 +475,106 @@ TEST_F(SmsServiceTest, Cancel) { ...@@ -460,18 +475,106 @@ TEST_F(SmsServiceTest, Cancel) {
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi"); service.NotifyReceive(GURL(kTestUrl), "hi");
service.DismissPrompt();
})); }));
EXPECT_CALL(*service.delegate(), CreateSmsPrompt(main_rfh(), _, _, _, _)) loop.Run();
.WillOnce(Invoke([](RenderFrameHost*, const Origin&, const std::string&,
base::OnceClosure, base::OnceClosure on_cancel) { ASSERT_FALSE(service.fetcher()->HasSubscribers());
// Simulates the user pressing "Cancel". }
std::move(on_cancel).Run();
TEST_F(SmsServiceTest, Abort) {
NavigateAndCommit(GURL(kTestUrl));
Service service(web_contents());
base::RunLoop loop;
service.MakeRequest(BindLambdaForTesting(
[&loop](SmsStatus status, const Optional<string>& sms) {
EXPECT_EQ(SmsStatus::kAborted, status);
EXPECT_EQ(base::nullopt, sms);
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>& sms) {
EXPECT_EQ(SmsStatus::kAborted, status);
EXPECT_EQ(base::nullopt, sms);
loop.Quit();
})); }));
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi");
}));
service.AbortRequest();
loop.Run(); loop.Run();
ASSERT_FALSE(service.fetcher()->HasSubscribers()); 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>& sms) {
EXPECT_EQ(SmsStatus::kAborted, status);
EXPECT_EQ(base::nullopt, sms);
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>& sms) {
EXPECT_EQ(SmsStatus::kSuccess, status);
EXPECT_EQ("hi", sms.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) { TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) {
...@@ -481,10 +584,11 @@ TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) { ...@@ -481,10 +584,11 @@ TEST_F(SmsServiceTest, RecordTimeMetricsForContinueOnSuccess) {
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh(), true); service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi"); service.NotifyReceive(GURL(kTestUrl), "hi");
service.ConfirmPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -513,8 +617,11 @@ TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) { ...@@ -513,8 +617,11 @@ TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) {
// Histogram will be recorded if the SMS has already arrived. // Histogram will be recorded if the SMS has already arrived.
base::RunLoop loop; base::RunLoop loop;
service.CreateSmsPrompt(main_rfh());
EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() { EXPECT_CALL(*service.provider(), Retrieve()).WillOnce(Invoke([&service]() {
service.NotifyReceive(GURL(kTestUrl), "hi"); service.NotifyReceive(GURL(kTestUrl), "hi");
service.DismissPrompt();
})); }));
service.MakeRequest( service.MakeRequest(
...@@ -523,8 +630,6 @@ TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) { ...@@ -523,8 +630,6 @@ TEST_F(SmsServiceTest, RecordMetricsForCancelOnSuccess) {
loop.Quit(); loop.Quit();
})); }));
service.CreateSmsPrompt(main_rfh(), false);
loop.Run(); loop.Run();
std::unique_ptr<base::HistogramSamples> samples( std::unique_ptr<base::HistogramSamples> samples(
......
...@@ -15,7 +15,8 @@ enum class SMSReceiverOutcome { ...@@ -15,7 +15,8 @@ enum class SMSReceiverOutcome {
kTimeout = 1, kTimeout = 1,
kConnectionError = 2, kConnectionError = 2,
kCancelled = 3, kCancelled = 3,
kMaxValue = kCancelled kAborted = 4,
kMaxValue = kAborted
}; };
} // namespace blink } // namespace blink
......
...@@ -12,7 +12,8 @@ import "mojo/public/mojom/base/time.mojom"; ...@@ -12,7 +12,8 @@ import "mojo/public/mojom/base/time.mojom";
enum SmsStatus { enum SmsStatus {
kSuccess, kSuccess,
kTimeout, kTimeout,
kCancelled kCancelled,
kAborted
}; };
// This interface is created per storage partition but its execution is context // This interface is created per storage partition but its execution is context
...@@ -24,4 +25,7 @@ interface SmsReceiver { ...@@ -24,4 +25,7 @@ interface SmsReceiver {
// Returns the raw content of the received SMS. // Returns the raw content of the received SMS.
// |message| is only set if status == kSuccess. // |message| is only set if status == kSuccess.
Receive() => (SmsStatus status, string? message); Receive() => (SmsStatus status, string? message);
// Aborts the current retrieval process and resolves it with an
// kAborted SmsStatus.
Abort();
}; };
...@@ -10,11 +10,13 @@ ...@@ -10,11 +10,13 @@
#include "third_party/blink/public/mojom/sms/sms_receiver.mojom-blink.h" #include "third_party/blink/public/mojom/sms/sms_receiver.mojom-blink.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise.h"
#include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h" #include "third_party/blink/renderer/bindings/core/v8/script_promise_resolver.h"
#include "third_party/blink/renderer/core/dom/abort_signal.h"
#include "third_party/blink/renderer/core/dom/document.h" #include "third_party/blink/renderer/core/dom/document.h"
#include "third_party/blink/renderer/core/dom/dom_exception.h" #include "third_party/blink/renderer/core/dom/dom_exception.h"
#include "third_party/blink/renderer/core/frame/local_frame.h" #include "third_party/blink/renderer/core/frame/local_frame.h"
#include "third_party/blink/renderer/modules/sms/sms.h" #include "third_party/blink/renderer/modules/sms/sms.h"
#include "third_party/blink/renderer/modules/sms/sms_metrics.h" #include "third_party/blink/renderer/modules/sms/sms_metrics.h"
#include "third_party/blink/renderer/modules/sms/sms_receiver_options.h"
#include "third_party/blink/renderer/platform/bindings/name_client.h" #include "third_party/blink/renderer/platform/bindings/name_client.h"
#include "third_party/blink/renderer/platform/bindings/script_state.h" #include "third_party/blink/renderer/platform/bindings/script_state.h"
#include "third_party/blink/renderer/platform/heap/heap.h" #include "third_party/blink/renderer/platform/heap/heap.h"
...@@ -25,20 +27,38 @@ namespace blink { ...@@ -25,20 +27,38 @@ namespace blink {
SMSReceiver::SMSReceiver(ExecutionContext* context) : ContextClient(context) {} SMSReceiver::SMSReceiver(ExecutionContext* context) : ContextClient(context) {}
SMSReceiver::~SMSReceiver() = default;
ScriptPromise SMSReceiver::receive(ScriptState* script_state, ScriptPromise SMSReceiver::receive(ScriptState* script_state,
const SMSReceiverOptions* options) { const SMSReceiverOptions* options,
ExceptionState& exception_state) {
ExecutionContext* context = ExecutionContext::From(script_state); ExecutionContext* context = ExecutionContext::From(script_state);
DCHECK(context->IsContextThread()); DCHECK(context->IsContextThread());
LocalFrame* frame = GetFrame(); LocalFrame* frame = GetFrame();
if (!frame->IsMainFrame() && frame->IsCrossOriginSubframe()) { if (!frame->IsMainFrame() && frame->IsCrossOriginSubframe()) {
return ScriptPromise::RejectWithDOMException( exception_state.ThrowDOMException(
script_state, MakeGarbageCollected<DOMException>( DOMExceptionCode::kNotAllowedError,
DOMExceptionCode::kNotAllowedError, "Must have the same origin as the top-level frame.");
"Must have the same origin as the top-level frame.")); return ScriptPromise();
}
if (options->hasSignal() && options->signal()->aborted()) {
RecordSMSOutcome(SMSReceiverOutcome::kAborted, GetDocument()->UkmSourceID(),
GetDocument()->UkmRecorder());
exception_state.ThrowDOMException(DOMExceptionCode::kAbortError,
"Request has been aborted.");
return ScriptPromise();
} }
auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state); auto* resolver = MakeGarbageCollected<ScriptPromiseResolver>(script_state);
if (options->hasSignal()) {
options->signal()->AddAlgorithm(WTF::Bind(&SMSReceiver::Abort,
WrapWeakPersistent(this),
WrapPersistent(resolver)));
}
requests_.insert(resolver); requests_.insert(resolver);
// See https://bit.ly/2S0zRAS for task types. // See https://bit.ly/2S0zRAS for task types.
...@@ -59,7 +79,16 @@ ScriptPromise SMSReceiver::receive(ScriptState* script_state, ...@@ -59,7 +79,16 @@ ScriptPromise SMSReceiver::receive(ScriptState* script_state,
return resolver->Promise(); return resolver->Promise();
} }
SMSReceiver::~SMSReceiver() = default; void SMSReceiver::Abort(ScriptPromiseResolver* resolver) {
RecordSMSOutcome(SMSReceiverOutcome::kAborted, GetDocument()->UkmSourceID(),
GetDocument()->UkmRecorder());
service_->Abort();
resolver->Reject(
MakeGarbageCollected<DOMException>(DOMExceptionCode::kAbortError));
requests_.erase(resolver);
}
void SMSReceiver::OnReceive(ScriptPromiseResolver* resolver, void SMSReceiver::OnReceive(ScriptPromiseResolver* resolver,
base::TimeTicks start_time, base::TimeTicks start_time,
......
...@@ -28,13 +28,17 @@ class SMSReceiver final : public ScriptWrappable, public ContextClient { ...@@ -28,13 +28,17 @@ class SMSReceiver final : public ScriptWrappable, public ContextClient {
~SMSReceiver() override; ~SMSReceiver() override;
// SMSReceiver IDL interface. // SMSReceiver IDL interface.
ScriptPromise receive(ScriptState*, const SMSReceiverOptions*); ScriptPromise receive(ScriptState*,
const SMSReceiverOptions*,
ExceptionState&);
void Trace(blink::Visitor*) override; void Trace(blink::Visitor*) override;
private: private:
HeapHashSet<Member<ScriptPromiseResolver>> requests_; HeapHashSet<Member<ScriptPromiseResolver>> requests_;
void Abort(ScriptPromiseResolver* resolver);
void OnReceive(ScriptPromiseResolver* resolver, void OnReceive(ScriptPromiseResolver* resolver,
base::TimeTicks start_time, base::TimeTicks start_time,
mojom::blink::SmsStatus status, mojom::blink::SmsStatus status,
......
...@@ -9,6 +9,6 @@ ...@@ -9,6 +9,6 @@
Exposed=Window, Exposed=Window,
RuntimeEnabled=SmsReceiver RuntimeEnabled=SmsReceiver
] interface SMSReceiver { ] interface SMSReceiver {
[CallWith=ScriptState, MeasureAs=SMSReceiverStart] [CallWith=ScriptState, RaisesException, MeasureAs=SMSReceiverStart]
Promise<SMS> receive(optional SMSReceiverOptions options); Promise<SMS> receive(optional SMSReceiverOptions options);
}; };
...@@ -5,5 +5,5 @@ ...@@ -5,5 +5,5 @@
// https://github.com/samuelgoto/sms-receiver // https://github.com/samuelgoto/sms-receiver
dictionary SMSReceiverOptions { dictionary SMSReceiverOptions {
// TODO(b/976401): Implement abort controller. AbortSignal signal;
}; };
...@@ -8,7 +8,7 @@ const SmsProvider = (() => { ...@@ -8,7 +8,7 @@ const SmsProvider = (() => {
this.mojoReceiver_ = new blink.mojom.SmsReceiverReceiver(this); this.mojoReceiver_ = new blink.mojom.SmsReceiverReceiver(this);
this.interceptor_ = new MojoInterfaceInterceptor( this.interceptor_ = new MojoInterfaceInterceptor(
blink.mojom.SmsReceiver.$interfaceName, "context", true) blink.mojom.SmsReceiver.$interfaceName, "context", true);
this.interceptor_.oninterfacerequest = (e) => { this.interceptor_.oninterfacerequest = (e) => {
this.mojoReceiver_.$.bindHandle(e.handle); this.mojoReceiver_.$.bindHandle(e.handle);
...@@ -18,15 +18,16 @@ const SmsProvider = (() => { ...@@ -18,15 +18,16 @@ const SmsProvider = (() => {
this.returnValues_ = {}; this.returnValues_ = {};
} }
receive() { async receive() {
let call = this.returnValues_.receive ? let call = this.returnValues_.receive ?
this.returnValues_.receive.shift() : null; this.returnValues_.receive.shift() : null;
if (!call) { if (!call)
throw new Error("Unexpected call."); return;
}
return call(); return call();
} }
async abort() {}
pushReturnValuesForTesting(callName, value) { pushReturnValuesForTesting(callName, value) {
this.returnValues_[callName] = this.returnValues_[callName] || []; this.returnValues_[callName] = this.returnValues_[callName] || [];
this.returnValues_[callName].push(value); this.returnValues_[callName].push(value);
......
...@@ -114,4 +114,21 @@ promise_test(async t => { ...@@ -114,4 +114,21 @@ promise_test(async t => {
} }
}, 'Deal with cancelled requests'); }, 'Deal with cancelled requests');
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
controller.abort();
await promise_rejects(t, 'AbortError', navigator.sms.receive({signal}));
}, 'Should abort request');
promise_test(async t => {
const controller = new AbortController();
const signal = controller.signal;
let error = navigator.sms.receive({signal});
controller.abort();
await promise_rejects(t, 'AbortError', error);
}, 'Should abort request even while request is in progress.');
</script> </script>
...@@ -13,7 +13,7 @@ interface SMS { ...@@ -13,7 +13,7 @@ interface SMS {
}; };
dictionary SMSReceiverOptions { dictionary SMSReceiverOptions {
// TODO(b/976401): Implement abort controller. AbortSignal signal;
}; };
[ [
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment