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