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.
This commit is contained in:
parent
ca87f977e6
commit
8dc8835b64
32 changed files with 369 additions and 221 deletions
|
|
@ -642,8 +642,8 @@
|
||||||
</div>
|
</div>
|
||||||
<div class="dialog-body">
|
<div class="dialog-body">
|
||||||
<div class="inline-container">
|
<div class="inline-container">
|
||||||
<label for="site-settings-global">Enable on all sites</label>
|
<label for="site-settings-policy">Default for all sites</label>
|
||||||
<input id="site-settings-global" type="checkbox" switch />
|
<select id="site-settings-policy"></select>
|
||||||
</div>
|
</div>
|
||||||
<hr />
|
<hr />
|
||||||
<label>Allowlist</label>
|
<label>Allowlist</label>
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
const siteSettings = document.querySelector("#site-settings");
|
const siteSettings = document.querySelector("#site-settings");
|
||||||
const siteSettingsAdd = document.querySelector("#site-settings-add");
|
const siteSettingsAdd = document.querySelector("#site-settings-add");
|
||||||
const siteSettingsClose = document.querySelector("#site-settings-close");
|
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 siteSettingsList = document.querySelector("#site-settings-list");
|
||||||
const siteSettingsInput = document.querySelector("#site-settings-input");
|
const siteSettingsInput = document.querySelector("#site-settings-input");
|
||||||
const siteSettingsRemoveAll = document.querySelector("#site-settings-remove-all");
|
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 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 = {};
|
let AUTOPLAY_SETTINGS = {};
|
||||||
|
|
||||||
function loadSiteSettings(settings) {
|
function loadSiteSettings(settings) {
|
||||||
|
|
@ -40,14 +48,29 @@ function currentSiteSetting() {
|
||||||
}
|
}
|
||||||
|
|
||||||
function showSiteSettings(title, settings) {
|
function showSiteSettings(title, settings) {
|
||||||
|
const setting = title.toLowerCase();
|
||||||
|
|
||||||
siteSettingsTitle.innerText = title;
|
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 = "";
|
siteSettingsList.innerHTML = "";
|
||||||
|
|
||||||
siteSettingsGlobal.onchange = () => {
|
siteSettingsPolicy.onchange = () => {
|
||||||
ladybird.sendMessage("setSiteSettingEnabledGlobally", {
|
ladybird.sendMessage("setSiteSettingPolicy", {
|
||||||
setting: currentSiteSetting(),
|
setting: currentSiteSetting(),
|
||||||
enabled: siteSettingsGlobal.checked,
|
policy: siteSettingsPolicy.value,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -474,6 +474,7 @@ set(SOURCES
|
||||||
HTML/AudioTrack.cpp
|
HTML/AudioTrack.cpp
|
||||||
HTML/AudioTrackList.cpp
|
HTML/AudioTrackList.cpp
|
||||||
HTML/AutocompleteElement.cpp
|
HTML/AutocompleteElement.cpp
|
||||||
|
HTML/AutoplaySettings.cpp
|
||||||
HTML/BarProp.cpp
|
HTML/BarProp.cpp
|
||||||
HTML/BeforeUnloadEvent.cpp
|
HTML/BeforeUnloadEvent.cpp
|
||||||
HTML/BroadcastChannel.cpp
|
HTML/BroadcastChannel.cpp
|
||||||
|
|
@ -912,7 +913,6 @@ set(SOURCES
|
||||||
PerformanceTimeline/PerformanceEntry.cpp
|
PerformanceTimeline/PerformanceEntry.cpp
|
||||||
PerformanceTimeline/PerformanceObserver.cpp
|
PerformanceTimeline/PerformanceObserver.cpp
|
||||||
PerformanceTimeline/PerformanceObserverEntryList.cpp
|
PerformanceTimeline/PerformanceObserverEntryList.cpp
|
||||||
PermissionsPolicy/AutoplayAllowlist.cpp
|
|
||||||
PermissionsAPI/Permissions.cpp
|
PermissionsAPI/Permissions.cpp
|
||||||
PermissionsAPI/PermissionStore.cpp
|
PermissionsAPI/PermissionStore.cpp
|
||||||
PermissionsAPI/PermissionStatus.cpp
|
PermissionsAPI/PermissionStatus.cpp
|
||||||
|
|
|
||||||
|
|
@ -197,7 +197,6 @@
|
||||||
#include <LibWeb/Painting/PaintableBox.h>
|
#include <LibWeb/Painting/PaintableBox.h>
|
||||||
#include <LibWeb/Painting/StackingContext.h>
|
#include <LibWeb/Painting/StackingContext.h>
|
||||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||||
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
|
||||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||||
#include <LibWeb/ResizeObserver/ResizeObserver.h>
|
#include <LibWeb/ResizeObserver/ResizeObserver.h>
|
||||||
#include <LibWeb/ResizeObserver/ResizeObserverEntry.h>
|
#include <LibWeb/ResizeObserver/ResizeObserverEntry.h>
|
||||||
|
|
@ -5808,9 +5807,8 @@ bool Document::is_allowed_to_use_feature(PolicyControlledFeature feature) const
|
||||||
// FIXME: This is ad-hoc. Implement the Permissions Policy specification.
|
// FIXME: This is ad-hoc. Implement the Permissions Policy specification.
|
||||||
switch (feature) {
|
switch (feature) {
|
||||||
case PolicyControlledFeature::Autoplay:
|
case PolicyControlledFeature::Autoplay:
|
||||||
if (PermissionsPolicy::AutoplayAllowlist::the().is_allowed_for_origin(*this, origin()) == PermissionsPolicy::Decision::Enabled)
|
// FIXME: Implement allowlist for this.
|
||||||
return true;
|
return true;
|
||||||
break;
|
|
||||||
case PolicyControlledFeature::Camera:
|
case PolicyControlledFeature::Camera:
|
||||||
// FIXME: Implement allowlist for this.
|
// FIXME: Implement allowlist for this.
|
||||||
return true;
|
return true;
|
||||||
|
|
|
||||||
|
|
@ -693,6 +693,7 @@ namespace Web::HTML {
|
||||||
class AnimationFrameCallbackDriver;
|
class AnimationFrameCallbackDriver;
|
||||||
class AudioTrack;
|
class AudioTrack;
|
||||||
class AudioTrackList;
|
class AudioTrackList;
|
||||||
|
class AutoplaySettings;
|
||||||
class BarProp;
|
class BarProp;
|
||||||
class BeforeUnloadEvent;
|
class BeforeUnloadEvent;
|
||||||
class BroadcastChannel;
|
class BroadcastChannel;
|
||||||
|
|
@ -1056,12 +1057,6 @@ class PerformanceObserverEntryList;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace Web::PermissionsPolicy {
|
|
||||||
|
|
||||||
class AutoplayAllowlist;
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
namespace Web::PermissionsAPI {
|
namespace Web::PermissionsAPI {
|
||||||
|
|
||||||
class Permissions;
|
class Permissions;
|
||||||
|
|
|
||||||
25
Libraries/LibWeb/HTML/AutoplayPolicy.h
Normal file
25
Libraries/LibWeb/HTML/AutoplayPolicy.h
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2025, Luke Wilde <luke@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Optional.h>
|
||||||
|
#include <AK/StringView.h>
|
||||||
|
#include <AK/Types.h>
|
||||||
|
#include <LibWeb/Export.h>
|
||||||
|
|
||||||
|
namespace Web::HTML {
|
||||||
|
|
||||||
|
enum class AutoplayPolicy : u8 {
|
||||||
|
AllowAudioAndVideo,
|
||||||
|
BlockAudio,
|
||||||
|
BlockAudioAndVideo,
|
||||||
|
};
|
||||||
|
|
||||||
|
WEB_API Optional<AutoplayPolicy> autoplay_policy_from_string(StringView);
|
||||||
|
WEB_API StringView autoplay_policy_to_string(AutoplayPolicy);
|
||||||
|
|
||||||
|
}
|
||||||
95
Libraries/LibWeb/HTML/AutoplaySettings.cpp
Normal file
95
Libraries/LibWeb/HTML/AutoplaySettings.cpp
Normal file
|
|
@ -0,0 +1,95 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#include <AK/String.h>
|
||||||
|
#include <LibURL/Origin.h>
|
||||||
|
#include <LibURL/Parser.h>
|
||||||
|
#include <LibURL/URL.h>
|
||||||
|
#include <LibWeb/DOM/Document.h>
|
||||||
|
#include <LibWeb/HTML/AutoplaySettings.h>
|
||||||
|
|
||||||
|
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<String> 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<AutoplayPolicy> 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();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
41
Libraries/LibWeb/HTML/AutoplaySettings.h
Normal file
41
Libraries/LibWeb/HTML/AutoplaySettings.h
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
/*
|
||||||
|
* Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
|
||||||
|
*
|
||||||
|
* SPDX-License-Identifier: BSD-2-Clause
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <AK/Vector.h>
|
||||||
|
#include <LibURL/Forward.h>
|
||||||
|
#include <LibWeb/Export.h>
|
||||||
|
#include <LibWeb/Forward.h>
|
||||||
|
#include <LibWeb/HTML/AutoplayPolicy.h>
|
||||||
|
|
||||||
|
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<String> allowlist);
|
||||||
|
|
||||||
|
private:
|
||||||
|
AutoplaySettings();
|
||||||
|
~AutoplaySettings();
|
||||||
|
|
||||||
|
AutoplayPolicy m_policy { AutoplayPolicy::BlockAudio };
|
||||||
|
Vector<URL::Origin> m_allowlist;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -30,6 +30,7 @@
|
||||||
#include <LibWeb/HTML/AudioPlayState.h>
|
#include <LibWeb/HTML/AudioPlayState.h>
|
||||||
#include <LibWeb/HTML/AudioTrack.h>
|
#include <LibWeb/HTML/AudioTrack.h>
|
||||||
#include <LibWeb/HTML/AudioTrackList.h>
|
#include <LibWeb/HTML/AudioTrackList.h>
|
||||||
|
#include <LibWeb/HTML/AutoplaySettings.h>
|
||||||
#include <LibWeb/HTML/CORSSettingAttribute.h>
|
#include <LibWeb/HTML/CORSSettingAttribute.h>
|
||||||
#include <LibWeb/HTML/HTMLAudioElement.h>
|
#include <LibWeb/HTML/HTMLAudioElement.h>
|
||||||
#include <LibWeb/HTML/HTMLMediaElement.h>
|
#include <LibWeb/HTML/HTMLMediaElement.h>
|
||||||
|
|
@ -531,7 +532,11 @@ GC::Ref<WebIDL::Promise> HTMLMediaElement::play()
|
||||||
{
|
{
|
||||||
auto& realm = this->realm();
|
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
|
// 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.
|
// 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));
|
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();
|
update_volume();
|
||||||
}
|
}
|
||||||
|
|
@ -2148,7 +2156,7 @@ void HTMLMediaElement::set_ready_state(ReadyState ready_state)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// The user agent may run the following substeps:
|
// The user agent may run the following substeps:
|
||||||
{
|
if (is_allowed_to_play()) {
|
||||||
// Set the paused attribute to false.
|
// Set the paused attribute to false.
|
||||||
set_paused(false);
|
set_paused(false);
|
||||||
|
|
||||||
|
|
@ -2686,6 +2694,29 @@ bool HTMLMediaElement::is_eligible_for_autoplay() const
|
||||||
document().is_allowed_to_use_feature(DOM::PolicyControlledFeature::Autoplay));
|
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
|
HTMLMediaElement::PlaybackDirection HTMLMediaElement::direction_of_playback() const
|
||||||
{
|
{
|
||||||
return m_playback_rate >= 0 ? PlaybackDirection::Forwards : PlaybackDirection::Backwards;
|
return m_playback_rate >= 0 ? PlaybackDirection::Forwards : PlaybackDirection::Backwards;
|
||||||
|
|
|
||||||
|
|
@ -255,6 +255,7 @@ private:
|
||||||
void update_current_video_frame();
|
void update_current_video_frame();
|
||||||
|
|
||||||
bool is_eligible_for_autoplay() const;
|
bool is_eligible_for_autoplay() const;
|
||||||
|
bool is_allowed_to_play() const;
|
||||||
|
|
||||||
enum class PlaybackDirection : u8 {
|
enum class PlaybackDirection : u8 {
|
||||||
Forwards,
|
Forwards,
|
||||||
|
|
|
||||||
|
|
@ -35,6 +35,7 @@
|
||||||
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
||||||
#include <LibWeb/Geometry/DOMRect.h>
|
#include <LibWeb/Geometry/DOMRect.h>
|
||||||
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
#include <LibWeb/HTML/AnimatedBitmapDecodedImageData.h>
|
||||||
|
#include <LibWeb/HTML/AutoplaySettings.h>
|
||||||
#include <LibWeb/HTML/BrowsingContext.h>
|
#include <LibWeb/HTML/BrowsingContext.h>
|
||||||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||||
#include <LibWeb/HTML/EventLoop/TaskQueue.h>
|
#include <LibWeb/HTML/EventLoop/TaskQueue.h>
|
||||||
|
|
@ -548,6 +549,12 @@ void Internals::set_content_blocking_enabled(bool enabled)
|
||||||
page().set_content_blocking_enabled(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
|
// NOLINTNEXTLINE(readability-convert-member-functions-to-static
|
||||||
String Internals::get_computed_role(DOM::Element& element)
|
String Internals::get_computed_role(DOM::Element& element)
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -88,6 +88,7 @@ public:
|
||||||
bool set_http_memory_cache_enabled(bool enabled);
|
bool set_http_memory_cache_enabled(bool enabled);
|
||||||
WebIDL::ExceptionOr<void> set_content_blockers(String const& patterns);
|
WebIDL::ExceptionOr<void> set_content_blockers(String const& patterns);
|
||||||
void set_content_blocking_enabled(bool enabled);
|
void set_content_blocking_enabled(bool enabled);
|
||||||
|
void set_autoplay_policy(String const& policy);
|
||||||
|
|
||||||
String get_computed_role(DOM::Element& element);
|
String get_computed_role(DOM::Element& element);
|
||||||
String get_computed_label(DOM::Element& element);
|
String get_computed_label(DOM::Element& element);
|
||||||
|
|
|
||||||
|
|
@ -79,6 +79,7 @@ interface Internals {
|
||||||
boolean setHttpMemoryCacheEnabled(boolean enabled);
|
boolean setHttpMemoryCacheEnabled(boolean enabled);
|
||||||
undefined setContentBlockers(DOMString patterns);
|
undefined setContentBlockers(DOMString patterns);
|
||||||
undefined setContentBlockingEnabled(boolean enabled);
|
undefined setContentBlockingEnabled(boolean enabled);
|
||||||
|
undefined setAutoplayPolicy(DOMString policy);
|
||||||
|
|
||||||
DOMString getComputedRole(Element element);
|
DOMString getComputedRole(Element element);
|
||||||
DOMString getComputedLabel(Element element);
|
DOMString getComputedLabel(Element element);
|
||||||
|
|
|
||||||
|
|
@ -1,98 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#include <AK/String.h>
|
|
||||||
#include <LibURL/Origin.h>
|
|
||||||
#include <LibURL/Parser.h>
|
|
||||||
#include <LibURL/URL.h>
|
|
||||||
#include <LibWeb/DOM/Document.h>
|
|
||||||
#include <LibWeb/DOMURL/DOMURL.h>
|
|
||||||
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
|
||||||
|
|
||||||
// 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<String> origins)
|
|
||||||
{
|
|
||||||
m_allowlist = Patterns {};
|
|
||||||
|
|
||||||
auto& allowlist = m_allowlist->get<Patterns>();
|
|
||||||
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());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023-2025, Tim Flynn <trflynn89@ladybird.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <AK/Forward.h>
|
|
||||||
#include <AK/Variant.h>
|
|
||||||
#include <AK/Vector.h>
|
|
||||||
#include <LibURL/Forward.h>
|
|
||||||
#include <LibWeb/Export.h>
|
|
||||||
#include <LibWeb/Forward.h>
|
|
||||||
#include <LibWeb/PermissionsPolicy/Decision.h>
|
|
||||||
|
|
||||||
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<String>);
|
|
||||||
|
|
||||||
private:
|
|
||||||
AutoplayAllowlist();
|
|
||||||
~AutoplayAllowlist();
|
|
||||||
|
|
||||||
using Patterns = Vector<URL::Origin>;
|
|
||||||
struct Global { };
|
|
||||||
|
|
||||||
Optional<Variant<Patterns, Global>> m_allowlist;
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,16 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright (c) 2023, Tim Flynn <trflynn89@serenityos.org>
|
|
||||||
*
|
|
||||||
* SPDX-License-Identifier: BSD-2-Clause
|
|
||||||
*/
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
namespace Web::PermissionsPolicy {
|
|
||||||
|
|
||||||
enum class Decision {
|
|
||||||
Enabled,
|
|
||||||
Disabled,
|
|
||||||
};
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -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_KEY = "autocompleteEngine"sv;
|
||||||
static constexpr auto AUTOCOMPLETE_ENGINE_NAME_KEY = "name"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 SITE_SETTING_SITE_FILTERS_KEY = "siteFilters"sv;
|
||||||
|
|
||||||
static constexpr auto AUTOPLAY_KEY = "autoplay"sv;
|
static constexpr auto AUTOPLAY_KEY = "autoplay"sv;
|
||||||
|
|
@ -242,13 +242,15 @@ Settings Settings::create(Badge<Application>)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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);
|
auto saved_settings = settings_json.value().get_object(key);
|
||||||
if (!saved_settings.has_value())
|
if (!saved_settings.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (auto enabled_globally = saved_settings->get_bool(SITE_SETTING_ENABLED_GLOBALLY_KEY); enabled_globally.has_value())
|
if (auto policy = saved_settings->get_string(SITE_SETTING_POLICY_KEY); policy.has_value()) {
|
||||||
site_setting.enabled_globally = *enabled_globally;
|
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()) {
|
if (auto site_filters = saved_settings->get_array(SITE_SETTING_SITE_FILTERS_KEY); site_filters.has_value()) {
|
||||||
site_setting.site_filters.clear();
|
site_setting.site_filters.clear();
|
||||||
|
|
@ -360,7 +362,7 @@ JsonValue Settings::serialize_json() const
|
||||||
settings.set(AUTOCOMPLETE_ENGINE_KEY, move(autocomplete_engine));
|
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;
|
JsonArray site_filters;
|
||||||
site_filters.ensure_capacity(site_setting.site_filters.size());
|
site_filters.ensure_capacity(site_setting.site_filters.size());
|
||||||
|
|
||||||
|
|
@ -368,8 +370,8 @@ JsonValue Settings::serialize_json() const
|
||||||
site_filters.must_append(site_filter);
|
site_filters.must_append(site_filter);
|
||||||
|
|
||||||
JsonObject setting;
|
JsonObject setting;
|
||||||
setting.set("enabledGlobally"sv, site_setting.enabled_globally);
|
setting.set(SITE_SETTING_POLICY_KEY, Web::HTML::autoplay_policy_to_string(site_setting.policy));
|
||||||
setting.set("siteFilters"sv, move(site_filters));
|
setting.set(SITE_SETTING_SITE_FILTERS_KEY, move(site_filters));
|
||||||
|
|
||||||
settings.set(key, move(setting));
|
settings.set(key, move(setting));
|
||||||
};
|
};
|
||||||
|
|
@ -654,9 +656,9 @@ void Settings::set_autocomplete_engine(Optional<StringView> autocomplete_engine_
|
||||||
observer.autocomplete_engine_changed();
|
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();
|
persist_settings();
|
||||||
|
|
||||||
for (auto& observer : m_observers)
|
for (auto& observer : m_observers)
|
||||||
|
|
@ -859,11 +861,6 @@ SettingsObserver::~SettingsObserver()
|
||||||
Settings::remove_observer({}, *this);
|
Settings::remove_observer({}, *this);
|
||||||
}
|
}
|
||||||
|
|
||||||
SiteSetting::SiteSetting()
|
|
||||||
{
|
|
||||||
site_filters.set("file://"_string);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
namespace IPC {
|
namespace IPC {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@
|
||||||
#include <LibHTTP/Cache/DiskCacheSettings.h>
|
#include <LibHTTP/Cache/DiskCacheSettings.h>
|
||||||
#include <LibIPC/Forward.h>
|
#include <LibIPC/Forward.h>
|
||||||
#include <LibURL/URL.h>
|
#include <LibURL/URL.h>
|
||||||
|
#include <LibWeb/HTML/AutoplayPolicy.h>
|
||||||
#include <LibWebView/Autocomplete.h>
|
#include <LibWebView/Autocomplete.h>
|
||||||
#include <LibWebView/Forward.h>
|
#include <LibWebView/Forward.h>
|
||||||
#include <LibWebView/Options.h>
|
#include <LibWebView/Options.h>
|
||||||
|
|
@ -35,10 +36,14 @@ struct BrowsingBehavior {
|
||||||
};
|
};
|
||||||
|
|
||||||
struct SiteSetting {
|
struct SiteSetting {
|
||||||
SiteSetting();
|
|
||||||
|
|
||||||
bool enabled_globally { false };
|
|
||||||
OrderedHashTable<String> site_filters;
|
OrderedHashTable<String> site_filters;
|
||||||
|
|
||||||
|
protected:
|
||||||
|
SiteSetting() = default;
|
||||||
|
};
|
||||||
|
|
||||||
|
struct AutoplaySiteSetting : public SiteSetting {
|
||||||
|
Web::HTML::AutoplayPolicy policy { Web::HTML::AutoplayPolicy::BlockAudio };
|
||||||
};
|
};
|
||||||
|
|
||||||
struct BrowsingDataSettings {
|
struct BrowsingDataSettings {
|
||||||
|
|
@ -137,8 +142,8 @@ public:
|
||||||
Optional<AutocompleteEngine> const& autocomplete_engine() const { return m_autocomplete_engine; }
|
Optional<AutocompleteEngine> const& autocomplete_engine() const { return m_autocomplete_engine; }
|
||||||
void set_autocomplete_engine(Optional<StringView> autocomplete_engine_name);
|
void set_autocomplete_engine(Optional<StringView> autocomplete_engine_name);
|
||||||
|
|
||||||
SiteSetting const& autoplay_settings() const { return m_autoplay; }
|
AutoplaySiteSetting const& autoplay_settings() const { return m_autoplay; }
|
||||||
void set_autoplay_enabled_globally(bool);
|
void set_autoplay_policy(Web::HTML::AutoplayPolicy);
|
||||||
void add_autoplay_site_filter(String const&);
|
void add_autoplay_site_filter(String const&);
|
||||||
void remove_autoplay_site_filter(String const&);
|
void remove_autoplay_site_filter(String const&);
|
||||||
void remove_all_autoplay_site_filters();
|
void remove_all_autoplay_site_filters();
|
||||||
|
|
@ -183,7 +188,7 @@ private:
|
||||||
Optional<SearchEngine> m_search_engine;
|
Optional<SearchEngine> m_search_engine;
|
||||||
Vector<SearchEngine> m_custom_search_engines;
|
Vector<SearchEngine> m_custom_search_engines;
|
||||||
Optional<AutocompleteEngine> m_autocomplete_engine;
|
Optional<AutocompleteEngine> m_autocomplete_engine;
|
||||||
SiteSetting m_autoplay;
|
AutoplaySiteSetting m_autoplay;
|
||||||
BrowsingDataSettings m_browsing_data_settings;
|
BrowsingDataSettings m_browsing_data_settings;
|
||||||
GlobalPrivacyControl m_global_privacy_control { GlobalPrivacyControl::No };
|
GlobalPrivacyControl m_global_privacy_control { GlobalPrivacyControl::No };
|
||||||
DNSSettings m_dns_settings { SystemDNS() };
|
DNSSettings m_dns_settings { SystemDNS() };
|
||||||
|
|
|
||||||
|
|
@ -2373,10 +2373,11 @@ void ViewImplementation::autoplay_settings_changed()
|
||||||
auto const& autoplay_settings = Application::settings().autoplay_settings();
|
auto const& autoplay_settings = Application::settings().autoplay_settings();
|
||||||
auto const& web_content_options = Application::web_content_options();
|
auto const& web_content_options = Application::web_content_options();
|
||||||
|
|
||||||
if (autoplay_settings.enabled_globally || web_content_options.enable_autoplay == EnableAutoplay::Yes)
|
auto policy = autoplay_settings.policy;
|
||||||
client().async_set_autoplay_allowed_on_all_websites(page_id());
|
if (web_content_options.enable_autoplay == EnableAutoplay::Yes)
|
||||||
else
|
policy = Web::HTML::AutoplayPolicy::AllowAudioAndVideo;
|
||||||
client().async_set_autoplay_allowlist(page_id(), autoplay_settings.site_filters.values());
|
|
||||||
|
client().async_set_autoplay_settings(page_id(), policy, autoplay_settings.site_filters.values());
|
||||||
}
|
}
|
||||||
|
|
||||||
void ViewImplementation::global_privacy_control_changed()
|
void ViewImplementation::global_privacy_control_changed()
|
||||||
|
|
|
||||||
|
|
@ -7,6 +7,7 @@
|
||||||
#include <AK/JsonArray.h>
|
#include <AK/JsonArray.h>
|
||||||
#include <AK/Platform.h>
|
#include <AK/Platform.h>
|
||||||
#include <LibURL/Parser.h>
|
#include <LibURL/Parser.h>
|
||||||
|
#include <LibWeb/HTML/AutoplayPolicy.h>
|
||||||
#include <LibWebView/Application.h>
|
#include <LibWebView/Application.h>
|
||||||
#include <LibWebView/SearchEngine.h>
|
#include <LibWebView/SearchEngine.h>
|
||||||
#include <LibWebView/WebUI/SettingsUI.h>
|
#include <LibWebView/WebUI/SettingsUI.h>
|
||||||
|
|
@ -92,8 +93,8 @@ void SettingsUI::register_interfaces()
|
||||||
register_interface("loadForciblyEnabledSiteSettings"sv, [this](auto const&) {
|
register_interface("loadForciblyEnabledSiteSettings"sv, [this](auto const&) {
|
||||||
load_forcibly_enabled_site_settings();
|
load_forcibly_enabled_site_settings();
|
||||||
});
|
});
|
||||||
register_interface("setSiteSettingEnabledGlobally"sv, [this](auto const& data) {
|
register_interface("setSiteSettingPolicy"sv, [this](auto const& data) {
|
||||||
set_site_setting_enabled_globally(data);
|
set_site_setting_policy(data);
|
||||||
});
|
});
|
||||||
register_interface("addSiteSettingFilter"sv, [this](auto const& data) {
|
register_interface("addSiteSettingFilter"sv, [this](auto const& data) {
|
||||||
add_site_setting_filter(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));
|
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);
|
auto setting = site_setting_type(site_setting);
|
||||||
if (!setting.has_value())
|
if (!setting.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
auto enabled = site_setting.as_object().get_bool("enabled"sv);
|
auto policy = site_setting.as_object().get_string("policy"sv);
|
||||||
if (!enabled.has_value())
|
if (!policy.has_value())
|
||||||
return;
|
return;
|
||||||
|
|
||||||
switch (*setting) {
|
switch (*setting) {
|
||||||
case SiteSettingType::Autoplay:
|
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;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ private:
|
||||||
void set_autocomplete_engine(JsonValue const&);
|
void set_autocomplete_engine(JsonValue const&);
|
||||||
|
|
||||||
void load_forcibly_enabled_site_settings();
|
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 add_site_setting_filter(JsonValue const&);
|
||||||
void remove_site_setting_filter(JsonValue const&);
|
void remove_site_setting_filter(JsonValue const&);
|
||||||
void remove_all_site_setting_filters(JsonValue const&);
|
void remove_all_site_setting_filters(JsonValue const&);
|
||||||
|
|
|
||||||
|
|
@ -44,6 +44,7 @@
|
||||||
#include <LibWeb/DOM/Text.h>
|
#include <LibWeb/DOM/Text.h>
|
||||||
#include <LibWeb/Dump.h>
|
#include <LibWeb/Dump.h>
|
||||||
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
#include <LibWeb/Fetch/Fetching/Fetching.h>
|
||||||
|
#include <LibWeb/HTML/AutoplaySettings.h>
|
||||||
#include <LibWeb/HTML/BroadcastChannel.h>
|
#include <LibWeb/HTML/BroadcastChannel.h>
|
||||||
#include <LibWeb/HTML/BrowsingContext.h>
|
#include <LibWeb/HTML/BrowsingContext.h>
|
||||||
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
#include <LibWeb/HTML/EventLoop/EventLoop.h>
|
||||||
|
|
@ -68,7 +69,6 @@
|
||||||
#include <LibWeb/Painting/FlexboxInspectorOverlay.h>
|
#include <LibWeb/Painting/FlexboxInspectorOverlay.h>
|
||||||
#include <LibWeb/Painting/StackingContext.h>
|
#include <LibWeb/Painting/StackingContext.h>
|
||||||
#include <LibWeb/Painting/ViewportPaintable.h>
|
#include <LibWeb/Painting/ViewportPaintable.h>
|
||||||
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
|
||||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||||
#include <LibWebView/Attribute.h>
|
#include <LibWebView/Attribute.h>
|
||||||
#include <LibWebView/ViewImplementation.h>
|
#include <LibWebView/ViewImplementation.h>
|
||||||
|
|
@ -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<String> allowlist)
|
||||||
{
|
{
|
||||||
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
|
Web::HTML::AutoplaySettings::the().set_policy(policy, allowlist);
|
||||||
autoplay_allowlist.enable_globally();
|
|
||||||
}
|
|
||||||
|
|
||||||
void ConnectionFromClient::set_autoplay_allowlist(u64, Vector<String> allowlist)
|
|
||||||
{
|
|
||||||
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
|
|
||||||
autoplay_allowlist.enable_for_origins(allowlist);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void ConnectionFromClient::set_proxy_mappings(u64, Vector<ByteString> proxies, HashMap<ByteString, size_t> mappings)
|
void ConnectionFromClient::set_proxy_mappings(u64, Vector<ByteString> proxies, HashMap<ByteString, size_t> mappings)
|
||||||
|
|
|
||||||
|
|
@ -24,6 +24,7 @@
|
||||||
#include <LibWeb/CSS/PreferredMotion.h>
|
#include <LibWeb/CSS/PreferredMotion.h>
|
||||||
#include <LibWeb/Compositor/Types.h>
|
#include <LibWeb/Compositor/Types.h>
|
||||||
#include <LibWeb/Forward.h>
|
#include <LibWeb/Forward.h>
|
||||||
|
#include <LibWeb/HTML/AutoplayPolicy.h>
|
||||||
#include <LibWeb/HTML/SessionHistoryEntry.h>
|
#include <LibWeb/HTML/SessionHistoryEntry.h>
|
||||||
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
#include <LibWeb/HTML/WorkerAgentTypes.h>
|
||||||
#include <LibWeb/Loader/FileRequest.h>
|
#include <LibWeb/Loader/FileRequest.h>
|
||||||
|
|
@ -140,8 +141,7 @@ private:
|
||||||
virtual void remove_dom_node(u64 page_id, Web::UniqueNodeID node_id) override;
|
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_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_settings(u64 page_id, Web::HTML::AutoplayPolicy policy, Vector<String> allowlist) override;
|
||||||
virtual void set_autoplay_allowlist(u64 page_id, Vector<String> allowlist) override;
|
|
||||||
virtual void set_proxy_mappings(u64 page_id, Vector<ByteString>, HashMap<ByteString, size_t>) override;
|
virtual void set_proxy_mappings(u64 page_id, Vector<ByteString>, HashMap<ByteString, size_t>) override;
|
||||||
virtual void set_preferred_color_scheme(u64 page_id, Web::CSS::PreferredColorScheme) 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;
|
virtual void set_preferred_contrast(u64 page_id, Web::CSS::PreferredContrast) override;
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <LibWeb/CSS/PreferredMotion.h>
|
#include <LibWeb/CSS/PreferredMotion.h>
|
||||||
#include <LibWeb/CSS/Selector.h>
|
#include <LibWeb/CSS/Selector.h>
|
||||||
#include <LibWeb/CSS/StyleSheetIdentifier.h>
|
#include <LibWeb/CSS/StyleSheetIdentifier.h>
|
||||||
|
#include <LibWeb/HTML/AutoplayPolicy.h>
|
||||||
#include <LibWeb/HTML/ColorPickerUpdateState.h>
|
#include <LibWeb/HTML/ColorPickerUpdateState.h>
|
||||||
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
#include <LibWeb/HTML/BroadcastChannelMessage.h>
|
||||||
#include <LibWeb/HTML/SelectedFile.h>
|
#include <LibWeb/HTML/SelectedFile.h>
|
||||||
|
|
@ -131,8 +132,7 @@ endpoint WebContentServer
|
||||||
find_in_page_previous_match(u64 page_id) =|
|
find_in_page_previous_match(u64 page_id) =|
|
||||||
|
|
||||||
set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns) =|
|
set_content_blockers(u64 page_id, Core::AnonymousBuffer patterns) =|
|
||||||
set_autoplay_allowed_on_all_websites(u64 page_id) =|
|
set_autoplay_settings(u64 page_id, Web::HTML::AutoplayPolicy policy, Vector<String> allowlist) =|
|
||||||
set_autoplay_allowlist(u64 page_id, Vector<String> allowlist) =|
|
|
||||||
set_proxy_mappings(u64 page_id, Vector<ByteString> proxies, HashMap<ByteString, size_t> mappings) =|
|
set_proxy_mappings(u64 page_id, Vector<ByteString> proxies, HashMap<ByteString, size_t> mappings) =|
|
||||||
set_preferred_color_scheme(u64 page_id, Web::CSS::PreferredColorScheme color_scheme) =|
|
set_preferred_color_scheme(u64 page_id, Web::CSS::PreferredColorScheme color_scheme) =|
|
||||||
set_preferred_contrast(u64 page_id, Web::CSS::PreferredContrast contrast) =|
|
set_preferred_contrast(u64 page_id, Web::CSS::PreferredContrast contrast) =|
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
muted autoplay started: true
|
||||||
|
paused after unmuting without a gesture: true
|
||||||
|
scripted audible play() rejected with: NotAllowedError
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
muted video autoplays: true
|
||||||
|
audible video autoplays: false
|
||||||
|
|
@ -0,0 +1,38 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- Pin the policy here (not via the browser setting test-web inherits) so the result is deterministic
|
||||||
|
regardless of test order on a reused WebContent process. Served over HTTP (see the .headers file)
|
||||||
|
so the file:// carve-out does not apply. With no user gesture, "Block Audio" must keep audible
|
||||||
|
playback from sneaking in: a muted autoplay that is later unmuted pauses, and a scripted play() of
|
||||||
|
audible media is rejected. -->
|
||||||
|
<script>internals.setAutoplayPolicy("block-audio");</script>
|
||||||
|
<video autoplay muted id="muted" src="../../../Assets/test-webm.webm"></video>
|
||||||
|
<video id="scripted" src="../../../Assets/test-webm.webm"></video>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
asyncTest(async done => {
|
||||||
|
const muted = document.getElementById("muted");
|
||||||
|
const scripted = document.getElementById("scripted");
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
println("FAIL: timeout waiting for muted autoplay");
|
||||||
|
done();
|
||||||
|
}, 15_000);
|
||||||
|
|
||||||
|
if (muted.paused)
|
||||||
|
await new Promise(resolve => muted.addEventListener("playing", resolve, { once: true }));
|
||||||
|
println(`muted autoplay started: ${!muted.paused}`);
|
||||||
|
|
||||||
|
muted.muted = false;
|
||||||
|
println(`paused after unmuting without a gesture: ${muted.paused}`);
|
||||||
|
|
||||||
|
let rejection = "none";
|
||||||
|
try {
|
||||||
|
await scripted.play();
|
||||||
|
} catch (error) {
|
||||||
|
rejection = error.name;
|
||||||
|
}
|
||||||
|
println(`scripted audible play() rejected with: ${rejection}`);
|
||||||
|
|
||||||
|
done();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Content-Type: text/html
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<!-- Pin the policy here (not via the browser setting test-web inherits) so the result is deterministic
|
||||||
|
regardless of test order on a reused WebContent process. Served over HTTP (see the .headers file)
|
||||||
|
so the file:// autoplay carve-out does not apply: under "Block Audio", muted media autoplays while
|
||||||
|
audible media does not. -->
|
||||||
|
<script>internals.setAutoplayPolicy("block-audio");</script>
|
||||||
|
<video autoplay muted id="muted" src="../../../Assets/test-webm.webm"></video>
|
||||||
|
<video autoplay id="audible" src="../../../Assets/test-webm.webm"></video>
|
||||||
|
<script src="../include.js"></script>
|
||||||
|
<script>
|
||||||
|
asyncTest(done => {
|
||||||
|
const results = {};
|
||||||
|
let remaining = 2;
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
println("FAIL: timeout waiting for media to load");
|
||||||
|
done();
|
||||||
|
}, 15_000);
|
||||||
|
|
||||||
|
function watch(key, video) {
|
||||||
|
const finish = value => {
|
||||||
|
results[key] = value;
|
||||||
|
if (--remaining === 0) {
|
||||||
|
println(`muted video autoplays: ${results.muted}`);
|
||||||
|
println(`audible video autoplays: ${results.audible}`);
|
||||||
|
done();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
video.addEventListener("canplaythrough", () => finish(!video.paused));
|
||||||
|
video.addEventListener("error", () => finish("error"));
|
||||||
|
}
|
||||||
|
|
||||||
|
watch("muted", document.getElementById("muted"));
|
||||||
|
watch("audible", document.getElementById("audible"));
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
Content-Type: text/html
|
||||||
|
|
@ -1,4 +1,7 @@
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
|
<!-- Allow autoplay so the scripted play() works without a user gesture, independent of the browser's
|
||||||
|
autoplay policy that test-web would otherwise inherit. -->
|
||||||
|
<script>internals.setAutoplayPolicy("allow-audio-and-video");</script>
|
||||||
<video id="video"></video>
|
<video id="video"></video>
|
||||||
<script src="../include.js"></script>
|
<script src="../include.js"></script>
|
||||||
<script>
|
<script>
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
#include <LibMedia/Audio/Loader.h>
|
#include <LibMedia/Audio/Loader.h>
|
||||||
#include <LibRequests/RequestClient.h>
|
#include <LibRequests/RequestClient.h>
|
||||||
#include <LibWeb/Bindings/MainThreadVM.h>
|
#include <LibWeb/Bindings/MainThreadVM.h>
|
||||||
|
#include <LibWeb/HTML/AutoplaySettings.h>
|
||||||
#include <LibWeb/HTML/Window.h>
|
#include <LibWeb/HTML/Window.h>
|
||||||
#include <LibWeb/Loader/ContentBlocker.h>
|
#include <LibWeb/Loader/ContentBlocker.h>
|
||||||
#include <LibWeb/Loader/GeneratedPagesLoader.h>
|
#include <LibWeb/Loader/GeneratedPagesLoader.h>
|
||||||
#include <LibWeb/Loader/ResourceLoader.h>
|
#include <LibWeb/Loader/ResourceLoader.h>
|
||||||
#include <LibWeb/PermissionsPolicy/AutoplayAllowlist.h>
|
|
||||||
#include <LibWeb/Platform/EventLoopPlugin.h>
|
#include <LibWeb/Platform/EventLoopPlugin.h>
|
||||||
#include <LibWeb/Platform/FontPlugin.h>
|
#include <LibWeb/Platform/FontPlugin.h>
|
||||||
#include <LibWebView/HelperProcess.h>
|
#include <LibWebView/HelperProcess.h>
|
||||||
|
|
@ -150,8 +150,7 @@ static ErrorOr<void> load_autoplay_allowlist()
|
||||||
TRY(origins.try_append(move(domain)));
|
TRY(origins.try_append(move(domain)));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto& autoplay_allowlist = Web::PermissionsPolicy::AutoplayAllowlist::the();
|
Web::HTML::AutoplaySettings::the().set_policy(Web::HTML::AutoplayPolicy::BlockAudio, origins);
|
||||||
autoplay_allowlist.enable_for_origins(origins);
|
|
||||||
|
|
||||||
return {};
|
return {};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue