Commit ec6c569f authored by Olivier Yiptong's avatar Olivier Yiptong Committed by Commit Bot

FontAccess: Require User Activation only when showing permission prompt

Following feedback from partners, requiring UA each time
navigator.fonts.query() is invoked is cumbersome for UX.

This change ensures that User Activation is checked before showing a
permission prompt, but isn't checked if the permission status is
either granted or denied.

Bug: 1043306
Change-Id: I37a848feaf7c02e881bb4a63f53594a3636a9f96
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2388982
Commit-Queue: Olivier Yiptong <oyiptong@chromium.org>
Reviewed-by: default avatarJoshua Bell <jsbell@chromium.org>
Cr-Commit-Position: refs/heads/master@{#803729}
parent ec0b6b07
...@@ -43,9 +43,6 @@ void FontAccessManagerImpl::RequestPermission( ...@@ -43,9 +43,6 @@ void FontAccessManagerImpl::RequestPermission(
const BindingContext& context = receivers_.current_context(); const BindingContext& context = receivers_.current_context();
RenderFrameHost* rfh = RenderFrameHost::FromID(context.frame_id); RenderFrameHost* rfh = RenderFrameHost::FromID(context.frame_id);
// Double checking: renderer processes should already have checked for user
// activation before the RPC has been made. It is not an error, because it is
// possible that user activation has lapsed before reaching here.
if (!rfh->HasTransientUserActivation()) { if (!rfh->HasTransientUserActivation()) {
std::move(callback).Run(blink::mojom::PermissionStatus::DENIED); std::move(callback).Run(blink::mojom::PermissionStatus::DENIED);
return; return;
...@@ -74,9 +71,17 @@ void FontAccessManagerImpl::EnumerateLocalFonts( ...@@ -74,9 +71,17 @@ void FontAccessManagerImpl::EnumerateLocalFonts(
const BindingContext& context = receivers_.current_context(); const BindingContext& context = receivers_.current_context();
RenderFrameHost* rfh = RenderFrameHost::FromID(context.frame_id); RenderFrameHost* rfh = RenderFrameHost::FromID(context.frame_id);
// Double checking: renderer processes should already have checked for user auto status = PermissionControllerImpl::FromBrowserContext(
// activation before the RPC has been made. It is not an error, because it is rfh->GetProcess()->GetBrowserContext())
// possible that user activation has lapsed before reaching here. ->GetPermissionStatusForFrame(PermissionType::FONT_ACCESS,
rfh, context.origin.GetURL());
if (status != blink::mojom::PermissionStatus::ASK) {
// Permission has been requested before.
DidRequestPermission(std::move(callback), std::move(status));
return;
}
if (!rfh->HasTransientUserActivation()) { if (!rfh->HasTransientUserActivation()) {
std::move(callback).Run( std::move(callback).Run(
blink::mojom::FontEnumerationStatus::kPermissionDenied, blink::mojom::FontEnumerationStatus::kPermissionDenied,
......
...@@ -48,13 +48,26 @@ class TestPermissionManager : public MockPermissionManager { ...@@ -48,13 +48,26 @@ class TestPermissionManager : public MockPermissionManager {
return 0; return 0;
} }
blink::mojom::PermissionStatus GetPermissionStatusForFrame(
PermissionType permission,
RenderFrameHost* render_frame_host,
const GURL& requesting_origin) override {
return permission_status_for_frame_;
}
void SetRequestCallback( void SetRequestCallback(
base::RepeatingCallback<void(PermissionCallback)> request_callback) { base::RepeatingCallback<void(PermissionCallback)> request_callback) {
request_callback_ = std::move(request_callback); request_callback_ = std::move(request_callback);
} }
void SetPermissionStatusForFrame(blink::mojom::PermissionStatus status) {
permission_status_for_frame_ = status;
}
private: private:
base::RepeatingCallback<void(PermissionCallback)> request_callback_; base::RepeatingCallback<void(PermissionCallback)> request_callback_;
blink::mojom::PermissionStatus permission_status_for_frame_ =
blink::mojom::PermissionStatus::ASK;
}; };
} // namespace } // namespace
...@@ -106,6 +119,8 @@ class FontAccessManagerImplTest : public RenderViewHostImplTestHarness { ...@@ -106,6 +119,8 @@ class FontAccessManagerImplTest : public RenderViewHostImplTestHarness {
base::BindRepeating([](PermissionCallback callback) { base::BindRepeating([](PermissionCallback callback) {
std::move(callback).Run(blink::mojom::PermissionStatus::GRANTED); std::move(callback).Run(blink::mojom::PermissionStatus::GRANTED);
})); }));
test_permission_manager()->SetPermissionStatusForFrame(
blink::mojom::PermissionStatus::GRANTED);
} }
void AutoDenyPermission() { void AutoDenyPermission() {
...@@ -113,6 +128,26 @@ class FontAccessManagerImplTest : public RenderViewHostImplTestHarness { ...@@ -113,6 +128,26 @@ class FontAccessManagerImplTest : public RenderViewHostImplTestHarness {
base::BindRepeating([](PermissionCallback callback) { base::BindRepeating([](PermissionCallback callback) {
std::move(callback).Run(blink::mojom::PermissionStatus::DENIED); std::move(callback).Run(blink::mojom::PermissionStatus::DENIED);
})); }));
test_permission_manager()->SetPermissionStatusForFrame(
blink::mojom::PermissionStatus::DENIED);
}
void AskGrantPermission() {
test_permission_manager()->SetPermissionStatusForFrame(
blink::mojom::PermissionStatus::ASK);
test_permission_manager()->SetRequestCallback(
base::BindRepeating([](PermissionCallback callback) {
std::move(callback).Run(blink::mojom::PermissionStatus::GRANTED);
}));
}
void AskDenyPermission() {
test_permission_manager()->SetPermissionStatusForFrame(
blink::mojom::PermissionStatus::ASK);
test_permission_manager()->SetRequestCallback(
base::BindRepeating([](PermissionCallback callback) {
std::move(callback).Run(blink::mojom::PermissionStatus::DENIED);
}));
} }
void SimulateUserActivation() { void SimulateUserActivation() {
...@@ -136,7 +171,7 @@ class FontAccessManagerImplTest : public RenderViewHostImplTestHarness { ...@@ -136,7 +171,7 @@ class FontAccessManagerImplTest : public RenderViewHostImplTestHarness {
#if defined(OS_MAC) #if defined(OS_MAC)
TEST_F(FontAccessManagerImplTest, NoUserActivationPermissionDenied) { TEST_F(FontAccessManagerImplTest, NoUserActivationPermissionDenied) {
ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected()); ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected());
AutoGrantPermission(); AskGrantPermission();
base::RunLoop loop; base::RunLoop loop;
bool permission_requested = false; bool permission_requested = false;
...@@ -154,7 +189,7 @@ TEST_F(FontAccessManagerImplTest, NoUserActivationPermissionDenied) { ...@@ -154,7 +189,7 @@ TEST_F(FontAccessManagerImplTest, NoUserActivationPermissionDenied) {
TEST_F(FontAccessManagerImplTest, UserActivationPermissionManagerTriggered) { TEST_F(FontAccessManagerImplTest, UserActivationPermissionManagerTriggered) {
ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected()); ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected());
AutoGrantPermission(); AskGrantPermission();
SimulateUserActivation(); SimulateUserActivation();
base::RunLoop loop; base::RunLoop loop;
...@@ -193,10 +228,9 @@ void ValidateFontEnumerationBasic(FontEnumerationStatus status, ...@@ -193,10 +228,9 @@ void ValidateFontEnumerationBasic(FontEnumerationStatus status,
} // namespace } // namespace
TEST_F(FontAccessManagerImplTest, ValidateEnumerationBasic) { TEST_F(FontAccessManagerImplTest, PreviouslyGrantedValidateEnumerationBasic) {
ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected()); ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected());
AutoGrantPermission(); AutoGrantPermission();
SimulateUserActivation();
base::RunLoop run_loop; base::RunLoop run_loop;
manager_remote_->EnumerateLocalFonts( manager_remote_->EnumerateLocalFonts(
...@@ -210,9 +244,25 @@ TEST_F(FontAccessManagerImplTest, ValidateEnumerationBasic) { ...@@ -210,9 +244,25 @@ TEST_F(FontAccessManagerImplTest, ValidateEnumerationBasic) {
run_loop.Run(); run_loop.Run();
} }
TEST_F(FontAccessManagerImplTest, UserActivationRequiredBeforeGrant) {
ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected());
AskGrantPermission();
SimulateUserActivation();
base::RunLoop run_loop;
manager_remote_->EnumerateLocalFonts(
base::BindLambdaForTesting([&](FontEnumerationStatus status,
base::ReadOnlySharedMemoryRegion region) {
EXPECT_EQ(status, FontEnumerationStatus::kOk)
<< "Font Enumeration was successful.";
run_loop.Quit();
}));
run_loop.Run();
}
TEST_F(FontAccessManagerImplTest, EnumerationPermissionDeniedIfNoActivation) { TEST_F(FontAccessManagerImplTest, EnumerationPermissionDeniedIfNoActivation) {
ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected()); ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected());
AutoGrantPermission(); AskGrantPermission();
base::RunLoop run_loop; base::RunLoop run_loop;
manager_remote_->EnumerateLocalFonts( manager_remote_->EnumerateLocalFonts(
...@@ -225,9 +275,9 @@ TEST_F(FontAccessManagerImplTest, EnumerationPermissionDeniedIfNoActivation) { ...@@ -225,9 +275,9 @@ TEST_F(FontAccessManagerImplTest, EnumerationPermissionDeniedIfNoActivation) {
run_loop.Run(); run_loop.Run();
} }
TEST_F(FontAccessManagerImplTest, PermissionDeniedErrors) { TEST_F(FontAccessManagerImplTest, PermissionDeniedOnAskErrors) {
ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected()); ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected());
AutoDenyPermission(); AskDenyPermission();
SimulateUserActivation(); SimulateUserActivation();
base::RunLoop run_loop; base::RunLoop run_loop;
...@@ -240,6 +290,21 @@ TEST_F(FontAccessManagerImplTest, PermissionDeniedErrors) { ...@@ -240,6 +290,21 @@ TEST_F(FontAccessManagerImplTest, PermissionDeniedErrors) {
})); }));
run_loop.Run(); run_loop.Run();
} }
TEST_F(FontAccessManagerImplTest, PermissionPreviouslyDeniedErrors) {
ASSERT_TRUE(manager_remote_.is_bound() && manager_remote_.is_connected());
AutoDenyPermission();
base::RunLoop run_loop;
manager_remote_->EnumerateLocalFonts(
base::BindLambdaForTesting([&](FontEnumerationStatus status,
base::ReadOnlySharedMemoryRegion region) {
EXPECT_EQ(status, FontEnumerationStatus::kPermissionDenied)
<< "Permission was denied.";
run_loop.Quit();
}));
run_loop.Run();
}
#endif #endif
} // namespace content } // namespace content
...@@ -26,7 +26,6 @@ void ReturnDataFunction(const v8::FunctionCallbackInfo<v8::Value>& info) { ...@@ -26,7 +26,6 @@ void ReturnDataFunction(const v8::FunctionCallbackInfo<v8::Value>& info) {
ScriptValue FontManager::query(ScriptState* script_state, ScriptValue FontManager::query(ScriptState* script_state,
ExceptionState& exception_state) { ExceptionState& exception_state) {
ValidateRequest(ExecutionContext::From(script_state), exception_state);
if (exception_state.HadException()) if (exception_state.HadException())
return ScriptValue(); return ScriptValue();
...@@ -51,15 +50,4 @@ void FontManager::Trace(blink::Visitor* visitor) const { ...@@ -51,15 +50,4 @@ void FontManager::Trace(blink::Visitor* visitor) const {
ScriptWrappable::Trace(visitor); ScriptWrappable::Trace(visitor);
} }
void FontManager::ValidateRequest(ExecutionContext* context,
ExceptionState& exception_state) {
DCHECK(context);
auto* frame = To<LocalDOMWindow>(context)->GetFrame();
if (!LocalFrame::HasTransientUserActivation(frame)) {
exception_state.ThrowSecurityError(
"Must be handling a user gesture to enumerate local fonts.");
}
}
} // namespace blink } // namespace blink
...@@ -14,7 +14,6 @@ namespace blink { ...@@ -14,7 +14,6 @@ namespace blink {
class ScriptState; class ScriptState;
class ScriptValue; class ScriptValue;
class ExecutionContext;
class FontManager final : public ScriptWrappable { class FontManager final : public ScriptWrappable {
DEFINE_WRAPPERTYPEINFO(); DEFINE_WRAPPERTYPEINFO();
...@@ -25,9 +24,6 @@ class FontManager final : public ScriptWrappable { ...@@ -25,9 +24,6 @@ class FontManager final : public ScriptWrappable {
DISALLOW_COPY_AND_ASSIGN(FontManager); DISALLOW_COPY_AND_ASSIGN(FontManager);
void Trace(blink::Visitor*) const override; void Trace(blink::Visitor*) const override;
private:
void ValidateRequest(ExecutionContext*, ExceptionState&);
}; };
} // namespace blink } // namespace blink
......
'use strict'; 'use strict';
promise_test(async t => { promise_test(async t => {
assert_throws_dom('SecurityError', () => { const iterator = navigator.fonts.query();
navigator.fonts.query();
}); await promise_rejects_dom(t, 'NotAllowedError', (async () => {
}, 'query(): fails if there is no user activation'); for await (const f of iterator) {
}
})());
}, 'iteration fails if there is no user activation');
font_access_test(async t => { font_access_test(async t => {
const iterator = navigator.fonts.query(); const iterator = navigator.fonts.query();
......
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