Auto merge of #25242 - paulrouget:prompt, r=manishearth

Mechanism to allow Servo to prompt the user

This blocks the embedder thread (compositor thread). Not ideal. I don't think it's too much work to only block script, I'll do that in a follow up bug.

Fix #23376

@Manishearth we have a few new APIs. Hopefully this will cover your needs. A thing I haven't implemented yet is a way to ask the user to pick from a list. Let me know if it's something you'll need.
This commit is contained in:
bors-servo 2020-02-10 03:33:50 -05:00 committed by GitHub
commit 504437938a
22 changed files with 473 additions and 118 deletions

View file

@ -13,6 +13,7 @@ path = "lib.rs"
[dependencies]
crossbeam-channel = "0.3"
devtools_traits = {path = "../devtools_traits"}
embedder_traits = {path = "../embedder_traits"}
headers = "0.2"
http = "0.1"
hyper = "0.12"

View file

@ -39,7 +39,8 @@ use crossbeam_channel::{unbounded, Receiver, Sender};
use devtools_traits::{ChromeToDevtoolsControlMsg, ConsoleMessage, DevtoolsControlMsg};
use devtools_traits::{DevtoolScriptControlMsg, DevtoolsPageInfo, LogLevel, NetworkEvent};
use devtools_traits::{PageError, ScriptToDevtoolsControlMsg, WorkerId};
use ipc_channel::ipc::IpcSender;
use embedder_traits::{EmbedderMsg, EmbedderProxy, PromptDefinition, PromptOrigin, PromptResult};
use ipc_channel::ipc::{self, IpcSender};
use msg::constellation_msg::PipelineId;
use std::borrow::ToOwned;
use std::cell::RefCell;
@ -135,13 +136,13 @@ struct ResponseStartUpdateMsg {
}
/// Spin up a devtools server that listens for connections on the specified port.
pub fn start_server(port: u16) -> Sender<DevtoolsControlMsg> {
pub fn start_server(port: u16, embedder: EmbedderProxy) -> Sender<DevtoolsControlMsg> {
let (sender, receiver) = unbounded();
{
let sender = sender.clone();
thread::Builder::new()
.name("Devtools".to_owned())
.spawn(move || run_server(sender, receiver, port))
.spawn(move || run_server(sender, receiver, port, embedder))
.expect("Thread spawning failed");
}
sender
@ -151,6 +152,7 @@ fn run_server(
sender: Sender<DevtoolsControlMsg>,
receiver: Receiver<DevtoolsControlMsg>,
port: u16,
embedder: EmbedderProxy,
) {
let listener = TcpListener::bind(&("0.0.0.0", port)).unwrap();
@ -553,7 +555,17 @@ fn run_server(
.spawn(move || {
// accept connections and process them, spawning a new thread for each one
for stream in listener.incoming() {
// connection succeeded
// Prompt user for permission
let (embedder_sender, receiver) =
ipc::channel().expect("Failed to create IPC channel!");
let message = "Accept incoming devtools connection?".to_owned();
let prompt = PromptDefinition::YesNo(message, embedder_sender);
let msg = EmbedderMsg::Prompt(prompt, PromptOrigin::Trusted);
embedder.send((None, msg));
if receiver.recv().unwrap() != PromptResult::Primary {
continue;
}
// connection succeeded and accepted
sender
.send(DevtoolsControlMsg::FromChrome(
ChromeToDevtoolsControlMsg::AddClient(stream.unwrap()),

View file

@ -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.
@ -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
@ -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"),

View file

@ -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);

View file

@ -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};
@ -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.

View file

@ -356,8 +356,11 @@ where
opts.profile_heartbeats,
);
let mem_profiler_chan = profile_mem::Profiler::create(opts.mem_profiler_period);
let debugger_chan = opts.debugger_port.map(|port| debugger::start_server(port));
let devtools_chan = opts.devtools_port.map(|port| devtools::start_server(port));
let devtools_chan = opts
.devtools_port
.map(|port| devtools::start_server(port, embedder_proxy.clone()));
let coordinates = window.get_coordinates();
let device_pixel_ratio = coordinates.hidpi_factor.get();