Skip to content

Commit

Permalink
fix(contextmenu): show context menu when receiving ShowContextMenu (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
pewsheen authored Feb 14, 2025
1 parent e278df1 commit f2e33bf
Show file tree
Hide file tree
Showing 3 changed files with 108 additions and 62 deletions.
27 changes: 24 additions & 3 deletions src/webview/context_menu.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
use embedder_traits::ContextMenuResult;
use ipc_channel::ipc::IpcSender;
/* macOS, Windows Native Implementation */
#[cfg(any(target_os = "macos", target_os = "windows"))]
use muda::{ContextMenu as MudaContextMenu, Menu as MudaMenu};
Expand Down Expand Up @@ -39,6 +41,8 @@ pub struct Menu(pub Vec<MenuItem>);
/// webview implementation.
#[derive(Clone)]
pub struct ContextMenu {
/// IpcSender to send the context menu result to the Servo
servo_result_sender: Option<IpcSender<ContextMenuResult>>, // None if sender already sent
#[cfg(any(target_os = "macos", target_os = "windows"))]
menu: MudaMenu,
#[cfg(linux)]
Expand All @@ -57,23 +61,40 @@ impl ContextMenu {
/// **Platform Specific**
/// - macOS / Windows: Creates a context menu by muda crate with natvie OS support
/// - Wayland: Creates a context menu with webview implementation
pub fn new_with_menu(menu: Menu) -> Self {
pub fn new_with_menu(servo_result_sender: IpcSender<ContextMenuResult>, menu: Menu) -> Self {
#[cfg(any(target_os = "macos", target_os = "windows"))]
{
Self { menu: menu.0 }
Self {
servo_result_sender: Some(servo_result_sender),
menu: menu.0,
}
}
#[cfg(linux)]
{
let webview_id = WebViewId::new();
let webview = WebView::new(webview_id, DeviceIntRect::zero());

Self {
servo_result_sender: Some(servo_result_sender),
menu_items: menu.0,
webview,
position: LogicalPosition::new(0.0, 0.0),
}
}
}

/// Send the context menu result back to the Servo. Can only be sent once.
pub fn send_result_to_servo(&mut self, result: ContextMenuResult) {
if let Some(sender) = self.servo_result_sender.take() {
let _ = sender.send(result);
}
}
}

impl Drop for ContextMenu {
fn drop(&mut self) {
self.send_result_to_servo(ContextMenuResult::Dismissed);
}
}

#[cfg(any(target_os = "macos", target_os = "windows"))]
Expand Down Expand Up @@ -178,7 +199,7 @@ impl MenuItem {
#[cfg(linux)]
#[derive(Debug, Clone, Serialize, Deserialize)]

pub struct ContextMenuResult {
pub struct ContextMenuUIResponse {
/// The id of the menu item
pub id: Option<String>,
/// Close the context menu
Expand Down
50 changes: 42 additions & 8 deletions src/webview/webview.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ use crate::{
};

#[cfg(linux)]
use crate::webview::context_menu::ContextMenuResult;
use crate::webview::context_menu::ContextMenuUIResponse;

/// A web view is an area to display web browsing context. It's what user will treat as a "web page".
#[derive(Debug, Clone)]
Expand Down Expand Up @@ -187,9 +187,21 @@ impl Window {
);
}
}
EmbedderMsg::ShowContextMenu(_webview_id, _sender, _title, _options) => {
// TODO: Implement context menu
let _ = _sender.send(embedder_traits::ContextMenuResult::Dismissed);
EmbedderMsg::ShowContextMenu(_webview_id, servo_sender, _title, _options) => {
#[cfg(linux)]
if self.context_menu.is_none() {
self.context_menu = Some(self.show_context_menu(sender, servo_sender));
} else {
let _ = servo_sender.send(embedder_traits::ContextMenuResult::Ignored);
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
{
let context_menu = self.show_context_menu(servo_sender);
// FIXME: there's chance to lose the event since the channel is async.
if let Ok(event) = self.menu_event_receiver.try_recv() {
self.handle_context_menu_event(context_menu, sender, event);
}
}
}
EmbedderMsg::Prompt(_webview_id, prompt_type, _origin) => {
if let Some(tab) = self.tab_manager.tab(webview_id) {
Expand Down Expand Up @@ -448,6 +460,22 @@ impl Window {
}
}
}
EmbedderMsg::ShowContextMenu(_, servo_sender, _, _) => {
#[cfg(linux)]
if self.context_menu.is_none() {
self.context_menu = Some(self.show_context_menu(sender, servo_sender));
} else {
let _ = servo_sender.send(embedder_traits::ContextMenuResult::Ignored);
}
#[cfg(any(target_os = "windows", target_os = "macos"))]
{
let context_menu = self.show_context_menu(servo_sender);
// FIXME: there's chance to lose the event since the channel is async.
if let Ok(event) = self.menu_event_receiver.try_recv() {
self.handle_context_menu_event(context_menu, sender, event);
}
}
}
e => {
log::trace!("Verso Panel isn't supporting this message yet: {e:?}")
}
Expand Down Expand Up @@ -479,16 +507,19 @@ impl Window {
if msg.starts_with("CONTEXT_MENU:") {
let json_str_msg = msg.strip_prefix("CONTEXT_MENU:").unwrap();
let result =
serde_json::from_str::<ContextMenuResult>(json_str_msg).unwrap();
serde_json::from_str::<ContextMenuUIResponse>(json_str_msg).unwrap();

self.handle_context_menu_event(sender, result);
}
}
_ => log::trace!("Verso context menu isn't supporting this prompt yet"),
},
EmbedderMsg::ShowContextMenu(_webview_id, _sender, _title, _options) => {
// TODO: Implement context menu
let _ = _sender.send(embedder_traits::ContextMenuResult::Dismissed);
EmbedderMsg::ShowContextMenu(_webview_id, servo_sender, _title, _options) => {
if self.context_menu.is_none() {
self.context_menu = Some(self.show_context_menu(sender, servo_sender));
} else {
let _ = servo_sender.send(embedder_traits::ContextMenuResult::Ignored);
}
}
e => {
log::trace!("Verso context menu isn't supporting this message yet: {e:?}")
Expand Down Expand Up @@ -599,6 +630,9 @@ impl Window {
log::trace!("Verso WebView isn't supporting this prompt yet")
}
},
EmbedderMsg::ShowContextMenu(_, sender, _, _) => {
let _ = sender.send(embedder_traits::ContextMenuResult::Ignored);
}
e => {
log::trace!("Verso Dialog isn't supporting this message yet: {e:?}")
}
Expand Down
93 changes: 42 additions & 51 deletions src/window.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,9 @@ use base::id::WebViewId;
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{
AllowOrDeny, Cursor, EmbedderMsg, InputEvent, MouseButton, MouseButtonAction, MouseButtonEvent,
MouseMoveEvent, PromptResult, TouchEventAction, TraversalDirection, WheelMode,
AllowOrDeny, ContextMenuResult, Cursor, EmbedderMsg, InputEvent, MouseButton,
MouseButtonAction, MouseButtonEvent, MouseMoveEvent, PromptResult, TouchEventAction,
TraversalDirection, WheelMode,
};
use euclid::{Point2D, Size2D};
use glutin::{
Expand Down Expand Up @@ -87,7 +88,7 @@ pub struct Window {
pub(crate) context_menu: Option<ContextMenu>,
/// Global menu event receiver for muda crate
#[cfg(any(target_os = "macos", target_os = "windows"))]
menu_event_receiver: MenuEventReceiver,
pub(crate) menu_event_receiver: MenuEventReceiver,
/// Window tabs manager
pub(crate) tab_manager: TabManager,
pub(crate) focused_webview_id: Option<WebViewId>,
Expand Down Expand Up @@ -412,38 +413,11 @@ impl Window {
};

/* handle context menu */
// TODO(context-menu): should create on ShowContextMenu event
let prompt = self.tab_manager.current_prompt();
match (state, button) {
#[cfg(any(target_os = "macos", target_os = "windows"))]
(ElementState::Pressed, winit::event::MouseButton::Right) => {
if prompt.is_some() {
return;
}
self.show_context_menu();
// FIXME: there's chance to lose the event since the channel is async.
if let Ok(event) = self.menu_event_receiver.try_recv() {
self.handle_context_menu_event(sender, event);
}
}
#[cfg(linux)]
(ElementState::Pressed, winit::event::MouseButton::Right) => {
if prompt.is_some() {
return;
}
if self.context_menu.is_none() {
self.context_menu = Some(self.show_context_menu(sender));
return;
}
}
#[cfg(linux)]
// TODO(context-menu): ignore first release event after context menu open or close to prevent click on background element
(ElementState::Released, winit::event::MouseButton::Right) => {
if self.context_menu.is_some() {
return;
}
if let (ElementState::Pressed, winit::event::MouseButton::Right) = (state, button) {
let prompt = self.tab_manager.current_prompt();
if prompt.is_some() {
return;
}
_ => {}
}

/* handle Windows and Linux non-decoration window resize */
Expand Down Expand Up @@ -830,7 +804,10 @@ impl Window {
// Context Menu methods
impl Window {
#[cfg(any(target_os = "macos", target_os = "windows"))]
pub(crate) fn show_context_menu(&self) {
pub(crate) fn show_context_menu(
&self,
servo_sender: IpcSender<ContextMenuResult>,
) -> ContextMenu {
let tab = self.tab_manager.current_tab().unwrap();
let history = tab.history();
let history_len = history.list.len();
Expand All @@ -848,12 +825,18 @@ impl Window {
let menu = MudaMenu::new();
let _ = menu.append_items(&[&back, &forward, &reload]);

let context_menu = ContextMenu::new_with_menu(Menu(menu));
let context_menu = ContextMenu::new_with_menu(servo_sender, Menu(menu));
context_menu.show(self.window.window_handle().unwrap());

context_menu
}

#[cfg(linux)]
pub(crate) fn show_context_menu(&mut self, sender: &Sender<ConstellationMsg>) -> ContextMenu {
pub(crate) fn show_context_menu(
&mut self,
sender: &Sender<ConstellationMsg>,
servo_sender: IpcSender<ContextMenuResult>,
) -> ContextMenu {
use crate::webview::context_menu::MenuItem;

let tab = self.tab_manager.current_tab().unwrap();
Expand All @@ -869,27 +852,23 @@ impl Window {
);
let reload = MenuItem::new(Some("reload"), "Reload", true);

let mut context_menu = ContextMenu::new_with_menu(Menu(vec![back, forward, reload]));
let mut context_menu =
ContextMenu::new_with_menu(servo_sender, Menu(vec![back, forward, reload]));

let position = self.mouse_position.get().unwrap();
context_menu.show(sender, self, position);

context_menu
}

/// Close window's context menu
pub(crate) fn close_context_menu(&self, sender: &Sender<ConstellationMsg>) {
#[cfg(linux)]
if let Some(context_menu) = &self.context_menu {
send_to_constellation(
sender,
ConstellationMsg::CloseWebView(context_menu.webview().webview_id),
);
}
}

#[cfg(any(target_os = "macos", target_os = "windows"))]
fn handle_context_menu_event(&self, sender: &Sender<ConstellationMsg>, event: MenuEvent) {
pub(crate) fn handle_context_menu_event(
&self,
mut context_menu: ContextMenu,
sender: &Sender<ConstellationMsg>,
event: MenuEvent,
) {
context_menu.send_result_to_servo(ContextMenuResult::Dismissed);
// TODO: should be more flexible to handle different menu items
let active_tab = self.tab_manager.current_tab().unwrap();
match event.id().0.as_str() {
Expand Down Expand Up @@ -922,7 +901,7 @@ impl Window {
pub(crate) fn handle_context_menu_event(
&mut self,
sender: &Sender<ConstellationMsg>,
event: crate::webview::context_menu::ContextMenuResult,
event: crate::webview::context_menu::ContextMenuUIResponse,
) {
self.close_context_menu(sender);
if let Some(id) = event.id {
Expand Down Expand Up @@ -953,6 +932,18 @@ impl Window {
}
};
}

/// Close window's context menu
pub(crate) fn close_context_menu(&mut self, _sender: &Sender<ConstellationMsg>) {
#[cfg(linux)]
if let Some(context_menu) = &mut self.context_menu {
context_menu.send_result_to_servo(ContextMenuResult::Dismissed);
send_to_constellation(
_sender,
ConstellationMsg::CloseWebView(context_menu.webview().webview_id),
);
}
}
}

// Prompt methods
Expand Down

0 comments on commit f2e33bf

Please sign in to comment.