diff --git a/Ladybird/Tab.cpp b/Ladybird/Tab.cpp index 06fb1d2f86..975ff90e31 100644 --- a/Ladybird/Tab.cpp +++ b/Ladybird/Tab.cpp @@ -376,6 +376,8 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView:: m_media_context_menu_play_icon = make(QString("%1/res/icons/16x16/play.png").arg(s_serenity_resource_root.characters())); m_media_context_menu_pause_icon = make(QString("%1/res/icons/16x16/pause.png").arg(s_serenity_resource_root.characters())); + m_media_context_menu_mute_icon = make(QString("%1/res/icons/16x16/audio-volume-muted.png").arg(s_serenity_resource_root.characters())); + m_media_context_menu_unmute_icon = make(QString("%1/res/icons/16x16/audio-volume-high.png").arg(s_serenity_resource_root.characters())); m_media_context_menu_play_pause_action = make("&Play", this); m_media_context_menu_play_pause_action->setIcon(*m_media_context_menu_play_icon); @@ -383,6 +385,12 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView:: view().toggle_media_play_state(); }); + m_media_context_menu_mute_unmute_action = make("&Mute", this); + m_media_context_menu_mute_unmute_action->setIcon(*m_media_context_menu_mute_icon); + QObject::connect(m_media_context_menu_mute_unmute_action, &QAction::triggered, this, [this]() { + view().toggle_media_mute_state(); + }); + m_media_context_menu_controls_action = make("Show &Controls", this); m_media_context_menu_controls_action->setCheckable(true); QObject::connect(m_media_context_menu_controls_action, &QAction::triggered, this, [this]() { @@ -403,6 +411,7 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView:: m_audio_context_menu = make("Audio context menu", this); m_audio_context_menu->addAction(m_media_context_menu_play_pause_action); + m_audio_context_menu->addAction(m_media_context_menu_mute_unmute_action); m_audio_context_menu->addAction(m_media_context_menu_controls_action); m_audio_context_menu->addAction(m_media_context_menu_loop_action); m_audio_context_menu->addSeparator(); @@ -430,6 +439,7 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView:: m_video_context_menu = make("Video context menu", this); m_video_context_menu->addAction(m_media_context_menu_play_pause_action); + m_video_context_menu->addAction(m_media_context_menu_mute_unmute_action); m_video_context_menu->addAction(m_media_context_menu_controls_action); m_video_context_menu->addAction(m_media_context_menu_loop_action); m_video_context_menu->addSeparator(); @@ -451,6 +461,14 @@ Tab::Tab(BrowserWindow* window, StringView webdriver_content_ipc_path, WebView:: m_media_context_menu_play_pause_action->setText("&Play"); } + if (menu.is_muted) { + m_media_context_menu_mute_unmute_action->setIcon(*m_media_context_menu_unmute_icon); + m_media_context_menu_mute_unmute_action->setText("Un&mute"); + } else { + m_media_context_menu_mute_unmute_action->setIcon(*m_media_context_menu_mute_icon); + m_media_context_menu_mute_unmute_action->setText("&Mute"); + } + m_media_context_menu_controls_action->setChecked(menu.has_user_agent_controls); m_media_context_menu_loop_action->setChecked(menu.is_looping); diff --git a/Ladybird/Tab.h b/Ladybird/Tab.h index 366bda0a91..3616af597e 100644 --- a/Ladybird/Tab.h +++ b/Ladybird/Tab.h @@ -100,7 +100,10 @@ private: OwnPtr m_video_context_menu; OwnPtr m_media_context_menu_play_icon; OwnPtr m_media_context_menu_pause_icon; + OwnPtr m_media_context_menu_mute_icon; + OwnPtr m_media_context_menu_unmute_icon; OwnPtr m_media_context_menu_play_pause_action; + OwnPtr m_media_context_menu_mute_unmute_action; OwnPtr m_media_context_menu_controls_action; OwnPtr m_media_context_menu_loop_action; URL m_media_context_menu_url; diff --git a/Userland/Applications/Browser/IconBag.cpp b/Userland/Applications/Browser/IconBag.cpp index 38b87a8d54..1b35be3084 100644 --- a/Userland/Applications/Browser/IconBag.cpp +++ b/Userland/Applications/Browser/IconBag.cpp @@ -45,6 +45,8 @@ ErrorOr IconBag::try_create() icon_bag.rename = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/rename.png"sv)); icon_bag.play = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/play.png"sv)); icon_bag.pause = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/pause.png"sv)); + icon_bag.mute = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-muted.png"sv)); + icon_bag.unmute = TRY(Gfx::Bitmap::load_from_file("/res/icons/16x16/audio-volume-high.png"sv)); return icon_bag; } diff --git a/Userland/Applications/Browser/IconBag.h b/Userland/Applications/Browser/IconBag.h index 8dda18ed17..c00d9499e9 100644 --- a/Userland/Applications/Browser/IconBag.h +++ b/Userland/Applications/Browser/IconBag.h @@ -47,5 +47,7 @@ struct IconBag final { RefPtr rename { nullptr }; RefPtr play { nullptr }; RefPtr pause { nullptr }; + RefPtr mute { nullptr }; + RefPtr unmute { nullptr }; }; } diff --git a/Userland/Applications/Browser/Tab.cpp b/Userland/Applications/Browser/Tab.cpp index c45a8a7dc4..9d714da91b 100644 --- a/Userland/Applications/Browser/Tab.cpp +++ b/Userland/Applications/Browser/Tab.cpp @@ -377,6 +377,9 @@ Tab::Tab(BrowserWindow& window) m_media_context_menu_play_pause_action = GUI::Action::create("&Play", g_icon_bag.play, [this](auto&) { view().toggle_media_play_state(); }); + m_media_context_menu_mute_unmute_action = GUI::Action::create("&Mute", g_icon_bag.mute, [this](auto&) { + view().toggle_media_mute_state(); + }); m_media_context_menu_controls_action = GUI::Action::create_checkable("Show &Controls", [this](auto&) { view().toggle_media_controls_state(); }); @@ -386,6 +389,7 @@ Tab::Tab(BrowserWindow& window) m_audio_context_menu = GUI::Menu::construct(); m_audio_context_menu->add_action(*m_media_context_menu_play_pause_action); + m_audio_context_menu->add_action(*m_media_context_menu_mute_unmute_action); m_audio_context_menu->add_action(*m_media_context_menu_controls_action); m_audio_context_menu->add_action(*m_media_context_menu_loop_action); m_audio_context_menu->add_separator(); @@ -401,6 +405,7 @@ Tab::Tab(BrowserWindow& window) m_video_context_menu = GUI::Menu::construct(); m_video_context_menu->add_action(*m_media_context_menu_play_pause_action); + m_video_context_menu->add_action(*m_media_context_menu_mute_unmute_action); m_video_context_menu->add_action(*m_media_context_menu_controls_action); m_video_context_menu->add_action(*m_media_context_menu_loop_action); m_video_context_menu->add_separator(); @@ -432,6 +437,14 @@ Tab::Tab(BrowserWindow& window) m_media_context_menu_play_pause_action->set_text("&Play"sv); } + if (menu.is_muted) { + m_media_context_menu_mute_unmute_action->set_icon(g_icon_bag.unmute); + m_media_context_menu_mute_unmute_action->set_text("Un&mute"sv); + } else { + m_media_context_menu_mute_unmute_action->set_icon(g_icon_bag.mute); + m_media_context_menu_mute_unmute_action->set_text("&Mute"sv); + } + m_media_context_menu_controls_action->set_checked(menu.has_user_agent_controls); m_media_context_menu_loop_action->set_checked(menu.is_looping); diff --git a/Userland/Applications/Browser/Tab.h b/Userland/Applications/Browser/Tab.h index 4a3a8c83ed..94ec973f8d 100644 --- a/Userland/Applications/Browser/Tab.h +++ b/Userland/Applications/Browser/Tab.h @@ -146,6 +146,7 @@ private: RefPtr m_audio_context_menu; RefPtr m_video_context_menu; RefPtr m_media_context_menu_play_pause_action; + RefPtr m_media_context_menu_mute_unmute_action; RefPtr m_media_context_menu_controls_action; RefPtr m_media_context_menu_loop_action; URL m_media_context_menu_url; diff --git a/Userland/Libraries/LibWeb/Page/EventHandler.cpp b/Userland/Libraries/LibWeb/Page/EventHandler.cpp index f396d9da31..5685b283f3 100644 --- a/Userland/Libraries/LibWeb/Page/EventHandler.cpp +++ b/Userland/Libraries/LibWeb/Page/EventHandler.cpp @@ -315,6 +315,7 @@ bool EventHandler::handle_mouseup(CSSPixelPoint position, unsigned button, unsig .media_url = media_element.document().parse_url(media_element.current_src()), .is_video = is(*node), .is_playing = media_element.potentially_playing(), + .is_muted = media_element.muted(), .has_user_agent_controls = media_element.has_attribute(HTML::AttributeNames::controls), .is_looping = media_element.has_attribute(HTML::AttributeNames::loop), }; diff --git a/Userland/Libraries/LibWeb/Page/Page.cpp b/Userland/Libraries/LibWeb/Page/Page.cpp index 475d50b3d4..8072c690dd 100644 --- a/Userland/Libraries/LibWeb/Page/Page.cpp +++ b/Userland/Libraries/LibWeb/Page/Page.cpp @@ -315,6 +315,21 @@ WebIDL::ExceptionOr Page::toggle_media_play_state() return {}; } +void Page::toggle_media_mute_state() +{ + auto media_element = media_context_menu_element(); + if (!media_element) + return; + + // FIXME: This runs from outside the context of any user script, so we do not have a running execution + // context. This pushes one to allow the promise creation hook to run. + auto& environment_settings = media_element->document().relevant_settings_object(); + environment_settings.prepare_to_run_script(); + + ScopeGuard guard { [&] { environment_settings.clean_up_after_running_script(); } }; + media_element->set_muted(!media_element->muted()); +} + WebIDL::ExceptionOr Page::toggle_media_loop_state() { auto media_element = media_context_menu_element(); @@ -380,6 +395,7 @@ ErrorOr IPC::encode(Encoder& encoder, Web::Page::MediaContextMenu const& m TRY(encoder.encode(menu.media_url)); TRY(encoder.encode(menu.is_video)); TRY(encoder.encode(menu.is_playing)); + TRY(encoder.encode(menu.is_muted)); TRY(encoder.encode(menu.has_user_agent_controls)); TRY(encoder.encode(menu.is_looping)); return {}; @@ -392,6 +408,7 @@ ErrorOr IPC::decode(Decoder& decoder) .media_url = TRY(decoder.decode()), .is_video = TRY(decoder.decode()), .is_playing = TRY(decoder.decode()), + .is_muted = TRY(decoder.decode()), .has_user_agent_controls = TRY(decoder.decode()), .is_looping = TRY(decoder.decode()), }; diff --git a/Userland/Libraries/LibWeb/Page/Page.h b/Userland/Libraries/LibWeb/Page/Page.h index 5a7a306c67..a23a09ebba 100644 --- a/Userland/Libraries/LibWeb/Page/Page.h +++ b/Userland/Libraries/LibWeb/Page/Page.h @@ -125,11 +125,13 @@ public: AK::URL media_url; bool is_video { false }; bool is_playing { false }; + bool is_muted { false }; bool has_user_agent_controls { false }; bool is_looping { false }; }; void did_request_media_context_menu(i32 media_id, CSSPixelPoint, DeprecatedString const& target, unsigned modifiers, MediaContextMenu); WebIDL::ExceptionOr toggle_media_play_state(); + void toggle_media_mute_state(); WebIDL::ExceptionOr toggle_media_loop_state(); WebIDL::ExceptionOr toggle_media_controls_state(); diff --git a/Userland/Libraries/LibWebView/ViewImplementation.cpp b/Userland/Libraries/LibWebView/ViewImplementation.cpp index 12815aa178..e45c84353a 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.cpp +++ b/Userland/Libraries/LibWebView/ViewImplementation.cpp @@ -156,6 +156,11 @@ void ViewImplementation::toggle_media_play_state() client().async_toggle_media_play_state(); } +void ViewImplementation::toggle_media_mute_state() +{ + client().async_toggle_media_mute_state(); +} + void ViewImplementation::toggle_media_loop_state() { client().async_toggle_media_loop_state(); diff --git a/Userland/Libraries/LibWebView/ViewImplementation.h b/Userland/Libraries/LibWebView/ViewImplementation.h index 06741d6372..070a623d66 100644 --- a/Userland/Libraries/LibWebView/ViewImplementation.h +++ b/Userland/Libraries/LibWebView/ViewImplementation.h @@ -75,6 +75,7 @@ public: void js_console_request_messages(i32 start_index); void toggle_media_play_state(); + void toggle_media_mute_state(); void toggle_media_loop_state(); void toggle_media_controls_state(); diff --git a/Userland/Services/WebContent/ConnectionFromClient.cpp b/Userland/Services/WebContent/ConnectionFromClient.cpp index 5bc14a481f..1f6fd1dba1 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.cpp +++ b/Userland/Services/WebContent/ConnectionFromClient.cpp @@ -790,6 +790,11 @@ void ConnectionFromClient::toggle_media_play_state() m_page_host->toggle_media_play_state().release_value_but_fixme_should_propagate_errors(); } +void ConnectionFromClient::toggle_media_mute_state() +{ + m_page_host->toggle_media_mute_state(); +} + void ConnectionFromClient::toggle_media_loop_state() { m_page_host->toggle_media_loop_state().release_value_but_fixme_should_propagate_errors(); diff --git a/Userland/Services/WebContent/ConnectionFromClient.h b/Userland/Services/WebContent/ConnectionFromClient.h index 9780c4b79f..f87bff314f 100644 --- a/Userland/Services/WebContent/ConnectionFromClient.h +++ b/Userland/Services/WebContent/ConnectionFromClient.h @@ -97,6 +97,7 @@ private: virtual void prompt_closed(Optional const& response) override; virtual void toggle_media_play_state() override; + virtual void toggle_media_mute_state() override; virtual void toggle_media_loop_state() override; virtual void toggle_media_controls_state() override; diff --git a/Userland/Services/WebContent/PageHost.cpp b/Userland/Services/WebContent/PageHost.cpp index 1b42386d87..8520a49fab 100644 --- a/Userland/Services/WebContent/PageHost.cpp +++ b/Userland/Services/WebContent/PageHost.cpp @@ -339,6 +339,11 @@ Web::WebIDL::ExceptionOr PageHost::toggle_media_play_state() return page().toggle_media_play_state(); } +void PageHost::toggle_media_mute_state() +{ + page().toggle_media_mute_state(); +} + Web::WebIDL::ExceptionOr PageHost::toggle_media_loop_state() { return page().toggle_media_loop_state(); diff --git a/Userland/Services/WebContent/PageHost.h b/Userland/Services/WebContent/PageHost.h index 0270eabd9c..0349f6a758 100644 --- a/Userland/Services/WebContent/PageHost.h +++ b/Userland/Services/WebContent/PageHost.h @@ -50,6 +50,7 @@ public: void prompt_closed(Optional response); Web::WebIDL::ExceptionOr toggle_media_play_state(); + void toggle_media_mute_state(); Web::WebIDL::ExceptionOr toggle_media_loop_state(); Web::WebIDL::ExceptionOr toggle_media_controls_state(); diff --git a/Userland/Services/WebContent/WebContentServer.ipc b/Userland/Services/WebContent/WebContentServer.ipc index da609f6be6..3659368287 100644 --- a/Userland/Services/WebContent/WebContentServer.ipc +++ b/Userland/Services/WebContent/WebContentServer.ipc @@ -79,6 +79,7 @@ endpoint WebContentServer prompt_closed(Optional response) =| toggle_media_play_state() =| + toggle_media_mute_state() =| toggle_media_loop_state() =| toggle_media_controls_state() =| }