update
This commit is contained in:
+47
-11
@@ -20,6 +20,7 @@ use gpui_component_assets::Assets;
|
|||||||
use shell::ElyShell;
|
use shell::ElyShell;
|
||||||
use shell::chrome::{TRAFFIC_LIGHT_ORIGIN_X, TRAFFIC_LIGHT_ORIGIN_Y};
|
use shell::chrome::{TRAFFIC_LIGHT_ORIGIN_X, TRAFFIC_LIGHT_ORIGIN_Y};
|
||||||
use shortcuts::bind_shortcuts;
|
use shortcuts::bind_shortcuts;
|
||||||
|
use url::Url;
|
||||||
|
|
||||||
use crate::brand::{DEEP_LINK_PREFIX, PRODUCT_NAME};
|
use crate::brand::{DEEP_LINK_PREFIX, PRODUCT_NAME};
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ actions!(
|
|||||||
fn main() {
|
fn main() {
|
||||||
init_tracing();
|
init_tracing();
|
||||||
let pending_deep_links = PendingDeepLinks::default();
|
let pending_deep_links = PendingDeepLinks::default();
|
||||||
pending_deep_links.push(startup_deep_links(env::args().skip(1)));
|
pending_deep_links.push(startup_open_urls(env::args().skip(1)));
|
||||||
let open_url_queue = pending_deep_links.clone();
|
let open_url_queue = pending_deep_links.clone();
|
||||||
let application = Application::new().with_assets(Assets);
|
let application = Application::new().with_assets(Assets);
|
||||||
|
|
||||||
@@ -248,7 +249,7 @@ fn open_deep_links(
|
|||||||
current_window: &Rc<RefCell<Option<BrowserWindowTarget>>>,
|
current_window: &Rc<RefCell<Option<BrowserWindowTarget>>>,
|
||||||
cx: &mut App,
|
cx: &mut App,
|
||||||
) {
|
) {
|
||||||
let urls = urls.into_iter().filter_map(|url| parse_ely_deep_link(&url)).collect::<Vec<_>>();
|
let urls = urls.into_iter().filter_map(|url| parse_open_url(&url)).collect::<Vec<_>>();
|
||||||
|
|
||||||
if urls.is_empty() {
|
if urls.is_empty() {
|
||||||
return;
|
return;
|
||||||
@@ -314,9 +315,19 @@ fn parse_ely_deep_link(value: &str) -> Option<UrlText> {
|
|||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn startup_deep_links(args: impl IntoIterator<Item = String>) -> Vec<String> {
|
fn parse_open_url(value: &str) -> Option<UrlText> {
|
||||||
|
parse_ely_deep_link(value).or_else(|| parse_external_open_url(value))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn parse_external_open_url(value: &str) -> Option<UrlText> {
|
||||||
|
let parsed = UrlText::from_address_text(value).ok()?;
|
||||||
|
let url = Url::parse(parsed.as_str()).ok()?;
|
||||||
|
matches!(url.scheme(), "http" | "https").then(|| UrlText::parse(url.to_string()).ok()).flatten()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn startup_open_urls(args: impl IntoIterator<Item = String>) -> Vec<String> {
|
||||||
args.into_iter()
|
args.into_iter()
|
||||||
.filter_map(|arg| parse_ely_deep_link(&arg).map(|url| url.as_str().to_string()))
|
.filter_map(|arg| parse_open_url(&arg).map(|url| url.as_str().to_string()))
|
||||||
.collect()
|
.collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -346,7 +357,7 @@ fn open_private_window(_: &OpenPrivateWindow, cx: &mut App) {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{PendingDeepLinks, parse_ely_deep_link, startup_deep_links};
|
use super::{PendingDeepLinks, parse_ely_deep_link, parse_open_url, startup_open_urls};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn pending_deep_links_drains_urls_in_order() {
|
fn pending_deep_links_drains_urls_in_order() {
|
||||||
@@ -381,16 +392,41 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn startup_deep_links_filter_and_normalize_args() {
|
fn parse_open_url_accepts_external_pages() {
|
||||||
let links = startup_deep_links(
|
let url = parse_open_url(" HTTPS://example.com ");
|
||||||
["--ignored", " ELY://history ", "https://example.com", "ely://auth/callback?code=abc"]
|
|
||||||
.into_iter()
|
assert_eq!(url.as_ref().map(|url| url.as_str()), Some("https://example.com/"));
|
||||||
.map(str::to_string),
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_open_url_accepts_address_text_domains() {
|
||||||
|
let url = parse_open_url("servo.org");
|
||||||
|
|
||||||
|
assert_eq!(url.as_ref().map(|url| url.as_str()), Some("https://servo.org/"));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn startup_open_urls_filter_and_normalize_args() {
|
||||||
|
let links = startup_open_urls(
|
||||||
|
[
|
||||||
|
"--ignored",
|
||||||
|
" ELY://history ",
|
||||||
|
"https://example.com",
|
||||||
|
"servo.org",
|
||||||
|
"ely://auth/callback?code=abc",
|
||||||
|
]
|
||||||
|
.into_iter()
|
||||||
|
.map(str::to_string),
|
||||||
);
|
);
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
links,
|
links,
|
||||||
vec!["ely://history".to_string(), "ely://auth/callback?code=abc".to_string()]
|
vec![
|
||||||
|
"ely://history".to_string(),
|
||||||
|
"https://example.com/".to_string(),
|
||||||
|
"https://servo.org/".to_string(),
|
||||||
|
"ely://auth/callback?code=abc".to_string(),
|
||||||
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use ely_domain::{
|
|||||||
use ely_servo_host::{
|
use ely_servo_host::{
|
||||||
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest,
|
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseHoverRequest,
|
||||||
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest, ResizeRequest,
|
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest, ResizeRequest,
|
||||||
ScrollRequest, ServoHost, ServoSurfaceSize, SoftwareServoHost,
|
ScrollRequest, ServoHost, ServoHostError, ServoSurfaceSize, SoftwareServoHost,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[path = "servo_live_types.rs"]
|
#[path = "servo_live_types.rs"]
|
||||||
@@ -54,24 +54,18 @@ impl ServoLiveClient {
|
|||||||
self.apply_navigation(&request, &webview_id, tab_id, requested_url)?;
|
self.apply_navigation(&request, &webview_id, tab_id, requested_url)?;
|
||||||
self.apply_input(&request, &webview_id)?;
|
self.apply_input(&request, &webview_id)?;
|
||||||
// Match Servo's `examples/winit_minimal.rs`: spin the event loop on
|
// Match Servo's `examples/winit_minimal.rs`: spin the event loop on
|
||||||
// the embedder-side hot path, never paint. Painting is reactive in
|
// the embedder-side hot path, never paint. Servo's public
|
||||||
// Servo — `notify_new_frame_ready` on the delegate flags the
|
// rendering contract says `notify_new_frame_ready` is the signal
|
||||||
// session, and the next `poll` (gated on `has_pending_frame`)
|
// for `WebView::paint`; URL/title/load-status callbacks are
|
||||||
// performs the single paint + present for that frame. Forcing a
|
// metadata updates and travel through snapshots. Painting here
|
||||||
// paint here would clear the surface to the WebRender background
|
// would present before Servo has composited the navigated page,
|
||||||
// and present it *before* Servo has composited the navigated
|
// which produced per-redirect white flashes.
|
||||||
// page, which is what produced the per-redirect white-flash on
|
|
||||||
// sites that perform a chain of redirects (google.com → /
|
|
||||||
// → /?zx=…). The `ServoLiveFrame` we return only carries the
|
|
||||||
// snapshot metadata; the on-screen surface is owned by Servo via
|
|
||||||
// the native NSView and updated through `poll`.
|
|
||||||
self.host.tick();
|
self.host.tick();
|
||||||
if !self.session_uses_native_surface(&request.tab_id) {
|
if self.session_uses_native_surface(&request.tab_id) {
|
||||||
return Err(ServoLiveError::NativeSurfaceUnavailable);
|
return self.presented_frame_from_session(&request.tab_id, &webview_id).map(Some);
|
||||||
}
|
}
|
||||||
|
|
||||||
let frame = self.frame_from_session(&request.tab_id, &webview_id)?;
|
Ok(None)
|
||||||
Ok(Some(frame))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn poll(&mut self, tab_id: String) -> Result<Option<ServoLiveFrame>, ServoLiveError> {
|
pub fn poll(&mut self, tab_id: String) -> Result<Option<ServoLiveFrame>, ServoLiveError> {
|
||||||
@@ -82,15 +76,22 @@ impl ServoLiveClient {
|
|||||||
let uses_native_surface = session.native_surface_id.is_some();
|
let uses_native_surface = session.native_surface_id.is_some();
|
||||||
|
|
||||||
self.host.tick();
|
self.host.tick();
|
||||||
if !self.host.snapshot(&webview_id)?.has_pending_frame() {
|
let snapshot = self.host.snapshot(&webview_id)?;
|
||||||
|
if !snapshot.has_pending_frame() && !snapshot.has_pending_metadata() {
|
||||||
return Ok(None);
|
return Ok(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
if !uses_native_surface {
|
if uses_native_surface {
|
||||||
return Err(ServoLiveError::NativeSurfaceUnavailable);
|
if snapshot.has_pending_frame() {
|
||||||
|
self.host.paint_without_readback_with_completion(&webview_id, false)?;
|
||||||
|
}
|
||||||
|
return self.presented_frame_from_session(&tab_id, &webview_id).map(Some);
|
||||||
}
|
}
|
||||||
self.host.paint_without_readback_with_completion(&webview_id, false)?;
|
|
||||||
self.frame_from_session(&tab_id, &webview_id).map(Some)
|
if snapshot.has_pending_frame() {
|
||||||
|
self.host.paint(&webview_id)?;
|
||||||
|
}
|
||||||
|
self.rendered_frame_from_session(&tab_id, &webview_id)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn close(&mut self, tab_id: String) -> Result<(), ServoLiveError> {
|
pub fn close(&mut self, tab_id: String) -> Result<(), ServoLiveError> {
|
||||||
@@ -252,8 +253,7 @@ impl ServoLiveClient {
|
|||||||
// `set_history`) is the source of truth — if it already
|
// `set_history`) is the source of truth — if it already
|
||||||
// matches the requested URL, this URL change came *from*
|
// matches the requested URL, this URL change came *from*
|
||||||
// Servo and only needs an embedder-side bookkeeping sync.
|
// Servo and only needs an embedder-side bookkeeping sync.
|
||||||
let servo_current_url =
|
let servo_current_url = self.host.snapshot(webview_id)?.url().map(str::to_string);
|
||||||
self.host.snapshot(webview_id)?.url().map(str::to_string);
|
|
||||||
if servo_current_url.as_deref() == Some(requested_url.as_str()) {
|
if servo_current_url.as_deref() == Some(requested_url.as_str()) {
|
||||||
if let Some(session) = self.sessions.get_mut(&request.tab_id) {
|
if let Some(session) = self.sessions.get_mut(&request.tab_id) {
|
||||||
session.requested_url = Some(requested_url.as_str().to_string());
|
session.requested_url = Some(requested_url.as_str().to_string());
|
||||||
@@ -314,7 +314,7 @@ impl ServoLiveClient {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn frame_from_session(
|
fn presented_frame_from_session(
|
||||||
&self,
|
&self,
|
||||||
tab_id: &str,
|
tab_id: &str,
|
||||||
webview_id: &WebViewId,
|
webview_id: &WebViewId,
|
||||||
@@ -325,7 +325,7 @@ impl ServoLiveClient {
|
|||||||
}));
|
}));
|
||||||
};
|
};
|
||||||
if session.native_surface_id.is_some() {
|
if session.native_surface_id.is_some() {
|
||||||
let snapshot = self.host.snapshot(webview_id)?;
|
let snapshot = self.host.snapshot_and_mark_metadata_observed(webview_id)?;
|
||||||
return Ok(ServoLiveFrame::from_presented(
|
return Ok(ServoLiveFrame::from_presented(
|
||||||
snapshot,
|
snapshot,
|
||||||
session.width,
|
session.width,
|
||||||
@@ -336,6 +336,29 @@ impl ServoLiveClient {
|
|||||||
Err(ServoLiveError::NativeSurfaceUnavailable)
|
Err(ServoLiveError::NativeSurfaceUnavailable)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn rendered_frame_from_session(
|
||||||
|
&self,
|
||||||
|
tab_id: &str,
|
||||||
|
webview_id: &WebViewId,
|
||||||
|
) -> Result<Option<ServoLiveFrame>, ServoLiveError> {
|
||||||
|
let Some(session) = self.sessions.get(tab_id) else {
|
||||||
|
return Err(ServoLiveError::Host(ServoHostError::WebViewNotFound {
|
||||||
|
id: webview_id.clone(),
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
let rendered_frame = match self.host.last_rendered_frame() {
|
||||||
|
Ok(frame) => frame,
|
||||||
|
Err(ServoHostError::RenderedFrameUnavailable) => return Ok(None),
|
||||||
|
Err(error) => return Err(ServoLiveError::Host(error)),
|
||||||
|
};
|
||||||
|
let snapshot = self.host.snapshot_and_mark_metadata_observed(webview_id)?;
|
||||||
|
Ok(Some(ServoLiveFrame::from_rendered(
|
||||||
|
snapshot,
|
||||||
|
rendered_frame,
|
||||||
|
session.device_pixel_ratio,
|
||||||
|
)))
|
||||||
|
}
|
||||||
|
|
||||||
fn session_uses_native_surface(&self, tab_id: &str) -> bool {
|
fn session_uses_native_surface(&self, tab_id: &str) -> bool {
|
||||||
self.sessions.get(tab_id).is_some_and(|session| session.native_surface_id.is_some())
|
self.sessions.get(tab_id).is_some_and(|session| session.native_surface_id.is_some())
|
||||||
}
|
}
|
||||||
@@ -425,12 +448,7 @@ mod tests {
|
|||||||
// physical dimensions, so only the hidpi push should fire.
|
// physical dimensions, so only the hidpi push should fire.
|
||||||
let change = ViewportChange::between(
|
let change = ViewportChange::between(
|
||||||
&request(2880, 1800, 2.0, SERVO_DEFAULT_PAGE_ZOOM_PERCENT),
|
&request(2880, 1800, 2.0, SERVO_DEFAULT_PAGE_ZOOM_PERCENT),
|
||||||
&session(
|
&session(2880, 1800, SERVO_DEFAULT_DEVICE_PIXEL_RATIO, SERVO_DEFAULT_PAGE_ZOOM_PERCENT),
|
||||||
2880,
|
|
||||||
1800,
|
|
||||||
SERVO_DEFAULT_DEVICE_PIXEL_RATIO,
|
|
||||||
SERVO_DEFAULT_PAGE_ZOOM_PERCENT,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
change,
|
change,
|
||||||
@@ -443,18 +461,8 @@ mod tests {
|
|||||||
// On a 1.0-DPR display the fresh session already matches the
|
// On a 1.0-DPR display the fresh session already matches the
|
||||||
// request — Servo's defaults are exactly what we asked for.
|
// request — Servo's defaults are exactly what we asked for.
|
||||||
let change = ViewportChange::between(
|
let change = ViewportChange::between(
|
||||||
&request(
|
&request(1280, 720, SERVO_DEFAULT_DEVICE_PIXEL_RATIO, SERVO_DEFAULT_PAGE_ZOOM_PERCENT),
|
||||||
1280,
|
&session(1280, 720, SERVO_DEFAULT_DEVICE_PIXEL_RATIO, SERVO_DEFAULT_PAGE_ZOOM_PERCENT),
|
||||||
720,
|
|
||||||
SERVO_DEFAULT_DEVICE_PIXEL_RATIO,
|
|
||||||
SERVO_DEFAULT_PAGE_ZOOM_PERCENT,
|
|
||||||
),
|
|
||||||
&session(
|
|
||||||
1280,
|
|
||||||
720,
|
|
||||||
SERVO_DEFAULT_DEVICE_PIXEL_RATIO,
|
|
||||||
SERVO_DEFAULT_PAGE_ZOOM_PERCENT,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
assert_eq!(change, ViewportChange::default());
|
assert_eq!(change, ViewportChange::default());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use ely_domain::SitePermissionDecision;
|
use ely_domain::SitePermissionDecision;
|
||||||
use ely_servo_host::{ServoHostError, WebViewSnapshot, WebViewState};
|
use ely_servo_host::{RenderedFrame, ServoHostError, WebViewSnapshot, WebViewState};
|
||||||
use gpui::NativeSurfaceHandle;
|
use gpui::NativeSurfaceHandle;
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
@@ -90,6 +90,34 @@ impl ServoLiveFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn from_rendered(
|
||||||
|
snapshot: WebViewSnapshot,
|
||||||
|
rendered_frame: RenderedFrame,
|
||||||
|
device_pixel_ratio: f32,
|
||||||
|
) -> Self {
|
||||||
|
let width = rendered_frame.width();
|
||||||
|
let height = rendered_frame.height();
|
||||||
|
let (css_viewport_width, css_viewport_height) =
|
||||||
|
css_viewport_size(width, height, device_pixel_ratio);
|
||||||
|
Self {
|
||||||
|
loaded_url: snapshot.url().map(str::to_string),
|
||||||
|
title: snapshot.title().map(str::to_string),
|
||||||
|
render_state: render_state_label(snapshot.state()).to_string(),
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
device_pixel_ratio,
|
||||||
|
css_viewport_width,
|
||||||
|
css_viewport_height,
|
||||||
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
|
non_white_pixel_count: rendered_frame.non_white_pixel_count(),
|
||||||
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
|
content_pixel_count: rendered_frame.content_pixel_count(),
|
||||||
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
|
sample_hash: rendered_frame.sample_hash(),
|
||||||
|
rgba_bytes: Some(rendered_frame.rgba_bytes().to_vec()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn loaded_url(&self) -> Option<&str> {
|
pub fn loaded_url(&self) -> Option<&str> {
|
||||||
self.loaded_url.as_deref()
|
self.loaded_url.as_deref()
|
||||||
@@ -155,6 +183,9 @@ impl ServoLiveFrame {
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
pub(crate) fn for_test(width: u32, height: u32, rgba_bytes: Vec<u8>) -> Self {
|
pub(crate) fn for_test(width: u32, height: u32, rgba_bytes: Vec<u8>) -> Self {
|
||||||
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
|
let summary =
|
||||||
|
ely_servo_host::RenderedFrameSummary::from_rgba_bytes(width, height, &rgba_bytes);
|
||||||
Self {
|
Self {
|
||||||
loaded_url: Some("https://example.com/".to_string()),
|
loaded_url: Some("https://example.com/".to_string()),
|
||||||
title: Some("Example".to_string()),
|
title: Some("Example".to_string()),
|
||||||
@@ -165,11 +196,11 @@ impl ServoLiveFrame {
|
|||||||
css_viewport_width: width,
|
css_viewport_width: width,
|
||||||
css_viewport_height: height,
|
css_viewport_height: height,
|
||||||
#[cfg(all(test, feature = "live-site-smoke"))]
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
non_white_pixel_count: 0,
|
non_white_pixel_count: summary.non_white_pixel_count(),
|
||||||
#[cfg(all(test, feature = "live-site-smoke"))]
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
content_pixel_count: 0,
|
content_pixel_count: summary.content_pixel_count(),
|
||||||
#[cfg(all(test, feature = "live-site-smoke"))]
|
#[cfg(all(test, feature = "live-site-smoke"))]
|
||||||
sample_hash: 0,
|
sample_hash: summary.sample_hash(),
|
||||||
rgba_bytes: Some(rgba_bytes),
|
rgba_bytes: Some(rgba_bytes),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -79,10 +79,8 @@ impl WebSurfaceStore {
|
|||||||
// which destroys and reallocates the framebuffer — the source of
|
// which destroys and reallocates the framebuffer — the source of
|
||||||
// the per-frame blank flash. The first ensure (when no prior
|
// the per-frame blank flash. The first ensure (when no prior
|
||||||
// `last_ensure_key` is set) always fires so the page can load.
|
// `last_ensure_key` is set) always fires so the page can load.
|
||||||
let already_ensured = self
|
let already_ensured =
|
||||||
.surfaces
|
self.surfaces.get(tab.id()).is_some_and(|surface| surface.last_ensure_key.is_some());
|
||||||
.get(tab.id())
|
|
||||||
.is_some_and(|surface| surface.last_ensure_key.is_some());
|
|
||||||
if already_ensured
|
if already_ensured
|
||||||
&& self
|
&& self
|
||||||
.surfaces
|
.surfaces
|
||||||
@@ -211,9 +209,9 @@ impl WebSurfaceStore {
|
|||||||
// one frame of the gesture settling. Without this boost the
|
// one frame of the gesture settling. Without this boost the
|
||||||
// idle 80 ms polling adds a noticeable lag between letting go
|
// idle 80 ms polling adds a noticeable lag between letting go
|
||||||
// of a resize and the page re-laying-out at the final size.
|
// of a resize and the page re-laying-out at the final size.
|
||||||
let any_settling = visible_tab_ids
|
let any_settling = visible_tab_ids.iter().any(|tab_id| {
|
||||||
.iter()
|
self.surfaces.get(tab_id).is_some_and(|s| s.viewport_size_is_settling(now))
|
||||||
.any(|tab_id| self.surfaces.get(tab_id).is_some_and(|s| s.viewport_size_is_settling(now)));
|
});
|
||||||
if any_settling {
|
if any_settling {
|
||||||
return ACTIVE_POLL_INTERVAL;
|
return ACTIVE_POLL_INTERVAL;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -201,7 +201,7 @@ fn assert_web_surface_resizes_prd_site() -> Result<(), Box<dyn Error>> {
|
|||||||
store.ensure_surface(&tab, ProfileDataMode::Transient, &[]);
|
store.ensure_surface(&tab, ProfileDataMode::Transient, &[]);
|
||||||
let _ = wait_for_ready_frame_at_size(
|
let _ = wait_for_ready_frame_at_size(
|
||||||
&mut store,
|
&mut store,
|
||||||
tab.id(),
|
&tab,
|
||||||
case,
|
case,
|
||||||
LIVE_SURFACE_WIDTH,
|
LIVE_SURFACE_WIDTH,
|
||||||
LIVE_SURFACE_HEIGHT,
|
LIVE_SURFACE_HEIGHT,
|
||||||
@@ -216,7 +216,7 @@ fn assert_web_surface_resizes_prd_site() -> Result<(), Box<dyn Error>> {
|
|||||||
store.ensure_surface(&tab, ProfileDataMode::Transient, &[]);
|
store.ensure_surface(&tab, ProfileDataMode::Transient, &[]);
|
||||||
wait_for_ready_frame_at_size(
|
wait_for_ready_frame_at_size(
|
||||||
&mut store,
|
&mut store,
|
||||||
tab.id(),
|
&tab,
|
||||||
case,
|
case,
|
||||||
RESIZED_LIVE_SURFACE_WIDTH,
|
RESIZED_LIVE_SURFACE_WIDTH,
|
||||||
RESIZED_LIVE_SURFACE_HEIGHT,
|
RESIZED_LIVE_SURFACE_HEIGHT,
|
||||||
@@ -377,7 +377,7 @@ fn wait_for_ready_frame_at_scroll(
|
|||||||
|
|
||||||
fn wait_for_ready_frame_at_size(
|
fn wait_for_ready_frame_at_size(
|
||||||
store: &mut WebSurfaceStore,
|
store: &mut WebSurfaceStore,
|
||||||
tab_id: &TabId,
|
tab: &BrowserTab,
|
||||||
case: &LiveSiteCase,
|
case: &LiveSiteCase,
|
||||||
expected_width: u32,
|
expected_width: u32,
|
||||||
expected_height: u32,
|
expected_height: u32,
|
||||||
@@ -386,8 +386,9 @@ fn wait_for_ready_frame_at_size(
|
|||||||
let mut last_error = None;
|
let mut last_error = None;
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
store.tick(std::slice::from_ref(tab_id));
|
store.ensure_surface(tab, ProfileDataMode::Transient, &[]);
|
||||||
match store.state(tab_id) {
|
store.tick(std::slice::from_ref(tab.id()));
|
||||||
|
match store.state(tab.id()) {
|
||||||
Some(WebSurfaceState::Ready(frame))
|
Some(WebSurfaceState::Ready(frame))
|
||||||
if frame.size().width == expected_width
|
if frame.size().width == expected_width
|
||||||
&& frame.size().height == expected_height =>
|
&& frame.size().height == expected_height =>
|
||||||
|
|||||||
@@ -395,7 +395,9 @@ impl WebSurfaceRuntime {
|
|||||||
let Some(scoped) = self.worker.take() else {
|
let Some(scoped) = self.worker.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Some(path) = scoped.transient_profile_data_dir {
|
let ScopedWorker { worker, transient_profile_data_dir } = scoped;
|
||||||
|
drop(worker);
|
||||||
|
if let Some(path) = transient_profile_data_dir {
|
||||||
let _ = fs::remove_dir_all(path);
|
let _ = fs::remove_dir_all(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -404,7 +406,9 @@ impl WebSurfaceRuntime {
|
|||||||
let Some(scoped) = self.direct_client.take() else {
|
let Some(scoped) = self.direct_client.take() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
if let Some(path) = scoped.transient_profile_data_dir {
|
let ScopedDirectClient { client, transient_profile_data_dir } = scoped;
|
||||||
|
drop(client);
|
||||||
|
if let Some(path) = transient_profile_data_dir {
|
||||||
let _ = fs::remove_dir_all(path);
|
let _ = fs::remove_dir_all(path);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -147,6 +147,29 @@ impl RenderedFrame {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
|
pub struct WebViewSnapshotPending {
|
||||||
|
has_pending_frame: bool,
|
||||||
|
has_pending_metadata: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl WebViewSnapshotPending {
|
||||||
|
#[must_use]
|
||||||
|
pub fn new(has_pending_frame: bool, has_pending_metadata: bool) -> Self {
|
||||||
|
Self { has_pending_frame, has_pending_metadata }
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn has_pending_frame(&self) -> bool {
|
||||||
|
self.has_pending_frame
|
||||||
|
}
|
||||||
|
|
||||||
|
#[must_use]
|
||||||
|
pub fn has_pending_metadata(&self) -> bool {
|
||||||
|
self.has_pending_metadata
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, Eq, PartialEq)]
|
#[derive(Clone, Debug, Eq, PartialEq)]
|
||||||
pub struct WebViewSnapshot {
|
pub struct WebViewSnapshot {
|
||||||
webview_id: WebViewId,
|
webview_id: WebViewId,
|
||||||
@@ -155,7 +178,7 @@ pub struct WebViewSnapshot {
|
|||||||
state: WebViewState,
|
state: WebViewState,
|
||||||
url: Option<String>,
|
url: Option<String>,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
has_pending_frame: bool,
|
pending: WebViewSnapshotPending,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebViewSnapshot {
|
impl WebViewSnapshot {
|
||||||
@@ -167,9 +190,9 @@ impl WebViewSnapshot {
|
|||||||
state: WebViewState,
|
state: WebViewState,
|
||||||
url: Option<String>,
|
url: Option<String>,
|
||||||
title: Option<String>,
|
title: Option<String>,
|
||||||
has_pending_frame: bool,
|
pending: WebViewSnapshotPending,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self { webview_id, tab_id, profile_id, state, url, title, has_pending_frame }
|
Self { webview_id, tab_id, profile_id, state, url, title, pending }
|
||||||
}
|
}
|
||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
@@ -204,7 +227,14 @@ impl WebViewSnapshot {
|
|||||||
|
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn has_pending_frame(&self) -> bool {
|
pub fn has_pending_frame(&self) -> bool {
|
||||||
self.has_pending_frame
|
self.pending.has_pending_frame()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns true when URL, title, or load-state changed since the
|
||||||
|
/// last embedder snapshot observation.
|
||||||
|
#[must_use]
|
||||||
|
pub fn has_pending_metadata(&self) -> bool {
|
||||||
|
self.pending.has_pending_metadata()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ pub use host::{
|
|||||||
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest, MouseHoverRequest,
|
HidpiScaleRequest, KeyboardTextRequest, MouseClickRequest, MouseDragRequest, MouseHoverRequest,
|
||||||
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest, RenderedFrame,
|
NavigationRequest, PageZoomRequest, PermissionDecision, PermissionRequest, RenderedFrame,
|
||||||
RenderedFrameSummary, ResizeRequest, ScrollRequest, ServoHost, TouchTapRequest,
|
RenderedFrameSummary, ResizeRequest, ScrollRequest, ServoHost, TouchTapRequest,
|
||||||
WebViewSnapshot, WebViewState,
|
WebViewSnapshot, WebViewSnapshotPending, WebViewState,
|
||||||
};
|
};
|
||||||
#[cfg(feature = "servo-engine")]
|
#[cfg(feature = "servo-engine")]
|
||||||
pub use runtime::{RenderingContextKind, ServoSurfaceSize, SoftwareServoHost};
|
pub use runtime::{RenderingContextKind, ServoSurfaceSize, SoftwareServoHost};
|
||||||
|
|||||||
@@ -45,6 +45,9 @@ pub struct SoftwareServoHost {
|
|||||||
default_surface_size: ServoSurfaceSize,
|
default_surface_size: ServoSurfaceSize,
|
||||||
rendering_context_kind: RenderingContextKind,
|
rendering_context_kind: RenderingContextKind,
|
||||||
webviews: HashMap<WebViewId, HostWebView>,
|
webviews: HashMap<WebViewId, HostWebView>,
|
||||||
|
// Servo 0.1.0 can still run script tasks after a remote page moves away.
|
||||||
|
// Retain hidden about:blank WebViews until host shutdown releases them together.
|
||||||
|
retired_webviews: Vec<HostWebView>,
|
||||||
permissions: PermissionStore,
|
permissions: PermissionStore,
|
||||||
wake_requested: Arc<AtomicBool>,
|
wake_requested: Arc<AtomicBool>,
|
||||||
last_rendered_frame: Option<RenderedFrame>,
|
last_rendered_frame: Option<RenderedFrame>,
|
||||||
@@ -120,7 +123,27 @@ impl SoftwareServoHost {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn close_webview(&mut self, webview_id: &WebViewId) -> bool {
|
pub fn close_webview(&mut self, webview_id: &WebViewId) -> bool {
|
||||||
self.webviews.remove(webview_id).is_some()
|
let Some(webview) = self.webviews.remove(webview_id) else {
|
||||||
|
return false;
|
||||||
|
};
|
||||||
|
self.retire_webview(webview);
|
||||||
|
true
|
||||||
|
}
|
||||||
|
|
||||||
|
fn retire_webview(&mut self, webview: HostWebView) {
|
||||||
|
webview.webview.hide();
|
||||||
|
if let Ok(blank_url) = Url::parse("about:blank") {
|
||||||
|
webview.webview.load(blank_url);
|
||||||
|
}
|
||||||
|
for _ in 0..32 {
|
||||||
|
self.servo.spin_event_loop();
|
||||||
|
if webview.current_url().as_deref() == Some("about:blank")
|
||||||
|
&& matches!(webview.state(), WebViewState::Complete)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.retired_webviews.push(webview);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn new_started(
|
fn new_started(
|
||||||
@@ -143,6 +166,7 @@ impl SoftwareServoHost {
|
|||||||
default_surface_size: size,
|
default_surface_size: size,
|
||||||
rendering_context_kind,
|
rendering_context_kind,
|
||||||
webviews: HashMap::new(),
|
webviews: HashMap::new(),
|
||||||
|
retired_webviews: Vec::new(),
|
||||||
permissions: Rc::new(RefCell::new(HashMap::new())),
|
permissions: Rc::new(RefCell::new(HashMap::new())),
|
||||||
wake_requested,
|
wake_requested,
|
||||||
last_rendered_frame: None,
|
last_rendered_frame: None,
|
||||||
@@ -190,6 +214,24 @@ impl SoftwareServoHost {
|
|||||||
self.last_rendered_frame = Some(rendered_frame);
|
self.last_rendered_frame = Some(rendered_frame);
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current snapshot and acknowledges metadata-only
|
||||||
|
/// updates without clearing Servo's frame-ready signal.
|
||||||
|
pub fn snapshot_and_mark_metadata_observed(
|
||||||
|
&self,
|
||||||
|
webview_id: &WebViewId,
|
||||||
|
) -> Result<WebViewSnapshot, ServoHostError> {
|
||||||
|
let webview = self.webview(webview_id)?;
|
||||||
|
let snapshot = webview.snapshot(webview_id);
|
||||||
|
webview.delegate.mark_metadata_observed();
|
||||||
|
Ok(snapshot)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn drain_after_webview_close(&self) {
|
||||||
|
for _ in 0..16 {
|
||||||
|
self.servo.spin_event_loop();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn install_rustls_provider() {
|
fn install_rustls_provider() {
|
||||||
@@ -395,6 +437,12 @@ impl ServoHost for SoftwareServoHost {
|
|||||||
|
|
||||||
impl Drop for SoftwareServoHost {
|
impl Drop for SoftwareServoHost {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
|
let webviews = std::mem::take(&mut self.webviews);
|
||||||
|
for webview in webviews.into_values() {
|
||||||
|
self.retire_webview(webview);
|
||||||
|
}
|
||||||
|
self.retired_webviews.clear();
|
||||||
|
self.drain_after_webview_close();
|
||||||
SERVO_RUNTIME_STARTED.store(false, Ordering::Release);
|
SERVO_RUNTIME_STARTED.store(false, Ordering::Release);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use servo::{LoadStatus, RenderingContext, WebView, WebViewDelegate};
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
PermissionDecision, WebViewSnapshot, WebViewState,
|
PermissionDecision, WebViewSnapshot, WebViewSnapshotPending, WebViewState,
|
||||||
runtime_permissions::{PermissionStore, permission_decision_for_webview},
|
runtime_permissions::{PermissionStore, permission_decision_for_webview},
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -27,7 +27,10 @@ impl HostWebView {
|
|||||||
self.state(),
|
self.state(),
|
||||||
self.current_url(),
|
self.current_url(),
|
||||||
self.current_title(),
|
self.current_title(),
|
||||||
self.delegate.has_pending_frame(),
|
WebViewSnapshotPending::new(
|
||||||
|
self.delegate.has_pending_frame(),
|
||||||
|
self.delegate.has_pending_metadata(),
|
||||||
|
),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,6 +65,7 @@ pub(super) struct HostWebViewDelegate {
|
|||||||
url: RefCell<Option<String>>,
|
url: RefCell<Option<String>>,
|
||||||
title: RefCell<Option<String>>,
|
title: RefCell<Option<String>>,
|
||||||
has_pending_frame: Cell<bool>,
|
has_pending_frame: Cell<bool>,
|
||||||
|
has_pending_metadata: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl HostWebViewDelegate {
|
impl HostWebViewDelegate {
|
||||||
@@ -73,6 +77,7 @@ impl HostWebViewDelegate {
|
|||||||
url: RefCell::new(None),
|
url: RefCell::new(None),
|
||||||
title: RefCell::new(None),
|
title: RefCell::new(None),
|
||||||
has_pending_frame: Cell::new(false),
|
has_pending_frame: Cell::new(false),
|
||||||
|
has_pending_metadata: Cell::new(false),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,12 +99,12 @@ impl HostWebViewDelegate {
|
|||||||
|
|
||||||
fn record_url_change(&self, url: String) {
|
fn record_url_change(&self, url: String) {
|
||||||
self.url.replace(Some(url));
|
self.url.replace(Some(url));
|
||||||
self.has_pending_frame.set(true);
|
self.has_pending_metadata.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_title_change(&self, title: Option<String>) {
|
fn record_title_change(&self, title: Option<String>) {
|
||||||
self.title.replace(title);
|
self.title.replace(title);
|
||||||
self.has_pending_frame.set(true);
|
self.has_pending_metadata.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn record_load_status(&self, status: LoadStatus) {
|
fn record_load_status(&self, status: LoadStatus) {
|
||||||
@@ -108,16 +113,24 @@ impl HostWebViewDelegate {
|
|||||||
LoadStatus::Complete => WebViewState::Complete,
|
LoadStatus::Complete => WebViewState::Complete,
|
||||||
};
|
};
|
||||||
self.set_state(state);
|
self.set_state(state);
|
||||||
self.has_pending_frame.set(true);
|
self.has_pending_metadata.set(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn has_pending_frame(&self) -> bool {
|
pub(super) fn has_pending_frame(&self) -> bool {
|
||||||
self.has_pending_frame.get()
|
self.has_pending_frame.get()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn has_pending_metadata(&self) -> bool {
|
||||||
|
self.has_pending_metadata.get()
|
||||||
|
}
|
||||||
|
|
||||||
pub(super) fn mark_frame_presented(&self) {
|
pub(super) fn mark_frame_presented(&self) {
|
||||||
self.has_pending_frame.set(false);
|
self.has_pending_frame.set(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub(super) fn mark_metadata_observed(&self) {
|
||||||
|
self.has_pending_metadata.set(false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl WebViewDelegate for HostWebViewDelegate {
|
impl WebViewDelegate for HostWebViewDelegate {
|
||||||
@@ -172,21 +185,25 @@ mod tests {
|
|||||||
use super::HostWebViewDelegate;
|
use super::HostWebViewDelegate;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn metadata_changes_mark_pending_frame() {
|
fn metadata_changes_are_separate_from_pending_frame() {
|
||||||
let delegate =
|
let delegate =
|
||||||
HostWebViewDelegate::new(ProfileId::new(), Rc::new(RefCell::new(HashMap::new())));
|
HostWebViewDelegate::new(ProfileId::new(), Rc::new(RefCell::new(HashMap::new())));
|
||||||
|
|
||||||
assert!(!delegate.has_pending_frame());
|
assert!(!delegate.has_pending_frame());
|
||||||
|
assert!(!delegate.has_pending_metadata());
|
||||||
|
|
||||||
delegate.record_title_change(Some("Example Domain".to_string()));
|
delegate.record_title_change(Some("Example Domain".to_string()));
|
||||||
assert_eq!(delegate.title().as_deref(), Some("Example Domain"));
|
assert_eq!(delegate.title().as_deref(), Some("Example Domain"));
|
||||||
assert!(delegate.has_pending_frame());
|
|
||||||
|
|
||||||
delegate.mark_frame_presented();
|
|
||||||
assert!(!delegate.has_pending_frame());
|
assert!(!delegate.has_pending_frame());
|
||||||
|
assert!(delegate.has_pending_metadata());
|
||||||
|
|
||||||
|
delegate.mark_metadata_observed();
|
||||||
|
assert!(!delegate.has_pending_frame());
|
||||||
|
assert!(!delegate.has_pending_metadata());
|
||||||
|
|
||||||
delegate.record_url_change("https://example.com/".to_string());
|
delegate.record_url_change("https://example.com/".to_string());
|
||||||
assert_eq!(delegate.url().as_deref(), Some("https://example.com/"));
|
assert_eq!(delegate.url().as_deref(), Some("https://example.com/"));
|
||||||
assert!(delegate.has_pending_frame());
|
assert!(!delegate.has_pending_frame());
|
||||||
|
assert!(delegate.has_pending_metadata());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
#!/usr/bin/env bash
|
#!/usr/bin/env bash
|
||||||
# T16 — real-window screencapture sanity check.
|
# T16 — real-window screencapture sanity check.
|
||||||
#
|
#
|
||||||
# Boots ./target/release/ely_app in the background, lets it open about:blank,
|
# Boots ./target/release/ely_app in the background, opens a live page through
|
||||||
# grabs a screencapture, then asserts the PNG is non-trivial (size + dimensions
|
# Servo's native surface path, grabs a screencapture, then asserts the PNG is
|
||||||
# + non-white center). Stderr from the app is tee'd to a log so a crash leaves
|
# non-trivial (size + dimensions + non-white center). Stderr from the app is
|
||||||
# evidence behind.
|
# tee'd to a log so a crash leaves evidence behind.
|
||||||
#
|
#
|
||||||
# Idempotent: kills any leftover ely_app processes from prior runs before
|
# Idempotent: kills any leftover ely_app processes from prior runs before
|
||||||
# starting, and cleans up its own background process on exit (success or fail).
|
# starting, and cleans up its own background process on exit (success or fail).
|
||||||
@@ -18,6 +18,7 @@ TIMESTAMP="$(date +%Y%m%d-%H%M%S)"
|
|||||||
SHOT_PATH="/tmp/ely-verify-${TIMESTAMP}.png"
|
SHOT_PATH="/tmp/ely-verify-${TIMESTAMP}.png"
|
||||||
STDERR_LOG="/tmp/ely-verify-stderr.log"
|
STDERR_LOG="/tmp/ely-verify-stderr.log"
|
||||||
APP_BIN="${REPO_ROOT}/target/release/ely_app"
|
APP_BIN="${REPO_ROOT}/target/release/ely_app"
|
||||||
|
SMOKE_URL="${ELY_VERIFY_URL:-https://servo.org/}"
|
||||||
APP_PID=""
|
APP_PID=""
|
||||||
|
|
||||||
cleanup() {
|
cleanup() {
|
||||||
@@ -32,6 +33,7 @@ cleanup() {
|
|||||||
fi
|
fi
|
||||||
# Stragglers from prior runs / child processes
|
# Stragglers from prior runs / child processes
|
||||||
pkill -f "target/release/ely_app" 2>/dev/null || true
|
pkill -f "target/release/ely_app" 2>/dev/null || true
|
||||||
|
pkill -x "ely_app" 2>/dev/null || true
|
||||||
}
|
}
|
||||||
trap cleanup EXIT INT TERM
|
trap cleanup EXIT INT TERM
|
||||||
|
|
||||||
@@ -47,6 +49,7 @@ fail() {
|
|||||||
|
|
||||||
echo "[1/6] killing stale ely_app processes"
|
echo "[1/6] killing stale ely_app processes"
|
||||||
pkill -f "target/release/ely_app" 2>/dev/null || true
|
pkill -f "target/release/ely_app" 2>/dev/null || true
|
||||||
|
pkill -x "ely_app" 2>/dev/null || true
|
||||||
sleep 0.5
|
sleep 0.5
|
||||||
|
|
||||||
echo "[2/6] cargo build --release -p ely_app"
|
echo "[2/6] cargo build --release -p ely_app"
|
||||||
@@ -55,21 +58,30 @@ if ! cargo build --release -p ely_app; then
|
|||||||
fi
|
fi
|
||||||
[[ -x "${APP_BIN}" ]] || fail "binary missing: ${APP_BIN}"
|
[[ -x "${APP_BIN}" ]] || fail "binary missing: ${APP_BIN}"
|
||||||
|
|
||||||
echo "[3/6] launching ${APP_BIN} (stderr -> ${STDERR_LOG})"
|
echo "[3/6] launching ${APP_BIN} ${SMOKE_URL} (stderr -> ${STDERR_LOG})"
|
||||||
: > "${STDERR_LOG}"
|
: > "${STDERR_LOG}"
|
||||||
"${APP_BIN}" >/dev/null 2>"${STDERR_LOG}" &
|
"${APP_BIN}" "${SMOKE_URL}" >/dev/null 2>"${STDERR_LOG}" &
|
||||||
APP_PID=$!
|
APP_PID=$!
|
||||||
echo " pid=${APP_PID}"
|
echo " pid=${APP_PID}"
|
||||||
|
|
||||||
echo "[4/6] waiting 8s for window + about:blank to settle"
|
echo "[4/6] waiting 12s for window + live page to settle"
|
||||||
for i in 1 2 3 4 5 6 7 8; do
|
for i in 1 2 3 4 5 6 7 8 9 10 11 12; do
|
||||||
sleep 1
|
sleep 1
|
||||||
if ! kill -0 "${APP_PID}" 2>/dev/null; then
|
if ! kill -0 "${APP_PID}" 2>/dev/null; then
|
||||||
fail "app exited early during warm-up (after ${i}s)"
|
fail "app exited early during warm-up (after ${i}s)"
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
echo "[5/6] screencapture -> ${SHOT_PATH}"
|
echo "[5/6] activating pid ${APP_PID} and screencapture -> ${SHOT_PATH}"
|
||||||
|
if ! osascript >/dev/null <<OSA
|
||||||
|
tell application "System Events"
|
||||||
|
set frontmost of first process whose unix id is ${APP_PID} to true
|
||||||
|
end tell
|
||||||
|
OSA
|
||||||
|
then
|
||||||
|
fail "could not activate ely_app process ${APP_PID}"
|
||||||
|
fi
|
||||||
|
sleep 1
|
||||||
# -x silences the shutter sound. Full screen is safer than -l <windowid> here
|
# -x silences the shutter sound. Full screen is safer than -l <windowid> here
|
||||||
# because we don't have a stable Cocoa window id; the app paints into the
|
# because we don't have a stable Cocoa window id; the app paints into the
|
||||||
# primary display and that's what we care about.
|
# primary display and that's what we care about.
|
||||||
|
|||||||
Reference in New Issue
Block a user