Wire sidecar profile data isolation
This commit is contained in:
@@ -4,7 +4,7 @@ use std::{
|
||||
time::{Duration, Instant},
|
||||
};
|
||||
|
||||
use ely_domain::{ProfileId, TabId};
|
||||
use ely_domain::TabId;
|
||||
use ely_servo_host::{
|
||||
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest, ScrollRequest,
|
||||
ServoHost, ServoHostError, ServoSurfaceSize, SoftwareServoHost, TouchTapRequest,
|
||||
@@ -51,10 +51,13 @@ enum SidecarError {
|
||||
}
|
||||
|
||||
fn run_snapshot(args: SnapshotArgs) -> Result<(), SidecarError> {
|
||||
let mut host = SoftwareServoHost::new(ServoSurfaceSize::new(args.width, args.height))?;
|
||||
std::fs::create_dir_all(&args.profile_data_dir)?;
|
||||
let mut host = SoftwareServoHost::new_with_config_dir(
|
||||
ServoSurfaceSize::new(args.width, args.height),
|
||||
Some(args.profile_data_dir.clone()),
|
||||
)?;
|
||||
let tab_id = TabId::new();
|
||||
let profile_id = ProfileId::new();
|
||||
let webview_id = host.create_webview(tab_id.clone(), profile_id)?;
|
||||
let webview_id = host.create_webview(tab_id.clone(), args.profile_id.clone())?;
|
||||
|
||||
host.navigate(NavigationRequest {
|
||||
webview_id: webview_id.clone(),
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use std::{env, num::ParseIntError, path::PathBuf};
|
||||
|
||||
use ely_domain::UrlText;
|
||||
use ely_domain::{ProfileId, UrlText};
|
||||
use thiserror::Error;
|
||||
|
||||
pub(super) enum SidecarCommand {
|
||||
@@ -9,6 +9,8 @@ pub(super) enum SidecarCommand {
|
||||
|
||||
pub(super) struct SnapshotArgs {
|
||||
pub(super) url: UrlText,
|
||||
pub(super) profile_id: ProfileId,
|
||||
pub(super) profile_data_dir: PathBuf,
|
||||
pub(super) rgba_out: PathBuf,
|
||||
pub(super) width: u32,
|
||||
pub(super) height: u32,
|
||||
@@ -69,8 +71,8 @@ pub(super) enum SidecarArgsError {
|
||||
#[error("--touch-x and --touch-y must be provided together")]
|
||||
IncompleteTouchPoint,
|
||||
|
||||
#[error("rgba output path is empty")]
|
||||
EmptyRgbaOutputPath,
|
||||
#[error("{name} path is empty")]
|
||||
EmptyPath { name: &'static str },
|
||||
|
||||
#[error(transparent)]
|
||||
Domain(#[from] ely_domain::DomainError),
|
||||
@@ -98,6 +100,8 @@ fn parse_snapshot_args(
|
||||
) -> Result<SnapshotArgs, SidecarArgsError> {
|
||||
let mut args = args.into_iter();
|
||||
let mut url = None;
|
||||
let mut profile_id = None;
|
||||
let mut profile_data_dir = None;
|
||||
let mut rgba_out = None;
|
||||
let mut width = None;
|
||||
let mut height = None;
|
||||
@@ -116,8 +120,17 @@ fn parse_snapshot_args(
|
||||
while let Some(name) = args.next() {
|
||||
match name.as_str() {
|
||||
"--url" => url = Some(UrlText::parse(next_argument(&mut args, "--url")?)?),
|
||||
"--profile-id" => {
|
||||
profile_id = Some(ProfileId::parse(next_argument(&mut args, "--profile-id")?)?)
|
||||
}
|
||||
"--profile-data-dir" => {
|
||||
profile_data_dir = Some(parse_path(
|
||||
"--profile-data-dir",
|
||||
next_argument(&mut args, "--profile-data-dir")?,
|
||||
)?)
|
||||
}
|
||||
"--rgba-out" => {
|
||||
rgba_out = Some(parse_output_path(next_argument(&mut args, "--rgba-out")?)?)
|
||||
rgba_out = Some(parse_path("--rgba-out", next_argument(&mut args, "--rgba-out")?)?)
|
||||
}
|
||||
"--width" => {
|
||||
width = Some(parse_dimension("--width", next_argument(&mut args, "--width")?)?)
|
||||
@@ -207,6 +220,10 @@ fn parse_snapshot_args(
|
||||
|
||||
Ok(SnapshotArgs {
|
||||
url: url.ok_or(SidecarArgsError::MissingRequiredArgument { name: "--url" })?,
|
||||
profile_id: profile_id
|
||||
.ok_or(SidecarArgsError::MissingRequiredArgument { name: "--profile-id" })?,
|
||||
profile_data_dir: profile_data_dir
|
||||
.ok_or(SidecarArgsError::MissingRequiredArgument { name: "--profile-data-dir" })?,
|
||||
rgba_out: rgba_out
|
||||
.ok_or(SidecarArgsError::MissingRequiredArgument { name: "--rgba-out" })?,
|
||||
width: width.ok_or(SidecarArgsError::MissingRequiredArgument { name: "--width" })?,
|
||||
@@ -248,10 +265,81 @@ fn parse_click_coordinate(name: &'static str, value: String) -> Result<u32, Side
|
||||
value.parse::<u32>().map_err(|source| SidecarArgsError::InvalidInteger { name, value, source })
|
||||
}
|
||||
|
||||
fn parse_output_path(value: String) -> Result<PathBuf, SidecarArgsError> {
|
||||
fn parse_path(name: &'static str, value: String) -> Result<PathBuf, SidecarArgsError> {
|
||||
if value.trim().is_empty() {
|
||||
return Err(SidecarArgsError::EmptyRgbaOutputPath);
|
||||
return Err(SidecarArgsError::EmptyPath { name });
|
||||
}
|
||||
|
||||
Ok(PathBuf::from(value))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{env, path::PathBuf};
|
||||
|
||||
use super::{SidecarArgsError, SidecarCommand, parse_command};
|
||||
use ely_domain::{DomainError, ProfileId};
|
||||
|
||||
#[test]
|
||||
fn parses_snapshot_profile_identity() -> Result<(), SidecarArgsError> {
|
||||
let profile_id = ProfileId::new();
|
||||
let profile_data_dir = env::temp_dir().join(profile_id.as_str());
|
||||
let command = parse_snapshot_command(&profile_id, profile_data_dir.clone())?;
|
||||
|
||||
let SidecarCommand::Snapshot(args) = command;
|
||||
assert_eq!(args.profile_id, profile_id);
|
||||
assert_eq!(args.profile_data_dir, profile_data_dir);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_invalid_snapshot_profile_id() {
|
||||
let command = parse_command(
|
||||
[
|
||||
"ely_servo_sidecar",
|
||||
"snapshot",
|
||||
"--url",
|
||||
"https://example.com",
|
||||
"--profile-id",
|
||||
"profile_invalid",
|
||||
"--profile-data-dir",
|
||||
"/tmp/profile",
|
||||
"--rgba-out",
|
||||
"/tmp/frame.rgba",
|
||||
"--width",
|
||||
"64",
|
||||
"--height",
|
||||
"64",
|
||||
]
|
||||
.into_iter()
|
||||
.map(str::to_string),
|
||||
);
|
||||
|
||||
assert!(matches!(
|
||||
command,
|
||||
Err(SidecarArgsError::Domain(DomainError::InvalidEntityId { .. }))
|
||||
));
|
||||
}
|
||||
|
||||
fn parse_snapshot_command(
|
||||
profile_id: &ProfileId,
|
||||
profile_data_dir: PathBuf,
|
||||
) -> Result<SidecarCommand, SidecarArgsError> {
|
||||
parse_command([
|
||||
"ely_servo_sidecar".to_string(),
|
||||
"snapshot".to_string(),
|
||||
"--url".to_string(),
|
||||
"https://example.com".to_string(),
|
||||
"--profile-id".to_string(),
|
||||
profile_id.as_str().to_string(),
|
||||
"--profile-data-dir".to_string(),
|
||||
profile_data_dir.display().to_string(),
|
||||
"--rgba-out".to_string(),
|
||||
"/tmp/frame.rgba".to_string(),
|
||||
"--width".to_string(),
|
||||
"64".to_string(),
|
||||
"--height".to_string(),
|
||||
"64".to_string(),
|
||||
])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,6 +14,7 @@ pub(super) struct SnapshotInputChanges {
|
||||
#[derive(Serialize)]
|
||||
pub(super) struct SnapshotReport {
|
||||
requested_url: String,
|
||||
profile_id: String,
|
||||
loaded_url: Option<String>,
|
||||
title: Option<String>,
|
||||
rgba_path: String,
|
||||
@@ -52,6 +53,7 @@ impl SnapshotReport {
|
||||
) -> Self {
|
||||
Self {
|
||||
requested_url: args.url.as_str().to_string(),
|
||||
profile_id: snapshot.profile_id().as_str().to_string(),
|
||||
loaded_url: snapshot.url().map(str::to_string),
|
||||
title: snapshot.title().map(str::to_string),
|
||||
rgba_path: args.rgba_out.display().to_string(),
|
||||
|
||||
@@ -7,6 +7,8 @@ mod runtime;
|
||||
#[cfg(feature = "servo-engine")]
|
||||
mod runtime_input;
|
||||
#[cfg(feature = "servo-engine")]
|
||||
mod runtime_permissions;
|
||||
#[cfg(feature = "servo-engine")]
|
||||
mod runtime_waker;
|
||||
|
||||
pub use error::ServoHostError;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
path::PathBuf,
|
||||
rc::Rc,
|
||||
sync::{
|
||||
Arc,
|
||||
@@ -13,7 +14,7 @@ use std::{
|
||||
use dpi::PhysicalSize;
|
||||
use ely_domain::{ProfileId, TabId, WebViewId};
|
||||
use servo::{
|
||||
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, DeviceVector2D, LoadStatus,
|
||||
DeviceIntPoint, DeviceIntRect, DeviceIntSize, DevicePoint, DeviceVector2D, LoadStatus, Opts,
|
||||
RenderingContext, Scroll, Servo, ServoBuilder, WebView, WebViewBuilder, WebViewDelegate,
|
||||
WebViewPoint, WebViewVector,
|
||||
};
|
||||
@@ -24,6 +25,7 @@ use crate::{
|
||||
PermissionDecision, PermissionRequest, RenderedFrame, ResizeRequest, ScreenshotRequest,
|
||||
ScrollRequest, ServoHost, ServoHostError, TouchTapRequest, WebViewSnapshot, WebViewState,
|
||||
runtime_input::{send_keyboard_text, send_mouse_click, send_mouse_drag, send_touch_tap},
|
||||
runtime_permissions::{PermissionKey, PermissionStore},
|
||||
runtime_waker::ServoWakeFlag,
|
||||
};
|
||||
|
||||
@@ -59,6 +61,13 @@ pub struct SoftwareServoHost {
|
||||
|
||||
impl SoftwareServoHost {
|
||||
pub fn new(size: ServoSurfaceSize) -> Result<Self, ServoHostError> {
|
||||
Self::new_with_config_dir(size, None)
|
||||
}
|
||||
|
||||
pub fn new_with_config_dir(
|
||||
size: ServoSurfaceSize,
|
||||
config_dir: Option<PathBuf>,
|
||||
) -> Result<Self, ServoHostError> {
|
||||
if SERVO_RUNTIME_STARTED
|
||||
.compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
|
||||
.is_err()
|
||||
@@ -66,14 +75,17 @@ impl SoftwareServoHost {
|
||||
return Err(ServoHostError::RuntimeAlreadyStarted);
|
||||
}
|
||||
|
||||
let host = Self::new_started(size);
|
||||
let host = Self::new_started(size, config_dir);
|
||||
if host.is_err() {
|
||||
SERVO_RUNTIME_STARTED.store(false, Ordering::Release);
|
||||
}
|
||||
host
|
||||
}
|
||||
|
||||
fn new_started(size: ServoSurfaceSize) -> Result<Self, ServoHostError> {
|
||||
fn new_started(
|
||||
size: ServoSurfaceSize,
|
||||
config_dir: Option<PathBuf>,
|
||||
) -> Result<Self, ServoHostError> {
|
||||
let rendering_context = Rc::new(
|
||||
servo::SoftwareRenderingContext::new(size.physical())
|
||||
.map_err(|_| ServoHostError::RenderingContextUnavailable)?,
|
||||
@@ -81,9 +93,12 @@ impl SoftwareServoHost {
|
||||
rendering_context.make_current().map_err(|_| ServoHostError::RenderingContextNotCurrent)?;
|
||||
|
||||
let wake_requested = Arc::new(AtomicBool::new(false));
|
||||
let servo = ServoBuilder::default()
|
||||
.event_loop_waker(Box::new(ServoWakeFlag::new(wake_requested.clone())))
|
||||
.build();
|
||||
let mut builder = ServoBuilder::default()
|
||||
.event_loop_waker(Box::new(ServoWakeFlag::new(wake_requested.clone())));
|
||||
if let Some(config_dir) = config_dir {
|
||||
builder = builder.opts(Opts { config_dir: Some(config_dir), ..Opts::default() });
|
||||
}
|
||||
let servo = builder.build();
|
||||
|
||||
Ok(Self {
|
||||
servo,
|
||||
@@ -481,18 +496,3 @@ impl WebViewDelegate for HostWebViewDelegate {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
type PermissionStore = Rc<RefCell<HashMap<PermissionKey, PermissionDecision>>>;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
struct PermissionKey {
|
||||
profile_id: ProfileId,
|
||||
tab_id: TabId,
|
||||
feature: String,
|
||||
}
|
||||
|
||||
impl PermissionKey {
|
||||
fn new(profile_id: ProfileId, tab_id: TabId, feature: String) -> Self {
|
||||
Self { profile_id, tab_id, feature }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||
|
||||
use ely_domain::{ProfileId, TabId};
|
||||
|
||||
use crate::PermissionDecision;
|
||||
|
||||
pub(super) type PermissionStore = Rc<RefCell<HashMap<PermissionKey, PermissionDecision>>>;
|
||||
|
||||
#[derive(Clone, Debug, Eq, Hash, PartialEq)]
|
||||
pub(super) struct PermissionKey {
|
||||
profile_id: ProfileId,
|
||||
tab_id: TabId,
|
||||
feature: String,
|
||||
}
|
||||
|
||||
impl PermissionKey {
|
||||
pub(super) fn new(profile_id: ProfileId, tab_id: TabId, feature: String) -> Self {
|
||||
Self { profile_id, tab_id, feature }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user