From 8dc8835b64f7f1b426d86165124ebfd30c7793eb Mon Sep 17 00:00:00 2001 From: Luke Wilde Date: Tue, 9 Jun 2026 17:55:09 +0100 Subject: [PATCH] LibWeb+LibWebView+WebContent: Allow muted media to autoplay by default The autoplay setting was binary and its default blocked all media, including muted video, leaving sites that rely on muted autoplay visibly broken. Replace it with a tri-state user-agent autoplay policy (allow audio and video, block audio, or block audio and video) defaulting to allowing only inaudible media to autoplay. This is enforced through the media element's "allowed to play" check, so unmuting a muted autoplay or calling `play()` cannot slip audio past the policy; audible playback is permitted once the document has been activated by the user. The policy lives in a dedicated AutoplaySettings consulted from HTMLMediaElement instead of the Permissions Policy "allowed to use feature" check it was previously conflated with. --- Base/res/ladybird/about-pages/settings.html | 4 +- .../about-pages/settings/permissions.js | 33 ++++++- Libraries/LibWeb/CMakeLists.txt | 2 +- Libraries/LibWeb/DOM/Document.cpp | 6 +- Libraries/LibWeb/Forward.h | 7 +- Libraries/LibWeb/HTML/AutoplayPolicy.h | 25 +++++ Libraries/LibWeb/HTML/AutoplaySettings.cpp | 95 ++++++++++++++++++ Libraries/LibWeb/HTML/AutoplaySettings.h | 41 ++++++++ Libraries/LibWeb/HTML/HTMLMediaElement.cpp | 37 ++++++- Libraries/LibWeb/HTML/HTMLMediaElement.h | 1 + Libraries/LibWeb/Internals/Internals.cpp | 7 ++ Libraries/LibWeb/Internals/Internals.h | 1 + Libraries/LibWeb/Internals/Internals.idl | 1 + .../PermissionsPolicy/AutoplayAllowlist.cpp | 98 ------------------- .../PermissionsPolicy/AutoplayAllowlist.h | 38 ------- Libraries/LibWeb/PermissionsPolicy/Decision.h | 16 --- Libraries/LibWebView/Settings.cpp | 25 +++-- Libraries/LibWebView/Settings.h | 17 ++-- Libraries/LibWebView/ViewImplementation.cpp | 9 +- Libraries/LibWebView/WebUI/SettingsUI.cpp | 14 +-- Libraries/LibWebView/WebUI/SettingsUI.h | 2 +- Services/WebContent/ConnectionFromClient.cpp | 13 +-- Services/WebContent/ConnectionFromClient.h | 4 +- Services/WebContent/WebContentServer.ipc | 4 +- ...utoplay-policy-blocks-becoming-audible.txt | 3 + .../autoplay-policy-default-blocks-audio.txt | 2 + ...toplay-policy-blocks-becoming-audible.html | 38 +++++++ ...olicy-blocks-becoming-audible.html.headers | 1 + .../autoplay-policy-default-blocks-audio.html | 37 +++++++ ...y-policy-default-blocks-audio.html.headers | 1 + .../Text/input/HTML/media-source-setup.html | 3 + UI/Android/src/main/cpp/WebContentService.cpp | 5 +- 32 files changed, 369 insertions(+), 221 deletions(-) create mode 100644 Libraries/LibWeb/HTML/AutoplayPolicy.h create mode 100644 Libraries/LibWeb/HTML/AutoplaySettings.cpp create mode 100644 Libraries/LibWeb/HTML/AutoplaySettings.h delete mode 100644 Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp delete mode 100644 Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h delete mode 100644 Libraries/LibWeb/PermissionsPolicy/Decision.h create mode 100644 Tests/LibWeb/Text/expected/HTML/autoplay-policy-blocks-becoming-audible.txt create mode 100644 Tests/LibWeb/Text/expected/HTML/autoplay-policy-default-blocks-audio.txt create mode 100644 Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html create mode 100644 Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html.headers create mode 100644 Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html create mode 100644 Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html.headers diff --git a/Base/res/ladybird/about-pages/settings.html b/Base/res/ladybird/about-pages/settings.html index 64c786f8c1..2ea31f45be 100644 --- a/Base/res/ladybird/about-pages/settings.html +++ b/Base/res/ladybird/about-pages/settings.html @@ -642,8 +642,8 @@
- - + +

