feat(prompt): add prompt support (#250)

* feat(context-menu): create webview on wayland

* dynamic generate menu items on html

* add context_menu.html

* add check mouse hit on context-menu webview

* fix: context menu prompt back to verso

* feat(context-menu): on linux, handle selection

* fix: disble right click on context menu

* organize code

* adding cfg target linux

* fix(linux): shift context menu to avoid overflow, best effort

* prompt temp

* add alert prompt

* refactor: move prompt and context menu to components

* remove redundant import

* feat(prompt): add ok/cancel, yes/no prompt

* feat(prompt): add input prompt dialog

* fix: add serde to all platform's dep

* refactor: organize verso html files

* update gitignore

* refactor: remove dialog show method depends on Window

* refactor: code clean

* fix: don't show context menu when prompt exist

* update css

* fix(prompt): handle resize when prompt exists

* fix(prompt): close prompt when navigate to new url

* chore: restore default home page

* chore: fix linux mod path, remove unused pipeline fn

* feat: handle EmbedderMsg::PromptPermission

* refactor: rename components to webview and move WebView into it
This commit is contained in:
Jason Tsai
2024-12-04 20:55:38 +08:00
committed by GitHub
parent 0bcade2f3c
commit 6ca28dd25a
17 changed files with 741 additions and 32 deletions

6
.gitignore vendored
View File

@@ -6,6 +6,6 @@ Cargo.lock
libmozjs*
cargo-sources.json
resources/
!resources/panel.html
!resources/context-menu.html
resources/*
!resources/components/
!resources/prefs.json

View File

@@ -106,8 +106,6 @@ cargo-packager-resource-resolver = { version = "0.1.1", features = [
url = { workspace = true }
headers = "0.3"
versoview_messages = { path = "./versoview_messages" }
[target.'cfg(all(unix, not(apple), not(android)))'.dependencies]
serde_json = "1.0"
serde = { workspace = true }

View File

@@ -0,0 +1,54 @@
<html>
<head>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
margin: 0;
background-color: #7d818644;
}
.dialog {
display: flex;
background: #ffffff;
width: 400px;
min-height: 110px;
max-height: 300px;
flex-direction: column;
align-items: center;
border-radius: 10px;
box-shadow: 0 0 50px #ccc;
box-sizing: border-box;
padding: 8px;
gap: 8px;
}
.msg {
display: inline-block;
width: 100%;
min-height: 90px;
text-align: center;
}
</style>
</head>
<body>
<div class="dialog">
<div id="msg" class="msg"></div>
<button onclick="sendToVersoAndClose()">Ok</button>
</div>
</body>
<script>
let url = URL.parse(window.location.href);
let msg = url.searchParams.get('msg');
// Set dialog message
const msgEl = document.getElementById('msg');
msgEl.textContent = msg ?? '';
function sendToVersoAndClose() {
window.alert(''); // Use as an IPC between Verso and WebView
window.close();
}
</script>
</html>

View File

@@ -0,0 +1,57 @@
<html>
<head>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
margin: 0;
background-color: #7d818644;
}
.dialog {
display: flex;
background: #ffffff;
width: 400px;
min-height: 110px;
max-height: 300px;
flex-direction: column;
align-items: center;
border-radius: 10px;
box-shadow: 0 0 50px #ccc;
box-sizing: border-box;
padding: 8px;
gap: 8px;
}
.msg {
display: inline-block;
width: 100%;
min-height: 90px;
text-align: center;
}
</style>
</head>
<body>
<div class="dialog">
<div id="msg" class="msg"></div>
<div class="btn-group">
<button onclick="sendToVersoAndClose('cancel')">Cancel</button>
<button onclick="sendToVersoAndClose('ok')">Ok</button>
</div>
</div>
</body>
<script>
let url = URL.parse(window.location.href);
let msg = url.searchParams.get('msg');
// Set dialog message
const msgEl = document.getElementById('msg');
msgEl.textContent = msg ?? '';
function sendToVersoAndClose(action) {
window.alert(action); // Use as an IPC between Verso and WebView
window.close();
}
</script>
</html>

View File

@@ -0,0 +1,72 @@
<html>
<head>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
margin: 0;
background-color: #7d818644;
}
.dialog {
display: flex;
background: #ffffff;
width: 400px;
min-height: 110px;
max-height: 300px;
flex-direction: column;
align-items: center;
border-radius: 10px;
box-shadow: 0 0 50px #ccc;
box-sizing: border-box;
padding: 8px;
gap: 8px;
}
.msg {
display: inline-block;
width: 100%;
min-height: 90px;
text-align: center;
}
</style>
</head>
<body>
<div class="dialog">
<div id="msg" class="msg"></div>
<input type="text" id="input" />
<div class="btn-group">
<button onclick="sendToVersoAndClose('cancel')">Cancel</button>
<button onclick="sendToVersoAndClose('ok')">Ok</button>
</div>
</div>
</body>
<script>
const inputEl = document.getElementById('input');
const msgEl = document.getElementById('msg');
const params = URL.parse(window.location.href).searchParams;
// Set input default value
const defaultValue = params.get('defaultValue');
if (typeof defaultValue === 'string' || defaultValue instanceof String) {
inputEl.defaultValue = defaultValue;
}
// Set dialog message
const msg = params.get('msg');
msgEl.textContent = msg ?? '';
function sendToVersoAndClose(action) {
// Use as an IPC between Verso and WebView
window.alert(
JSON.stringify({
action,
value: inputEl.value,
})
);
window.close();
}
</script>
</html>

View File

@@ -0,0 +1,30 @@
<html>
<body>
<div style="display: block">
<button onclick="window.alert('<a> HREF </a>');">alert</button>
<button onclick="sendConfirm();">confirm</button>
<button onclick="sendPrompt('');">prompt</button>
<button onclick="sendPrompt(null, '>> default value >>');">
prompt with default value
</button>
</div>
<div style="display: inline-block" id="result"></div>
<script>
const resultEl = document.getElementById('result');
function sendConfirm(text) {
let result = window.confirm(text);
resultEl.textContent = JSON.stringify(result);
console.log(result);
}
function sendPrompt(text, defaultValue) {
let result = window.prompt(text, defaultValue);
resultEl.textContent = JSON.stringify(result);
console.log(result);
}
</script>
</body>
</html>

View File

@@ -0,0 +1,57 @@
<html>
<head>
<style>
body {
font-family: Arial, Helvetica, sans-serif;
display: flex;
justify-content: center;
align-items: center;
padding: 0;
margin: 0;
background-color: #7d818644;
}
.dialog {
display: flex;
background: #ffffff;
width: 400px;
min-height: 110px;
max-height: 300px;
flex-direction: column;
align-items: center;
border-radius: 10px;
box-shadow: 0 0 50px #ccc;
box-sizing: border-box;
padding: 8px;
gap: 8px;
}
.msg {
display: inline-block;
width: 100%;
min-height: 90px;
text-align: center;
}
</style>
</head>
<body>
<div class="dialog">
<div id="msg" class="msg"></div>
<div class="btn-group">
<button onclick="sendToVersoAndClose('no')">No</button>
<button onclick="sendToVersoAndClose('yes')">Yes</button>
</div>
</div>
</body>
<script>
let url = URL.parse(window.location.href);
let msg = url.searchParams.get('msg');
// Set dialog message
const msgEl = document.getElementById('msg');
msgEl.textContent = msg ?? '';
function sendToVersoAndClose(action) {
window.alert(action); // Use as an IPC between Verso and WebView
window.close();
}
</script>
</html>

View File

@@ -916,19 +916,6 @@ impl IOCompositor {
.expect("Insert then get failed!")
}
fn pipeline(&self, pipeline_id: PipelineId) -> Option<&CompositionPipeline> {
match self.pipeline_details.get(&pipeline_id) {
Some(details) => details.pipeline.as_ref(),
None => {
warn!(
"Compositor layer has an unknown pipeline ({:?}).",
pipeline_id
);
None
}
}
}
/// Set the root pipeline for our WebRender scene to a display list that consists of an iframe
/// for each visible top-level browsing context, applying a transformation on the root for
/// pinch zoom, page zoom, and HiDPI scaling.
@@ -1246,6 +1233,10 @@ impl IOCompositor {
w.set_size(content_size);
self.on_resize_webview_event(w.webview_id, w.rect);
}
if let Some(prompt) = &mut window.prompt {
prompt.resize(content_size);
self.on_resize_webview_event(prompt.webview().webview_id, rect);
}
self.send_root_pipeline_display_list(window);
}

View File

@@ -314,7 +314,9 @@ impl ProtocolHandler for ResourceReader {
_done_chan: &mut net::fetch::methods::DoneChannel,
_context: &net::fetch::methods::FetchContext,
) -> std::pin::Pin<Box<dyn std::future::Future<Output = Response> + Send>> {
let path = self.0.join(request.current_url().domain().unwrap());
let current_url = request.current_url();
let path = current_url.path();
let path = self.0.join(path.strip_prefix('/').unwrap_or(path));
let response = if let Ok(file) = fs::read(path) {
let mut response = Response::new(

View File

@@ -29,5 +29,3 @@ pub use errors::{Error, Result};
pub use verso::Verso;
/// Re-exporting Winit for the sake of convenience.
pub use winit;
/// Context
pub mod context_menu;

View File

@@ -131,7 +131,10 @@ impl ContextMenu {
/// Get resource URL of the context menu
fn resource_url(&self) -> ServoUrl {
let items_json: String = self.to_items_json();
let url_str = format!("verso://context_menu.html?items={}", items_json);
let url_str = format!(
"verso://resources/components/context_menu.html?items={}",
items_json
);
ServoUrl::parse(&url_str).unwrap()
}
@@ -221,8 +224,9 @@ impl MenuItem {
/// Context Menu Click Result
#[cfg(linux)]
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ContextMenuResult {
/// The id of the menu ite /// Get the label of the menu item
/// The id of the menu item
pub id: String,
/// Close the context menu
pub close: bool,

7
src/webview/mod.rs Normal file
View File

@@ -0,0 +1,7 @@
mod webview;
/// WebView
pub use webview::{Panel, WebView};
/// Context Menu
pub mod context_menu;
/// Prompt Dialog
pub mod prompt;

229
src/webview/prompt.rs Normal file
View File

@@ -0,0 +1,229 @@
use base::id::WebViewId;
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{PermissionRequest, PromptResult};
use ipc_channel::ipc::IpcSender;
use serde::{Deserialize, Serialize};
use servo_url::ServoUrl;
use webrender_api::units::DeviceIntRect;
use crate::{verso::send_to_constellation, webview::WebView};
/// Prompt Type
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
enum PromptType {
/// Alert dialog
///
/// <https://developer.mozilla.org/en-US/docs/Web/API/Window/alert>
Alert(String),
/// Confitm dialog, Ok/Cancel
///
/// <https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm>
OkCancel(String),
/// Confirm dialog, Yes/No
///
/// <https://developer.mozilla.org/en-US/docs/Web/API/Window/confirm>
YesNo(String),
/// Input dialog
///
/// <https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt>
Input(String, Option<String>),
}
/// Prompt Sender, used to send prompt result back to the caller
#[derive(Clone)]
pub enum PromptSender {
/// Alert sender
AlertSender(IpcSender<()>),
/// Ok/Cancel, Yes/No sender
ConfirmSender(IpcSender<PromptResult>),
/// Input sender
InputSender(IpcSender<Option<String>>),
/// Yes/No Permission sender
PermissionSender(IpcSender<PermissionRequest>),
}
/// Prompt input result send from prompt dialog to backend
/// - action: "ok" / "cancel"
/// - value: user input value in input prompt
///
/// Behavior:
/// - **Ok**: return string, or an empty string if user leave input empty
/// - **Cancel**: return null
///
/// <https://developer.mozilla.org/en-US/docs/Web/API/Window/prompt#return_value>
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PromptInputResult {
/// User action: "ok" / "cancel"
pub action: String,
/// User input value
pub value: String,
}
/// Prompt Dialog
#[derive(Clone)]
pub struct PromptDialog {
webview: WebView,
prompt_sender: Option<PromptSender>,
}
impl PromptDialog {
/// New prompt dialog
pub fn new() -> Self {
PromptDialog {
webview: WebView::new(WebViewId::new(), DeviceIntRect::zero()),
prompt_sender: None,
}
}
/// Get prompt webview
pub fn webview(&self) -> &WebView {
&self.webview
}
/// Get prompt sender. Send user interaction result back to caller.
pub fn sender(&self) -> Option<PromptSender> {
self.prompt_sender.clone()
}
/// Resize prompt webview size with new window context size
///
/// ## Example:
/// ```rust
/// let rect = window.webview.as_ref().unwrap().rect;
/// let content_size = window.get_content_size(rect);
/// prompt.resize(content_size);
/// ```
pub fn resize(&mut self, rect: DeviceIntRect) {
self.webview.set_size(rect);
}
/// Show alert prompt.
///
/// After you call `alert(..)`, you must call `sender()` to get prompt sender,
/// then send user interaction result back to caller.
///
/// ## Example
///
/// ```rust
/// if let Some(PromptSender::AlertSender(sender)) = prompt.sender() {
/// let _ = sender.send(());
/// }
/// ```
pub fn alert(
&mut self,
sender: &Sender<ConstellationMsg>,
rect: DeviceIntRect,
message: String,
prompt_sender: IpcSender<()>,
) {
self.prompt_sender = Some(PromptSender::AlertSender(prompt_sender));
self.show(sender, rect, PromptType::Alert(message));
}
/// Show Ok/Cancel confirm prompt
///
/// After you call `ok_cancel(..)`, you must call `sender()` to get prompt sender,
/// then send user interaction result back to caller.
///
/// ## Example
///
/// ```rust
/// if let Some(PromptSender::ConfirmSender(sender)) = prompt.sender() {
/// let _ = sender.send(PromptResult::Primary);
/// }
/// ```
pub fn ok_cancel(
&mut self,
sender: &Sender<ConstellationMsg>,
rect: DeviceIntRect,
message: String,
prompt_sender: IpcSender<PromptResult>,
) {
self.prompt_sender = Some(PromptSender::ConfirmSender(prompt_sender));
self.show(sender, rect, PromptType::OkCancel(message));
}
/// Show Yes/No confirm prompt
///
/// After you call `yes_no(..)`, you must call `sender()` to get prompt sender,
/// then send user interaction result back to caller.
///
/// ## Example
///
/// ```rust
/// let mut prompt = PromptDialog::new();
/// prompt.yes_no(sender, rect, message, prompt_sender);
/// if let Some(PromptSender::PermissionSender(sender)) = prompt.sender() {
/// let _ = sender.send(PermissionRequest::Granted);
/// }
/// ```
pub fn yes_no(
&mut self,
sender: &Sender<ConstellationMsg>,
rect: DeviceIntRect,
message: String,
prompt_sender: PromptSender,
) {
self.prompt_sender = Some(prompt_sender);
self.show(sender, rect, PromptType::YesNo(message));
}
/// Show input prompt
///
/// After you call `input(..)`, you must call `sender()` to get prompt sender,
/// then send user interaction result back to caller.
///
/// ## Example
///
/// ```rust
/// if let Some(PromptSender::InputSender(sender)) = prompt.sender() {
/// let _ = sender.send(Some("user input value".to_string()));
/// }
/// ```
pub fn input(
&mut self,
sender: &Sender<ConstellationMsg>,
rect: DeviceIntRect,
message: String,
default_value: Option<String>,
prompt_sender: IpcSender<Option<String>>,
) {
self.prompt_sender = Some(PromptSender::InputSender(prompt_sender));
self.show(sender, rect, PromptType::Input(message, default_value));
}
fn show(
&mut self,
sender: &Sender<ConstellationMsg>,
rect: DeviceIntRect,
prompt_type: PromptType,
) {
self.webview.set_size(rect);
send_to_constellation(
sender,
ConstellationMsg::NewWebView(self.resource_url(prompt_type), self.webview.webview_id),
);
}
fn resource_url(&self, prompt_type: PromptType) -> ServoUrl {
let url = match prompt_type {
PromptType::Alert(msg) => {
format!("verso://resources/components/prompt/alert.html?msg={msg}")
}
PromptType::OkCancel(msg) => {
format!("verso://resources/components/prompt/ok_cancel.html?msg={msg}")
}
PromptType::YesNo(msg) => {
format!("verso://resources/components/prompt/yes_no.html?msg={msg}")
}
PromptType::Input(msg, default_value) => {
let mut url = format!("verso://resources/components/prompt/prompt.html?msg={msg}");
if let Some(default_value) = default_value {
url.push_str(&format!("&defaultValue={}", default_value));
}
url
}
};
ServoUrl::parse(&url).unwrap()
}
}

View File

@@ -2,7 +2,10 @@ use arboard::Clipboard;
use base::id::{BrowsingContextId, WebViewId};
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{CompositorEventVariant, EmbedderMsg, PromptDefinition};
use embedder_traits::{
CompositorEventVariant, EmbedderMsg, PermissionPrompt, PermissionRequest, PromptDefinition,
PromptResult,
};
use ipc_channel::ipc;
use script_traits::{
webdriver_msg::{WebDriverJSResult, WebDriverScriptCommand},
@@ -12,10 +15,15 @@ use servo_url::ServoUrl;
use url::Url;
use webrender_api::units::DeviceIntRect;
use crate::{compositor::IOCompositor, verso::send_to_constellation, window::Window};
use crate::{
compositor::IOCompositor,
verso::send_to_constellation,
webview::prompt::{PromptDialog, PromptInputResult, PromptSender},
window::Window,
};
#[cfg(linux)]
use crate::context_menu::ContextMenuResult;
use crate::webview::context_menu::ContextMenuResult;
/// A web view is an area to display web browsing context. It's what user will treat as a "web page".
#[derive(Debug, Clone)]
@@ -121,6 +129,7 @@ impl Window {
}
}
EmbedderMsg::HistoryChanged(list, index) => {
self.close_prompt_dialog();
self.update_history(&list, index);
let url = list.get(index).unwrap();
if let Some(panel) = self.panel.as_ref() {
@@ -146,6 +155,61 @@ impl Window {
EmbedderMsg::ShowContextMenu(_sender, _title, _options) => {
// TODO: Implement context menu
}
EmbedderMsg::Prompt(prompt_type, _origin) => {
let mut prompt = PromptDialog::new();
let rect = self.webview.as_ref().unwrap().rect;
match prompt_type {
PromptDefinition::Alert(message, prompt_sender) => {
prompt.alert(sender, rect, message, prompt_sender);
}
PromptDefinition::OkCancel(message, prompt_sender) => {
prompt.ok_cancel(sender, rect, message, prompt_sender);
}
PromptDefinition::YesNo(message, prompt_sender) => {
prompt.yes_no(
sender,
rect,
message,
PromptSender::ConfirmSender(prompt_sender),
);
}
PromptDefinition::Input(message, default_value, prompt_sender) => {
prompt.input(sender, rect, message, Some(default_value), prompt_sender);
}
}
// save prompt in window to keep prompt_sender alive
// so that we can send the result back to the prompt after user clicked the button
self.prompt = Some(prompt);
}
EmbedderMsg::PromptPermission(prompt, prompt_sender) => {
let message = match prompt {
PermissionPrompt::Request(permission_name) => {
format!(
"This website would like to request permission for {:?}.",
permission_name
)
}
PermissionPrompt::Insecure(permission_name) => {
format!(
"This website would like to request permission for {:?}. However current connection is not secure. Do you want to proceed?",
permission_name
)
}
};
let mut prompt = PromptDialog::new();
let rect = self.webview.as_ref().unwrap().rect;
prompt.yes_no(
sender,
rect,
message,
PromptSender::PermissionSender(prompt_sender),
);
self.prompt = Some(prompt);
}
e => {
log::trace!("Verso WebView isn't supporting this message yet: {e:?}")
}
@@ -327,4 +391,82 @@ impl Window {
}
false
}
/// Handle servo messages with prompt. Return true it requests a new window.
pub fn handle_servo_messages_with_prompt(
&mut self,
webview_id: WebViewId,
message: EmbedderMsg,
_sender: &Sender<ConstellationMsg>,
_clipboard: Option<&mut Clipboard>,
_compositor: &mut IOCompositor,
) -> bool {
log::trace!("Verso Prompt {webview_id:?} is handling Embedder message: {message:?}",);
match message {
EmbedderMsg::Prompt(prompt, _origin) => match prompt {
PromptDefinition::Alert(msg, ignored_prompt_sender) => {
let prompt = self.prompt.as_ref().unwrap();
let prompt_sender = prompt.sender().unwrap();
match prompt_sender {
PromptSender::AlertSender(sender) => {
let _ = sender.send(());
}
PromptSender::ConfirmSender(sender) => {
let result: PromptResult = match msg.as_str() {
"ok" | "yes" => PromptResult::Primary,
"cancel" | "no" => PromptResult::Secondary,
_ => {
log::error!("prompt result message invalid: {msg}");
PromptResult::Dismissed
}
};
let _ = sender.send(result);
}
PromptSender::InputSender(sender) => {
if let Ok(PromptInputResult { action, value }) =
serde_json::from_str::<PromptInputResult>(&msg)
{
match action.as_str() {
"ok" => {
let _ = sender.send(Some(value));
}
"cancel" => {
let _ = sender.send(None);
}
_ => {
log::error!("prompt result message invalid: {msg}");
let _ = sender.send(None);
}
}
} else {
log::error!("prompt result message invalid: {msg}");
let _ = sender.send(None);
}
}
PromptSender::PermissionSender(sender) => {
let result: PermissionRequest = match msg.as_str() {
"ok" | "yes" => PermissionRequest::Granted,
"cancel" | "no" => PermissionRequest::Denied,
_ => {
log::error!("prompt result message invalid: {msg}");
PermissionRequest::Denied
}
};
let _ = sender.send(result);
}
}
let _ = ignored_prompt_sender.send(());
}
_ => {
log::trace!("Verso WebView isn't supporting this prompt yet")
}
},
e => {
log::trace!("Verso Dialog isn't supporting this message yet: {e:?}")
}
}
false
}
}

View File

@@ -3,7 +3,7 @@ use std::cell::Cell;
use base::id::WebViewId;
use compositing_traits::ConstellationMsg;
use crossbeam_channel::Sender;
use embedder_traits::{Cursor, EmbedderMsg};
use embedder_traits::{Cursor, EmbedderMsg, PermissionRequest, PromptResult};
use euclid::{Point2D, Size2D};
use glutin::{
config::{ConfigTemplateBuilder, GlConfig},
@@ -33,11 +33,14 @@ use winit::{
use crate::{
compositor::{IOCompositor, MouseWindowEvent},
context_menu::{ContextMenu, Menu},
keyboard::keyboard_event_from_winit,
rendering::{gl_config_picker, RenderingContext},
verso::send_to_constellation,
webview::{Panel, WebView},
webview::{
context_menu::{ContextMenu, Menu},
prompt::{PromptDialog, PromptSender},
Panel, WebView,
},
};
use arboard::Clipboard;
@@ -69,6 +72,9 @@ pub struct Window {
/// Global menu event receiver for muda crate
#[cfg(any(target_os = "macos", target_os = "windows"))]
menu_event_receiver: MenuEventReceiver,
/// Current Prompt
pub(crate) prompt: Option<PromptDialog>,
}
impl Window {
@@ -123,6 +129,7 @@ impl Window {
context_menu: None,
#[cfg(any(target_os = "macos", target_os = "windows"))]
menu_event_receiver: MenuEvent::receiver().clone(),
prompt: None,
},
rendering_context,
)
@@ -166,6 +173,7 @@ impl Window {
context_menu: None,
#[cfg(any(target_os = "macos", target_os = "windows"))]
menu_event_receiver: MenuEvent::receiver().clone(),
prompt: None,
};
compositor.swap_current_window(&mut window);
window
@@ -200,7 +208,7 @@ impl Window {
},
});
let url = ServoUrl::parse("verso://panel.html").unwrap();
let url = ServoUrl::parse("verso://resources/components/panel.html").unwrap();
send_to_constellation(
constellation_sender,
ConstellationMsg::NewWebView(url, panel_id),
@@ -293,6 +301,9 @@ impl Window {
match (state, button) {
#[cfg(any(target_os = "macos", target_os = "windows"))]
(ElementState::Pressed, winit::event::MouseButton::Right) => {
if self.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() {
@@ -301,6 +312,9 @@ impl Window {
}
#[cfg(linux)]
(ElementState::Pressed, winit::event::MouseButton::Right) => {
if self.prompt.is_some() {
return;
}
if self.context_menu.is_none() {
self.context_menu = Some(self.show_context_menu(sender));
return;
@@ -445,6 +459,15 @@ impl Window {
return false;
}
}
if let Some(prompt) = &self.prompt {
if prompt.webview().webview_id == webview_id {
self.handle_servo_messages_with_prompt(
webview_id, message, sender, clipboard, compositor,
);
return false;
}
}
// Handle message in Verso WebView
self.handle_servo_messages_with_webview(webview_id, message, sender, clipboard, compositor);
false
@@ -482,6 +505,14 @@ impl Window {
return true;
}
if self
.prompt
.as_ref()
.map_or(false, |w| w.webview().webview_id == id)
{
return true;
}
self.panel
.as_ref()
.map_or(false, |w| w.webview.webview_id == id)
@@ -506,6 +537,16 @@ impl Window {
return (Some(context_menu.webview().clone()), false);
}
if self
.prompt
.as_ref()
.filter(|menu| menu.webview().webview_id == id)
.is_some()
{
let prompt = self.prompt.take().expect("Prompt should exist");
return (Some(prompt.webview().clone()), false);
}
if self
.panel
.as_ref()
@@ -546,6 +587,10 @@ impl Window {
order.push(context_menu.webview());
}
if let Some(prompt) = &self.prompt {
order.push(prompt.webview());
}
order
}
@@ -623,7 +668,7 @@ impl Window {
#[cfg(linux)]
pub(crate) fn show_context_menu(&mut self, sender: &Sender<ConstellationMsg>) -> ContextMenu {
use crate::context_menu::MenuItem;
use crate::webview::context_menu::MenuItem;
let history_len = self.history.len();
@@ -694,7 +739,7 @@ impl Window {
pub(crate) fn handle_context_menu_event(
&mut self,
sender: &Sender<ConstellationMsg>,
event: crate::context_menu::ContextMenuResult,
event: crate::webview::context_menu::ContextMenuResult,
) {
self.close_context_menu(sender);
match event.id.as_str() {
@@ -727,6 +772,29 @@ impl Window {
}
}
// Prompt methods
impl Window {
/// Close window's prompt dialog
pub(crate) fn close_prompt_dialog(&mut self) {
if let Some(sender) = self.prompt.take().and_then(|prompt| prompt.sender()) {
match sender {
PromptSender::AlertSender(sender) => {
let _ = sender.send(());
}
PromptSender::ConfirmSender(sender) => {
let _ = sender.send(PromptResult::Dismissed);
}
PromptSender::InputSender(sender) => {
let _ = sender.send(None);
}
PromptSender::PermissionSender(sender) => {
let _ = sender.send(PermissionRequest::Denied);
}
}
}
}
}
// Non-decorated window resizing for Windows and Linux.
#[cfg(any(linux, target_os = "windows"))]
impl Window {