Context Menu: API

This commit is contained in:
Paul Rouget 2020-03-27 09:27:31 +01:00
parent ecdbea518f
commit ed46f5985c
6 changed files with 86 additions and 3 deletions

1
Cargo.lock generated
View file

@ -5386,6 +5386,7 @@ dependencies = [
"core-foundation 0.6.4", "core-foundation 0.6.4",
"getopts", "getopts",
"gl_generator 0.11.0", "gl_generator 0.11.0",
"ipc-channel",
"libc", "libc",
"libloading", "libloading",
"libservo", "libservo",

View file

@ -106,6 +106,13 @@ impl EmbedderReceiver {
} }
} }
#[derive(Deserialize, Serialize)]
pub enum ContextMenuResult {
Dismissed,
Ignored,
Selected(usize),
}
#[derive(Deserialize, Serialize)] #[derive(Deserialize, Serialize)]
pub enum PromptDefinition { pub enum PromptDefinition {
/// Show a message. /// Show a message.
@ -149,6 +156,8 @@ pub enum EmbedderMsg {
ResizeTo(DeviceIntSize), ResizeTo(DeviceIntSize),
/// Show dialog to user /// Show dialog to user
Prompt(PromptDefinition, PromptOrigin), Prompt(PromptDefinition, PromptOrigin),
/// Show a context menu to the user
ShowContextMenu(IpcSender<ContextMenuResult>, Vec<String>),
/// Whether or not to allow a pipeline to load a url. /// Whether or not to allow a pipeline to load a url.
AllowNavigationRequest(PipelineId, ServoUrl), AllowNavigationRequest(PipelineId, ServoUrl),
/// Whether or not to allow script to open a new tab/browser /// 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::ReportProfile(..) => write!(f, "ReportProfile"),
EmbedderMsg::MediaSessionEvent(..) => write!(f, "MediaSessionEvent"), EmbedderMsg::MediaSessionEvent(..) => write!(f, "MediaSessionEvent"),
EmbedderMsg::OnDevtoolsStarted(..) => write!(f, "OnDevtoolsStarted"), EmbedderMsg::OnDevtoolsStarted(..) => write!(f, "OnDevtoolsStarted"),
EmbedderMsg::ShowContextMenu(..) => write!(f, "ShowContextMenu"),
} }
} }
} }

View file

@ -8,7 +8,7 @@ use euclid::{Point2D, Vector2D};
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher}; use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent}; use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent};
use servo::embedder_traits::{ use servo::embedder_traits::{
EmbedderMsg, FilterPattern, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult, ContextMenuResult, EmbedderMsg, FilterPattern, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult,
PermissionPrompt, PermissionPrompt,
}; };
use servo::msg::constellation_msg::TopLevelBrowsingContextId as BrowserId; use servo::msg::constellation_msg::TopLevelBrowsingContextId as BrowserId;
@ -521,6 +521,9 @@ where
Err(()) => error!("Error running devtools server"), Err(()) => error!("Error running devtools server"),
} }
}, },
EmbedderMsg::ShowContextMenu(sender, _) => {
let _ = sender.send(ContextMenuResult::Ignored);
}
} }
} }
} }

View file

@ -8,6 +8,7 @@ publish = false
[dependencies] [dependencies]
getopts = "0.2.11" getopts = "0.2.11"
ipc-channel = "0.14"
libservo = { path = "../../../components/servo" } libservo = { path = "../../../components/servo" }
log = "0.4" log = "0.4"
servo-media = { git = "https://github.com/servo/media" } servo-media = { git = "https://github.com/servo/media" }

View file

