ladybird/Libraries/LibWeb/PermissionsAPI/Permissions.cpp

197 lines
8.8 KiB
C++
Raw Normal View History

/*
* Copyright (c) 2026, Niccolo Antonelli-Dziri <niccolo.antonelli-dziri@protonmail.com>
*
* SPDX-License-Identifier: BSD-2-Clause
*/
#include <LibJS/Runtime/Realm.h>
#include <LibJS/Runtime/VM.h>
#include <LibWeb/Bindings/Intrinsics.h>
#include <LibWeb/Bindings/Permissions.h>
#include <LibWeb/DOM/Document.h>
#include <LibWeb/HTML/Scripting/Environments.h>
#include <LibWeb/HTML/Scripting/TemporaryExecutionContext.h>
#include <LibWeb/HTML/Window.h>
#include <LibWeb/PermissionsAPI/PermissionStatus.h>
#include <LibWeb/PermissionsAPI/PermissionStore.h>
#include <LibWeb/PermissionsAPI/Permissions.h>
#include <LibWeb/WebIDL/DOMException.h>
#include <LibWeb/WebIDL/Promise.h>
namespace Web::PermissionsAPI {
bool is_permission_supported(String const& name)
{
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)
{
// 1. If settings wasn't passed, set it to the current settings object.
auto& settings_object = settings.has_value() ? settings.value() : HTML::current_settings_object();
// 2. If settings is a non-secure context, return "denied".
if (!HTML::is_secure_context(settings_object))
return Bindings::PermissionState::Denied;
// FIXME: 3. Let feature be descriptor's name.
// FIXME: 4. If there exists a policy-controlled feature for feature and settings' relevant global object has an associated Document run the following step:
if (false) {
// 1. Let document be settings' relevant global object's associated Document.
// 2. If document is not allowed to use feature, return "denied".
}
// 5. Let key be the result of generating a permission key for descriptor with settings's top-level origin and settings's origin.
VERIFY(settings_object.top_level_origin.has_value());
auto key = permission_key_generation_algorithm(settings_object.top_level_origin.value(), settings_object.origin());
// 6. Let entry be the result of getting a permission store entry with descriptor and key.
auto entry = PermissionStore::the().get_permission_store_entry(descriptor, key);
// 7. If entry is not null, return a PermissionState enum value from entry's state.
if (entry.has_value())
return entry.release_value().state;
// 8. Return the PermissionState enum value that represents the permission state of feature, taking into account any permission state constraints for descriptor's name.
return Bindings::PermissionState::Prompt;
}
GC_DEFINE_ALLOCATOR(Permissions);
GC::Ref<Permissions> Permissions::create(JS::Realm& realm)
{
return realm.create<Permissions>(realm);
}
Permissions::Permissions(JS::Realm& realm)
: Bindings::PlatformObject(realm)
{
}
void Permissions::initialize(JS::Realm& realm)
{
WEB_SET_PROTOTYPE_FOR_INTERFACE(Permissions);
Base::initialize(realm);
}
// https://w3c.github.io/permissions/#query-method
GC::Ref<Web::WebIDL::Promise> Permissions::query(JS::Value permission_desc)
{
auto& realm = this->realm();
auto& relevant_global_object = HTML::relevant_global_object(*this);
// 1. If this's relevant global object is a Window object, then:
if (auto* window = as_if<HTML::Window>(relevant_global_object)) {
// 1. If the current settings object's associated Document is not fully active, return a promise rejected with an "InvalidStateError" DOMException.
if (!window->associated_document().is_fully_active()) {
auto error = WebIDL::InvalidStateError::create(realm, "The document is not fully active."_utf16);
return WebIDL::create_rejected_promise_from_exception(realm, error);
}
}
// 2. Let rootDesc be the object permissionDesc refers to, converted to an IDL value of type PermissionDescriptor.
// 3. If the conversion throws an exception, return a promise rejected with that exception.
auto root_desc_or_error = Bindings::convert_to_idl_value_for_permission_descriptor(vm(), permission_desc);
if (root_desc_or_error.is_error()) {
return WebIDL::create_rejected_promise_from_exception(realm, root_desc_or_error.release_error());
}
auto root_desc = root_desc_or_error.release_value();
// 4. If rootDesc["name"] is not supported, return a promise rejected with a TypeError.
if (!is_permission_supported(root_desc.name)) {
auto error = vm().throw_completion<JS::TypeError>(JS::ErrorType::InvalidEnumerationValue, root_desc.name, "PermissionName"sv);
return WebIDL::create_rejected_promise_from_exception(realm, error.release_error());
}
// 5. Let typedDescriptor be the object permissionDesc refers to, converted to an IDL value of rootDesc's name's permission descriptor type.
// 6. If the conversion throws an exception, return a promise rejected with that exception.
// FIXME: Support specific permission descriptor types. For now, we only support the base PermissionDescriptor.
auto typed_descriptor = root_desc;
// 7. Let promise be a new promise.
auto promise = WebIDL::create_promise(realm);
// 8. Return promise and continue in parallel:
// FIXME: Continue in parallel.
{
// 1. Let status be create a PermissionStatus with typedDescriptor.
auto status = PermissionStatus::create(realm, typed_descriptor);
// 2. Let query be status's [[query]] internal slot.
auto const& query = status->query();
// 3. Run query's name's permission query algorithm, passing query and status.
// FIXME: For now, only the base PermissionDescriptor is supported so there is only one permission query algorithm.
permission_query_algorithm(query, status);
// 4. Queue a global task on the permissions task source with this's relevant global object to resolve promise with status.
HTML::queue_global_task(HTML::Task::Source::Permissions, relevant_global_object, GC::create_function(realm.heap(), [&realm, promise, status]() mutable {
HTML::TemporaryExecutionContext execution_context { realm };
WebIDL::resolve_promise(realm, promise, status);
}));
}
return promise;
}
// https://w3c.github.io/permissions/#dfn-getting-the-current-permission-state
Bindings::PermissionState get_current_permission_state(String const& name, Optional<HTML::EnvironmentSettingsObject&> settings)
{
// 1. Let descriptor be a newly-created PermissionDescriptor with name initialized to name.
Bindings::PermissionDescriptor descriptor { name };
// 2. Return the permission state of descriptor with settings.
return permission_state(descriptor, settings);
}
// https://w3c.github.io/permissions/#dfn-permission-query-algorithm
void permission_query_algorithm(Bindings::PermissionDescriptor const& permission_desc, PermissionStatus& status)
{
// 1. Set status's state to permissionDesc's permission state.
status.set_state(permission_state(permission_desc));
}
}