Allow OHOS servoshell to have a simple multiple tab implementation. (#36891)

Currently we just pause the compositor and replace the window in it
while having separate bookkeeping to remember which window belongs to
which tab.
Currently there are no tests for OHOS, so we cannot test the changes.

---------

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
Co-authored-by: Jonathan Schwender <55576758+jschwe@users.noreply.github.com>
This commit is contained in:
Narfinger 2025-06-16 10:17:31 +02:00 committed by GitHub
parent 71bf9fb92d
commit 3b73b83a9f
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 245 additions and 87 deletions

View file

@ -6,8 +6,9 @@
use std::cell::RefCell;
use std::mem::MaybeUninit;
use std::os::raw::c_void;
use std::rc::Rc;
use std::sync::mpsc::{Receiver, Sender};
use std::sync::{LazyLock, Once, OnceLock, mpsc};
use std::sync::{LazyLock, Mutex, Once, OnceLock, mpsc};
use std::thread;
use std::thread::sleep;
use std::time::Duration;
@ -23,7 +24,7 @@ use ohos_ime_sys::types::InputMethod_EnterKeyType;
use servo::style::Zero;
use servo::{
AlertResponse, EventLoopWaker, InputMethodType, LoadStatus, MediaSessionPlaybackState,
PermissionRequest, SimpleDialog, WebView,
PermissionRequest, SimpleDialog, WebView, WebViewId,
};
use xcomponent_sys::{
OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetKeyEvent,
@ -65,9 +66,11 @@ fn call(action: ServoAction) -> Result<(), CallError> {
}
#[repr(transparent)]
struct XComponentWrapper(*mut OH_NativeXComponent);
#[derive(Clone)]
pub(crate) struct XComponentWrapper(*mut OH_NativeXComponent);
#[repr(transparent)]
struct WindowWrapper(*mut c_void);
#[derive(Clone)]
pub(crate) struct WindowWrapper(*mut c_void);
unsafe impl Send for XComponentWrapper {}
unsafe impl Send for WindowWrapper {}
@ -80,7 +83,6 @@ pub(super) enum TouchEventType {
Unknown,
}
#[derive(Debug)]
pub(super) enum ServoAction {
WakeUp,
LoadUrl(String),
@ -104,6 +106,8 @@ pub(super) enum ServoAction {
width: i32,
height: i32,
},
FocusWebview(u32),
NewWebview(XComponentWrapper, WindowWrapper),
}
/// Queue length for the thread-safe function to submit URL updates to ArkTS
@ -125,6 +129,20 @@ static PROMPT_TOAST: OnceLock<
ThreadsafeFunction<String, (), String, false, false, PROMPT_QUEUE_SIZE>,
> = OnceLock::new();
/// Storing webview related items
struct NativeWebViewComponents {
/// The id of the related webview
id: WebViewId,
/// The XComponentWrapper for the above webview
xcomponent: XComponentWrapper,
/// The WindowWrapper for the above webview
window: WindowWrapper,
}
/// Currently we do not support different contexts for different windows but we might want to change tabs.
/// For this we store the window context for every tab and change the compositor by hand.
static NATIVE_WEBVIEWS: Mutex<Vec<NativeWebViewComponents>> = Mutex::new(Vec::new());
impl ServoAction {
fn dispatch_touch_event(
servo: &RunningAppState,
@ -143,7 +161,7 @@ impl ServoAction {
}
// todo: consider making this take `self`, so we don't need to needlessly clone.
fn do_action(&self, servo: &RunningAppState) {
fn do_action(&self, servo: &Rc<RunningAppState>) {
use ServoAction::*;
match self {
WakeUp => servo.perform_updates(),
@ -155,7 +173,7 @@ impl ServoAction {
x,
y,
pointer_id,
} => Self::dispatch_touch_event(servo, *kind, *x, *y, *pointer_id),
} => Self::dispatch_touch_event(&servo, *kind, *x, *y, *pointer_id),
KeyUp(k) => servo.key_up(k.clone()),
KeyDown(k) => servo.key_down(k.clone()),
InsertText(text) => servo.ime_insert_text(text.clone()),
@ -183,6 +201,56 @@ impl ServoAction {
servo.present_if_needed();
},
Resize { width, height } => servo.resize(Coordinates::new(0, 0, *width, *height)),
FocusWebview(arkts_id) => {
if let Some(native_webview_components) =
NATIVE_WEBVIEWS.lock().unwrap().get(*arkts_id as usize)
{
if (servo.active_webview().id() != native_webview_components.id) {
servo.focus_webview(native_webview_components.id);
servo.pause_compositor();
let (window_handle, _, coordinates) = simpleservo::get_raw_window_handle(
native_webview_components.xcomponent.0,
native_webview_components.window.0,
);
servo.resume_compositor(window_handle, coordinates);
let url = servo
.active_webview()
.url()
.map(|u| u.to_string())
.unwrap_or(String::from("about:blank"));
SET_URL_BAR_CB
.get()
.map(|f| f.call(url, ThreadsafeFunctionCallMode::Blocking));
}
} else {
error!("Could not find webview to focus");
}
},
NewWebview(xcomponent, window) => {
servo.pause_compositor();
servo.new_toplevel_webview("about:blank".parse().unwrap());
let (window_handle, _, coordinates) =
simpleservo::get_raw_window_handle(xcomponent.0, window.0);
servo.resume_compositor(window_handle, coordinates);
let webview = servo.newest_webview().expect("There should always be one");
let id = webview.id();
NATIVE_WEBVIEWS
.lock()
.unwrap()
.push(NativeWebViewComponents {
id: id,
xcomponent: xcomponent.clone(),
window: window.clone(),
});
let url = webview
.url()
.map(|u| u.to_string())
.unwrap_or(String::from("about:blank"));
SET_URL_BAR_CB
.get()
.map(|f| f.call(url, ThreadsafeFunctionCallMode::Blocking));
},
};
}
}
@ -221,50 +289,64 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window
let xc_wrapper = XComponentWrapper(xcomponent);
let window_wrapper = WindowWrapper(window);
// Todo: Perhaps it would be better to move this thread into the vsync signal thread.
// This would allow us to save one thread and the IPC for the vsync signal.
//
// Each thread will send its id via the channel
let _main_surface_thread = thread::spawn(move || {
let (tx, rx): (Sender<ServoAction>, Receiver<ServoAction>) = mpsc::channel();
if !SERVO_CHANNEL.get().is_some() {
// Todo: Perhaps it would be better to move this thread into the vsync signal thread.
// This would allow us to save one thread and the IPC for the vsync signal.
//
// Each thread will send its id via the channel
let _main_surface_thread = thread::spawn(move || {
let (tx, rx): (Sender<ServoAction>, Receiver<ServoAction>) = mpsc::channel();
SERVO_CHANNEL
.set(tx.clone())
.expect("Servo channel already initialized");
SERVO_CHANNEL
.set(tx.clone())
.expect("Servo channel already initialized");
let wakeup = Box::new(WakeupCallback::new(tx));
let callbacks = Box::new(HostCallbacks::new());
let wakeup = Box::new(WakeupCallback::new(tx));
let callbacks = Box::new(HostCallbacks::new());
let xc = xc_wrapper;
let window = window_wrapper;
let init_opts = if let Ok(ServoAction::Initialize(init_opts)) = rx.recv() {
init_opts
} else {
panic!("Servos GL thread received another event before it was initialized")
};
let servo = simpleservo::init(*init_opts, window.0, xc.0, wakeup, callbacks)
.expect("Servo initialization failed");
let xc = xc_wrapper;
let window = window_wrapper;
info!("Surface created!");
let native_vsync =
ohos_vsync::NativeVsync::new("ServoVsync").expect("Failed to create NativeVsync");
// get_period() returns an error - perhaps we need to wait until the first callback?
// info!("Native vsync period is {} nanoseconds", native_vsync.get_period().unwrap());
unsafe {
native_vsync
.request_raw_callback_with_self(Some(on_vsync_cb))
.expect("Failed to request vsync callback")
}
info!("Enabled Vsync!");
let init_opts = if let Ok(ServoAction::Initialize(init_opts)) = rx.recv() {
init_opts
} else {
panic!("Servos GL thread received another event before it was initialized")
};
let servo = simpleservo::init(*init_opts, window.0, xc.0, wakeup, callbacks)
.expect("Servo initialization failed");
while let Ok(action) = rx.recv() {
trace!("Wakeup message received!");
action.do_action(&servo);
}
NATIVE_WEBVIEWS
.lock()
.unwrap()
.push(NativeWebViewComponents {
id: servo.active_webview().id(),
xcomponent: xc,
window,
});
info!("Sender disconnected - Terminating main surface thread");
});
info!("Surface created!");
let native_vsync =
ohos_vsync::NativeVsync::new("ServoVsync").expect("Failed to create NativeVsync");
// get_period() returns an error - perhaps we need to wait until the first callback?
// info!("Native vsync period is {} nanoseconds", native_vsync.get_period().unwrap());
unsafe {
native_vsync
.request_raw_callback_with_self(Some(on_vsync_cb))
.expect("Failed to request vsync callback")
}
info!("Enabled Vsync!");
while let Ok(action) = rx.recv() {
trace!("Wakeup message received!");
action.do_action(&servo);
}
info!("Sender disconnected - Terminating main surface thread");
});
} else {
call(ServoAction::NewWebview(xc_wrapper, window_wrapper))
.expect("Could not create new webview");
}
info!("Returning from on_surface_created_cb");
}
@ -647,6 +729,12 @@ pub fn init_servo(init_opts: InitOpts) -> napi_ohos::Result<()> {
Ok(())
}
#[napi]
fn focus_webview(id: u32) {
debug!("Focusing webview {id} from napi");
call(ServoAction::FocusWebview(id)).expect("Could not focus webview");
}
struct OhosImeOptions {
input_type: ohos_ime_sys::types::InputMethod_TextInputType,
enterkey_type: InputMethod_EnterKeyType,