From 7966fc4780d3e5e40b397098d4214d37a2d0b01f Mon Sep 17 00:00:00 2001 From: Timothy Flynn Date: Mon, 17 Apr 2023 13:21:19 -0400 Subject: [PATCH] LibWeb+LibWebView+WebContent: Add APIs to manage an autoplay allowlist The spec defines a Permissions Policy to control some browser behaviors on a per-origin basis. Management of these permissions live in their own spec: https://w3c.github.io/webappsec-permissions-policy/ This implements a somewhat ad-hoc Permissions Policy for autoplaying media elements. We will need to implement the entire policy spec for this to be more general. --- Userland/Libraries/LibWeb/CMakeLists.txt | 1 + Userland/Libraries/LibWeb/DOM/Document.cpp | 26 ++++++ Userland/Libraries/LibWeb/DOM/Document.h | 6 ++ Userland/Libraries/LibWeb/Forward.h | 4 + .../PermissionsPolicy/AutoplayAllowlist.cpp | 91 +++++++++++++++++++ .../PermissionsPolicy/AutoplayAllowlist.h | 36 ++++++++ .../LibWeb/PermissionsPolicy/Decision.h | 16 ++++ .../LibWebView/OutOfProcessWebView.cpp | 10 ++ .../LibWebView/OutOfProcessWebView.h | 2 + .../WebContent/ConnectionFromClient.cpp | 13 +++ .../WebContent/ConnectionFromClient.h | 2 + .../Services/WebContent/WebContentServer.ipc | 2 + 12 files changed, 209 insertions(+) create mode 100644 Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp create mode 100644 Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h create mode 100644 Userland/Libraries/LibWeb/PermissionsPolicy/Decision.h diff --git a/Userland/Libraries/LibWeb/CMakeLists.txt b/Userland/Libraries/LibWeb/CMakeLists.txt index 34493eb520..b94d44728b 100644 --- a/Userland/Libraries/LibWeb/CMakeLists.txt +++ b/Userland/Libraries/LibWeb/CMakeLists.txt @@ -453,6 +453,7 @@ set(SOURCES Painting/VideoPaintable.cpp PerformanceTimeline/EntryTypes.cpp PerformanceTimeline/PerformanceEntry.cpp + PermissionsPolicy/AutoplayAllowlist.cpp Platform/EventLoopPlugin.cpp Platform/EventLoopPluginSerenity.cpp Platform/FontPlugin.cpp diff --git a/Userland/Libraries/LibWeb/DOM/Document.cpp b/Userland/Libraries/LibWeb/DOM/Document.cpp index 8e87bb1082..e9e8666407 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.cpp +++ b/Userland/Libraries/LibWeb/DOM/Document.cpp @@ -72,6 +72,7 @@ #include #include #include +#include #include #include #include @@ -2398,6 +2399,31 @@ void Document::unload(bool recursive_flag, Optional un m_unload_counter -= 1; } +// https://html.spec.whatwg.org/multipage/iframe-embed-object.html#allowed-to-use +bool Document::is_allowed_to_use_feature(PolicyControlledFeature feature) const +{ + // 1. If document's browsing context is null, then return false. + if (browsing_context() == nullptr) + return false; + + // 2. If document is not fully active, then return false. + if (!is_fully_active()) + return false; + + // 3. If the result of running is feature enabled in document for origin on feature, document, and document's origin + // is "Enabled", then return true. + // FIXME: This is ad-hoc. Implement the Permissions Policy specification. + switch (feature) { + case PolicyControlledFeature::Autoplay: + if (PermissionsPolicy::AutoplayAllowlist::the().is_allowed_for_origin(*this, origin()) == PermissionsPolicy::Decision::Enabled) + return true; + break; + } + + // 4. Return false. + return false; +} + void Document::did_stop_being_active_document_in_browsing_context(Badge) { tear_down_layout_tree(); diff --git a/Userland/Libraries/LibWeb/DOM/Document.h b/Userland/Libraries/LibWeb/DOM/Document.h index 2a74504b0a..84fad47796 100644 --- a/Userland/Libraries/LibWeb/DOM/Document.h +++ b/Userland/Libraries/LibWeb/DOM/Document.h @@ -74,6 +74,10 @@ struct ElementCreationOptions { DeprecatedString is; }; +enum class PolicyControlledFeature { + Autoplay, +}; + class Document : public ParentNode , public NonElementParentNode @@ -456,6 +460,8 @@ public: DocumentUnloadTimingInfo const& previous_document_unload_timing() const { return m_previous_document_unload_timing; } void set_previous_document_unload_timing(DocumentUnloadTimingInfo const& previous_document_unload_timing) { m_previous_document_unload_timing = previous_document_unload_timing; } + bool is_allowed_to_use_feature(PolicyControlledFeature) const; + void did_stop_being_active_document_in_browsing_context(Badge); bool query_command_supported(DeprecatedString const&) const; diff --git a/Userland/Libraries/LibWeb/Forward.h b/Userland/Libraries/LibWeb/Forward.h index 70a230002b..d8e2fceabf 100644 --- a/Userland/Libraries/LibWeb/Forward.h +++ b/Userland/Libraries/LibWeb/Forward.h @@ -475,6 +475,10 @@ namespace Web::PerformanceTimeline { class PerformanceEntry; } +namespace Web::PermissionsPolicy { +class AutoplayAllowlist; +} + namespace Web::Platform { class Timer; } diff --git a/Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp b/Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp new file mode 100644 index 0000000000..b0d97c52e1 --- /dev/null +++ b/Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp @@ -0,0 +1,91 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +// FIXME: This is an ad-hoc implementation of the "autoplay" policy-controlled feature: +// https://w3c.github.io/webappsec-permissions-policy/#policy-controlled-feature + +namespace Web::PermissionsPolicy { + +AutoplayAllowlist& AutoplayAllowlist::the() +{ + static AutoplayAllowlist filter; + return filter; +} + +AutoplayAllowlist::AutoplayAllowlist() = default; +AutoplayAllowlist::~AutoplayAllowlist() = default; + +// https://w3c.github.io/webappsec-permissions-policy/#is-feature-enabled +Decision AutoplayAllowlist::is_allowed_for_origin(DOM::Document const& document, HTML::Origin const& origin) const +{ + // FIXME: 1. Let policy be document’s Permissions Policy + // FIXME: 2. If policy’s inherited policy for feature is Disabled, return "Disabled". + + // 3. If feature is present in policy’s declared policy: + if (m_allowlist.has_value()) { + // 1. If the allowlist for feature in policy’s declared policy matches origin, then return "Enabled". + // 2. Otherwise return "Disabled". + return m_allowlist->visit( + [](Global) { + return Decision::Enabled; + }, + [&](auto const& patterns) { + for (auto const& pattern : patterns) { + if (pattern.is_same_origin_domain(origin)) + return Decision::Enabled; + } + + return Decision::Disabled; + }); + } + + // 4. If feature’s default allowlist is *, return "Enabled". + // 5. If feature’s default allowlist is 'self', and origin is same origin with document’s origin, return "Enabled". + // NOTE: The "autoplay" feature's default allowlist is 'self'. + // https://html.spec.whatwg.org/multipage/infrastructure.html#autoplay-feature + if (origin.is_same_origin(document.origin())) + return Decision::Enabled; + + // 6. Return "Disabled". + return Decision::Disabled; +} + +void AutoplayAllowlist::enable_globally() +{ + m_allowlist = Global {}; +} + +ErrorOr AutoplayAllowlist::enable_for_origins(ReadonlySpan origins) +{ + m_allowlist = Patterns {}; + + auto& allowlist = m_allowlist->get(); + TRY(allowlist.try_ensure_capacity(origins.size())); + + for (auto const& origin : origins) { + AK::URL url { origin }; + + if (!url.is_valid()) + url = TRY(String::formatted("https://{}", origin)); + if (!url.is_valid()) { + dbgln("Invalid origin for autoplay allowlist: {}", origin); + continue; + } + + TRY(allowlist.try_append(URL::url_origin(url))); + } + + return {}; +} + +} diff --git a/Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h b/Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h new file mode 100644 index 0000000000..25e89bf45f --- /dev/null +++ b/Userland/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::PermissionsPolicy { + +class AutoplayAllowlist { +public: + static AutoplayAllowlist& the(); + + Decision is_allowed_for_origin(DOM::Document const&, HTML::Origin const&) const; + + void enable_globally(); + ErrorOr enable_for_origins(ReadonlySpan); + +private: + AutoplayAllowlist(); + ~AutoplayAllowlist(); + + using Patterns = Vector; + struct Global { }; + + Optional> m_allowlist; +}; + +} diff --git a/Userland/Libraries/LibWeb/PermissionsPolicy/Decision.h b/Userland/Libraries/LibWeb/PermissionsPolicy/Decision.h new file mode 100644 index 0000000000..8b163013b7 --- /dev/null +++ b/Userland/Libraries/LibWeb/PermissionsPolicy/Decision.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2023, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +namespace Web::PermissionsPolicy { + +enum class Decision { + Enabled, + Disabled, +}; + +} diff --git a/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp b/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp index e6505acdd6..81ec7af17a 100644 --- a/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp +++ b/Userland/Libraries/LibWebView/OutOfProcessWebView.cpp @@ -600,6 +600,16 @@ void OutOfProcessWebView::set_content_filters(Vector filters) client().async_set_content_filters(filters); } +void OutOfProcessWebView::set_autoplay_allowed_on_all_websites() +{ + client().async_set_autoplay_allowed_on_all_websites(); +} + +void OutOfProcessWebView::set_autoplay_allowlist(Vector allowlist) +{ + client().async_set_autoplay_allowlist(move(allowlist)); +} + void OutOfProcessWebView::set_proxy_mappings(Vector proxies, HashMap mappings) { client().async_set_proxy_mappings(move(proxies), move(mappings)); diff --git a/Userland/Libraries/LibWebView/OutOfProcessWebView.h b/Userland/Libraries/LibWebView/OutOfProcessWebView.h index 1e66ecd62c..4744efc0cb 100644 --- a/Userland/Libraries/LibWebView/OutOfProcessWebView.h +++ b/Userland/Libraries/LibWebView/OutOfProcessWebView.h @@ -43,6 +43,8 @@ public: OrderedHashMap get_session_storage_entries(); void set_content_filters(Vector); + void set_autoplay_allowed_on_all_websites(); + void set_autoplay_allowlist(Vector); void set_proxy_mappings(Vector proxies, HashMap mappings); void connect_to_webdriver(DeprecatedString const& webdriver_ipc_path); diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index f254c7708b..2f2558ba29 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -639,6 +640,18 @@ void ConnectionFromClient::set_content_filters(Vector const& f Web::ContentFilter::the().add_pattern(filter); } +void ConnectionFromClient::set_autoplay_allowed_on_all_websites() +{ + auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the(); + autoplay_allowlist.enable_globally(); +} + +void ConnectionFromClient::set_autoplay_allowlist(Vector const& allowlist) +{ + auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the(); + autoplay_allowlist.enable_for_origins(allowlist).release_value_but_fixme_should_propagate_errors(); +} + void ConnectionFromClient::set_proxy_mappings(Vector const& proxies, HashMap const& mappings) { auto keys = mappings.keys(); diff --git a/Userland/Services/WebContent/ConnectionFromClient.h b/Userland/Services/WebContent/ConnectionFromClient.h index 57d5c92404..61353c3ef5 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.h +++ b/Userland/Services/WebContent/ConnectionFromClient.h @@ -75,6 +75,8 @@ private: virtual Messages::WebContentServer::GetHoveredNodeIdResponse get_hovered_node_id() override; virtual Messages::WebContentServer::DumpLayoutTreeResponse dump_layout_tree() override; virtual void set_content_filters(Vector const&) override; + virtual void set_autoplay_allowed_on_all_websites() override; + virtual void set_autoplay_allowlist(Vector const& allowlist) override; virtual void set_proxy_mappings(Vector const&, HashMap const&) override; virtual void set_preferred_color_scheme(Web::CSS::PreferredColorScheme const&) override; virtual void set_has_focus(bool) override; diff --git a/Userland/Services/WebContent/WebContentServer.ipc b/Userland/Services/WebContent/WebContentServer.ipc index a6b534b973..31db29d292 100644 --- a/Userland/Services/WebContent/WebContentServer.ipc +++ b/Userland/Services/WebContent/WebContentServer.ipc @@ -55,6 +55,8 @@ endpoint WebContentServer select_all() =| set_content_filters(Vector filters) =| + set_autoplay_allowed_on_all_websites() =| + set_autoplay_allowlist(Vector allowlist) =| set_proxy_mappings(Vector proxies, HashMap mappings) =| set_preferred_color_scheme(Web::CSS::PreferredColorScheme color_scheme) =| set_has_focus(bool has_focus) =|