Pass site permission grants to sidecar snapshots
This commit is contained in:
@@ -10,7 +10,7 @@ use ely_domain::ProfileId;
|
|||||||
use serde::Deserialize;
|
use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub use super::servo_sidecar_request::SidecarSnapshotRequest;
|
pub use super::servo_sidecar_request::{SidecarSitePermission, SidecarSnapshotRequest};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
servo_profile_data::{
|
servo_profile_data::{
|
||||||
@@ -112,6 +112,9 @@ impl ServoSidecarClient {
|
|||||||
if let Some(typed_text) = request.typed_text.as_deref() {
|
if let Some(typed_text) = request.typed_text.as_deref() {
|
||||||
command.arg("--type-text").arg(typed_text);
|
command.arg("--type-text").arg(typed_text);
|
||||||
}
|
}
|
||||||
|
for permission in &request.site_permissions {
|
||||||
|
command.arg("--site-permission").arg(permission.to_arg());
|
||||||
|
}
|
||||||
|
|
||||||
let mut child = command
|
let mut child = command
|
||||||
.stdout(Stdio::piped())
|
.stdout(Stdio::piped())
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use ely_domain::{ProfileId, UrlText};
|
use ely_domain::{ProfileId, SiteOrigin, SitePermissionDecision, SitePermissionFeature, UrlText};
|
||||||
|
|
||||||
use super::ProfileDataMode;
|
use super::ProfileDataMode;
|
||||||
|
|
||||||
@@ -13,6 +13,7 @@ pub struct SidecarSnapshotRequest {
|
|||||||
pub(in crate::services) scroll_y: i32,
|
pub(in crate::services) scroll_y: i32,
|
||||||
pub(in crate::services) click_point: Option<SidecarClickPoint>,
|
pub(in crate::services) click_point: Option<SidecarClickPoint>,
|
||||||
pub(in crate::services) typed_text: Option<String>,
|
pub(in crate::services) typed_text: Option<String>,
|
||||||
|
pub(in crate::services) site_permissions: Vec<SidecarSitePermission>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SidecarSnapshotRequest {
|
impl SidecarSnapshotRequest {
|
||||||
@@ -28,6 +29,7 @@ impl SidecarSnapshotRequest {
|
|||||||
scroll_y: 0,
|
scroll_y: 0,
|
||||||
click_point: None,
|
click_point: None,
|
||||||
typed_text: None,
|
typed_text: None,
|
||||||
|
site_permissions: Vec::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -56,6 +58,12 @@ impl SidecarSnapshotRequest {
|
|||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn with_site_permissions(mut self, site_permissions: Vec<SidecarSitePermission>) -> Self {
|
||||||
|
self.site_permissions = site_permissions;
|
||||||
|
self
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn typed_text_for_test(&self) -> Option<&str> {
|
pub(crate) fn typed_text_for_test(&self) -> Option<&str> {
|
||||||
self.typed_text.as_deref()
|
self.typed_text.as_deref()
|
||||||
@@ -77,3 +85,45 @@ pub(in crate::services) struct SidecarClickPoint {
|
|||||||
pub(in crate::services) x: u32,
|
pub(in crate::services) x: u32,
|
||||||
pub(in crate::services) y: u32,
|
pub(in crate::services) y: u32,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct SidecarSitePermission {
|
||||||
|
origin: SiteOrigin,
|
||||||
|
feature: SitePermissionFeature,
|
||||||
|
decision: SitePermissionDecision,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl SidecarSitePermission {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(
|
||||||
|
origin: SiteOrigin,
|
||||||
|
feature: SitePermissionFeature,
|
||||||
|
decision: SitePermissionDecision,
|
||||||
|
) -> Self {
|
||||||
|
Self { origin, feature, decision }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(in crate::services) fn to_arg(&self) -> String {
|
||||||
|
serde_json::json!({
|
||||||
|
"origin": self.origin.as_str(),
|
||||||
|
"feature": self.feature.as_str(),
|
||||||
|
"decision": self.decision.as_str(),
|
||||||
|
})
|
||||||
|
.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn origin(&self) -> &SiteOrigin {
|
||||||
|
&self.origin
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn feature(&self) -> SitePermissionFeature {
|
||||||
|
self.feature
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
pub(crate) fn decision(&self) -> SitePermissionDecision {
|
||||||
|
self.decision
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ mod web_surface_frame;
|
|||||||
mod web_surface_geometry;
|
mod web_surface_geometry;
|
||||||
mod web_surface_image;
|
mod web_surface_image;
|
||||||
mod web_surface_keyboard;
|
mod web_surface_keyboard;
|
||||||
|
mod web_surface_permissions;
|
||||||
mod web_surface_state;
|
mod web_surface_state;
|
||||||
mod web_surface_view;
|
mod web_surface_view;
|
||||||
|
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ use gpui::{AnyElement, Bounds, Context, Pixels, Point};
|
|||||||
|
|
||||||
use crate::services::{
|
use crate::services::{
|
||||||
ProfileDataMode,
|
ProfileDataMode,
|
||||||
servo_sidecar::{ServoSidecarError, SidecarSnapshot},
|
servo_sidecar::{ServoSidecarError, SidecarSitePermission, SidecarSnapshot},
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{
|
use super::{
|
||||||
ElyShell,
|
ElyShell,
|
||||||
web_surface_frame::WebSurfaceFrame,
|
web_surface_frame::WebSurfaceFrame,
|
||||||
web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollOffset, WebSurfaceSize},
|
web_surface_geometry::{WebSurfaceClickPoint, WebSurfaceScrollOffset, WebSurfaceSize},
|
||||||
|
web_surface_permissions::sidecar_site_permissions_for_tab,
|
||||||
web_surface_state::{WebSurfaceRequest, WebSurfaceState},
|
web_surface_state::{WebSurfaceRequest, WebSurfaceState},
|
||||||
web_surface_view::{
|
web_surface_view::{
|
||||||
render_failed_web_surface, render_loading_web_surface, render_ready_web_surface,
|
render_failed_web_surface, render_loading_web_surface, render_ready_web_surface,
|
||||||
@@ -37,8 +38,9 @@ impl ElyShell {
|
|||||||
let Some(profile_data_mode) = profile_data_mode_for(tab, snapshot) else {
|
let Some(profile_data_mode) = profile_data_mode_for(tab, snapshot) else {
|
||||||
return render_failed_web_surface(tab, "Profile context is unavailable.", state_entity);
|
return render_failed_web_surface(tab, "Profile context is unavailable.", state_entity);
|
||||||
};
|
};
|
||||||
|
let site_permissions = sidecar_site_permissions_for_tab(tab, snapshot);
|
||||||
|
|
||||||
self.ensure_external_web_frame(tab, profile_data_mode, cx);
|
self.ensure_external_web_frame(tab, profile_data_mode, site_permissions, cx);
|
||||||
|
|
||||||
match self.web_surfaces.state(tab.id()) {
|
match self.web_surfaces.state(tab.id()) {
|
||||||
Some(WebSurfaceState::Ready(frame)) => {
|
Some(WebSurfaceState::Ready(frame)) => {
|
||||||
@@ -60,6 +62,7 @@ impl ElyShell {
|
|||||||
&mut self,
|
&mut self,
|
||||||
tab: &BrowserTab,
|
tab: &BrowserTab,
|
||||||
profile_data_mode: ProfileDataMode,
|
profile_data_mode: ProfileDataMode,
|
||||||
|
site_permissions: Vec<SidecarSitePermission>,
|
||||||
cx: &mut Context<Self>,
|
cx: &mut Context<Self>,
|
||||||
) {
|
) {
|
||||||
let Some(request) = self.web_surfaces.prepare_request(tab, profile_data_mode) else {
|
let Some(request) = self.web_surfaces.prepare_request(tab, profile_data_mode) else {
|
||||||
@@ -74,8 +77,9 @@ impl ElyShell {
|
|||||||
click_point,
|
click_point,
|
||||||
typed_text,
|
typed_text,
|
||||||
client,
|
client,
|
||||||
snapshot_request,
|
mut snapshot_request,
|
||||||
} = request;
|
} = request;
|
||||||
|
snapshot_request = snapshot_request.with_site_permissions(site_permissions);
|
||||||
let pending_frame = PendingWebSurfaceFrame {
|
let pending_frame = PendingWebSurfaceFrame {
|
||||||
tab_id,
|
tab_id,
|
||||||
requested_url,
|
requested_url,
|
||||||
|
|||||||
@@ -0,0 +1,91 @@
|
|||||||
|
use ely_browser_core::BrowserSnapshot;
|
||||||
|
use ely_domain::{BrowserTab, SiteOrigin};
|
||||||
|
|
||||||
|
use crate::services::servo_sidecar::SidecarSitePermission;
|
||||||
|
|
||||||
|
pub(super) fn sidecar_site_permissions_for_tab(
|
||||||
|
tab: &BrowserTab,
|
||||||
|
snapshot: &BrowserSnapshot,
|
||||||
|
) -> Vec<SidecarSitePermission> {
|
||||||
|
let Ok(Some(origin)) = SiteOrigin::from_url(tab.url()) else {
|
||||||
|
return Vec::new();
|
||||||
|
};
|
||||||
|
|
||||||
|
snapshot
|
||||||
|
.site_permissions
|
||||||
|
.iter()
|
||||||
|
.filter(|entry| entry.profile_id() == tab.profile_id())
|
||||||
|
.filter(|entry| entry.origin() == &origin)
|
||||||
|
.map(|entry| {
|
||||||
|
SidecarSitePermission::new(entry.origin().clone(), entry.feature(), entry.decision())
|
||||||
|
})
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::error::Error;
|
||||||
|
|
||||||
|
use ely_browser_core::{BrowserCore, BrowserSnapshot, InitialBrowserConfig};
|
||||||
|
use ely_domain::{
|
||||||
|
BrowserTab, SiteOrigin, SitePermissionDecision, SitePermissionFeature, UrlText,
|
||||||
|
};
|
||||||
|
|
||||||
|
use super::sidecar_site_permissions_for_tab;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn includes_matching_profile_and_origin_permissions() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = browser_core_for("https://example.com/page")?;
|
||||||
|
let origin = SiteOrigin::parse("https://example.com")?;
|
||||||
|
core.set_site_permission(
|
||||||
|
origin,
|
||||||
|
SitePermissionFeature::Camera,
|
||||||
|
SitePermissionDecision::AllowAlways,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
let tab = active_tab(&snapshot)?;
|
||||||
|
let permissions = sidecar_site_permissions_for_tab(tab, &snapshot);
|
||||||
|
|
||||||
|
assert_eq!(permissions.len(), 1);
|
||||||
|
assert_eq!(permissions[0].origin().as_str(), "https://example.com");
|
||||||
|
assert_eq!(permissions[0].feature(), SitePermissionFeature::Camera);
|
||||||
|
assert_eq!(permissions[0].decision(), SitePermissionDecision::AllowAlways);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn filters_other_origins() -> Result<(), Box<dyn Error>> {
|
||||||
|
let mut core = browser_core_for("https://example.com/page")?;
|
||||||
|
core.set_site_permission(
|
||||||
|
SiteOrigin::parse("https://other.test")?,
|
||||||
|
SitePermissionFeature::Location,
|
||||||
|
SitePermissionDecision::AllowOnce,
|
||||||
|
)?;
|
||||||
|
|
||||||
|
let snapshot = core.snapshot()?;
|
||||||
|
let tab = active_tab(&snapshot)?;
|
||||||
|
|
||||||
|
assert!(sidecar_site_permissions_for_tab(tab, &snapshot).is_empty());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn browser_core_for(url: &str) -> Result<BrowserCore, Box<dyn Error>> {
|
||||||
|
let mut core = BrowserCore::new(InitialBrowserConfig {
|
||||||
|
space_name: "Work".to_string(),
|
||||||
|
space_icon: "W".to_string(),
|
||||||
|
profile_name: "Default".to_string(),
|
||||||
|
new_tab_destination: Default::default(),
|
||||||
|
})?;
|
||||||
|
core.open_tab(UrlText::parse(url)?);
|
||||||
|
Ok(core)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn active_tab(snapshot: &BrowserSnapshot) -> Result<&BrowserTab, Box<dyn Error>> {
|
||||||
|
snapshot
|
||||||
|
.tabs
|
||||||
|
.iter()
|
||||||
|
.find(|tab| tab.id() == &snapshot.active_tab_id)
|
||||||
|
.ok_or_else(|| std::io::Error::other("active tab missing").into())
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -11,6 +11,12 @@ pub enum DomainError {
|
|||||||
#[error("invalid site origin: {value}")]
|
#[error("invalid site origin: {value}")]
|
||||||
InvalidSiteOrigin { value: String },
|
InvalidSiteOrigin { value: String },
|
||||||
|
|
||||||
|
#[error("invalid site permission feature: {value}")]
|
||||||
|
InvalidSitePermissionFeature { value: String },
|
||||||
|
|
||||||
|
#[error("invalid site permission decision: {value}")]
|
||||||
|
InvalidSitePermissionDecision { value: String },
|
||||||
|
|
||||||
#[error("invalid file name: {value}")]
|
#[error("invalid file name: {value}")]
|
||||||
InvalidFileName { value: String },
|
InvalidFileName { value: String },
|
||||||
|
|
||||||
|
|||||||
@@ -142,6 +142,28 @@ impl SitePermissionFeature {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn parse(value: &str) -> Result<Self, DomainError> {
|
||||||
|
match value {
|
||||||
|
"camera" => Ok(Self::Camera),
|
||||||
|
"microphone" => Ok(Self::Microphone),
|
||||||
|
"screen-capture" => Ok(Self::ScreenCapture),
|
||||||
|
"location" => Ok(Self::Location),
|
||||||
|
"notifications" => Ok(Self::Notifications),
|
||||||
|
"clipboard-read" => Ok(Self::ClipboardRead),
|
||||||
|
"clipboard-write" => Ok(Self::ClipboardWrite),
|
||||||
|
"downloads" => Ok(Self::Downloads),
|
||||||
|
"popups" => Ok(Self::Popups),
|
||||||
|
"autoplay" => Ok(Self::Autoplay),
|
||||||
|
"webusb" => Ok(Self::WebUsb),
|
||||||
|
"webhid" => Ok(Self::WebHid),
|
||||||
|
"webserial" => Ok(Self::WebSerial),
|
||||||
|
"storage-persistence" => Ok(Self::StoragePersistence),
|
||||||
|
"insecure-content" => Ok(Self::InsecureContent),
|
||||||
|
"certificate-exception" => Ok(Self::CertificateException),
|
||||||
|
_ => Err(DomainError::InvalidSitePermissionFeature { value: value.to_string() }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn label(&self) -> &'static str {
|
pub fn label(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
@@ -166,6 +188,24 @@ impl SitePermissionFeature {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl SitePermissionDecision {
|
impl SitePermissionDecision {
|
||||||
|
#[must_use]
|
||||||
|
pub fn as_str(&self) -> &'static str {
|
||||||
|
match self {
|
||||||
|
Self::AllowOnce => "allow-once",
|
||||||
|
Self::AllowAlways => "allow-always",
|
||||||
|
Self::DenyAlways => "deny-always",
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(value: &str) -> Result<Self, DomainError> {
|
||||||
|
match value {
|
||||||
|
"allow-once" => Ok(Self::AllowOnce),
|
||||||
|
"allow-always" => Ok(Self::AllowAlways),
|
||||||
|
"deny-always" => Ok(Self::DenyAlways),
|
||||||
|
_ => Err(DomainError::InvalidSitePermissionDecision { value: value.to_string() }),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn label(&self) -> &'static str {
|
pub fn label(&self) -> &'static str {
|
||||||
match self {
|
match self {
|
||||||
|
|||||||
@@ -6,8 +6,8 @@ use std::{
|
|||||||
|
|
||||||
use ely_domain::TabId;
|
use ely_domain::TabId;
|
||||||
use ely_servo_host::{
|
use ely_servo_host::{
|
||||||
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest, ScrollRequest,
|
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest, PermissionRequest,
|
||||||
ServoHost, ServoHostError, ServoSurfaceSize, SoftwareServoHost, TouchTapRequest,
|
ScrollRequest, ServoHost, ServoHostError, ServoSurfaceSize, SoftwareServoHost, TouchTapRequest,
|
||||||
WebViewSnapshot, WebViewState,
|
WebViewSnapshot, WebViewState,
|
||||||
};
|
};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -58,6 +58,7 @@ fn run_snapshot(args: SnapshotArgs) -> Result<(), SidecarError> {
|
|||||||
)?;
|
)?;
|
||||||
let tab_id = TabId::new();
|
let tab_id = TabId::new();
|
||||||
let webview_id = host.create_webview(tab_id.clone(), args.profile_id.clone())?;
|
let webview_id = host.create_webview(tab_id.clone(), args.profile_id.clone())?;
|
||||||
|
apply_site_permissions(&mut host, &webview_id, &args)?;
|
||||||
|
|
||||||
host.navigate(NavigationRequest {
|
host.navigate(NavigationRequest {
|
||||||
webview_id: webview_id.clone(),
|
webview_id: webview_id.clone(),
|
||||||
@@ -100,6 +101,26 @@ fn run_snapshot(args: SnapshotArgs) -> Result<(), SidecarError> {
|
|||||||
std::process::exit(0);
|
std::process::exit(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn apply_site_permissions(
|
||||||
|
host: &mut SoftwareServoHost,
|
||||||
|
webview_id: &ely_domain::WebViewId,
|
||||||
|
args: &SnapshotArgs,
|
||||||
|
) -> Result<(), SidecarError> {
|
||||||
|
for permission in &args.site_permissions {
|
||||||
|
host.set_permission(
|
||||||
|
PermissionRequest {
|
||||||
|
webview_id: webview_id.clone(),
|
||||||
|
profile_id: args.profile_id.clone(),
|
||||||
|
origin: permission.origin.clone(),
|
||||||
|
feature: permission.feature,
|
||||||
|
},
|
||||||
|
permission.decision.into(),
|
||||||
|
)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn apply_scroll_if_requested(
|
fn apply_scroll_if_requested(
|
||||||
host: &mut SoftwareServoHost,
|
host: &mut SoftwareServoHost,
|
||||||
webview_id: &ely_domain::WebViewId,
|
webview_id: &ely_domain::WebViewId,
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::{env, num::ParseIntError, path::PathBuf};
|
use std::{env, num::ParseIntError, path::PathBuf};
|
||||||
|
|
||||||
use ely_domain::{ProfileId, UrlText};
|
use ely_domain::{ProfileId, SiteOrigin, SitePermissionDecision, SitePermissionFeature, UrlText};
|
||||||
|
use serde::Deserialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
pub(super) enum SidecarCommand {
|
pub(super) enum SidecarCommand {
|
||||||
@@ -20,6 +21,7 @@ pub(super) struct SnapshotArgs {
|
|||||||
pub(super) drag_points: Option<DragPoints>,
|
pub(super) drag_points: Option<DragPoints>,
|
||||||
pub(super) touch_point: Option<ClickPoint>,
|
pub(super) touch_point: Option<ClickPoint>,
|
||||||
pub(super) typed_text: Option<String>,
|
pub(super) typed_text: Option<String>,
|
||||||
|
pub(super) site_permissions: Vec<SidecarSitePermission>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy)]
|
#[derive(Clone, Copy)]
|
||||||
@@ -34,6 +36,12 @@ pub(super) struct DragPoints {
|
|||||||
pub(super) to: ClickPoint,
|
pub(super) to: ClickPoint,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) struct SidecarSitePermission {
|
||||||
|
pub(super) origin: SiteOrigin,
|
||||||
|
pub(super) feature: SitePermissionFeature,
|
||||||
|
pub(super) decision: SitePermissionDecision,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, Error)]
|
#[derive(Debug, Error)]
|
||||||
pub(super) enum SidecarArgsError {
|
pub(super) enum SidecarArgsError {
|
||||||
#[error("missing sidecar command")]
|
#[error("missing sidecar command")]
|
||||||
@@ -74,6 +82,13 @@ pub(super) enum SidecarArgsError {
|
|||||||
#[error("{name} path is empty")]
|
#[error("{name} path is empty")]
|
||||||
EmptyPath { name: &'static str },
|
EmptyPath { name: &'static str },
|
||||||
|
|
||||||
|
#[error("invalid --site-permission JSON: {value}")]
|
||||||
|
InvalidSitePermissionJson {
|
||||||
|
value: String,
|
||||||
|
#[source]
|
||||||
|
source: serde_json::Error,
|
||||||
|
},
|
||||||
|
|
||||||
#[error(transparent)]
|
#[error(transparent)]
|
||||||
Domain(#[from] ely_domain::DomainError),
|
Domain(#[from] ely_domain::DomainError),
|
||||||
}
|
}
|
||||||
@@ -116,6 +131,7 @@ fn parse_snapshot_args(
|
|||||||
let mut touch_x = None;
|
let mut touch_x = None;
|
||||||
let mut touch_y = None;
|
let mut touch_y = None;
|
||||||
let mut typed_text = None;
|
let mut typed_text = None;
|
||||||
|
let mut site_permissions = Vec::new();
|
||||||
|
|
||||||
while let Some(name) = args.next() {
|
while let Some(name) = args.next() {
|
||||||
match name.as_str() {
|
match name.as_str() {
|
||||||
@@ -195,6 +211,8 @@ fn parse_snapshot_args(
|
|||||||
)?)
|
)?)
|
||||||
}
|
}
|
||||||
"--type-text" => typed_text = Some(next_argument(&mut args, "--type-text")?),
|
"--type-text" => typed_text = Some(next_argument(&mut args, "--type-text")?),
|
||||||
|
"--site-permission" => site_permissions
|
||||||
|
.push(parse_site_permission(next_argument(&mut args, "--site-permission")?)?),
|
||||||
_ => return Err(SidecarArgsError::UnknownArgument { value: name }),
|
_ => return Err(SidecarArgsError::UnknownArgument { value: name }),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -234,6 +252,25 @@ fn parse_snapshot_args(
|
|||||||
drag_points,
|
drag_points,
|
||||||
touch_point,
|
touch_point,
|
||||||
typed_text,
|
typed_text,
|
||||||
|
site_permissions,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Deserialize)]
|
||||||
|
struct SitePermissionArg {
|
||||||
|
origin: String,
|
||||||
|
feature: String,
|
||||||
|
decision: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_site_permission(value: String) -> Result<SidecarSitePermission, SidecarArgsError> {
|
||||||
|
let parsed: SitePermissionArg = serde_json::from_str(&value)
|
||||||
|
.map_err(|source| SidecarArgsError::InvalidSitePermissionJson { value, source })?;
|
||||||
|
|
||||||
|
Ok(SidecarSitePermission {
|
||||||
|
origin: SiteOrigin::parse(parsed.origin)?,
|
||||||
|
feature: SitePermissionFeature::parse(parsed.feature.as_str())?,
|
||||||
|
decision: SitePermissionDecision::parse(parsed.decision.as_str())?,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -278,7 +315,7 @@ mod tests {
|
|||||||
use std::{env, path::PathBuf};
|
use std::{env, path::PathBuf};
|
||||||
|
|
||||||
use super::{SidecarArgsError, SidecarCommand, parse_command};
|
use super::{SidecarArgsError, SidecarCommand, parse_command};
|
||||||
use ely_domain::{DomainError, ProfileId};
|
use ely_domain::{DomainError, ProfileId, SitePermissionDecision, SitePermissionFeature};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parses_snapshot_profile_identity() -> Result<(), SidecarArgsError> {
|
fn parses_snapshot_profile_identity() -> Result<(), SidecarArgsError> {
|
||||||
@@ -321,11 +358,35 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parses_snapshot_site_permissions() -> Result<(), SidecarArgsError> {
|
||||||
|
let profile_id = ProfileId::new();
|
||||||
|
let profile_data_dir = env::temp_dir().join(profile_id.as_str());
|
||||||
|
let mut command = snapshot_command_args(&profile_id, profile_data_dir);
|
||||||
|
command.push("--site-permission".to_string());
|
||||||
|
command.push(
|
||||||
|
r#"{"origin":"https://example.com","feature":"camera","decision":"allow-once"}"#
|
||||||
|
.to_string(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let SidecarCommand::Snapshot(args) = parse_command(command)?;
|
||||||
|
assert_eq!(args.site_permissions.len(), 1);
|
||||||
|
let permission = &args.site_permissions[0];
|
||||||
|
assert_eq!(permission.origin.as_str(), "https://example.com");
|
||||||
|
assert_eq!(permission.feature, SitePermissionFeature::Camera);
|
||||||
|
assert_eq!(permission.decision, SitePermissionDecision::AllowOnce);
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
fn parse_snapshot_command(
|
fn parse_snapshot_command(
|
||||||
profile_id: &ProfileId,
|
profile_id: &ProfileId,
|
||||||
profile_data_dir: PathBuf,
|
profile_data_dir: PathBuf,
|
||||||
) -> Result<SidecarCommand, SidecarArgsError> {
|
) -> Result<SidecarCommand, SidecarArgsError> {
|
||||||
parse_command([
|
parse_command(snapshot_command_args(profile_id, profile_data_dir))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn snapshot_command_args(profile_id: &ProfileId, profile_data_dir: PathBuf) -> Vec<String> {
|
||||||
|
[
|
||||||
"ely_servo_sidecar".to_string(),
|
"ely_servo_sidecar".to_string(),
|
||||||
"snapshot".to_string(),
|
"snapshot".to_string(),
|
||||||
"--url".to_string(),
|
"--url".to_string(),
|
||||||
@@ -340,6 +401,8 @@ mod tests {
|
|||||||
"64".to_string(),
|
"64".to_string(),
|
||||||
"--height".to_string(),
|
"--height".to_string(),
|
||||||
"64".to_string(),
|
"64".to_string(),
|
||||||
])
|
]
|
||||||
|
.into_iter()
|
||||||
|
.collect()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,6 @@
|
|||||||
use ely_domain::{ProfileId, SiteOrigin, SitePermissionFeature, TabId, UrlText, WebViewId};
|
use ely_domain::{
|
||||||
|
ProfileId, SiteOrigin, SitePermissionDecision, SitePermissionFeature, TabId, UrlText, WebViewId,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::ServoHostError;
|
use crate::ServoHostError;
|
||||||
|
|
||||||
@@ -276,6 +278,16 @@ pub enum PermissionDecision {
|
|||||||
DenyAlways,
|
DenyAlways,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SitePermissionDecision> for PermissionDecision {
|
||||||
|
fn from(decision: SitePermissionDecision) -> Self {
|
||||||
|
match decision {
|
||||||
|
SitePermissionDecision::AllowOnce => Self::AllowOnce,
|
||||||
|
SitePermissionDecision::AllowAlways => Self::AllowAlways,
|
||||||
|
SitePermissionDecision::DenyAlways => Self::DenyAlways,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub trait ServoHost {
|
pub trait ServoHost {
|
||||||
fn create_webview(
|
fn create_webview(
|
||||||
&mut self,
|
&mut self,
|
||||||
|
|||||||
Reference in New Issue
Block a user