@ -8,11 +8,12 @@ extern crate log;
pub mod gl_glue; pub mod gl_glue;
pub use servo::embedder_traits::{ pub use servo::embedder_traits::{
MediaSessionPlaybackState, PermissionPrompt, PermissionRequest, PromptResult, ContextMenuResult, MediaSessionPlaybackState, PermissionPrompt, PermissionRequest, PromptResult,
}; };
pub use servo::script_traits::{MediaSessionActionType, MouseButton}; pub use servo::script_traits::{MediaSessionActionType, MouseButton};
use getopts::Options; use getopts::Options;
use ipc_channel::ipc::IpcSender;
use servo::canvas::{SurfaceProviders, WebGlExecutor}; use servo::canvas::{SurfaceProviders, WebGlExecutor};
use servo::compositing::windowing::{ use servo::compositing::windowing::{
AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowEvent, AnimationState, EmbedderCoordinates, EmbedderMethods, MouseWindowEvent, WindowEvent,
@ -103,6 +104,8 @@ pub trait HostTrait {
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult; fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult;
/// Ask for string /// Ask for string
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>; fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>;
/// Show context menu
fn show_context_menu(&self, items: Vec<String>);
/// Page starts loading. /// Page starts loading.
/// "Reload button" should be disabled. /// "Reload button" should be disabled.
/// "Stop button" should be enabled. /// "Stop button" should be enabled.
@ -164,6 +167,7 @@ pub struct ServoGlue {
browsers: Vec<BrowserId>, browsers: Vec<BrowserId>,
events: Vec<WindowEvent>, events: Vec<WindowEvent>,
current_url: Option<ServoUrl>, current_url: Option<ServoUrl>,
context_menu_sender: Option<IpcSender<ContextMenuResult>>,
} }
pub fn servo_version() -> String { pub fn servo_version() -> String {
@ -240,6 +244,7 @@ pub fn init(
browsers: vec![], browsers: vec![],
events: vec![], events: vec![],
current_url: Some(url.clone()), current_url: Some(url.clone()),
context_menu_sender: None,
}; };
let browser_id = BrowserId::new(); let browser_id = BrowserId::new();
let _ = servo_glue.process_event(WindowEvent::NewBrowser(url, browser_id)); 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> { fn process_event(&mut self, event: WindowEvent) -> Result<(), &'static str> {
self.events.push(event); self.events.push(event);
if !self.batch_mode { if !self.batch_mode {
@ -562,6 +579,17 @@ impl ServoGlue {
EmbedderMsg::AllowUnload(sender) => { EmbedderMsg::AllowUnload(sender) => {
let _ = sender.send(true); 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) => { EmbedderMsg::Prompt(definition, origin) => {
let cb = &self.callbacks.host_callbacks; let cb = &self.callbacks.host_callbacks;
let trusted = origin == PromptOrigin::Trusted; let trusted = origin == PromptOrigin::Trusted;

View file

@ -17,7 +17,7 @@ use env_logger;
use log::LevelFilter; use log::LevelFilter;
use simpleservo::{self, gl_glue, ServoGlue, SERVO}; use simpleservo::{self, gl_glue, ServoGlue, SERVO};
use simpleservo::{ use simpleservo::{
Coordinates, EventLoopWaker, HostTrait, InitOptions, MediaSessionActionType, ContextMenuResult, Coordinates, EventLoopWaker, HostTrait, InitOptions, MediaSessionActionType,
MediaSessionPlaybackState, MouseButton, PromptResult, VRInitOptions, MediaSessionPlaybackState, MouseButton, PromptResult, VRInitOptions,
}; };
use std::ffi::{CStr, CString}; use std::ffi::{CStr, CString};
@ -230,6 +230,7 @@ pub struct CHostCallbacks {
trusted: bool, trusted: bool,
) -> *const c_char, ) -> *const c_char,
pub on_devtools_started: extern "C" fn(result: CDevtoolsServerState, port: c_uint), 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 /// 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. /// The returned string is not freed. This will leak.
#[no_mangle] #[no_mangle]
pub extern "C" fn servo_version() -> *const c_char { 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] #[no_mangle]
pub extern "C" fn resize(width: i32, height: i32) { pub extern "C" fn resize(width: i32, height: i32) {
catch_any_panic(|| { catch_any_panic(|| {
@ -874,4 +902,16 @@ impl HostTrait for HostCallbacks {
}, },
} }
} }
fn show_context_menu(&self, items: Vec<String>) {
debug!("show_context_menu");
let size = items.len() as u32;
let cstrs: Vec<CString> = 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
}
} }