Pass site permission grants to sidecar snapshots

This commit is contained in:
2026-05-09 00:01:14 -04:00
parent 4ed1666ece
commit 79d9646ab8
10 changed files with 303 additions and 12 deletions
@@ -6,8 +6,8 @@ use std::{
use ely_domain::TabId;
use ely_servo_host::{
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest, ScrollRequest,
ServoHost, ServoHostError, ServoSurfaceSize, SoftwareServoHost, TouchTapRequest,
KeyboardTextRequest, MouseClickRequest, MouseDragRequest, NavigationRequest, PermissionRequest,
ScrollRequest, ServoHost, ServoHostError, ServoSurfaceSize, SoftwareServoHost, TouchTapRequest,
WebViewSnapshot, WebViewState,
};
use thiserror::Error;
@@ -58,6 +58,7 @@ fn run_snapshot(args: SnapshotArgs) -> Result<(), SidecarError> {
)?;
let tab_id = TabId::new();
let webview_id = host.create_webview(tab_id.clone(), args.profile_id.clone())?;
apply_site_permissions(&mut host, &webview_id, &args)?;
host.navigate(NavigationRequest {
webview_id: webview_id.clone(),
@@ -100,6 +101,26 @@ fn run_snapshot(args: SnapshotArgs) -> Result<(), SidecarError> {
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(
host: &mut SoftwareServoHost,
webview_id: &ely_domain::WebViewId,
@@ -1,6 +1,7 @@
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;
pub(super) enum SidecarCommand {
@@ -20,6 +21,7 @@ pub(super) struct SnapshotArgs {
pub(super) drag_points: Option<DragPoints>,
pub(super) touch_point: Option<ClickPoint>,
pub(super) typed_text: Option<String>,
pub(super) site_permissions: Vec<SidecarSitePermission>,
}
#[derive(Clone, Copy)]
@@ -34,6 +36,12 @@ pub(super) struct DragPoints {
pub(super) to: ClickPoint,
}
pub(super) struct SidecarSitePermission {
pub(super) origin: SiteOrigin,
pub(super) feature: SitePermissionFeature,
pub(super) decision: SitePermissionDecision,
}
#[derive(Debug, Error)]
pub(super) enum SidecarArgsError {
#[error("missing sidecar command")]
@@ -74,6 +82,13 @@ pub(super) enum SidecarArgsError {
#[error("{name} path is empty")]
EmptyPath { name: &'static str },
#[error("invalid --site-permission JSON: {value}")]
InvalidSitePermissionJson {
value: String,
#[source]
source: serde_json::Error,
},
#[error(transparent)]
Domain(#[from] ely_domain::DomainError),
}
@@ -116,6 +131,7 @@ fn parse_snapshot_args(
let mut touch_x = None;
let mut touch_y = None;
let mut typed_text = None;
let mut site_permissions = Vec::new();
while let Some(name) = args.next() {
match name.as_str() {
@@ -195,6 +211,8 @@ fn parse_snapshot_args(
)?)
}
"--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 }),
}
}
@@ -234,6 +252,25 @@ fn parse_snapshot_args(
drag_points,
touch_point,
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 super::{SidecarArgsError, SidecarCommand, parse_command};
use ely_domain::{DomainError, ProfileId};
use ely_domain::{DomainError, ProfileId, SitePermissionDecision, SitePermissionFeature};
#[test]
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(
profile_id: &ProfileId,
profile_data_dir: PathBuf,
) -> 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(),
"snapshot".to_string(),
"--url".to_string(),
@@ -340,6 +401,8 @@ mod tests {
"64".to_string(),
"--height".to_string(),
"64".to_string(),
])
]
.into_iter()
.collect()
}
}