fix(servo): bound live protocol metadata
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
collections::{HashMap, VecDeque},
|
||||
fs::{self, File, OpenOptions, TryLockError},
|
||||
io::{self, BufRead},
|
||||
path::Path,
|
||||
@@ -17,7 +17,7 @@ use super::{
|
||||
live_output::write_outcome,
|
||||
live_protocol::{
|
||||
LIVE_PROTOCOL_VERSION, LiveFrameReport, LiveOutcome, LiveRequest, LiveSidecarError,
|
||||
validated_frame_byte_count,
|
||||
MAX_LOADED_URL_BYTES, MAX_PERMISSION_CONSUMPTIONS_PER_RESPONSE, validated_frame_byte_count,
|
||||
},
|
||||
live_session::{
|
||||
LiveInput, LiveSession, apply_input, apply_layout, apply_permissions, bind_profile,
|
||||
@@ -55,6 +55,7 @@ pub(super) fn run(args: LiveArgs) -> Result<(), LiveSidecarError> {
|
||||
let mut sessions = HashMap::new();
|
||||
let mut active_profile = None;
|
||||
let mut handshake_complete = false;
|
||||
let mut pending_permission_consumptions = VecDeque::new();
|
||||
let stdin = io::stdin();
|
||||
let mut stdout = io::stdout().lock();
|
||||
|
||||
@@ -78,9 +79,13 @@ 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)?;
|
||||
pending_permission_consumptions.extend(host.take_consumed_permissions());
|
||||
let outcome = match outcome {
|
||||
Ok(outcome) => outcome,
|
||||
Err(error) => LiveOutcome::error(error.to_string()),
|
||||
}
|
||||
.with_permission_consumptions(take_permission_batch(&mut pending_permission_consumptions));
|
||||
write_outcome(&mut stdout, Ok(outcome))?;
|
||||
if should_shutdown {
|
||||
break;
|
||||
}
|
||||
@@ -88,6 +93,11 @@ pub(super) fn run(args: LiveArgs) -> Result<(), LiveSidecarError> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn take_permission_batch<T>(pending: &mut VecDeque<T>) -> Vec<T> {
|
||||
let count = pending.len().min(MAX_PERMISSION_CONSUMPTIONS_PER_RESPONSE);
|
||||
pending.drain(..count).collect()
|
||||
}
|
||||
|
||||
fn acquire_profile_data_lease(profile_data_dir: &Path) -> Result<File, LiveSidecarError> {
|
||||
let lease = OpenOptions::new()
|
||||
.create(true)
|
||||
@@ -110,7 +120,7 @@ fn handle_request(
|
||||
active_profile: &mut Option<ProfileId>,
|
||||
handshake_complete: &mut bool,
|
||||
rendering_context_kind: RenderingContextKind,
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))] hardware_transport: Option<
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))] mut hardware_transport: Option<
|
||||
&mut HardwareSurfaceTransport,
|
||||
>,
|
||||
request: LiveRequest,
|
||||
@@ -154,6 +164,9 @@ fn handle_request(
|
||||
pending_surface_ids,
|
||||
} => {
|
||||
validated_frame_byte_count(width, height)?;
|
||||
if url.len() > MAX_LOADED_URL_BYTES {
|
||||
return Err(LiveSidecarError::RequestUrlTooLong { limit: MAX_LOADED_URL_BYTES });
|
||||
}
|
||||
let tab = TabId::parse(tab_id.clone())?;
|
||||
let profile = ProfileId::parse(profile_id)?;
|
||||
bind_profile(active_profile, &profile)?;
|
||||
@@ -205,7 +218,16 @@ fn handle_request(
|
||||
rendering_context_kind,
|
||||
&ready_surface_ids,
|
||||
&pending_surface_ids,
|
||||
)?;
|
||||
);
|
||||
retire_oversized_loaded_url_session(
|
||||
host,
|
||||
sessions,
|
||||
&tab_id,
|
||||
&outcome,
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
hardware_transport.as_deref_mut(),
|
||||
);
|
||||
let outcome = outcome?;
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
let outcome = {
|
||||
let mut outcome = outcome;
|
||||
@@ -236,7 +258,16 @@ fn handle_request(
|
||||
rendering_context_kind,
|
||||
&ready_surface_ids,
|
||||
&pending_surface_ids,
|
||||
)?;
|
||||
);
|
||||
retire_oversized_loaded_url_session(
|
||||
host,
|
||||
sessions,
|
||||
&tab_id,
|
||||
&outcome,
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
hardware_transport.as_deref_mut(),
|
||||
);
|
||||
let outcome = outcome?;
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
let outcome = {
|
||||
let mut outcome = outcome;
|
||||
@@ -270,6 +301,27 @@ fn handle_request(
|
||||
}
|
||||
}
|
||||
|
||||
fn retire_oversized_loaded_url_session(
|
||||
host: &mut SoftwareServoHost,
|
||||
sessions: &mut HashMap<String, LiveSession>,
|
||||
tab_id: &str,
|
||||
outcome: &Result<LiveOutcome, LiveSidecarError>,
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))] hardware_transport: Option<
|
||||
&mut HardwareSurfaceTransport,
|
||||
>,
|
||||
) {
|
||||
if !matches!(outcome, Err(LiveSidecarError::LoadedUrlTooLong { .. })) {
|
||||
return;
|
||||
}
|
||||
if let Some(session) = sessions.remove(tab_id) {
|
||||
host.close_webview(&session.webview_id);
|
||||
}
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
if let Some(transport) = hardware_transport {
|
||||
transport.close_tab(tab_id);
|
||||
}
|
||||
}
|
||||
|
||||
fn poll_frame(
|
||||
host: &mut SoftwareServoHost,
|
||||
session: &mut LiveSession,
|
||||
@@ -303,7 +355,7 @@ fn poll_software_frame(
|
||||
{
|
||||
let snapshot = host.snapshot_and_mark_metadata_observed(&session.webview_id)?;
|
||||
let report =
|
||||
LiveFrameReport::new(&snapshot, &frame, session.device_pixel_ratio(), false);
|
||||
LiveFrameReport::new(&snapshot, &frame, session.device_pixel_ratio(), false)?;
|
||||
return Ok(LiveOutcome::frame(report, frame));
|
||||
}
|
||||
return Ok(LiveOutcome::empty());
|
||||
@@ -313,7 +365,7 @@ fn poll_software_frame(
|
||||
let snapshot = host.snapshot_and_mark_metadata_observed(&session.webview_id)?;
|
||||
let frame = host.last_rendered_frame()?;
|
||||
session.last_frame = Some(frame.clone());
|
||||
let report = LiveFrameReport::new(&snapshot, &frame, session.device_pixel_ratio(), true);
|
||||
let report = LiveFrameReport::new(&snapshot, &frame, session.device_pixel_ratio(), true)?;
|
||||
Ok(LiveOutcome::frame(report, frame))
|
||||
}
|
||||
|
||||
@@ -358,7 +410,7 @@ fn poll_hardware_frame(
|
||||
identity.height,
|
||||
session.device_pixel_ratio(),
|
||||
false,
|
||||
);
|
||||
)?;
|
||||
return Ok(LiveOutcome::surface(report));
|
||||
}
|
||||
HardwarePollAction::Empty => return Ok(LiveOutcome::empty()),
|
||||
@@ -380,7 +432,7 @@ fn poll_hardware_frame(
|
||||
identity.height,
|
||||
session.device_pixel_ratio(),
|
||||
true,
|
||||
);
|
||||
)?;
|
||||
Ok(LiveOutcome::surface(report))
|
||||
}
|
||||
|
||||
@@ -414,35 +466,6 @@ fn hardware_poll_action(
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "hardware-render", target_os = "macos"))]
|
||||
mod tests {
|
||||
use super::{HardwarePollAction, hardware_poll_action};
|
||||
|
||||
#[test]
|
||||
fn newly_ready_surface_replays_before_pending_frame() {
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, false, true, false, true, true),
|
||||
HardwarePollAction::ReplaySurface
|
||||
);
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, false, false, false, true, true),
|
||||
HardwarePollAction::PaintFrame
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn awaiting_ready_surface_backpressures_pending_frame() {
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, true, false, false, true, false),
|
||||
HardwarePollAction::Empty
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_surface_replays_before_first_ready() {
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, false, false, true, true, false),
|
||||
HardwarePollAction::ReplaySurface
|
||||
);
|
||||
}
|
||||
}
|
||||
#[cfg(test)]
|
||||
#[path = "live_tests.rs"]
|
||||
mod tests;
|
||||
|
||||
@@ -1,6 +1,12 @@
|
||||
use std::io::Write;
|
||||
|
||||
use super::live_protocol::{LiveOutcome, LiveSidecarError, validated_frame_byte_count};
|
||||
use super::live_protocol::{
|
||||
LiveOutcome, LiveResponse, LiveSidecarError, MAX_ERROR_BYTES, MAX_RESPONSE_HEADER_BYTES,
|
||||
MAX_TITLE_BYTES, validated_frame_byte_count,
|
||||
};
|
||||
|
||||
const TITLE_TRUNCATION_SUFFIX: &str = "…";
|
||||
const ERROR_TRUNCATION_SUFFIX: &str = "… [truncated]";
|
||||
|
||||
pub(super) fn write_outcome(
|
||||
stdout: &mut impl Write,
|
||||
@@ -17,8 +23,17 @@ pub(super) fn write_outcome(
|
||||
outcome = LiveOutcome::error(error.to_string());
|
||||
outcome.response.permission_consumptions = consumptions;
|
||||
}
|
||||
bound_response_text(&mut outcome.response);
|
||||
|
||||
serde_json::to_writer(&mut *stdout, &outcome.response)?;
|
||||
let header = serde_json::to_vec(&outcome.response)?;
|
||||
let wire_bytes = header.len().saturating_add(1);
|
||||
if wire_bytes > MAX_RESPONSE_HEADER_BYTES {
|
||||
return Err(LiveSidecarError::ResponseHeaderTooLarge {
|
||||
bytes: wire_bytes,
|
||||
limit: MAX_RESPONSE_HEADER_BYTES,
|
||||
});
|
||||
}
|
||||
stdout.write_all(&header)?;
|
||||
stdout.write_all(b"\n")?;
|
||||
if let Some(frame) = outcome.frame.as_ref() {
|
||||
stdout.write_all(frame.rgba_bytes())?;
|
||||
@@ -27,6 +42,33 @@ pub(super) fn write_outcome(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn bound_response_text(response: &mut LiveResponse) {
|
||||
response.error = response
|
||||
.error
|
||||
.take()
|
||||
.map(|error| truncate_utf8(error, MAX_ERROR_BYTES, ERROR_TRUNCATION_SUFFIX));
|
||||
if let Some(frame) = response.frame.as_mut() {
|
||||
frame.title = frame
|
||||
.title
|
||||
.take()
|
||||
.map(|title| truncate_utf8(title, MAX_TITLE_BYTES, TITLE_TRUNCATION_SUFFIX));
|
||||
}
|
||||
}
|
||||
|
||||
fn truncate_utf8(mut value: String, limit: usize, suffix: &str) -> String {
|
||||
if value.len() <= limit {
|
||||
return value;
|
||||
}
|
||||
|
||||
let mut end = limit - suffix.len();
|
||||
while !value.is_char_boundary(end) {
|
||||
end -= 1;
|
||||
}
|
||||
value.truncate(end);
|
||||
value.push_str(suffix);
|
||||
value
|
||||
}
|
||||
|
||||
fn validate_frame(width: u32, height: u32, actual: usize) -> Result<(), LiveSidecarError> {
|
||||
let expected = validated_frame_byte_count(width, height)?;
|
||||
if expected != actual {
|
||||
@@ -37,8 +79,107 @@ fn validate_frame(width: u32, height: u32, actual: usize) -> Result<(), LiveSide
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::super::live_protocol::{
|
||||
LiveFrameReport, LivePermissionConsumption, MAX_LOADED_URL_BYTES,
|
||||
MAX_PERMISSION_CONSUMPTIONS_PER_RESPONSE,
|
||||
};
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn overlong_utf8_title_stays_within_the_app_header_limit() -> Result<(), LiveSidecarError> {
|
||||
let frame = one_pixel_frame();
|
||||
let snapshot = snapshot(Some("https://example.com"), Some(&"界\"\n".repeat(40_000)));
|
||||
let report = LiveFrameReport::new(&snapshot, &frame, 1.0, true)?;
|
||||
let mut output = Vec::new();
|
||||
|
||||
write_outcome(&mut output, Ok(LiveOutcome::frame(report, frame)))?;
|
||||
|
||||
assert!(header_bytes(&output) <= MAX_RESPONSE_HEADER_BYTES);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn wire_text_preserves_exact_boundary_and_marks_ascii_overflow() {
|
||||
let exact = "a".repeat(MAX_TITLE_BYTES);
|
||||
assert_eq!(truncate_utf8(exact.clone(), MAX_TITLE_BYTES, TITLE_TRUNCATION_SUFFIX), exact);
|
||||
|
||||
let overflow = truncate_utf8(
|
||||
"a".repeat(MAX_TITLE_BYTES + 1),
|
||||
MAX_TITLE_BYTES,
|
||||
TITLE_TRUNCATION_SUFFIX,
|
||||
);
|
||||
assert_eq!(overflow.len(), MAX_TITLE_BYTES);
|
||||
assert!(overflow.ends_with(TITLE_TRUNCATION_SUFFIX));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn error_text_truncates_on_a_utf8_boundary() {
|
||||
let mut response = LiveOutcome::error("界".repeat(MAX_ERROR_BYTES)).response;
|
||||
|
||||
bound_response_text(&mut response);
|
||||
|
||||
let error = response.error.as_deref().unwrap_or_default();
|
||||
assert!(error.len() <= MAX_ERROR_BYTES);
|
||||
assert!(error.ends_with(ERROR_TRUNCATION_SUFFIX));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_heavy_maximum_frame_and_permission_batch_fit_header() -> Result<(), LiveSidecarError>
|
||||
{
|
||||
let frame = one_pixel_frame();
|
||||
let report = LiveFrameReport {
|
||||
loaded_url: Some("\u{2}".repeat(MAX_LOADED_URL_BYTES)),
|
||||
title: Some("\u{1}".repeat(MAX_TITLE_BYTES)),
|
||||
state: "complete",
|
||||
width: 1,
|
||||
height: 1,
|
||||
device_pixel_ratio: 1.0,
|
||||
css_viewport_width: 1,
|
||||
css_viewport_height: 1,
|
||||
rgba_byte_count: 4,
|
||||
pixels_changed: true,
|
||||
non_white_pixel_count: 0,
|
||||
content_pixel_count: 0,
|
||||
sample_hash: 0,
|
||||
};
|
||||
let mut outcome = LiveOutcome::frame(report, frame);
|
||||
let profile_id = ely_domain::ProfileId::new().as_str().to_string();
|
||||
outcome.response.permission_consumptions = (0..MAX_PERMISSION_CONSUMPTIONS_PER_RESPONSE)
|
||||
.map(|revision| LivePermissionConsumption {
|
||||
profile_id: profile_id.clone(),
|
||||
origin: "\u{3}".repeat(ely_domain::MAX_SITE_ORIGIN_BYTES),
|
||||
feature: "storage-persistence".to_string(),
|
||||
grant_revision: revision as u64,
|
||||
})
|
||||
.collect();
|
||||
let mut output = Vec::new();
|
||||
|
||||
write_outcome(&mut output, Ok(outcome))?;
|
||||
|
||||
assert!(header_bytes(&output) <= MAX_RESPONSE_HEADER_BYTES);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn loaded_url_preserves_exact_boundary_and_rejects_overflow() -> Result<(), LiveSidecarError> {
|
||||
let prefix = "https://example.com/";
|
||||
let exact_url = format!("{prefix}{}", "a".repeat(MAX_LOADED_URL_BYTES - prefix.len()));
|
||||
assert!(url::Url::parse(&exact_url).is_ok());
|
||||
let exact_snapshot = snapshot(Some(&exact_url), None);
|
||||
let exact = LiveFrameReport::new(&exact_snapshot, &one_pixel_frame(), 1.0, true)?;
|
||||
assert_eq!(exact.loaded_url.as_deref(), Some(exact_url.as_str()));
|
||||
|
||||
let overflow_url =
|
||||
format!("{prefix}{}", "a".repeat(MAX_LOADED_URL_BYTES + 1 - prefix.len()));
|
||||
assert!(url::Url::parse(&overflow_url).is_ok());
|
||||
let overflow_snapshot = snapshot(Some(&overflow_url), None);
|
||||
assert!(matches!(
|
||||
LiveFrameReport::new(&overflow_snapshot, &one_pixel_frame(), 1.0, true),
|
||||
Err(LiveSidecarError::LoadedUrlTooLong { limit: MAX_LOADED_URL_BYTES })
|
||||
));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn mismatched_frame_becomes_header_only_error() -> Result<(), LiveSidecarError> {
|
||||
let profile_id = ely_domain::ProfileId::new();
|
||||
@@ -52,8 +193,7 @@ mod tests {
|
||||
None,
|
||||
ely_servo_host::WebViewSnapshotPending::new(false, false),
|
||||
);
|
||||
let report =
|
||||
super::super::live_protocol::LiveFrameReport::new(&snapshot, &frame, 1.0, true);
|
||||
let report = 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 {
|
||||
@@ -74,4 +214,24 @@ mod tests {
|
||||
assert_eq!(response["permission_consumptions"][0]["grant_revision"], 7);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn one_pixel_frame() -> ely_servo_host::RenderedFrame {
|
||||
ely_servo_host::RenderedFrame::from_rgba_bytes(1, 1, vec![255; 4])
|
||||
}
|
||||
|
||||
fn snapshot(url: Option<&str>, title: Option<&str>) -> ely_servo_host::WebViewSnapshot {
|
||||
ely_servo_host::WebViewSnapshot::new(
|
||||
ely_domain::WebViewId::new(),
|
||||
ely_domain::TabId::new(),
|
||||
ely_domain::ProfileId::new(),
|
||||
ely_servo_host::WebViewState::Complete,
|
||||
url.map(str::to_string),
|
||||
title.map(str::to_string),
|
||||
ely_servo_host::WebViewSnapshotPending::new(false, false),
|
||||
)
|
||||
}
|
||||
|
||||
fn header_bytes(output: &[u8]) -> usize {
|
||||
output.iter().position(|byte| *byte == b'\n').map_or(0, |end| end + 1)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,11 @@ use super::iosurface_mach::IOSurfaceMachError;
|
||||
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;
|
||||
pub(super) const MAX_RESPONSE_HEADER_BYTES: usize = 256 * 1024;
|
||||
pub(super) const MAX_TITLE_BYTES: usize = ely_servo_host::MAX_PAGE_TITLE_BYTES;
|
||||
pub(super) const MAX_LOADED_URL_BYTES: usize = 32 * 1024;
|
||||
pub(super) const MAX_ERROR_BYTES: usize = 32 * 1024;
|
||||
pub(super) const MAX_PERMISSION_CONSUMPTIONS_PER_RESPONSE: usize = 8;
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
#[serde(tag = "type", rename_all = "snake_case")]
|
||||
@@ -208,12 +213,12 @@ impl LiveFrameReport {
|
||||
frame: &RenderedFrame,
|
||||
device_pixel_ratio: f32,
|
||||
pixels_changed: bool,
|
||||
) -> Self {
|
||||
) -> Result<Self, LiveSidecarError> {
|
||||
let device_pixel_ratio = normalized_device_pixel_ratio(device_pixel_ratio);
|
||||
let css_viewport_width = css_dimension(frame.width(), device_pixel_ratio);
|
||||
let css_viewport_height = css_dimension(frame.height(), device_pixel_ratio);
|
||||
Self {
|
||||
loaded_url: snapshot.url().map(str::to_string),
|
||||
Ok(Self {
|
||||
loaded_url: loaded_url_for_response(snapshot.url())?,
|
||||
title: snapshot.title().map(str::to_string),
|
||||
state: state_label(snapshot.state()),
|
||||
width: frame.width(),
|
||||
@@ -226,7 +231,7 @@ impl LiveFrameReport {
|
||||
non_white_pixel_count: frame.non_white_pixel_count(),
|
||||
content_pixel_count: frame.content_pixel_count(),
|
||||
sample_hash: frame.sample_hash(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
@@ -236,10 +241,10 @@ impl LiveFrameReport {
|
||||
height: u32,
|
||||
device_pixel_ratio: f32,
|
||||
pixels_changed: bool,
|
||||
) -> Self {
|
||||
) -> Result<Self, LiveSidecarError> {
|
||||
let device_pixel_ratio = normalized_device_pixel_ratio(device_pixel_ratio);
|
||||
Self {
|
||||
loaded_url: snapshot.url().map(str::to_string),
|
||||
Ok(Self {
|
||||
loaded_url: loaded_url_for_response(snapshot.url())?,
|
||||
title: snapshot.title().map(str::to_string),
|
||||
state: state_label(snapshot.state()),
|
||||
width,
|
||||
@@ -252,7 +257,17 @@ impl LiveFrameReport {
|
||||
non_white_pixel_count: 0,
|
||||
content_pixel_count: 0,
|
||||
sample_hash: 0,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn loaded_url_for_response(value: Option<&str>) -> Result<Option<String>, LiveSidecarError> {
|
||||
match value {
|
||||
Some(url) if url.len() > MAX_LOADED_URL_BYTES => {
|
||||
Err(LiveSidecarError::LoadedUrlTooLong { limit: MAX_LOADED_URL_BYTES })
|
||||
}
|
||||
Some(url) => Ok(Some(url.to_string())),
|
||||
None => Ok(None),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,6 +297,15 @@ pub(super) enum LiveSidecarError {
|
||||
#[error("live protocol mismatch: expected {expected}, received {actual}")]
|
||||
ProtocolVersionMismatch { expected: u32, actual: u32 },
|
||||
|
||||
#[error("request URL exceeds the {limit}-byte live protocol limit")]
|
||||
RequestUrlTooLong { limit: usize },
|
||||
|
||||
#[error("loaded URL exceeds the {limit}-byte live protocol limit")]
|
||||
LoadedUrlTooLong { limit: usize },
|
||||
|
||||
#[error("response header requires {bytes} bytes; the live protocol limit is {limit}")]
|
||||
ResponseHeaderTooLarge { bytes: usize, limit: usize },
|
||||
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
#[error("hardware rendering requires --iosurface-mach-service")]
|
||||
IOSurfaceMachServiceRequired,
|
||||
|
||||
@@ -0,0 +1,44 @@
|
||||
use std::collections::VecDeque;
|
||||
|
||||
use super::take_permission_batch;
|
||||
|
||||
#[test]
|
||||
fn nine_permission_consumptions_are_sent_in_order_across_two_responses() {
|
||||
let mut pending = (1_u8..=9).collect::<VecDeque<_>>();
|
||||
|
||||
assert_eq!(take_permission_batch(&mut pending), (1_u8..=8).collect::<Vec<_>>());
|
||||
assert_eq!(take_permission_batch(&mut pending), vec![9]);
|
||||
}
|
||||
|
||||
#[cfg(all(feature = "hardware-render", target_os = "macos"))]
|
||||
mod hardware {
|
||||
use super::super::{HardwarePollAction, hardware_poll_action};
|
||||
|
||||
#[test]
|
||||
fn newly_ready_surface_replays_before_pending_frame() {
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, false, true, false, true, true),
|
||||
HardwarePollAction::ReplaySurface
|
||||
);
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, false, false, false, true, true),
|
||||
HardwarePollAction::PaintFrame
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn awaiting_ready_surface_backpressures_pending_frame() {
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, true, false, false, true, false),
|
||||
HardwarePollAction::Empty
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn missing_surface_replays_before_first_ready() {
|
||||
assert_eq!(
|
||||
hardware_poll_action(true, false, false, true, true, false),
|
||||
HardwarePollAction::ReplaySurface
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user