mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Merge d1389378ad
into 5ef66ce386
This commit is contained in:
commit
70162c7406
7 changed files with 258 additions and 99 deletions
|
@ -292,6 +292,10 @@ impl TouchHandler {
|
|||
.expect("Current Touch sequence does not exist")
|
||||
}
|
||||
|
||||
fn try_get_current_touch_sequence_mut(&mut self) -> Option<&mut TouchSequenceInfo> {
|
||||
self.touch_sequence_map.get_mut(&self.current_sequence_id)
|
||||
}
|
||||
|
||||
pub(crate) fn get_touch_sequence(&self, sequence_id: TouchSequenceId) -> &TouchSequenceInfo {
|
||||
self.touch_sequence_map
|
||||
.get(&sequence_id)
|
||||
|
@ -374,7 +378,12 @@ impl TouchHandler {
|
|||
id: TouchId,
|
||||
point: Point2D<f32, DevicePixel>,
|
||||
) -> TouchMoveAction {
|
||||
let touch_sequence = self.get_current_touch_sequence_mut();
|
||||
// As `TouchHandler` is per `WebViewRenderer` which is per `WebView` we might get a Touch Sequence Move that
|
||||
// started with a down on a different webview. As the touch_sequence id is only changed on touch_down this
|
||||
// move event gets a touch id which is already cleaned up.
|
||||
let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
|
||||
return TouchMoveAction::NoAction;
|
||||
};
|
||||
let idx = match touch_sequence
|
||||
.active_touch_points
|
||||
.iter_mut()
|
||||
|
@ -529,7 +538,10 @@ impl TouchHandler {
|
|||
}
|
||||
|
||||
pub fn on_touch_cancel(&mut self, id: TouchId, _point: Point2D<f32, DevicePixel>) {
|
||||
let touch_sequence = self.get_current_touch_sequence_mut();
|
||||
// A similar thing with touch move can happen here where the event is coming from a different webview.
|
||||
let Some(touch_sequence) = self.try_get_current_touch_sequence_mut() else {
|
||||
return;
|
||||
};
|
||||
match touch_sequence
|
||||
.active_touch_points
|
||||
.iter()
|
||||
|
|
|
@ -404,6 +404,8 @@ impl WebViewRenderer {
|
|||
self.dispatch_input_event(event);
|
||||
}
|
||||
|
||||
/// Send a [`TouchEvent`] to the Constellation for this [`WebViewRenderer`].
|
||||
/// Returns true if the event was send
|
||||
fn send_touch_event(&self, mut event: TouchEvent) -> bool {
|
||||
let get_pipeline_details = |pipeline_id| self.pipelines.get(&pipeline_id);
|
||||
let Some(result) = self
|
||||
|
|
|
@ -180,6 +180,7 @@ pub enum TouchEventType {
|
|||
pub struct TouchId(pub i32);
|
||||
|
||||
/// An ID for a sequence of touch events between a `Down` and the `Up` or `Cancel` event.
|
||||
/// The ID is the same for all events between `Down` and `Up` or `Cancel`
|
||||
#[repr(transparent)]
|
||||
#[derive(Clone, Copy, Debug, Default, Deserialize, Eq, Hash, PartialEq, Serialize)]
|
||||
pub struct TouchSequenceId(u32);
|
||||
|
|
|
@ -355,6 +355,15 @@ impl RunningAppState {
|
|||
self.inner_mut().webviews.insert(webview.id(), webview);
|
||||
}
|
||||
|
||||
/// The focused webview will not be immediately valid via `active_webview()`
|
||||
pub(crate) fn focus_webview(&self, id: WebViewId) {
|
||||
if let Some(webview) = self.inner().webviews.get(&id) {
|
||||
webview.focus();
|
||||
} else {
|
||||
error!("We could not find the webview with this id {id}");
|
||||
}
|
||||
}
|
||||
|
||||
fn inner(&self) -> Ref<RunningAppStateInner> {
|
||||
self.inner.borrow()
|
||||
}
|
||||
|
@ -371,14 +380,14 @@ impl RunningAppState {
|
|||
Ok(webview_id)
|
||||
}
|
||||
|
||||
fn newest_webview(&self) -> Option<WebView> {
|
||||
pub(crate) fn newest_webview(&self) -> Option<WebView> {
|
||||
self.inner()
|
||||
.creation_order
|
||||
.last()
|
||||
.and_then(|id| self.inner().webviews.get(id).cloned())
|
||||
}
|
||||
|
||||
fn active_webview(&self) -> WebView {
|
||||
pub(crate) fn active_webview(&self) -> WebView {
|
||||
self.inner()
|
||||
.focused_webview_id
|
||||
.and_then(|id| self.inner().webviews.get(&id).cloned())
|
||||
|
|
|
@ -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,
|
||||
|
@ -69,9 +70,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 {}
|
||||
|
||||
|
@ -84,7 +87,6 @@ pub(super) enum TouchEventType {
|
|||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(super) enum ServoAction {
|
||||
WakeUp,
|
||||
LoadUrl(String),
|
||||
|
@ -108,6 +110,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
|
||||
|
@ -127,6 +131,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,
|
||||
|
@ -145,7 +163,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(),
|
||||
|
@ -157,7 +175,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()),
|
||||
|
@ -185,6 +203,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));
|
||||
},
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -223,6 +291,7 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window
|
|||
let xc_wrapper = XComponentWrapper(xcomponent);
|
||||
let window_wrapper = WindowWrapper(window);
|
||||
|
||||
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.
|
||||
//
|
||||
|
@ -239,6 +308,7 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window
|
|||
|
||||
let xc = xc_wrapper;
|
||||
let window = window_wrapper;
|
||||
|
||||
let init_opts = if let Ok(ServoAction::Initialize(init_opts)) = rx.recv() {
|
||||
init_opts
|
||||
} else {
|
||||
|
@ -247,6 +317,15 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window
|
|||
let servo = simpleservo::init(*init_opts, window.0, xc.0, wakeup, callbacks)
|
||||
.expect("Servo initialization failed");
|
||||
|
||||
NATIVE_WEBVIEWS
|
||||
.lock()
|
||||
.unwrap()
|
||||
.push(NativeWebViewComponents {
|
||||
id: servo.active_webview().id(),
|
||||
xcomponent: xc,
|
||||
window,
|
||||
});
|
||||
|
||||
info!("Surface created!");
|
||||
let native_vsync =
|
||||
ohos_vsync::NativeVsync::new("ServoVsync").expect("Failed to create NativeVsync");
|
||||
|
@ -266,7 +345,10 @@ extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window
|
|||
|
||||
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");
|
||||
}
|
||||
|
||||
|
@ -644,6 +726,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,
|
||||
|
|
|
@ -23,6 +23,20 @@ use crate::egl::ohos::InitOpts;
|
|||
use crate::egl::ohos::resources::ResourceReaderInstance;
|
||||
use crate::prefs::{ArgumentParsingResult, parse_command_line_arguments};
|
||||
|
||||
pub(crate) fn get_raw_window_handle(
|
||||
xcomponent: *mut OH_NativeXComponent,
|
||||
window: *mut c_void,
|
||||
) -> (RawWindowHandle, euclid::default::Size2D<i32>, Coordinates) {
|
||||
let window_size = unsafe { super::get_xcomponent_size(xcomponent, window) }
|
||||
.expect("Could not get native window size");
|
||||
let (x, y) = unsafe { super::get_xcomponent_offset(xcomponent, window) }
|
||||
.expect("Could not get native window offset");
|
||||
let coordinates = Coordinates::new(x, y, window_size.width, window_size.height);
|
||||
let native_window = NonNull::new(window).expect("Could not get native window");
|
||||
let window_handle = RawWindowHandle::OhosNdk(OhosNdkWindowHandle::new(native_window));
|
||||
(window_handle, window_size, coordinates)
|
||||
}
|
||||
|
||||
/// Initialize Servo. At that point, we need a valid GL context.
|
||||
/// In the future, this will be done in multiple steps.
|
||||
pub fn init(
|
||||
|
@ -94,19 +108,12 @@ pub fn init(
|
|||
#[cfg(target_env = "ohos")]
|
||||
crate::egl::ohos::set_log_filter(servoshell_preferences.log_filter.as_deref());
|
||||
|
||||
let Ok(window_size) = (unsafe { super::get_xcomponent_size(xcomponent, native_window) }) else {
|
||||
return Err("Failed to get xcomponent size");
|
||||
};
|
||||
let Ok((x, y)) = (unsafe { super::get_xcomponent_offset(xcomponent, native_window) }) else {
|
||||
return Err("Failed to get xcomponent offset");
|
||||
};
|
||||
let coordinates = Coordinates::new(x, y, window_size.width, window_size.height);
|
||||
let (window_handle, window_size, coordinates) =
|
||||
get_raw_window_handle(xcomponent, native_window);
|
||||
|
||||
let display_handle = RawDisplayHandle::Ohos(OhosDisplayHandle::new());
|
||||
let display_handle = unsafe { DisplayHandle::borrow_raw(display_handle) };
|
||||
|
||||
let native_window = NonNull::new(native_window).expect("Could not get native window");
|
||||
let window_handle = RawWindowHandle::OhosNdk(OhosNdkWindowHandle::new(native_window));
|
||||
let window_handle = unsafe { WindowHandle::borrow_raw(window_handle) };
|
||||
|
||||
let rendering_context = Rc::new(
|
||||
|
|
|
@ -5,10 +5,17 @@ import promptAction from '@ohos.promptAction';
|
|||
|
||||
interface ServoXComponentInterface {
|
||||
loadURL(url: string): void;
|
||||
|
||||
goBack(): void;
|
||||
|
||||
goForward(): void;
|
||||
|
||||
registerURLcallback(callback: (url: string) => void): void;
|
||||
|
||||
registerPromptToastCallback(callback: (msg: string) => void): void
|
||||
|
||||
focusWebview(index: number):void;
|
||||
|
||||
initServo(options: InitOpts): void;
|
||||
}
|
||||
|
||||
|
@ -62,11 +69,12 @@ struct Index {
|
|||
type: XComponentType.SURFACE,
|
||||
libraryname: 'servoshell',
|
||||
}
|
||||
|
||||
private context = getContext(this) as common.UIAbilityContext;
|
||||
@LocalStorageProp('InitialURI') InitialURI: string = "unused"
|
||||
@LocalStorageProp('CommandlineArgs') CommandlineArgs: string = ""
|
||||
@State urlToLoad: string = this.InitialURI
|
||||
@State tablist: Array<number> = [];
|
||||
@State currentIndex: number = 0;
|
||||
|
||||
// Called when the user swipes from the right or left edge to the middle
|
||||
// Default behavior is bringing the app to the background.
|
||||
|
@ -81,33 +89,55 @@ struct Index {
|
|||
// Flex.
|
||||
Flex({ direction: FlexDirection.Column}) {
|
||||
Row() {
|
||||
Button('⇦').backgroundColor(Color.White)
|
||||
Button('+')
|
||||
.backgroundColor(Color.White)
|
||||
.fontColor(Color.Black)
|
||||
.fontWeight(FontWeight.Bolder)
|
||||
.fontSize(22)
|
||||
.width('12%')
|
||||
.onClick((event) => {
|
||||
if (this.tablist.length==0) {
|
||||
this.tablist.push(2);
|
||||
} else {
|
||||
this.tablist.push(this.tablist[this.tablist.length-1]+1);
|
||||
}
|
||||
// yes this is correct as we always have one tab extra
|
||||
// The tab extra is seperate for the initialization and will always exist.
|
||||
// It is not in the tablist.
|
||||
this.currentIndex = this.tablist.length;
|
||||
})
|
||||
Button('⇦')
|
||||
.backgroundColor(Color.White)
|
||||
.fontColor(Color.Black)
|
||||
.fontWeight(FontWeight.Bolder)
|
||||
.width('12%')
|
||||
.fontSize(12)
|
||||
.onClick(()=>{
|
||||
.onClick(() => {
|
||||
this.onBackPress()
|
||||
})
|
||||
Button('⇨').backgroundColor(Color.White)
|
||||
Button('⇨')
|
||||
.backgroundColor(Color.White)
|
||||
.fontColor(Color.Black)
|
||||
.fontWeight(FontWeight.Bolder)
|
||||
.fontSize(12)
|
||||
.width('12%')
|
||||
.onClick(()=> {
|
||||
.onClick(() => {
|
||||
this.xComponentContext?.goForward()
|
||||
})
|
||||
TextInput({placeholder:'URL',text: $$this.urlToLoad})
|
||||
TextInput({ placeholder: 'URL', text: $$this.urlToLoad })
|
||||
.type(InputType.Normal)
|
||||
.width('76%')
|
||||
.onChange((value) => {
|
||||
this.urlToLoad = value
|
||||
})
|
||||
.onSubmit((EnterKeyType)=>{
|
||||
.onSubmit((EnterKeyType) => {
|
||||
this.xComponentContext?.loadURL(this.urlToLoad)
|
||||
console.info('Load URL: ', this.urlToLoad)
|
||||
})
|
||||
}
|
||||
|
||||
Tabs({ barPosition: BarPosition.Start, index: this.currentIndex}) {
|
||||
TabContent() {
|
||||
XComponent(this.xComponentAttrs)
|
||||
.focusable(true)
|
||||
.onLoad((xComponentContext) => {
|
||||
|
@ -132,6 +162,16 @@ struct Index {
|
|||
})
|
||||
this.xComponentContext.registerPromptToastCallback(prompt_toast)
|
||||
})
|
||||
}.tabBar('1')
|
||||
ForEach(this.tablist, (item: number) => {
|
||||
TabContent() {
|
||||
XComponent(this.xComponentAttrs)
|
||||
.focusable(true)
|
||||
}.tabBar(String(item))
|
||||
})
|
||||
}.onChange((index: number) => {
|
||||
this.xComponentContext?.focusWebview(index);
|
||||
})
|
||||
}
|
||||
.width('100%')
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue