Skip to content

Commit

Permalink
Embedder Prompt API
Browse files Browse the repository at this point in the history
  • Loading branch information
paulrouget committed Feb 10, 2020
1 parent 5f55cd5 commit 51f15a0
Show file tree
Hide file tree
Showing 17 changed files with 447 additions and 110 deletions.
37 changes: 34 additions & 3 deletions components/embedder_traits/lib.rs
Expand Up @@ -106,6 +106,37 @@ impl EmbedderReceiver {
}
}

#[derive(Deserialize, Serialize)]
pub enum PromptDefinition {
/// Show a message.
Alert(String, IpcSender<()>),
/// Ask a Ok/Cancel question.
OkCancel(String, IpcSender<PromptResult>),
/// Ask a Yes/No question.
YesNo(String, IpcSender<PromptResult>),
/// Ask the user to enter text.
Input(String, String, IpcSender<Option<String>>),
}

#[derive(Deserialize, PartialEq, Serialize)]
pub enum PromptOrigin {
/// Prompt is triggered from content (window.prompt/alert/confirm/…).
/// Prompt message is unknown.
Untrusted,
/// Prompt is triggered from Servo (ask for permission, show error,…).
Trusted,
}

#[derive(Deserialize, PartialEq, Serialize)]
pub enum PromptResult {
/// Prompt was closed by clicking on the primary button (ok/yes)
Primary,
/// Prompt was closed by clicking on the secondary button (cancel/no)
Secondary,
/// Prompt was dismissed
Dismissed,
}

#[derive(Deserialize, Serialize)]
pub enum EmbedderMsg {
/// A status message to be displayed by the browser chrome.
Expand All @@ -116,8 +147,8 @@ pub enum EmbedderMsg {
MoveTo(DeviceIntPoint),
/// Resize the window to size
ResizeTo(DeviceIntSize),
// Show an alert message.
Alert(String, IpcSender<()>),
/// Show dialog to user
Prompt(PromptDefinition, PromptOrigin),
/// Wether 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
Expand Down Expand Up @@ -174,7 +205,7 @@ impl Debug for EmbedderMsg {
EmbedderMsg::ChangePageTitle(..) => write!(f, "ChangePageTitle"),
EmbedderMsg::MoveTo(..) => write!(f, "MoveTo"),
EmbedderMsg::ResizeTo(..) => write!(f, "ResizeTo"),
EmbedderMsg::Alert(..) => write!(f, "Alert"),
EmbedderMsg::Prompt(..) => write!(f, "Prompt"),
EmbedderMsg::AllowUnload(..) => write!(f, "AllowUnload"),
EmbedderMsg::AllowNavigationRequest(..) => write!(f, "AllowNavigationRequest"),
EmbedderMsg::Keyboard(..) => write!(f, "Keyboard"),
Expand Down
4 changes: 2 additions & 2 deletions components/script/dom/webidls/Window.webidl
Expand Up @@ -55,8 +55,8 @@
// user prompts
void alert(DOMString message);
void alert();
//boolean confirm(optional DOMString message = "");
//DOMString? prompt(optional DOMString message = "", optional DOMString default = "");
boolean confirm(optional DOMString message = "");
DOMString? prompt(optional DOMString message = "", optional DOMString default = "");
//void print();
//any showModalDialog(DOMString url, optional any argument);

Expand Down
25 changes: 23 additions & 2 deletions components/script/dom/window.rs
Expand Up @@ -74,7 +74,7 @@ use crossbeam_channel::{unbounded, Sender, TryRecvError};
use cssparser::{Parser, ParserInput, SourceLocation};
use devtools_traits::{ScriptToDevtoolsControlMsg, TimelineMarker, TimelineMarkerType};
use dom_struct::dom_struct;
use embedder_traits::{EmbedderMsg, EventLoopWaker};
use embedder_traits::{EmbedderMsg, EventLoopWaker, PromptDefinition, PromptOrigin, PromptResult};
use euclid::default::{Point2D as UntypedPoint2D, Rect as UntypedRect};
use euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
use ipc_channel::ipc::{channel, IpcSender};
Expand Down Expand Up @@ -620,11 +620,32 @@ impl WindowMethods for Window {
}
let (sender, receiver) =
ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
let msg = EmbedderMsg::Alert(s.to_string(), sender);
let prompt = PromptDefinition::Alert(s.to_string(), sender);
let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted);
self.send_to_embedder(msg);
receiver.recv().unwrap();
}

// https://html.spec.whatwg.org/multipage/#dom-confirm
fn Confirm(&self, s: DOMString) -> bool {
let (sender, receiver) =
ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
let prompt = PromptDefinition::OkCancel(s.to_string(), sender);
let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted);
self.send_to_embedder(msg);
receiver.recv().unwrap() == PromptResult::Primary
}

// https://html.spec.whatwg.org/multipage/#dom-prompt
fn Prompt(&self, message: DOMString, default: DOMString) -> Option<DOMString> {
let (sender, receiver) =
ProfiledIpc::channel(self.global().time_profiler_chan().clone()).unwrap();
let prompt = PromptDefinition::Input(message.to_string(), default.to_string(), sender);
let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Untrusted);
self.send_to_embedder(msg);
receiver.recv().unwrap().map(|s| s.into())
}

// https://html.spec.whatwg.org/multipage/#dom-window-stop
fn Stop(&self) {
// TODO: Cancel ongoing navigation.
Expand Down
83 changes: 69 additions & 14 deletions ports/glutin/browser.rs
Expand Up @@ -7,7 +7,7 @@ use crate::window_trait::{WindowPortsMethods, LINE_HEIGHT};
use euclid::{Point2D, Vector2D};
use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher};
use servo::compositing::windowing::{WebRenderDebugOption, WindowEvent};
use servo::embedder_traits::{EmbedderMsg, FilterPattern};
use servo::embedder_traits::{EmbedderMsg, FilterPattern, PromptDefinition, PromptOrigin, PromptResult};
use servo::msg::constellation_msg::TopLevelBrowsingContextId as BrowserId;
use servo::msg::constellation_msg::TraversalDirection;
use servo::net_traits::pub_domains::is_reg_domain;
Expand All @@ -24,7 +24,7 @@ use std::mem;
use std::rc::Rc;
use std::thread;
use std::time::Duration;
use tinyfiledialogs::{self, MessageBoxIcon};
use tinyfiledialogs::{self, MessageBoxIcon, OkCancel, YesNo};

pub struct Browser<Window: WindowPortsMethods + ?Sized> {
current_url: Option<ServoUrl>,
Expand Down Expand Up @@ -299,23 +299,78 @@ where
EmbedderMsg::ResizeTo(size) => {
self.window.set_inner_size(size);
},
EmbedderMsg::Alert(message, sender) => {
if !opts::get().headless {
let _ = thread::Builder::new()
EmbedderMsg::Prompt(definition, origin) => {
let res = if opts::get().headless {
match definition {
PromptDefinition::Alert(_message, sender) => {
sender.send(())
}
PromptDefinition::YesNo(_message, sender) => {
sender.send(PromptResult::Primary)
}
PromptDefinition::OkCancel(_message, sender) => {
sender.send(PromptResult::Primary)
}
PromptDefinition::Input(_message, default, sender) => {
sender.send(Some(default.to_owned()))
}
}
} else {
thread::Builder::new()
.name("display alert dialog".to_owned())
.spawn(move || {
tinyfiledialogs::message_box_ok(
"Alert!",
&tiny_dialog_escape(&message),
MessageBoxIcon::Warning,
);
match definition {
PromptDefinition::Alert(mut message, sender) => {
if origin == PromptOrigin::Untrusted {
message = tiny_dialog_escape(&message);
}
tinyfiledialogs::message_box_ok(
"Alert!",
&message,
MessageBoxIcon::Warning,
);
sender.send(())
}
PromptDefinition::YesNo(mut message, sender) => {
if origin == PromptOrigin::Untrusted {
message = tiny_dialog_escape(&message);
}
let result = tinyfiledialogs::message_box_yes_no(
"", &message, MessageBoxIcon::Warning, YesNo::No,
);
sender.send(match result {
YesNo::Yes => PromptResult::Primary,
YesNo::No => PromptResult::Secondary,
})
}
PromptDefinition::OkCancel(mut message, sender) => {
if origin == PromptOrigin::Untrusted {
message = tiny_dialog_escape(&message);
}
let result = tinyfiledialogs::message_box_ok_cancel(
"", &message, MessageBoxIcon::Warning, OkCancel::Cancel,
);
sender.send(match result {
OkCancel::Ok => PromptResult::Primary,
OkCancel::Cancel => PromptResult::Secondary,
})
}
PromptDefinition::Input(mut message, mut default, sender) => {
if origin == PromptOrigin::Untrusted {
message = tiny_dialog_escape(&message);
default = tiny_dialog_escape(&default);
}
let result = tinyfiledialogs::input_box("", &message, &default);
sender.send(result)
}
}
})
.unwrap()
.join()
.expect("Thread spawning failed");
}
if let Err(e) = sender.send(()) {
let reason = format!("Failed to send Alert response: {}", e);
.expect("Thread spawning failed")
};
if let Err(e) = res {
let reason = format!("Failed to send Prompt response: {}", e);
self.event_queue
.push(WindowEvent::SendError(browser_id, reason));
}
Expand Down
24 changes: 22 additions & 2 deletions ports/libmlservo/src/lib.rs
Expand Up @@ -16,7 +16,9 @@ use servo::keyboard_types::Key;
use servo::servo_url::ServoUrl;
use servo::webrender_api::units::{DevicePixel, DevicePoint, LayoutPixel};
use simpleservo::{self, deinit, gl_glue, MouseButton, ServoGlue, SERVO};
use simpleservo::{Coordinates, EventLoopWaker, HostTrait, InitOptions, VRInitOptions};
use simpleservo::{
Coordinates, EventLoopWaker, HostTrait, InitOptions, PromptResult, VRInitOptions,
};
use smallvec::SmallVec;
use std::cell::Cell;
use std::ffi::CStr;
Expand Down Expand Up @@ -365,7 +367,25 @@ impl HostTrait for HostCallbacks {
MakeCurrent(self.disp, self.surf, self.surf, self.ctxt);
}

fn on_alert(&self, _message: String) {}
fn prompt_alert(&self, message: String, _trusted: bool) {
warn!("Prompt Alert: {}", message);
}

fn prompt_ok_cancel(&self, message: String, _trusted: bool) -> PromptResult {
warn!("Prompt not implemented. Cancelled. {}", message);
PromptResult::Secondary
}

fn prompt_yes_no(&self, message: String, _trusted: bool) -> PromptResult {
warn!("Prompt not implemented. Cancelled. {}", message);
PromptResult::Secondary
}

fn prompt_input(&self, message: String, default: String, _trusted: bool) -> Option<String> {
warn!("Input prompt not implemented. {}", message);
Some(default)
}

fn on_load_started(&self) {}
fn on_load_ended(&self) {}
fn on_title_changed(&self, _title: String) {}
Expand Down
39 changes: 31 additions & 8 deletions ports/libsimpleservo/api/src/lib.rs
Expand Up @@ -7,7 +7,7 @@ extern crate log;

pub mod gl_glue;

pub use servo::embedder_traits::MediaSessionPlaybackState;
pub use servo::embedder_traits::{MediaSessionPlaybackState, PromptResult};
pub use servo::script_traits::{MediaSessionActionType, MouseButton};

use getopts::Options;
Expand All @@ -16,7 +16,7 @@ use servo::compositing::windowing::{
WindowMethods,
};
use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
use servo::embedder_traits::{EmbedderMsg, MediaSessionEvent};
use servo::embedder_traits::{EmbedderMsg, MediaSessionEvent, PromptDefinition, PromptOrigin};
use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
use servo::msg::constellation_msg::TraversalDirection;
Expand Down Expand Up @@ -92,8 +92,14 @@ pub trait HostTrait {
/// Will be called before drawing.
/// Time to make the targetted GL context current.
fn make_current(&self);
/// javascript window.alert()
fn on_alert(&self, msg: String);
/// Show alert.
fn prompt_alert(&self, msg: String, trusted: bool);
/// Ask Yes/No question.
fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult;
/// Ask Ok/Cancel question.
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult;
/// Ask for string
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>;
/// Page starts loading.
/// "Reload button" should be disabled.
/// "Stop button" should be enabled.
Expand Down Expand Up @@ -540,10 +546,27 @@ impl ServoGlue {
EmbedderMsg::AllowUnload(sender) => {
let _ = sender.send(true);
},
EmbedderMsg::Alert(message, sender) => {
info!("Alert: {}", message);
self.callbacks.host_callbacks.on_alert(message);
let _ = sender.send(());
EmbedderMsg::Prompt(definition, origin) => {
let cb = &self.callbacks.host_callbacks;
let trusted = origin == PromptOrigin::Trusted;
let res = match definition {
PromptDefinition::Alert(message, sender) => {
sender.send(cb.prompt_alert(message, trusted))
},
PromptDefinition::OkCancel(message, sender) => {
sender.send(cb.prompt_ok_cancel(message, trusted))
},
PromptDefinition::YesNo(message, sender) => {
sender.send(cb.prompt_yes_no(message, trusted))
},
PromptDefinition::Input(message, default, sender) => {
sender.send(cb.prompt_input(message, default, trusted))
},
};
if let Err(e) = res {
let reason = format!("Failed to send Prompt response: {}", e);
self.events.push(WindowEvent::SendError(browser_id, reason));
}
},
EmbedderMsg::AllowOpeningBrowser(response_chan) => {
// Note: would be a place to handle pop-ups config.
Expand Down

0 comments on commit 51f15a0

Please sign in to comment.