fix(permissions): make profile snapshots authoritative
This commit is contained in:
@@ -74,6 +74,8 @@ pub(super) fn run(args: LiveArgs) -> Result<(), LiveSidecarError> {
|
||||
request,
|
||||
)
|
||||
});
|
||||
let outcome = outcome
|
||||
.map(|outcome| outcome.with_permission_consumptions(host.take_consumed_permissions()));
|
||||
write_outcome(&mut stdout, outcome)?;
|
||||
if should_shutdown {
|
||||
break;
|
||||
@@ -126,6 +128,7 @@ fn handle_request(
|
||||
hover_x,
|
||||
hover_y,
|
||||
typed_text,
|
||||
site_permission_generation,
|
||||
site_permissions,
|
||||
ready_surface_ids,
|
||||
pending_surface_ids,
|
||||
@@ -138,7 +141,13 @@ fn handle_request(
|
||||
let session =
|
||||
ensure_session(host, sessions, tab_id.clone(), &tab, &profile, width, height)?;
|
||||
apply_layout(host, session, width, height, page_zoom_percent, device_pixel_ratio)?;
|
||||
apply_permissions(host, session, &profile, site_permissions)?;
|
||||
apply_permissions(
|
||||
host,
|
||||
session,
|
||||
&profile,
|
||||
site_permission_generation,
|
||||
site_permissions,
|
||||
)?;
|
||||
if session.requested_url != url.as_str() {
|
||||
let servo_current_url =
|
||||
host.snapshot(&session.webview_id)?.url().map(str::to_string);
|
||||
|
||||
@@ -13,7 +13,9 @@ pub(super) fn write_outcome(
|
||||
if let Some(frame) = outcome.frame.as_ref()
|
||||
&& let Err(error) = validate_frame(frame.width(), frame.height(), frame.rgba_bytes().len())
|
||||
{
|
||||
let consumptions = std::mem::take(&mut outcome.response.permission_consumptions);
|
||||
outcome = LiveOutcome::error(error.to_string());
|
||||
outcome.response.permission_consumptions = consumptions;
|
||||
}
|
||||
|
||||
serde_json::to_writer(&mut *stdout, &outcome.response)?;
|
||||
@@ -39,11 +41,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn mismatched_frame_becomes_header_only_error() -> Result<(), LiveSidecarError> {
|
||||
let profile_id = ely_domain::ProfileId::new();
|
||||
let frame = ely_servo_host::RenderedFrame::from_rgba_bytes(2, 2, vec![0; 4]);
|
||||
let snapshot = ely_servo_host::WebViewSnapshot::new(
|
||||
ely_domain::WebViewId::new(),
|
||||
ely_domain::TabId::new(),
|
||||
ely_domain::ProfileId::new(),
|
||||
profile_id.clone(),
|
||||
ely_servo_host::WebViewState::Complete,
|
||||
None,
|
||||
None,
|
||||
@@ -52,13 +55,23 @@ mod tests {
|
||||
let report =
|
||||
super::super::live_protocol::LiveFrameReport::new(&snapshot, &frame, 1.0, true);
|
||||
let mut output = Vec::new();
|
||||
let outcome = LiveOutcome::frame(report, frame).with_permission_consumptions(vec![
|
||||
ely_servo_host::ConsumedPermission {
|
||||
profile_id: profile_id.clone(),
|
||||
origin: ely_domain::SiteOrigin::parse("https://example.com")?,
|
||||
feature: ely_domain::SitePermissionFeature::Camera,
|
||||
grant_revision: 7,
|
||||
},
|
||||
]);
|
||||
|
||||
write_outcome(&mut output, Ok(LiveOutcome::frame(report, frame)))?;
|
||||
write_outcome(&mut output, Ok(outcome))?;
|
||||
|
||||
assert!(output.ends_with(b"\n"));
|
||||
let response: serde_json::Value = serde_json::from_slice(&output)?;
|
||||
assert!(response["error"].as_str().is_some());
|
||||
assert!(response["frame"].is_null());
|
||||
assert_eq!(response["permission_consumptions"][0]["profile_id"], profile_id.as_str());
|
||||
assert_eq!(response["permission_consumptions"][0]["grant_revision"], 7);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
use std::io;
|
||||
|
||||
use ely_servo_host::{
|
||||
IOSurfaceHandle, RenderedFrame, ServoHostError, WebViewSnapshot, WebViewState,
|
||||
ConsumedPermission, IOSurfaceHandle, RenderedFrame, ServoHostError, WebViewSnapshot,
|
||||
WebViewState,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use thiserror::Error;
|
||||
@@ -9,7 +10,7 @@ use thiserror::Error;
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
use super::iosurface_mach::IOSurfaceMachError;
|
||||
|
||||
pub(super) const LIVE_PROTOCOL_VERSION: u32 = 2;
|
||||
pub(super) const LIVE_PROTOCOL_VERSION: u32 = 3;
|
||||
pub(super) const MAX_FRAME_DIMENSION: u32 = 16_384;
|
||||
pub(super) const MAX_FRAME_BYTE_COUNT: usize = 256 * 1024 * 1024;
|
||||
|
||||
@@ -47,6 +48,7 @@ pub(super) enum LiveRequest {
|
||||
hover_y: Option<u32>,
|
||||
#[serde(default)]
|
||||
typed_text: Option<String>,
|
||||
site_permission_generation: u64,
|
||||
#[serde(default)]
|
||||
site_permissions: Vec<LiveSitePermission>,
|
||||
#[serde(default)]
|
||||
@@ -79,7 +81,8 @@ const fn default_device_pixel_ratio() -> f32 {
|
||||
pub(super) struct LiveSitePermission {
|
||||
pub(super) origin: String,
|
||||
pub(super) feature: String,
|
||||
pub(super) decision: String,
|
||||
pub(super) state: String,
|
||||
pub(super) revision: u64,
|
||||
}
|
||||
|
||||
pub(super) struct LiveOutcome {
|
||||
@@ -100,6 +103,15 @@ impl LiveOutcome {
|
||||
Self { response: LiveResponse::frame(report), frame: Some(frame) }
|
||||
}
|
||||
|
||||
pub(super) fn with_permission_consumptions(
|
||||
mut self,
|
||||
consumptions: Vec<ConsumedPermission>,
|
||||
) -> Self {
|
||||
self.response.permission_consumptions =
|
||||
consumptions.into_iter().map(LivePermissionConsumption::from).collect();
|
||||
self
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
pub(super) fn surface(report: LiveFrameReport) -> Self {
|
||||
Self { response: LiveResponse::frame(report), frame: None }
|
||||
@@ -115,6 +127,27 @@ pub(super) struct LiveResponse {
|
||||
pub(super) surface_handle: Option<IOSurfaceHandle>,
|
||||
#[serde(skip_serializing_if = "Option::is_none")]
|
||||
pub(super) current_surface_id: Option<u64>,
|
||||
#[serde(skip_serializing_if = "Vec::is_empty")]
|
||||
pub(super) permission_consumptions: Vec<LivePermissionConsumption>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize)]
|
||||
pub(super) struct LivePermissionConsumption {
|
||||
pub(super) profile_id: String,
|
||||
pub(super) origin: String,
|
||||
pub(super) feature: String,
|
||||
pub(super) grant_revision: u64,
|
||||
}
|
||||
|
||||
impl From<ConsumedPermission> for LivePermissionConsumption {
|
||||
fn from(consumed: ConsumedPermission) -> Self {
|
||||
Self {
|
||||
profile_id: consumed.profile_id.as_str().to_string(),
|
||||
origin: consumed.origin.as_str().to_string(),
|
||||
feature: consumed.feature.as_str().to_string(),
|
||||
grant_revision: consumed.grant_revision,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl LiveResponse {
|
||||
@@ -125,6 +158,7 @@ impl LiveResponse {
|
||||
frame: None,
|
||||
surface_handle: None,
|
||||
current_surface_id: None,
|
||||
permission_consumptions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -135,6 +169,7 @@ impl LiveResponse {
|
||||
frame: None,
|
||||
surface_handle: None,
|
||||
current_surface_id: None,
|
||||
permission_consumptions: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,6 +180,7 @@ impl LiveResponse {
|
||||
frame: Some(frame),
|
||||
surface_handle: None,
|
||||
current_surface_id: None,
|
||||
permission_consumptions: Vec::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,7 +368,7 @@ mod tests {
|
||||
#[test]
|
||||
fn ensure_defaults_optional_input_fields() -> Result<(), serde_json::Error> {
|
||||
let request = serde_json::from_str::<LiveRequest>(
|
||||
r#"{"type":"ensure","tab_id":"tab","profile_id":"profile","url":"https://example.com","width":800,"height":600}"#,
|
||||
r#"{"type":"ensure","tab_id":"tab","profile_id":"profile","url":"https://example.com","width":800,"height":600,"site_permission_generation":0}"#,
|
||||
)?;
|
||||
|
||||
assert!(matches!(
|
||||
@@ -353,12 +389,21 @@ mod tests {
|
||||
#[test]
|
||||
fn handshake_deserializes_protocol_version() -> Result<(), serde_json::Error> {
|
||||
let request =
|
||||
serde_json::from_str::<LiveRequest>(r#"{"type":"handshake","protocol_version":2}"#)?;
|
||||
serde_json::from_str::<LiveRequest>(r#"{"type":"handshake","protocol_version":3}"#)?;
|
||||
|
||||
assert!(matches!(request, LiveRequest::Handshake { protocol_version: 2 }));
|
||||
assert!(matches!(request, LiveRequest::Handshake { protocol_version: 3 }));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn site_permission_requires_revision() {
|
||||
let request = serde_json::from_str::<LiveRequest>(
|
||||
r#"{"type":"ensure","tab_id":"tab","profile_id":"profile","url":"https://example.com","width":800,"height":600,"site_permission_generation":0,"site_permissions":[{"origin":"https://example.com","feature":"camera","state":"allow-once"}]}"#,
|
||||
);
|
||||
|
||||
assert!(request.is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn frame_layout_enforces_dimension_and_byte_limits() {
|
||||
assert!(matches!(
|
||||
|
||||
@@ -3,7 +3,8 @@ use std::collections::{HashMap, hash_map::Entry};
|
||||
use ely_domain::{ProfileId, TabId, validate_zoom_percent};
|
||||
use ely_servo_host::{
|
||||
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest, PageZoomRequest,
|
||||
PermissionDecision, PermissionRequest, RenderedFrame, ResizeRequest, ScrollRequest, ServoHost,
|
||||
PermissionDecision, PermissionSnapshotEntry, PermissionSnapshotRequest,
|
||||
PermissionSnapshotState, RenderedFrame, ResizeRequest, ScrollRequest, ServoHost,
|
||||
ServoSurfaceSize, SoftwareServoHost,
|
||||
};
|
||||
|
||||
@@ -132,22 +133,41 @@ pub(super) fn apply_permissions(
|
||||
host: &mut SoftwareServoHost,
|
||||
session: &LiveSession,
|
||||
profile_id: &ProfileId,
|
||||
generation: u64,
|
||||
permissions: Vec<LiveSitePermission>,
|
||||
) -> Result<(), LiveSidecarError> {
|
||||
for permission in permissions {
|
||||
host.set_permission(
|
||||
PermissionRequest {
|
||||
webview_id: session.webview_id.clone(),
|
||||
profile_id: profile_id.clone(),
|
||||
let entries = permissions
|
||||
.into_iter()
|
||||
.map(|permission| {
|
||||
let state = match permission.state.as_str() {
|
||||
"allow-once" => PermissionSnapshotState::Decision(PermissionDecision::AllowOnce),
|
||||
"allow-always" => {
|
||||
PermissionSnapshotState::Decision(PermissionDecision::AllowAlways)
|
||||
}
|
||||
"deny-always" => PermissionSnapshotState::Decision(PermissionDecision::DenyAlways),
|
||||
"transferred-allow-once" => PermissionSnapshotState::TransferredAllowOnce,
|
||||
value => {
|
||||
return Err(ely_domain::DomainError::InvalidSitePermissionDecision {
|
||||
value: value.to_string(),
|
||||
}
|
||||
.into());
|
||||
}
|
||||
};
|
||||
Ok(PermissionSnapshotEntry {
|
||||
origin: ely_domain::SiteOrigin::parse(permission.origin)?,
|
||||
feature: ely_domain::SitePermissionFeature::parse(&permission.feature)?,
|
||||
},
|
||||
PermissionDecision::from(ely_domain::SitePermissionDecision::parse(
|
||||
&permission.decision,
|
||||
)?),
|
||||
)?;
|
||||
}
|
||||
Ok(())
|
||||
state,
|
||||
revision: permission.revision,
|
||||
})
|
||||
})
|
||||
.collect::<Result<Vec<_>, LiveSidecarError>>()?;
|
||||
host.replace_permissions(PermissionSnapshotRequest {
|
||||
webview_id: session.webview_id.clone(),
|
||||
profile_id: profile_id.clone(),
|
||||
generation,
|
||||
entries,
|
||||
})
|
||||
.map_err(LiveSidecarError::from)
|
||||
}
|
||||
|
||||
pub(super) struct LiveInput {
|
||||
|
||||
Reference in New Issue
Block a user