LibWeb: Support Geolocation permission

Add the 'requesting permission to use a powerful feature' algorithm to
PermissionsAPI.
In requesting geolocation, now checks for permission status. UI element
to request permission to the user does not exist yet, and like
previously, defaults to denied.

A few tests pass because they use "geolocation" feature.
This commit is contained in:
Niccolo Antonelli-Dziri 2026-04-18 16:29:18 +02:00 committed by Shannon Booth
parent 9f4a712e57
commit 920bb84fd7
8 changed files with 149 additions and 8 deletions

View file

@ -7,14 +7,17 @@
#include <AK/Time.h>
#include <LibWeb/Bindings/Geolocation.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/Permissions.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/DOM/DocumentObserver.h>
#include <LibWeb/Geolocation/Geolocation.h>
#include <LibWeb/Geolocation/GeolocationPosition.h>
#include <LibWeb/HTML/EventLoop/Task.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/TraversableNavigable.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/PermissionsAPI/Permissions.h>
#include <LibWeb/Platform/EventLoopPlugin.h>
#include <LibWeb/Platform/Timer.h>
#include <LibWeb/WebIDL/AbstractOperations.h>
@ -324,15 +327,17 @@ void Geolocation::request_a_position(GC::Ref<WebIDL::CallbackType> success_callb
// 2. Continue to the next steps below.
// AD-HOC: This is implemented by run_in_parallel_when_document_is_visible().
// FIXME: 6. Let descriptor be a new PermissionDescriptor whose name is "geolocation".
// 6. Let descriptor be a new PermissionDescriptor whose name is "geolocation".
auto descriptor = Bindings::PermissionDescriptor { "geolocation"_string };
// 7. In parallel:
// AD-HOC: run_in_parallel_when_document_is_visible() already runs this in parallel.
{
// FIXME: 1. Set permission to request permission to use descriptor.
// 1. Set permission to request permission to use descriptor.
auto permission = Web::PermissionsAPI::request_permission(descriptor);
// FIXME: 2. If permission is "denied", then:
if (false) {
// 2. If permission is "denied", then:
if (permission == Bindings::PermissionState::Denied) {
// 1. If watchId was passed, remove watchId from watchIDs.
if (watch_id.has_value())
m_watch_ids.remove(watch_id.value());
@ -371,7 +376,11 @@ void Geolocation::run_in_parallel_when_document_is_visible(DOM::Document& docume
{
// Run callback in parallel if the document is already visible.
if (document.visibility_state_value() == HTML::VisibilityState::Visible) {
Platform::EventLoopPlugin::the().deferred_invoke(callback);
auto callback_with_context = GC::create_function(heap(), [this, callback] {
HTML::TemporaryExecutionContext execution_context { realm() };
callback->function()();
});
Platform::EventLoopPlugin::the().deferred_invoke(callback_with_context);
return;
}
@ -389,9 +398,10 @@ void Geolocation::run_in_parallel_when_document_is_visible(DOM::Document& docume
clear_observer_and_timer();
});
document_observer->set_document_visibility_state_observer([clear_observer_and_timer, callback](HTML::VisibilityState state) {
document_observer->set_document_visibility_state_observer([this, clear_observer_and_timer, callback](HTML::VisibilityState state) {
if (state == HTML::VisibilityState::Visible) {
clear_observer_and_timer();
HTML::TemporaryExecutionContext execution_context { realm() };
callback->function()();
}
});

View file

@ -20,12 +20,50 @@
namespace Web::PermissionsAPI {
bool is_permission_supported(String const&)
bool is_permission_supported(String const& name)
{
// FIXME: Actually support permissions
if (name == "geolocation") {
return true;
}
return false;
}
// https://w3c.github.io/permissions/#dfn-request-permission-to-use
Bindings::PermissionState request_permission(Bindings::PermissionDescriptor const& descriptor)
{
// 1. Let current state be the descriptor's permission state.
auto current_state = permission_state(descriptor);
// 2. If current state is not "prompt", return current state and abort these steps.
if (current_state != Bindings::PermissionState::Prompt)
return current_state;
// FIXME: 3. Ask the user for express permission for the calling algorithm to use the powerful feature described by descriptor.
// 4. If the user gives express permission to use the powerful feature, set current state to "granted"; otherwise to "denied".
// The user's interaction may provide new information about the user's intent for the origin.
if (false) {
current_state = Bindings::PermissionState::Granted;
} else {
current_state = Bindings::PermissionState::Denied;
}
// 5. Let settings be the current settings object.
auto& settings = HTML::current_settings_object();
// 6. Let key be the result of generating a permission key for descriptor with settings's top-level origin and settings's origin.
VERIFY(settings.top_level_origin.has_value());
auto key = permission_key_generation_algorithm(settings.top_level_origin.value(), settings.origin());
// 7. Queue a task on the current settings object's responsible event loop to set a permission store entry with descriptor, key, and current state.
HTML::queue_global_task(HTML::Task::Source::Permissions, settings.global_object(), GC::create_function(settings.realm().heap(), [descriptor, key, current_state] {
PermissionStore::the().set_permission_store_entry(descriptor, key, current_state);
}));
// 8. Return current state.
return current_state;
}
// https://w3c.github.io/permissions/#dfn-permission-state
Bindings::PermissionState permission_state(Bindings::PermissionDescriptor descriptor, Optional<HTML::EnvironmentSettingsObject&> settings)
{

View file

@ -20,6 +20,8 @@ Bindings::PermissionState permission_state(Bindings::PermissionDescriptor descri
Bindings::PermissionState get_current_permission_state(String const& name, Optional<HTML::EnvironmentSettingsObject&> settings = {});
Bindings::PermissionState request_permission(Bindings::PermissionDescriptor const& descriptor);
class WEB_API Permissions : public Bindings::PlatformObject {
WEB_PLATFORM_OBJECT(Permissions, Bindings::PlatformObject);
GC_DECLARE_ALLOCATOR(Permissions);

View file

@ -353,6 +353,8 @@ PerformanceObserverEntryList
PerformanceResourceTiming
PerformanceTiming
PeriodicWave
PermissionStatus
Permissions
Plugin
PluginArray
PointerEvent

View file

@ -0,0 +1,25 @@
Harness status: OK
Found 19 tests
1 Pass
18 Fail
Fail Query "camera" permission
Pass Query "geolocation" permission
Fail Query "microphone" permission
Fail Query "notifications" permission
Fail Query "persistent-storage" permission
Fail Query "push" permission
Fail Query "accelerometer" permission
Fail Query "ambient-light-sensor" permission
Fail Query "background-fetch" permission
Fail Query "background-sync" permission
Fail Query "bluetooth" permission
Fail Query "gyroscope" permission
Fail Query "magnetometer" permission
Fail Query "midi" permission
Fail Query "nfc" permission
Fail Query "screen-wake-lock" permission
Fail Query "display-capture" permission
Fail Query "speaker-selection" permission
Fail Query "xr-spatial-tracking" permission

View file

@ -0,0 +1,6 @@
Harness status: OK
Found 1 tests
1 Pass
Pass Test PermissionStatus's name attribute.

View file

@ -0,0 +1,43 @@
<!doctype html>
<meta charset=utf-8>
<title>Test all known permissions support</title>
<script src=../resources/testharness.js></script>
<script src=../resources/testharnessreport.js></script>
<div id="log"></div>
<script>
// These are marked "at risk" in the spec...
const atRisk = [
"accelerometer",
"ambient-light-sensor",
"background-fetch",
"background-sync",
"bluetooth",
"gyroscope",
"magnetometer",
"midi",
"nfc",
"screen-wake-lock",
"display-capture",
"speaker-selection",
"xr-spatial-tracking",
];
// These are known to be supported by multiple engines...
const permissions = [
"camera",
"geolocation",
"microphone",
"notifications",
"persistent-storage",
"push",
];
for (const name of [...permissions, ...atRisk]) {
promise_test(async (test) => {
const status = await navigator.permissions.query({ name });
assert_true(status instanceof PermissionStatus);
//assert_equals(status.name, name, `permission's name should be "${name}"`);
}, `Query "${name}" permission`);
}
</script>

View file

@ -0,0 +1,15 @@
<!doctype html>
<meta charset=utf-8>
<title>Test PermissionStatus's name attribute.</title>
<script src=../resources/testharness.js></script>
<script src=../resources/testharnessreport.js></script>
<div id="log"></div>
<script>
promise_test(async () => {
const result = await navigator.permissions.query({
name: "geolocation",
});
assert_equals(result.name, "geolocation", "Name was geolocation");
});
</script>