diff --git a/Cargo.lock b/Cargo.lock index 3002d5a7ac6..dcfb3ed24eb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -5386,6 +5386,7 @@ dependencies = [ "core-foundation 0.6.4", "getopts", "gl_generator 0.11.0", + "ipc-channel", "libc", "libloading", "libservo", diff --git a/components/embedder_traits/lib.rs b/components/embedder_traits/lib.rs index 45fa6e1d7b4..961cd5e53a4 100644 --- a/components/embedder_traits/lib.rs +++ b/components/embedder_traits/lib.rs @@ -106,6 +106,13 @@ impl EmbedderReceiver { } } +#[derive(Deserialize, Serialize)] +pub enum ContextMenuResult { + Dismissed, + Ignored, + Selected(usize), +} + #[derive(Deserialize, Serialize)] pub enum PromptDefinition { /// Show a message. @@ -149,6 +156,8 @@ pub enum EmbedderMsg { ResizeTo(DeviceIntSize), /// Show dialog to user Prompt(PromptDefinition, PromptOrigin), + /// Show a context menu to the user + ShowContextMenu(IpcSender, Vec), /// Whether or not to allow a pipeline to load a url. AllowNavigationRequest(PipelineId, ServoUrl), /// Whether or not to allow script to open a new tab/browser @@ -235,6 +244,7 @@ impl Debug for EmbedderMsg { EmbedderMsg::ReportProfile(..) => write!(f, "ReportProfile"), EmbedderMsg::MediaSessionEvent(..) => write!(f, "MediaSessionEvent"), EmbedderMsg::OnDevtoolsStarted(..) => write!(f, "OnDevtoolsStarted"), + EmbedderMsg::ShowContextMenu(..) => write!(f, "ShowContextMenu"), } } } diff --git a/ports/glutin/browser.rs b/ports/glutin/browser.rs index 3aec703c8fc..d92eb1bde9b 100644 --- a/ports/glutin/browser.rs +++ b/ports/glutin/browser.rs @@ -8,7 +8,7 @@ use euclid::{Point2D, Vector2D}; use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher}; use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent}; use servo::embedder_traits::{ - EmbedderMsg, FilterPattern, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, + ContextMenuResult, EmbedderMsg, FilterPattern, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, PermissionPrompt, }; use servo::msg::constellation_msg::TopLevelBrowsingContextId as BrowserId; @@ -521,6 +521,9 @@ where Err(()) => error!("Error running devtools server"), } }, + EmbedderMsg::ShowContextMenu(sender, _) => { + let _ = sender.send(ContextMenuResult::Ignored); + } } } } diff --git a/ports/libsimpleservo/api/Cargo.toml b/ports/libsimpleservo/api/Cargo.toml index 66a0cd83a3d..61dc1106de3 100644 --- a/ports/libsimpleservo/api/Cargo.toml +++ b/ports/libsimpleservo/api/Cargo.toml @@ -8,6 +8,7 @@ publish = false [dependencies] getopts = "0.2.11" +ipc-channel = "0.14" libservo = { path = "../../../components/servo" } log = "0.4" servo-media = { git = "https://github.com/servo/media" } diff --git a/ports/libsimpleservo/api/src/lib.rs b/ports/libsimpleservo/api/src/lib.rs index 98a2029d402..c876ffb9fc0 100644 --- a/ports/libsimpleservo/api/src/lib.rs +++ b/ports/libsimpleservo/api/src/lib.rs @@ -8,11 +8,12 @@ extern crate log; pub mod gl_glue; pub use servo::embedder_traits::{ - MediaSessionPlaybackState, PermissionPrompt, PermissionRequest, PromptResult, + ContextMenuResult, MediaSessionPlaybackState, PermissionPrompt, PermissionRequest, PromptResult, }; pub use servo::script_traits::{MediaSessionActionType, MouseButton}; use getopts::Options; +use ipc_channel::ipc::IpcSender; use servo::canvas::{SurfaceProviders, WebGlExecutor}; use servo::compositing::windowing::{ AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowEvent, @@ -103,6 +104,8 @@ pub trait HostTrait { fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult; /// Ask for string fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option; + /// Show context menu + fn show_context_menu(&self, items: Vec); /// Page starts loading. /// "Reload button" should be disabled. /// "Stop button" should be enabled. @@ -164,6 +167,7 @@ pub struct ServoGlue { browsers: Vec, events: Vec, current_url: Option, + context_menu_sender: Option>, } pub fn servo_version() -> String { @@ -240,6 +244,7 @@ pub fn init( browsers: vec![], events: vec![], current_url: Some(url.clone()), + context_menu_sender: None, }; let browser_id = BrowserId::new(); let _ = servo_glue.process_event(WindowEvent::NewBrowser(url, browser_id)); @@ -503,6 +508,18 @@ impl ServoGlue { } } + pub fn on_context_menu_closed( + &mut self, + result: ContextMenuResult, + ) -> Result<(), &'static str> { + if let Some(sender) = self.context_menu_sender.take() { + let _ = sender.send(result); + } else { + warn!("Trying to close a context menu when no context menu is active"); + } + Ok(()) + } + fn process_event(&mut self, event: WindowEvent) -> Result<(), &'static str> { self.events.push(event); if !self.batch_mode { @@ -562,6 +579,17 @@ impl ServoGlue { EmbedderMsg::AllowUnload(sender) => { let _ = sender.send(true); }, + EmbedderMsg::ShowContextMenu(sender, items) => { + if self.context_menu_sender.is_some() { + warn!( + "Trying to show a context menu when a context menu is already active" + ); + let _ = sender.send(ContextMenuResult::Ignored); + } else { + self.context_menu_sender = Some(sender); + self.callbacks.host_callbacks.show_context_menu(items); + } + }, EmbedderMsg::Prompt(definition, origin) => { let cb = &self.callbacks.host_callbacks; let trusted = origin == PromptOrigin::Trusted; diff --git a/ports/libsimpleservo/capi/src/lib.rs b/ports/libsimpleservo/capi/src/lib.rs index 2fd0e48fabe..d55d9f41175 100644 --- a/ports/libsimpleservo/capi/src/lib.rs +++ b/ports/libsimpleservo/capi/src/lib.rs @@ -17,7 +17,7 @@ use env_logger; use log::LevelFilter; use simpleservo::{self, gl_glue, ServoGlue, SERVO}; use simpleservo::{ - Coordinates, EventLoopWaker, HostTrait, InitOptions, MediaSessionActionType, + ContextMenuResult, Coordinates, EventLoopWaker, HostTrait, InitOptions, MediaSessionActionType, MediaSessionPlaybackState, MouseButton, PromptResult, VRInitOptions, }; use std::ffi::{CStr, CString}; @@ -230,6 +230,7 @@ pub struct CHostCallbacks { trusted: bool, ) -> *const c_char, pub on_devtools_started: extern "C" fn(result: CDevtoolsServerState, port: c_uint), + pub show_context_menu: extern "C" fn(items_list: *const *const c_char, items_size: u32), } /// Servo options @@ -332,6 +333,25 @@ impl CMediaSessionActionType { } } +#[repr(C)] +pub enum CContextMenuResult { + Ignored, + Selected, + // Can't use Dismissed. Already used by PromptResult. See: + // https://github.com/servo/servo/issues/25986 + Dismissed_, +} + +impl CContextMenuResult { + pub fn convert(&self, idx: u32) -> ContextMenuResult { + match self { + CContextMenuResult::Ignored => ContextMenuResult::Ignored, + CContextMenuResult::Dismissed_ => ContextMenuResult::Dismissed, + CContextMenuResult::Selected => ContextMenuResult::Selected(idx as usize), + } + } +} + /// The returned string is not freed. This will leak. #[no_mangle] pub extern "C" fn servo_version() -> *const c_char { @@ -494,6 +514,14 @@ pub extern "C" fn set_batch_mode(batch: bool) { }); } +#[no_mangle] +pub extern "C" fn on_context_menu_closed(result: CContextMenuResult, item: u32) { + catch_any_panic(|| { + debug!("on_context_menu_closed"); + call(|s| s.on_context_menu_closed(result.convert(item))); + }); +} + #[no_mangle] pub extern "C" fn resize(width: i32, height: i32) { catch_any_panic(|| { @@ -874,4 +902,16 @@ impl HostTrait for HostCallbacks { }, } } + + fn show_context_menu(&self, items: Vec) { + debug!("show_context_menu"); + let size = items.len() as u32; + let cstrs: Vec = items + .into_iter() + .map(|i| CString::new(i).expect("Can't create string")) + .collect(); + let ptrs: Vec<*const c_char> = cstrs.iter().map(|cstr| cstr.as_ptr()).collect(); + (self.0.show_context_menu)(ptrs.as_ptr(), size); + // let _ = cstrs; // Don't drop these too early + } }