diff --git a/Base/res/ladybird/about-pages/settings/permissions.js b/Base/res/ladybird/about-pages/settings/permissions.js index 3f0022c04e..b14c4d3acc 100644 --- a/Base/res/ladybird/about-pages/settings/permissions.js +++ b/Base/res/ladybird/about-pages/settings/permissions.js @@ -1,7 +1,7 @@ const siteSettings = document.querySelector("#site-settings"); const siteSettingsAdd = document.querySelector("#site-settings-add"); const siteSettingsClose = document.querySelector("#site-settings-close"); -const siteSettingsGlobal = document.querySelector("#site-settings-global"); +const siteSettingsPolicy = document.querySelector("#site-settings-policy"); const siteSettingsList = document.querySelector("#site-settings-list"); const siteSettingsInput = document.querySelector("#site-settings-input"); const siteSettingsRemoveAll = document.querySelector("#site-settings-remove-all"); @@ -9,6 +9,14 @@ const siteSettingsTitle = document.querySelector("#site-settings-title"); const autoplaySettings = document.querySelector("#autoplay-settings"); +const SITE_SETTING_POLICY_OPTIONS = { + autoplay: [ + { value: "allow-audio-and-video", label: "Allow Audio and Video" }, + { value: "block-audio", label: "Block Audio" }, + { value: "block-audio-and-video", label: "Block Audio and Video" }, + ], +}; + let AUTOPLAY_SETTINGS = {}; function loadSiteSettings(settings) { @@ -40,14 +48,29 @@ function currentSiteSetting() { } function showSiteSettings(title, settings) { + const setting = title.toLowerCase(); + siteSettingsTitle.innerText = title; - siteSettingsGlobal.checked = settings.enabledGlobally; + + siteSettingsPolicy.innerHTML = ""; + + const policyOptions = SITE_SETTING_POLICY_OPTIONS[setting] ?? []; + + policyOptions.forEach(({ value, label }) => { + const option = document.createElement("option"); + option.value = value; + option.textContent = label; + + siteSettingsPolicy.appendChild(option); + }); + + siteSettingsPolicy.value = settings.policy; siteSettingsList.innerHTML = ""; - siteSettingsGlobal.onchange = () => { - ladybird.sendMessage("setSiteSettingEnabledGlobally", { + siteSettingsPolicy.onchange = () => { + ladybird.sendMessage("setSiteSettingPolicy", { setting: currentSiteSetting(), - enabled: siteSettingsGlobal.checked, + policy: siteSettingsPolicy.value, }); }; diff --git a/Libraries/LibWeb/CMakeLists.txt b/Libraries/LibWeb/CMakeLists.txt index 243b8b067f..ba70f5ea21 100644 --- a/Libraries/LibWeb/CMakeLists.txt +++ b/Libraries/LibWeb/CMakeLists.txt @@ -474,6 +474,7 @@ set(SOURCES HTML/AudioTrack.cpp HTML/AudioTrackList.cpp HTML/AutocompleteElement.cpp + HTML/AutoplaySettings.cpp HTML/BarProp.cpp HTML/BeforeUnloadEvent.cpp HTML/BroadcastChannel.cpp @@ -912,7 +913,6 @@ set(SOURCES PerformanceTimeline/PerformanceEntry.cpp PerformanceTimeline/PerformanceObserver.cpp PerformanceTimeline/PerformanceObserverEntryList.cpp - PermissionsPolicy/AutoplayAllowlist.cpp PermissionsAPI/Permissions.cpp PermissionsAPI/PermissionStore.cpp PermissionsAPI/PermissionStatus.cpp diff --git a/Libraries/LibWeb/DOM/Document.cpp b/Libraries/LibWeb/DOM/Document.cpp index a41a173e7d..286f295231 100644 --- a/Libraries/LibWeb/DOM/Document.cpp +++ b/Libraries/LibWeb/DOM/Document.cpp @@ -197,7 +197,6 @@ #include #include #include -#include #include #include #include @@ -5808,9 +5807,8 @@ bool Document::is_allowed_to_use_feature(PolicyControlledFeature feature) const // 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; + // FIXME: Implement allowlist for this. + return true; case PolicyControlledFeature::Camera: // FIXME: Implement allowlist for this. return true; diff --git a/Libraries/LibWeb/Forward.h b/Libraries/LibWeb/Forward.h index a905b5fc7b..33c75c8a8a 100644 --- a/Libraries/LibWeb/Forward.h +++ b/Libraries/LibWeb/Forward.h @@ -693,6 +693,7 @@ namespace Web::HTML { class AnimationFrameCallbackDriver; class AudioTrack; class AudioTrackList; +class AutoplaySettings; class BarProp; class BeforeUnloadEvent; class BroadcastChannel; @@ -1056,12 +1057,6 @@ class PerformanceObserverEntryList; } -namespace Web::PermissionsPolicy { - -class AutoplayAllowlist; - -} - namespace Web::PermissionsAPI { class Permissions; diff --git a/Libraries/LibWeb/HTML/AutoplayPolicy.h b/Libraries/LibWeb/HTML/AutoplayPolicy.h new file mode 100644 index 0000000000..1a9b4f0dbf --- /dev/null +++ b/Libraries/LibWeb/HTML/AutoplayPolicy.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2025, Luke Wilde + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include + +namespace Web::HTML { + +enum class AutoplayPolicy : u8 { + AllowAudioAndVideo, + BlockAudio, + BlockAudioAndVideo, +}; + +WEB_API Optional autoplay_policy_from_string(StringView); +WEB_API StringView autoplay_policy_to_string(AutoplayPolicy); + +} diff --git a/Libraries/LibWeb/HTML/AutoplaySettings.cpp b/Libraries/LibWeb/HTML/AutoplaySettings.cpp new file mode 100644 index 0000000000..a9e47d3c3c --- /dev/null +++ b/Libraries/LibWeb/HTML/AutoplaySettings.cpp @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2023-2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#include +#include +#include +#include +#include +#include + +namespace Web::HTML { + +AutoplaySettings& AutoplaySettings::the() +{ + static auto& settings = *new AutoplaySettings; + return settings; +} + +AutoplaySettings::AutoplaySettings() = default; +AutoplaySettings::~AutoplaySettings() = default; + +AutoplayDecision AutoplaySettings::decision_for_origin(DOM::Document const& document, URL::Origin const& origin) const +{ + // An origin in the allowlist may always autoplay, with or without audio. + for (auto const& allowed : m_allowlist) { + if (allowed.is_same_origin_domain(origin)) + return AutoplayDecision::Allowed; + } + + // AD-HOC: Allow autoplay for file:// URLs if the document is also from a file:// URL. + if (origin.is_opaque_file_origin() && document.origin().is_opaque_file_origin()) + return AutoplayDecision::Allowed; + + switch (m_policy) { + case AutoplayPolicy::AllowAudioAndVideo: + return AutoplayDecision::Allowed; + case AutoplayPolicy::BlockAudio: + return AutoplayDecision::AllowedIfInaudible; + case AutoplayPolicy::BlockAudioAndVideo: + return AutoplayDecision::Blocked; + } + + VERIFY_NOT_REACHED(); +} + +void AutoplaySettings::set_policy(AutoplayPolicy policy, ReadonlySpan allowlist) +{ + m_policy = policy; + + m_allowlist.clear_with_capacity(); + m_allowlist.ensure_capacity(allowlist.size()); + + for (auto const& origin : allowlist) { + auto url = URL::Parser::basic_parse(origin); + + if (!url.has_value()) + url = URL::Parser::basic_parse(MUST(String::formatted("https://{}", origin))); + if (!url.has_value()) { + dbgln("Invalid origin for autoplay allowlist: {}", origin); + continue; + } + + m_allowlist.append(url->origin()); + } +} + +Optional autoplay_policy_from_string(StringView string) +{ + if (string == "allow-audio-and-video"sv) + return AutoplayPolicy::AllowAudioAndVideo; + if (string == "block-audio"sv) + return AutoplayPolicy::BlockAudio; + if (string == "block-audio-and-video"sv) + return AutoplayPolicy::BlockAudioAndVideo; + return {}; +} + +StringView autoplay_policy_to_string(AutoplayPolicy policy) +{ + switch (policy) { + case AutoplayPolicy::AllowAudioAndVideo: + return "allow-audio-and-video"sv; + case AutoplayPolicy::BlockAudio: + return "block-audio"sv; + case AutoplayPolicy::BlockAudioAndVideo: + return "block-audio-and-video"sv; + } + + VERIFY_NOT_REACHED(); +} + +} diff --git a/Libraries/LibWeb/HTML/AutoplaySettings.h b/Libraries/LibWeb/HTML/AutoplaySettings.h new file mode 100644 index 0000000000..f95f2fb3f6 --- /dev/null +++ b/Libraries/LibWeb/HTML/AutoplaySettings.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2023-2025, Tim Flynn + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#pragma once + +#include +#include +#include +#include +#include + +namespace Web::HTML { + +enum class AutoplayDecision : u8 { + Allowed, + AllowedIfInaudible, + Blocked, +}; + +// Holds the user agent's autoplay configuration for this process: the global policy plus the set of +// origins that may always autoplay regardless of that policy. Populated from the browser process. +class WEB_API AutoplaySettings { +public: + static AutoplaySettings& the(); + + AutoplayDecision decision_for_origin(DOM::Document const&, URL::Origin const&) const; + + void set_policy(AutoplayPolicy, ReadonlySpan allowlist); + +private: + AutoplaySettings(); + ~AutoplaySettings(); + + AutoplayPolicy m_policy { AutoplayPolicy::BlockAudio }; + Vector m_allowlist; +}; + +} diff --git a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp index 8c586b7423..8a70a8a642 100644 --- a/Libraries/LibWeb/HTML/HTMLMediaElement.cpp +++ b/Libraries/LibWeb/HTML/HTMLMediaElement.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #include #include #include @@ -531,7 +532,11 @@ GC::Ref HTMLMediaElement::play() { auto& realm = this->realm(); - // FIXME: 1. If the media element is not allowed to play, then return a promise rejected with a "NotAllowedError" DOMException. + // 1. If the media element is not allowed to play, then return a promise rejected with a "NotAllowedError" DOMException. + if (!is_allowed_to_play()) { + auto exception = WebIDL::NotAllowedError::create(realm, "Media playback is not allowed without user interaction"_utf16); + return WebIDL::create_rejected_promise_from_exception(realm, exception); + } // 2. If the media element's error attribute is not null and its code is MEDIA_ERR_SRC_NOT_SUPPORTED, then return a promise // rejected with a "NotSupportedError" DOMException. @@ -612,7 +617,10 @@ void HTMLMediaElement::volume_or_muted_attribute_changed() self.dispatch_event(DOM::Event::create(self.realm(), HTML::EventNames::volumechange)); }); - // FIXME: Then, if the media element is not allowed to play, the user agent must run the internal pause steps for the media element. + // Then, if the media element is not allowed to play, the user agent must run the internal pause steps for the + // media element. + if (!is_allowed_to_play()) + pause_element(); update_volume(); } @@ -2148,7 +2156,7 @@ void HTMLMediaElement::set_ready_state(ReadyState ready_state) return; // The user agent may run the following substeps: - { + if (is_allowed_to_play()) { // Set the paused attribute to false. set_paused(false); @@ -2686,6 +2694,29 @@ bool HTMLMediaElement::is_eligible_for_autoplay() const document().is_allowed_to_use_feature(DOM::PolicyControlledFeature::Autoplay)); } +// https://html.spec.whatwg.org/multipage/media.html#allowed-to-play +bool HTMLMediaElement::is_allowed_to_play() const +{ + // A media element is said to be allowed to play if the user agent and the system allow media playback in the + // current context. + // NB: We allow playback once the document has been activated by the user, with an exception for inaudible media. + // Gating on transient activation instead pauses audible media once the activation expires, e.g. between ads, + // or media in a playlist. + if (auto window = document().window(); window && window->has_sticky_activation()) + return true; + + switch (AutoplaySettings::the().decision_for_origin(document(), document().origin())) { + case AutoplayDecision::Allowed: + return true; + case AutoplayDecision::AllowedIfInaudible: + return effective_media_volume() == 0.0; + case AutoplayDecision::Blocked: + return false; + } + + VERIFY_NOT_REACHED(); +} + HTMLMediaElement::PlaybackDirection HTMLMediaElement::direction_of_playback() const { return m_playback_rate >= 0 ? PlaybackDirection::Forwards : PlaybackDirection::Backwards; diff --git a/Libraries/LibWeb/HTML/HTMLMediaElement.h b/Libraries/LibWeb/HTML/HTMLMediaElement.h index 2f74ee4b72..d3302e860c 100644 --- a/Libraries/LibWeb/HTML/HTMLMediaElement.h +++ b/Libraries/LibWeb/HTML/HTMLMediaElement.h @@ -255,6 +255,7 @@ private: void update_current_video_frame(); bool is_eligible_for_autoplay() const; + bool is_allowed_to_play() const; enum class PlaybackDirection : u8 { Forwards, diff --git a/Libraries/LibWeb/Internals/Internals.cpp b/Libraries/LibWeb/Internals/Internals.cpp index 8835d57400..eb5dd463e5 100644 --- a/Libraries/LibWeb/Internals/Internals.cpp +++ b/Libraries/LibWeb/Internals/Internals.cpp @@ -35,6 +35,7 @@ #include #include #include +#include #include #include #include @@ -548,6 +549,12 @@ void Internals::set_content_blocking_enabled(bool enabled) page().set_content_blocking_enabled(enabled); } +void Internals::set_autoplay_policy(String const& policy) +{ + if (auto parsed = HTML::autoplay_policy_from_string(policy); parsed.has_value()) + HTML::AutoplaySettings::the().set_policy(*parsed, {}); +} + // NOLINTNEXTLINE(readability-convert-member-functions-to-static String Internals::get_computed_role(DOM::Element& element) { diff --git a/Libraries/LibWeb/Internals/Internals.h b/Libraries/LibWeb/Internals/Internals.h index d1dd29d6f0..24631cfbbc 100644 --- a/Libraries/LibWeb/Internals/Internals.h +++ b/Libraries/LibWeb/Internals/Internals.h @@ -88,6 +88,7 @@ public: bool set_http_memory_cache_enabled(bool enabled); WebIDL::ExceptionOr set_content_blockers(String const& patterns); void set_content_blocking_enabled(bool enabled); + void set_autoplay_policy(String const& policy); String get_computed_role(DOM::Element& element); String get_computed_label(DOM::Element& element); diff --git a/Libraries/LibWeb/Internals/Internals.idl b/Libraries/LibWeb/Internals/Internals.idl index d021a4c7c2..66033bf656 100644 --- a/Libraries/LibWeb/Internals/Internals.idl +++ b/Libraries/LibWeb/Internals/Internals.idl @@ -79,6 +79,7 @@ interface Internals { boolean setHttpMemoryCacheEnabled(boolean enabled); undefined setContentBlockers(DOMString patterns); undefined setContentBlockingEnabled(boolean enabled); + undefined setAutoplayPolicy(DOMString policy); DOMString getComputedRole(Element element); DOMString getComputedLabel(Element element); diff --git a/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp b/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp deleted file mode 100644 index e151fec5c7..0000000000 --- a/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.cpp +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2023-2025, Tim Flynn - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#include -#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 auto& filter = *new AutoplayAllowlist; - 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, URL::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; - - // AD-HOC: Allow autoplay for file:// URLs if the document is also from a file:// URL. - if (origin.is_opaque_file_origin() && document.origin().is_opaque_file_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; - - // AD-HOC: Allow autoplay for file:// URLs if the document is also from a file:// URL. - if (origin.is_opaque_file_origin() && document.origin().is_opaque_file_origin()) - return Decision::Enabled; - - // 6. Return "Disabled". - return Decision::Disabled; -} - -void AutoplayAllowlist::enable_globally() -{ - m_allowlist = Global {}; -} - -void AutoplayAllowlist::enable_for_origins(ReadonlySpan origins) -{ - m_allowlist = Patterns {}; - - auto& allowlist = m_allowlist->get(); - allowlist.ensure_capacity(origins.size()); - - for (auto const& origin : origins) { - auto url = URL::Parser::basic_parse(origin); - - if (!url.has_value()) - url = URL::Parser::basic_parse(MUST(String::formatted("https://{}", origin))); - if (!url.has_value()) { - dbgln("Invalid origin for autoplay allowlist: {}", origin); - continue; - } - - allowlist.append(url->origin()); - } -} - -} diff --git a/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h b/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h deleted file mode 100644 index 2bd7795dd8..0000000000 --- a/Libraries/LibWeb/PermissionsPolicy/AutoplayAllowlist.h +++ /dev/null @@ -1,38 +0,0 @@ -/* - * Copyright (c) 2023-2025, Tim Flynn - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -#include -#include -#include -#include -#include -#include -#include - -namespace Web::PermissionsPolicy { - -class WEB_API AutoplayAllowlist { -public: - static AutoplayAllowlist& the(); - - Decision is_allowed_for_origin(DOM::Document const&, URL::Origin const&) const; - - void enable_globally(); - void enable_for_origins(ReadonlySpan); - -private: - AutoplayAllowlist(); - ~AutoplayAllowlist(); - - using Patterns = Vector; - struct Global { }; - - Optional> m_allowlist; -}; - -} diff --git a/Libraries/LibWeb/PermissionsPolicy/Decision.h b/Libraries/LibWeb/PermissionsPolicy/Decision.h deleted file mode 100644 index 8b163013b7..0000000000 --- a/Libraries/LibWeb/PermissionsPolicy/Decision.h +++ /dev/null @@ -1,16 +0,0 @@ -/* - * Copyright (c) 2023, Tim Flynn - * - * SPDX-License-Identifier: BSD-2-Clause - */ - -#pragma once - -namespace Web::PermissionsPolicy { - -enum class Decision { - Enabled, - Disabled, -}; - -} diff --git a/Libraries/LibWebView/Settings.cpp b/Libraries/LibWebView/Settings.cpp index f73e74da0d..bb8de941ff 100644 --- a/Libraries/LibWebView/Settings.cpp +++ b/Libraries/LibWebView/Settings.cpp @@ -55,7 +55,7 @@ static constexpr auto SEARCH_ENGINE_URL_KEY = "url"sv; static constexpr auto AUTOCOMPLETE_ENGINE_KEY = "autocompleteEngine"sv; static constexpr auto AUTOCOMPLETE_ENGINE_NAME_KEY = "name"sv; -static constexpr auto SITE_SETTING_ENABLED_GLOBALLY_KEY = "enabledGlobally"sv; +static constexpr auto SITE_SETTING_POLICY_KEY = "policy"sv; static constexpr auto SITE_SETTING_SITE_FILTERS_KEY = "siteFilters"sv; static constexpr auto AUTOPLAY_KEY = "autoplay"sv; @@ -242,13 +242,15 @@ Settings Settings::create(Badge) } } - auto load_site_setting = [&](SiteSetting& site_setting, StringView key) { + auto load_site_setting = [&](AutoplaySiteSetting& site_setting, StringView key) { auto saved_settings = settings_json.value().get_object(key); if (!saved_settings.has_value()) return; - if (auto enabled_globally = saved_settings->get_bool(SITE_SETTING_ENABLED_GLOBALLY_KEY); enabled_globally.has_value()) - site_setting.enabled_globally = *enabled_globally; + if (auto policy = saved_settings->get_string(SITE_SETTING_POLICY_KEY); policy.has_value()) { + if (auto parsed = Web::HTML::autoplay_policy_from_string(*policy); parsed.has_value()) + site_setting.policy = *parsed; + } if (auto site_filters = saved_settings->get_array(SITE_SETTING_SITE_FILTERS_KEY); site_filters.has_value()) { site_setting.site_filters.clear(); @@ -360,7 +362,7 @@ JsonValue Settings::serialize_json() const settings.set(AUTOCOMPLETE_ENGINE_KEY, move(autocomplete_engine)); } - auto save_site_setting = [&](SiteSetting const& site_setting, StringView key) { + auto save_site_setting = [&](AutoplaySiteSetting const& site_setting, StringView key) { JsonArray site_filters; site_filters.ensure_capacity(site_setting.site_filters.size()); @@ -368,8 +370,8 @@ JsonValue Settings::serialize_json() const site_filters.must_append(site_filter); JsonObject setting; - setting.set("enabledGlobally"sv, site_setting.enabled_globally); - setting.set("siteFilters"sv, move(site_filters)); + setting.set(SITE_SETTING_POLICY_KEY, Web::HTML::autoplay_policy_to_string(site_setting.policy)); + setting.set(SITE_SETTING_SITE_FILTERS_KEY, move(site_filters)); settings.set(key, move(setting)); }; @@ -654,9 +656,9 @@ void Settings::set_autocomplete_engine(Optional autocomplete_engine_ observer.autocomplete_engine_changed(); } -void Settings::set_autoplay_enabled_globally(bool enabled_globally) +void Settings::set_autoplay_policy(Web::HTML::AutoplayPolicy policy) { - m_autoplay.enabled_globally = enabled_globally; + m_autoplay.policy = policy; persist_settings(); for (auto& observer : m_observers) @@ -859,11 +861,6 @@ SettingsObserver::~SettingsObserver() Settings::remove_observer({}, *this); } -SiteSetting::SiteSetting() -{ - site_filters.set("file://"_string); -} - } namespace IPC { diff --git a/Libraries/LibWebView/Settings.h b/Libraries/LibWebView/Settings.h index b37464428d..6b5a193442 100644 --- a/Libraries/LibWebView/Settings.h +++ b/Libraries/LibWebView/Settings.h @@ -15,6 +15,7 @@ #include #include #include +#include #include #include #include @@ -35,10 +36,14 @@ struct BrowsingBehavior { }; struct SiteSetting { - SiteSetting(); - - bool enabled_globally { false }; OrderedHashTable site_filters; + +protected: + SiteSetting() = default; +}; + +struct AutoplaySiteSetting : public SiteSetting { + Web::HTML::AutoplayPolicy policy { Web::HTML::AutoplayPolicy::BlockAudio }; }; struct BrowsingDataSettings { @@ -137,8 +142,8 @@ public: Optional const& autocomplete_engine() const { return m_autocomplete_engine; } void set_autocomplete_engine(Optional autocomplete_engine_name); - SiteSetting const& autoplay_settings() const { return m_autoplay; } - void set_autoplay_enabled_globally(bool); + AutoplaySiteSetting const& autoplay_settings() const { return m_autoplay; } + void set_autoplay_policy(Web::HTML::AutoplayPolicy); void add_autoplay_site_filter(String const&); void remove_autoplay_site_filter(String const&); void remove_all_autoplay_site_filters(); @@ -183,7 +188,7 @@ private: Optional m_search_engine; Vector m_custom_search_engines; Optional m_autocomplete_engine; - SiteSetting m_autoplay; + AutoplaySiteSetting m_autoplay; BrowsingDataSettings m_browsing_data_settings; GlobalPrivacyControl m_global_privacy_control { GlobalPrivacyControl::No }; DNSSettings m_dns_settings { SystemDNS() }; diff --git a/Libraries/LibWebView/ViewImplementation.cpp b/Libraries/LibWebView/ViewImplementation.cpp index 17c5c7ac55..12b79688c7 100644 --- a/Libraries/LibWebView/ViewImplementation.cpp +++ b/Libraries/LibWebView/ViewImplementation.cpp @@ -2373,10 +2373,11 @@ void ViewImplementation::autoplay_settings_changed() auto const& autoplay_settings = Application::settings().autoplay_settings(); auto const& web_content_options = Application::web_content_options(); - if (autoplay_settings.enabled_globally || web_content_options.enable_autoplay == EnableAutoplay::Yes) - client().async_set_autoplay_allowed_on_all_websites(page_id()); - else - client().async_set_autoplay_allowlist(page_id(), autoplay_settings.site_filters.values()); + auto policy = autoplay_settings.policy; + if (web_content_options.enable_autoplay == EnableAutoplay::Yes) + policy = Web::HTML::AutoplayPolicy::AllowAudioAndVideo; + + client().async_set_autoplay_settings(page_id(), policy, autoplay_settings.site_filters.values()); } void ViewImplementation::global_privacy_control_changed() diff --git a/Libraries/LibWebView/WebUI/SettingsUI.cpp b/Libraries/LibWebView/WebUI/SettingsUI.cpp index c0d6f97075..81025652ac 100644 --- a/Libraries/LibWebView/WebUI/SettingsUI.cpp +++ b/Libraries/LibWebView/WebUI/SettingsUI.cpp @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -92,8 +93,8 @@ void SettingsUI::register_interfaces() register_interface("loadForciblyEnabledSiteSettings"sv, [this](auto const&) { load_forcibly_enabled_site_settings(); }); - register_interface("setSiteSettingEnabledGlobally"sv, [this](auto const& data) { - set_site_setting_enabled_globally(data); + register_interface("setSiteSettingPolicy"sv, [this](auto const& data) { + set_site_setting_policy(data); }); register_interface("addSiteSettingFilter"sv, [this](auto const& data) { add_site_setting_filter(data); @@ -316,19 +317,20 @@ void SettingsUI::load_forcibly_enabled_site_settings() async_send_message("forciblyEnableSiteSettings"sv, move(site_settings)); } -void SettingsUI::set_site_setting_enabled_globally(JsonValue const& site_setting) +void SettingsUI::set_site_setting_policy(JsonValue const& site_setting) { auto setting = site_setting_type(site_setting); if (!setting.has_value()) return; - auto enabled = site_setting.as_object().get_bool("enabled"sv); - if (!enabled.has_value()) + auto policy = site_setting.as_object().get_string("policy"sv); + if (!policy.has_value()) return; switch (*setting) { case SiteSettingType::Autoplay: - WebView::Application::settings().set_autoplay_enabled_globally(*enabled); + if (auto parsed = Web::HTML::autoplay_policy_from_string(*policy); parsed.has_value()) + WebView::Application::settings().set_autoplay_policy(*parsed); break; } diff --git a/Libraries/LibWebView/WebUI/SettingsUI.h b/Libraries/LibWebView/WebUI/SettingsUI.h index 2813d4da6c..a9bc18e2c8 100644 --- a/Libraries/LibWebView/WebUI/SettingsUI.h +++ b/Libraries/LibWebView/WebUI/SettingsUI.h @@ -34,7 +34,7 @@ private: void set_autocomplete_engine(JsonValue const&); void load_forcibly_enabled_site_settings(); - void set_site_setting_enabled_globally(JsonValue const&); + void set_site_setting_policy(JsonValue const&); void add_site_setting_filter(JsonValue const&); void remove_site_setting_filter(JsonValue const&); void remove_all_site_setting_filters(JsonValue const&); diff --git a/Services/WebContent/ConnectionFromClient.cpp b/Services/WebContent/ConnectionFromClient.cpp index baf16655a1..4e740c82b8 100644 --- a/Services/WebContent/ConnectionFromClient.cpp +++ b/Services/WebContent/ConnectionFromClient.cpp @@ -44,6 +44,7 @@ #include #include #include +#include #include #include #include @@ -68,7 +69,6 @@ #include #include #include -#include #include #include #include @@ -1972,16 +1972,9 @@ void ConnectionFromClient::set_content_blockers(u64 page_id, Core::AnonymousBuff } } -void ConnectionFromClient::set_autoplay_allowed_on_all_websites(u64) +void ConnectionFromClient::set_autoplay_settings(u64, Web::HTML::AutoplayPolicy policy, Vector allowlist) { - auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the(); - autoplay_allowlist.enable_globally(); -} - -void ConnectionFromClient::set_autoplay_allowlist(u64, Vector allowlist) -{ - auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the(); - autoplay_allowlist.enable_for_origins(allowlist); + Web::HTML::AutoplaySettings::the().set_policy(policy, allowlist); } void ConnectionFromClient::set_proxy_mappings(u64, Vector proxies, HashMap mappings) diff --git a/Services/WebContent/ConnectionFromClient.h b/Services/WebContent/ConnectionFromClient.h index 02c3fa2e14..3788c194fc 100644 --- a/Services/WebContent/ConnectionFromClient.h +++ b/Services/WebContent/ConnectionFromClient.h @@ -24,6 +24,7 @@ #include #include #include +#include #include #include #include @@ -140,8 +141,7 @@ private: virtual void remove_dom_node(u64 page_id, Web::UniqueNodeID node_id) override; virtual void set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns) override; - virtual void set_autoplay_allowed_on_all_websites(u64 page_id) override; - virtual void set_autoplay_allowlist(u64 page_id, Vector allowlist) override; + virtual void set_autoplay_settings(u64 page_id, Web::HTML::AutoplayPolicy policy, Vector allowlist) override; virtual void set_proxy_mappings(u64 page_id, Vector, HashMap) override; virtual void set_preferred_color_scheme(u64 page_id, Web::CSS::PreferredColorScheme) override; virtual void set_preferred_contrast(u64 page_id, Web::CSS::PreferredContrast) override; diff --git a/Services/WebContent/WebContentServer.ipc b/Services/WebContent/WebContentServer.ipc index 97280a3848..542b85d76f 100644 --- a/Services/WebContent/WebContentServer.ipc +++ b/Services/WebContent/WebContentServer.ipc @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #include @@ -131,8 +132,7 @@ endpoint WebContentServer find_in_page_previous_match(u64 page_id) =| set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns) =| - set_autoplay_allowed_on_all_websites(u64 page_id) =| - set_autoplay_allowlist(u64 page_id, Vector allowlist) =| + set_autoplay_settings(u64 page_id, Web::HTML::AutoplayPolicy policy, Vector allowlist) =| set_proxy_mappings(u64 page_id, Vector proxies, HashMap mappings) =| set_preferred_color_scheme(u64 page_id, Web::CSS::PreferredColorScheme color_scheme) =| set_preferred_contrast(u64 page_id, Web::CSS::PreferredContrast contrast) =| diff --git a/Tests/LibWeb/Text/expected/HTML/autoplay-policy-blocks-becoming-audible.txt b/Tests/LibWeb/Text/expected/HTML/autoplay-policy-blocks-becoming-audible.txt new file mode 100644 index 0000000000..3ebabb8b99 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/autoplay-policy-blocks-becoming-audible.txt @@ -0,0 +1,3 @@ +muted autoplay started: true +paused after unmuting without a gesture: true +scripted audible play() rejected with: NotAllowedError diff --git a/Tests/LibWeb/Text/expected/HTML/autoplay-policy-default-blocks-audio.txt b/Tests/LibWeb/Text/expected/HTML/autoplay-policy-default-blocks-audio.txt new file mode 100644 index 0000000000..8b12bb7fe0 --- /dev/null +++ b/Tests/LibWeb/Text/expected/HTML/autoplay-policy-default-blocks-audio.txt @@ -0,0 +1,2 @@ +muted video autoplays: true +audible video autoplays: false diff --git a/Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html b/Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html new file mode 100644 index 0000000000..d4799b8522 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html @@ -0,0 +1,38 @@ + + + + + + + diff --git a/Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html.headers b/Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html.headers new file mode 100644 index 0000000000..156209f9c8 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/autoplay-policy-blocks-becoming-audible.html.headers @@ -0,0 +1 @@ +Content-Type: text/html diff --git a/Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html b/Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html new file mode 100644 index 0000000000..c6f36671c1 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html @@ -0,0 +1,37 @@ + + + + + + + diff --git a/Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html.headers b/Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html.headers new file mode 100644 index 0000000000..156209f9c8 --- /dev/null +++ b/Tests/LibWeb/Text/input/HTML/autoplay-policy-default-blocks-audio.html.headers @@ -0,0 +1 @@ +Content-Type: text/html diff --git a/Tests/LibWeb/Text/input/HTML/media-source-setup.html b/Tests/LibWeb/Text/input/HTML/media-source-setup.html index bd99856ecb..97e02f657f 100644 --- a/Tests/LibWeb/Text/input/HTML/media-source-setup.html +++ b/Tests/LibWeb/Text/input/HTML/media-source-setup.html @@ -1,4 +1,7 @@ + +