From 676feadee98d464e95b39c7a1fda35977873a9c5 Mon Sep 17 00:00:00 2001 From: Narfinger Date: Tue, 3 Jun 2025 13:50:14 +0200 Subject: [PATCH] Refactor save_output_image into own file so it can be reused in servoshell and other embedders and let OHOS accept -x and -o flag. Signed-off-by: Narfinger --- ports/servoshell/desktop/app_state.rs | 38 ++++-------------- ports/servoshell/egl/app_state.rs | 9 ++++- ports/servoshell/egl/ohos.rs | 19 ++++++++- ports/servoshell/lib.rs | 1 + ports/servoshell/output_image.rs | 39 +++++++++++++++++++ .../entry/src/main/ets/pages/Index.ets | 2 + 6 files changed, 75 insertions(+), 33 deletions(-) create mode 100644 ports/servoshell/output_image.rs diff --git a/ports/servoshell/desktop/app_state.rs b/ports/servoshell/desktop/app_state.rs index 464e344424a..e6e9f5471c4 100644 --- a/ports/servoshell/desktop/app_state.rs +++ b/ports/servoshell/desktop/app_state.rs @@ -7,15 +7,14 @@ use std::collections::HashMap; use std::path::PathBuf; use std::rc::Rc; -use euclid::{Point2D, Vector2D}; -use image::{DynamicImage, ImageFormat}; +use euclid::Vector2D; use keyboard_types::{Key, KeyboardEvent, Modifiers, ShortcutMatcher}; use log::{error, info}; use servo::base::id::WebViewId; use servo::config::pref; use servo::ipc_channel::ipc::IpcSender; use servo::webrender_api::ScrollLocation; -use servo::webrender_api::units::{DeviceIntPoint, DeviceIntRect, DeviceIntSize}; +use servo::webrender_api::units::{DeviceIntPoint, DeviceIntSize}; use servo::{ AllowOrDenyRequest, AuthenticationRequest, FilterPattern, FormControl, GamepadHapticEffectType, LoadStatus, PermissionRequest, Servo, ServoDelegate, ServoError, SimpleDialog, TouchEventType, @@ -28,6 +27,7 @@ use super::dialog::Dialog; use super::gamepad::GamepadSupport; use super::keyutils::CMD_OR_CONTROL; use super::window_trait::{LINE_HEIGHT, WindowPortsMethods}; +use crate::output_image::save_output_image_if_necessary; use crate::prefs::ServoShellPreferences; pub(crate) enum AppState { @@ -140,31 +140,6 @@ impl RunningAppState { } } - pub(crate) fn save_output_image_if_necessary(&self) { - let Some(output_path) = self.servoshell_preferences.output_image_path.as_ref() else { - return; - }; - - let inner = self.inner(); - let size = inner.window.rendering_context().size2d().to_i32(); - let viewport_rect = DeviceIntRect::from_origin_and_size(Point2D::origin(), size); - let Some(image) = inner - .window - .rendering_context() - .read_to_image(viewport_rect) - else { - error!("Failed to read output image."); - return; - }; - - let image_format = ImageFormat::from_path(output_path).unwrap_or(ImageFormat::Png); - if let Err(error) = - DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format) - { - error!("Failed to save {output_path}: {error}."); - } - } - /// Repaint the Servo view is necessary, returning true if anything was actually /// painted or false otherwise. Something may not be painted if Servo is waiting /// for a stable image to paint. @@ -179,9 +154,10 @@ impl RunningAppState { return; } - // This needs to be done before presenting(), because `ReneringContext::read_to_image` reads - // from the back buffer. - self.save_output_image_if_necessary(); + save_output_image_if_necessary( + &self.servoshell_preferences, + &self.inner().window.rendering_context(), + ); let mut inner_mut = self.inner_mut(); inner_mut.window.rendering_context().present(); diff --git a/ports/servoshell/egl/app_state.rs b/ports/servoshell/egl/app_state.rs index 114c9c5f4d0..cb57147f52d 100644 --- a/ports/servoshell/egl/app_state.rs +++ b/ports/servoshell/egl/app_state.rs @@ -25,6 +25,7 @@ use servo::{ use url::Url; use crate::egl::host_trait::HostTrait; +use crate::output_image::save_output_image_if_necessary; use crate::prefs::ServoShellPreferences; #[derive(Clone, Debug)] @@ -718,8 +719,14 @@ impl RunningAppState { pub fn present_if_needed(&self) { if self.inner().need_present { self.inner_mut().need_present = false; - self.active_webview().paint(); + if !self.active_webview().paint() { + return; + } + save_output_image_if_necessary(&self.servoshell_preferences, &self.rendering_context); self.rendering_context.present(); + if self.servoshell_preferences.exit_after_stable_image { + self.request_shutdown(); + } } } } diff --git a/ports/servoshell/egl/ohos.rs b/ports/servoshell/egl/ohos.rs index b9587771a50..41b1804a104 100644 --- a/ports/servoshell/egl/ohos.rs +++ b/ports/servoshell/egl/ohos.rs @@ -123,6 +123,8 @@ const PROMPT_QUEUE_SIZE: usize = 4; static SET_URL_BAR_CB: OnceLock< ThreadsafeFunction, > = OnceLock::new(); +static TERMINATE_CALLBACK: OnceLock> = + OnceLock::new(); static PROMPT_TOAST: OnceLock< ThreadsafeFunction, > = OnceLock::new(); @@ -601,6 +603,15 @@ pub fn register_url_callback(callback: Function) -> napi_ohos::Resul }) } +#[napi(js_name = "registerTerminateCallback")] +pub fn register_terminate_callback(callback: Function<(), ()>) -> napi_ohos::Result<()> { + let tsfn_builder = callback.build_threadsafe_function(); + let function = tsfn_builder.max_queue_size::<1>().build()?; + TERMINATE_CALLBACK + .set(function) + .map_err(|_| napi_ohos::Error::from_reason("Failed to set terminate function")) +} + #[napi] pub fn register_prompt_toast_callback(callback: Function) -> napi_ohos::Result<()> { debug!("register_prompt_toast_callback called!"); @@ -850,7 +861,13 @@ impl HostTrait for HostCallbacks { // todo: should we tell the vsync thread that it should perform updates? } - fn on_shutdown_complete(&self) {} + fn on_shutdown_complete(&self) { + if let Some(terminate_fn) = TERMINATE_CALLBACK.get() { + terminate_fn.call((), ThreadsafeFunctionCallMode::Blocking); + } else { + error!("Could not shut down despite servo shutting down"); + } + } /// Shows the Inputmethod /// diff --git a/ports/servoshell/lib.rs b/ports/servoshell/lib.rs index 54b4d2d54a0..8f05b5b56b5 100644 --- a/ports/servoshell/lib.rs +++ b/ports/servoshell/lib.rs @@ -15,6 +15,7 @@ mod crash_handler; pub(crate) mod desktop; #[cfg(any(target_os = "android", target_env = "ohos"))] mod egl; +mod output_image; #[cfg(not(any(target_os = "android", target_env = "ohos")))] mod panic_hook; mod parser; diff --git a/ports/servoshell/output_image.rs b/ports/servoshell/output_image.rs new file mode 100644 index 00000000000..fc4c3b941fc --- /dev/null +++ b/ports/servoshell/output_image.rs @@ -0,0 +1,39 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ + +use std::rc::Rc; + +use euclid::Point2D; +use image::{DynamicImage, ImageFormat}; +use log::error; +use servo::RenderingContext; +use servo::webrender_api::units::DeviceIntRect; + +use crate::prefs::ServoShellPreferences; + +// This needs to be done before presenting(), because `ReneringContext::read_to_image` reads +// from the back buffer. +pub(crate) fn save_output_image_if_necessary( + prefs: &ServoShellPreferences, + rendering_context: &Rc, +) where + T: RenderingContext + ?Sized, +{ + let Some(output_path) = prefs.output_image_path.as_ref() else { + return; + }; + + let size = rendering_context.size2d().to_i32(); + let viewport_rect = DeviceIntRect::from_origin_and_size(Point2D::origin(), size); + let Some(image) = rendering_context.read_to_image(viewport_rect) else { + error!("Failed to read output image."); + return; + }; + + let image_format = ImageFormat::from_path(output_path).unwrap_or(ImageFormat::Png); + if let Err(error) = DynamicImage::ImageRgba8(image).save_with_format(output_path, image_format) + { + error!("Failed to save {output_path}: {error}."); + } +} diff --git a/support/openharmony/entry/src/main/ets/pages/Index.ets b/support/openharmony/entry/src/main/ets/pages/Index.ets index 9c6cb06537b..14eeb996ac5 100644 --- a/support/openharmony/entry/src/main/ets/pages/Index.ets +++ b/support/openharmony/entry/src/main/ets/pages/Index.ets @@ -8,6 +8,7 @@ interface ServoXComponentInterface { goBack(): void; goForward(): void; registerURLcallback(callback: (url: string) => void): void; + registerTerminateCallback(callback: () => void): void; registerPromptToastCallback(callback: (msg: string) => void): void initServo(options: InitOpts): void; } @@ -130,6 +131,7 @@ struct Index { console.info('New URL from native: ', new_url) this.urlToLoad = new_url }) + this.xComponentContext.registerTerminateCallback(() => { this.context?.terminateSelf(); }) this.xComponentContext.registerPromptToastCallback(prompt_toast) }) }