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 @@ + +