mirror of
https://github.com/servo/servo.git
synced 2025-06-06 16:45:39 +00:00
Add OpenHarmony support to servoshell (#32594)
* Generate EGL bindings for ohos Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Adjust servoshell `bin` error message for android/ohos Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * ohos: disable WebGL offscreen buffers are not implemented yet on ohos. Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Add OpenHarmony support to servoshell Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Share ResourceReaderInstance Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Share android/ohos HostTrait Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Share servo glue Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com> * Pass Init options from ArkTS to Servo Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * f rebase ResourceReaderMethods Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * fixup! Share ResourceReaderInstance Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Fix typo Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Update Cargo.lock Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * ohos: Move WebGL check to webgl thread Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Remove commented code Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> * Remove commented and duplicate / unused code Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> --------- Signed-off-by: Jonathan Schwender <jonathan.schwender@huawei.com> Signed-off-by: Jonathan Schwender <schwenderjonathan@gmail.com>
This commit is contained in:
parent
a7ebc28738
commit
9455169813
16 changed files with 1748 additions and 810 deletions
99
Cargo.lock
generated
99
Cargo.lock
generated
|
@ -977,6 +977,15 @@ dependencies = [
|
|||
"url",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "convert_case"
|
||||
version = "0.6.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ec182b0ca2f35d8fc196cf3404988fd8b8c739a4d270ff118a398feb0cbec1ca"
|
||||
dependencies = [
|
||||
"unicode-segmentation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cookie"
|
||||
version = "0.12.0"
|
||||
|
@ -1167,6 +1176,16 @@ dependencies = [
|
|||
"quote",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ctor"
|
||||
version = "0.2.8"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "edb49164822f3ee45b17acd4a208cfc1251410cf0cad9a833234c9890774dd9f"
|
||||
dependencies = [
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cursor-icon"
|
||||
version = "1.1.0"
|
||||
|
@ -2741,6 +2760,26 @@ version = "0.2.1"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "dfa686283ad6dd069f105e5ab091b04c62850d3e4cf5d67debad1933f55023df"
|
||||
|
||||
[[package]]
|
||||
name = "hilog"
|
||||
version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0d5646a745e293209c82e88f05b40bb596479cd84738408410ea16d5242e7ad0"
|
||||
dependencies = [
|
||||
"env_filter",
|
||||
"hilog-sys",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "hilog-sys"
|
||||
version = "0.1.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0de0e35e8534a70b5af5ccc943ffa3e2dcfe481b2b983c9fd514d7421a46b69e"
|
||||
dependencies = [
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "histogram"
|
||||
version = "0.6.9"
|
||||
|
@ -3551,7 +3590,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||
checksum = "e310b3a6b5907f99202fcdb4960ff45b93735d7c7d96b760fcff8db2dc0e103d"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"windows-targets 0.48.5",
|
||||
"windows-targets 0.52.5",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -4020,6 +4059,55 @@ dependencies = [
|
|||
"unicode-xid",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-backend-ohos"
|
||||
version = "0.0.7"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f6b18d697bedddd2d4c9f8f76b49fe65bd81ed1c55a7eec21ba40c176c236ddc"
|
||||
dependencies = [
|
||||
"convert_case",
|
||||
"once_cell",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"regex",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-derive-ohos"
|
||||
version = "0.0.9"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "8462d74a2d6c7a671bd610f99f9ba34c739aadd2da4d8dd9f109a7e666cc2ad2"
|
||||
dependencies = [
|
||||
"cfg-if",
|
||||
"convert_case",
|
||||
"napi-derive-backend-ohos",
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.68",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-ohos"
|
||||
version = "0.1.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ad5a3bbb2ae61f345b8c11776f2e79fc2bb71d1901af9a5f81f03c9238a05d86"
|
||||
dependencies = [
|
||||
"bitflags 2.6.0",
|
||||
"ctor",
|
||||
"napi-sys-ohos",
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "napi-sys-ohos"
|
||||
version = "0.0.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f101404db01422d034db5afa63eefff6d9c8f66c0894278bc456b4c30954e166"
|
||||
dependencies = [
|
||||
"libloading 0.8.4",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ndk"
|
||||
version = "0.8.0"
|
||||
|
@ -4361,6 +4449,11 @@ dependencies = [
|
|||
"memchr",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ohos-sys"
|
||||
version = "0.1.0"
|
||||
source = "git+https://github.com/jschwe/ohos-sys.git#bf0625fc7ea8035c2991626fb0a593e06c9c8c21"
|
||||
|
||||
[[package]]
|
||||
name = "once_cell"
|
||||
version = "1.19.0"
|
||||
|
@ -5706,6 +5799,7 @@ dependencies = [
|
|||
"gl_generator",
|
||||
"gleam",
|
||||
"glow",
|
||||
"hilog",
|
||||
"image",
|
||||
"ipc-channel",
|
||||
"jni",
|
||||
|
@ -5715,6 +5809,9 @@ dependencies = [
|
|||
"libloading 0.8.4",
|
||||
"libservo",
|
||||
"log",
|
||||
"napi-derive-ohos",
|
||||
"napi-ohos",
|
||||
"ohos-sys",
|
||||
"raw-window-handle",
|
||||
"serde_json",
|
||||
"servo-media",
|
||||
|
|
|
@ -479,6 +479,8 @@ impl WebGLThread {
|
|||
"WebGLThread::create_webgl_context({:?}, {:?}, {:?})",
|
||||
webgl_version, requested_size, attributes
|
||||
);
|
||||
#[cfg(target_env = "ohos")]
|
||||
return Err("WebGL is not working yet on ohos".into());
|
||||
|
||||
// Creating a new GLContext may make the current bound context_id dirty.
|
||||
// Clear it to ensure that make_current() is called in subsequent commands.
|
||||
|
|
|
@ -60,6 +60,8 @@ log = { workspace = true }
|
|||
lazy_static = { workspace = true }
|
||||
getopts = { workspace = true }
|
||||
url = { workspace = true }
|
||||
servo-media = { workspace = true }
|
||||
|
||||
|
||||
[target.'cfg(target_os = "android")'.dependencies]
|
||||
android_logger = "0.14"
|
||||
|
@ -67,7 +69,6 @@ ipc-channel = { workspace = true }
|
|||
jni = "0.21.1"
|
||||
libloading = "0.8"
|
||||
serde_json = { workspace = true }
|
||||
servo-media = { workspace = true }
|
||||
surfman = { workspace = true, features = ["sm-angle-default"] }
|
||||
webxr = { git = "https://github.com/servo/webxr" }
|
||||
|
||||
|
@ -79,6 +80,13 @@ backtrace = { workspace = true }
|
|||
# force inprocess, until libc-rs 0.2.156 is released containing
|
||||
# https://github.com/rust-lang/libc/commit/9e248e212c5602cb4e98676e4c21ea0382663a12
|
||||
ipc-channel = { workspace = true, features = ["force-inprocess"] }
|
||||
hilog = "0.1.0"
|
||||
napi-derive-ohos = "0.0.9"
|
||||
napi-ohos = "0.1"
|
||||
serde_json = { workspace = true }
|
||||
surfman = { workspace = true, features = ["sm-angle-default"] }
|
||||
webxr = { git = "https://github.com/servo/webxr" }
|
||||
ohos-sys = { git = "https://github.com/jschwe/ohos-sys.git" }
|
||||
|
||||
|
||||
[target.'cfg(not(any(target_os = "android", target_env = "ohos")))'.dependencies]
|
||||
|
@ -94,7 +102,6 @@ gleam = { workspace = true }
|
|||
glow = "0.13.1"
|
||||
keyboard-types = { workspace = true }
|
||||
raw-window-handle = "0.6"
|
||||
servo-media = { workspace = true }
|
||||
shellwords = "1.0.0"
|
||||
surfman = { workspace = true, features = ["sm-x11", "sm-raw-window-handle-06"] }
|
||||
tinyfiledialogs = "3.0"
|
||||
|
|
|
@ -24,6 +24,17 @@ pub(crate) fn print(w: &mut dyn std::io::Write) -> Result<(), std::io::Error> {
|
|||
)
|
||||
}
|
||||
|
||||
#[cfg(target_env = "ohos")]
|
||||
pub(crate) fn print_ohos() {
|
||||
// Print to `hilog`
|
||||
log::error!(
|
||||
"{:?}",
|
||||
Print {
|
||||
print_fn_address: print as usize,
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
struct Print {
|
||||
print_fn_address: usize,
|
||||
}
|
||||
|
|
|
@ -10,6 +10,16 @@ use std::path::Path;
|
|||
use gl_generator::{Api, Fallbacks, Profile, Registry};
|
||||
use vergen::EmitBuilder;
|
||||
|
||||
// We can make this configurable in the future if different platforms start to have
|
||||
// different needs.
|
||||
fn generate_egl_bindings(out_dir: &Path) {
|
||||
let mut file = File::create(out_dir.join("egl_bindings.rs")).unwrap();
|
||||
Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [])
|
||||
.write_bindings(gl_generator::StaticStructGenerator, &mut file)
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-lib=EGL");
|
||||
}
|
||||
|
||||
fn main() -> Result<(), Box<dyn Error>> {
|
||||
// Cargo does not expose the profile name to crates or their build scripts,
|
||||
// but we can extract it from OUT_DIR and set a custom cfg() ourselves.
|
||||
|
@ -27,6 +37,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
// Note: We can't use `#[cfg(windows)]`, since that would check the host platform
|
||||
// and not the target platform
|
||||
let target_os = std::env::var("CARGO_CFG_TARGET_OS").unwrap();
|
||||
let target_env = std::env::var("CARGO_CFG_TARGET_ENV").unwrap();
|
||||
|
||||
if target_os == "windows" {
|
||||
#[cfg(windows)]
|
||||
|
@ -43,12 +54,7 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
.file("platform/macos/count_threads.c")
|
||||
.compile("count_threads");
|
||||
} else if target_os == "android" {
|
||||
// Generate GL bindings. For now, we only support EGL.
|
||||
let mut file = File::create(out.join("egl_bindings.rs")).unwrap();
|
||||
Registry::new(Api::Egl, (1, 5), Profile::Core, Fallbacks::All, [])
|
||||
.write_bindings(gl_generator::StaticStructGenerator, &mut file)
|
||||
.unwrap();
|
||||
println!("cargo:rustc-link-lib=EGL");
|
||||
generate_egl_bindings(out);
|
||||
|
||||
// FIXME: We need this workaround since jemalloc-sys still links
|
||||
// to libgcc instead of libunwind, but Android NDK 23c and above
|
||||
|
@ -59,6 +65,8 @@ fn main() -> Result<(), Box<dyn Error>> {
|
|||
let mut libgcc = File::create(out.join("libgcc.a")).unwrap();
|
||||
libgcc.write_all(b"INPUT(-lunwind)").unwrap();
|
||||
println!("cargo:rustc-link-search=native={}", out.display());
|
||||
} else if target_env == "ohos" {
|
||||
generate_egl_bindings(out);
|
||||
}
|
||||
|
||||
if let Err(error) = EmitBuilder::builder()
|
||||
|
|
|
@ -18,11 +18,13 @@ use jni::{JNIEnv, JavaVM};
|
|||
use libc::{dup2, pipe, read};
|
||||
use log::{debug, error, info, warn};
|
||||
use simpleservo::{
|
||||
Coordinates, DeviceIntRect, EventLoopWaker, HostTrait, InitOptions, InputMethodType,
|
||||
MediaSessionPlaybackState, PromptResult, ServoGlue, SERVO,
|
||||
DeviceIntRect, EventLoopWaker, InitOptions, InputMethodType, MediaSessionPlaybackState,
|
||||
PromptResult, SERVO,
|
||||
};
|
||||
|
||||
use super::gl_glue;
|
||||
use super::host_trait::HostTrait;
|
||||
use super::servo_glue::{Coordinates, ServoGlue};
|
||||
|
||||
struct HostCallbacks {
|
||||
callbacks: GlobalRef,
|
||||
|
|
|
@ -6,50 +6,36 @@ use std::cell::RefCell;
|
|||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use getopts::Options;
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use log::{debug, info, warn};
|
||||
use servo::base::id::WebViewId;
|
||||
use servo::compositing::windowing::{
|
||||
AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent,
|
||||
WindowMethods,
|
||||
};
|
||||
use log::info;
|
||||
use servo::compositing::windowing::EmbedderEvent;
|
||||
use servo::compositing::CompositeTarget;
|
||||
use servo::config::prefs::pref_map;
|
||||
pub use servo::config::prefs::{add_user_prefs, PrefValue};
|
||||
use servo::embedder_traits::resources::{self, Resource, ResourceReaderMethods};
|
||||
pub use servo::embedder_traits::{
|
||||
ContextMenuResult, InputMethodType, MediaSessionPlaybackState, PermissionPrompt,
|
||||
PermissionRequest, PromptResult,
|
||||
};
|
||||
use servo::embedder_traits::{
|
||||
EmbedderMsg, EmbedderProxy, MediaSessionEvent, PromptDefinition, PromptOrigin,
|
||||
};
|
||||
use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
|
||||
use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
|
||||
use servo::net_traits::pub_domains::is_reg_domain;
|
||||
pub use servo::script_traits::{MediaSessionActionType, MouseButton};
|
||||
use servo::script_traits::{TouchEventType, TouchId, TraversalDirection};
|
||||
use servo::servo_config::{opts, pref};
|
||||
use servo::servo_url::ServoUrl;
|
||||
pub use servo::webrender_api::units::DeviceIntRect;
|
||||
use servo::webrender_api::units::DevicePixel;
|
||||
use servo::webrender_api::ScrollLocation;
|
||||
use servo::webrender_traits::RenderingContext;
|
||||
use servo::{self, gl, Servo, TopLevelBrowsingContextId};
|
||||
use surfman::{Connection, SurfaceType};
|
||||
|
||||
thread_local! {
|
||||
pub static SERVO: RefCell<Option<ServoGlue>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
use servo::embedder_traits::resources;
|
||||
/// The EventLoopWaker::wake function will be called from any thread.
|
||||
/// It will be called to notify embedder that some events are available,
|
||||
/// and that perform_updates need to be called
|
||||
pub use servo::embedder_traits::EventLoopWaker;
|
||||
pub use servo::embedder_traits::{InputMethodType, MediaSessionPlaybackState, PromptResult};
|
||||
use servo::servo_config::{opts, pref};
|
||||
use servo::servo_url::ServoUrl;
|
||||
pub use servo::webrender_api::units::DeviceIntRect;
|
||||
use servo::webrender_traits::RenderingContext;
|
||||
use servo::{self, gl, Servo};
|
||||
use surfman::{Connection, SurfaceType};
|
||||
|
||||
use crate::egl::host_trait::HostTrait;
|
||||
use crate::egl::resources::ResourceReaderInstance;
|
||||
use crate::egl::servo_glue::{
|
||||
Coordinates, ServoEmbedderCallbacks, ServoGlue, ServoWindowCallbacks,
|
||||
};
|
||||
|
||||
thread_local! {
|
||||
pub static SERVO: RefCell<Option<ServoGlue>> = RefCell::new(None);
|
||||
}
|
||||
|
||||
pub struct InitOptions {
|
||||
pub args: Vec<String>,
|
||||
|
@ -69,121 +55,6 @@ pub enum SurfmanIntegration {
|
|||
Surface,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Coordinates {
|
||||
pub viewport: Rect<i32, DevicePixel>,
|
||||
pub framebuffer: Size2D<i32, DevicePixel>,
|
||||
}
|
||||
|
||||
impl Coordinates {
|
||||
pub fn new(
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
fb_width: i32,
|
||||
fb_height: i32,
|
||||
) -> Coordinates {
|
||||
Coordinates {
|
||||
viewport: Rect::new(Point2D::new(x, y), Size2D::new(width, height)),
|
||||
framebuffer: Size2D::new(fb_width, fb_height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Callbacks. Implemented by embedder. Called by Servo.
|
||||
pub trait HostTrait {
|
||||
/// Show alert.
|
||||
fn prompt_alert(&self, msg: String, trusted: bool);
|
||||
/// Ask Yes/No question.
|
||||
fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult;
|
||||
/// Ask Ok/Cancel question.
|
||||
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult;
|
||||
/// Ask for string
|
||||
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>;
|
||||
/// Show context menu
|
||||
fn show_context_menu(&self, title: Option<String>, items: Vec<String>);
|
||||
/// Page starts loading.
|
||||
/// "Reload button" should be disabled.
|
||||
/// "Stop button" should be enabled.
|
||||
/// Throbber starts spinning.
|
||||
fn on_load_started(&self);
|
||||
/// Page has loaded.
|
||||
/// "Reload button" should be enabled.
|
||||
/// "Stop button" should be disabled.
|
||||
/// Throbber stops spinning.
|
||||
fn on_load_ended(&self);
|
||||
/// Page title has changed.
|
||||
fn on_title_changed(&self, title: Option<String>);
|
||||
/// Allow Navigation.
|
||||
fn on_allow_navigation(&self, url: String) -> bool;
|
||||
/// Page URL has changed.
|
||||
fn on_url_changed(&self, url: String);
|
||||
/// Back/forward state has changed.
|
||||
/// Back/forward buttons need to be disabled/enabled.
|
||||
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool);
|
||||
/// Page animation state has changed. If animating, it's recommended
|
||||
/// that the embedder doesn't wait for the wake function to be called
|
||||
/// to call perform_updates. Usually, it means doing:
|
||||
/// while true { servo.perform_updates() }. This will end up calling flush
|
||||
/// which will call swap_buffer which will be blocking long enough to limit
|
||||
/// drawing at 60 FPS.
|
||||
/// If not animating, call perform_updates only when needed (when the embedder
|
||||
/// has events for Servo, or Servo has woken up the embedder event loop via
|
||||
/// EventLoopWaker).
|
||||
fn on_animating_changed(&self, animating: bool);
|
||||
/// Servo finished shutting down.
|
||||
fn on_shutdown_complete(&self);
|
||||
/// A text input is focused.
|
||||
fn on_ime_show(
|
||||
&self,
|
||||
input_type: InputMethodType,
|
||||
text: Option<(String, i32)>,
|
||||
multiline: bool,
|
||||
bounds: DeviceIntRect,
|
||||
);
|
||||
/// Input lost focus
|
||||
fn on_ime_hide(&self);
|
||||
/// Gets sytem clipboard contents.
|
||||
fn get_clipboard_contents(&self) -> Option<String>;
|
||||
/// Sets system clipboard contents.
|
||||
fn set_clipboard_contents(&self, contents: String);
|
||||
/// Called when we get the media session metadata/
|
||||
fn on_media_session_metadata(&self, title: String, artist: String, album: String);
|
||||
/// Called when the media session playback state changes.
|
||||
fn on_media_session_playback_state_change(&self, state: MediaSessionPlaybackState);
|
||||
/// Called when the media session position state is set.
|
||||
fn on_media_session_set_position_state(&self, duration: f64, position: f64, playback_rate: f64);
|
||||
/// Called when devtools server is started
|
||||
fn on_devtools_started(&self, port: Result<u16, ()>, token: String);
|
||||
/// Called when we get a panic message from constellation
|
||||
fn on_panic(&self, reason: String, backtrace: Option<String>);
|
||||
}
|
||||
|
||||
pub struct ServoGlue {
|
||||
rendering_context: RenderingContext,
|
||||
servo: Servo<ServoWindowCallbacks>,
|
||||
batch_mode: bool,
|
||||
callbacks: Rc<ServoWindowCallbacks>,
|
||||
events: Vec<EmbedderEvent>,
|
||||
context_menu_sender: Option<IpcSender<ContextMenuResult>>,
|
||||
|
||||
/// List of top-level browsing contexts.
|
||||
/// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
|
||||
/// and we exit if it ever becomes empty.
|
||||
webviews: HashMap<WebViewId, WebView>,
|
||||
|
||||
/// The order in which the webviews were created.
|
||||
creation_order: Vec<WebViewId>,
|
||||
|
||||
/// The webview that is currently focused.
|
||||
/// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
|
||||
focused_webview_id: Option<WebViewId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebView {}
|
||||
|
||||
/// Test if a url is valid.
|
||||
pub fn is_uri_valid(url: &str) -> bool {
|
||||
info!("load_uri: {}", url);
|
||||
|
@ -280,18 +151,18 @@ pub fn init(
|
|||
let rendering_context = RenderingContext::create(&connection, &adapter, surface_type)
|
||||
.or(Err("Failed to create surface manager"))?;
|
||||
|
||||
let window_callbacks = Rc::new(ServoWindowCallbacks {
|
||||
host_callbacks: callbacks,
|
||||
coordinates: RefCell::new(init_opts.coordinates),
|
||||
density: init_opts.density,
|
||||
rendering_context: rendering_context.clone(),
|
||||
});
|
||||
let window_callbacks = Rc::new(ServoWindowCallbacks::new(
|
||||
callbacks,
|
||||
RefCell::new(init_opts.coordinates),
|
||||
init_opts.density,
|
||||
rendering_context.clone(),
|
||||
));
|
||||
|
||||
let embedder_callbacks = Box::new(ServoEmbedderCallbacks {
|
||||
xr_discovery: init_opts.xr_discovery,
|
||||
let embedder_callbacks = Box::new(ServoEmbedderCallbacks::new(
|
||||
waker,
|
||||
gl: gl.clone(),
|
||||
});
|
||||
init_opts.xr_discovery,
|
||||
gl.clone(),
|
||||
));
|
||||
|
||||
let servo = Servo::new(
|
||||
embedder_callbacks,
|
||||
|
@ -301,17 +172,7 @@ pub fn init(
|
|||
);
|
||||
|
||||
SERVO.with(|s| {
|
||||
let mut servo_glue = ServoGlue {
|
||||
rendering_context,
|
||||
servo: servo.servo,
|
||||
batch_mode: false,
|
||||
callbacks: window_callbacks,
|
||||
events: vec![],
|
||||
context_menu_sender: None,
|
||||
webviews: HashMap::default(),
|
||||
creation_order: vec![],
|
||||
focused_webview_id: None,
|
||||
};
|
||||
let mut servo_glue = ServoGlue::new(rendering_context, servo.servo, window_callbacks);
|
||||
let _ = servo_glue.process_event(EmbedderEvent::NewWebView(url, servo.browser_id));
|
||||
*s.borrow_mut() = Some(servo_glue);
|
||||
});
|
||||
|
@ -322,626 +183,3 @@ pub fn init(
|
|||
pub fn deinit() {
|
||||
SERVO.with(|s| s.replace(None).unwrap().deinit());
|
||||
}
|
||||
|
||||
impl ServoGlue {
|
||||
fn get_browser_id(&self) -> Result<TopLevelBrowsingContextId, &'static str> {
|
||||
let webview_id = match self.focused_webview_id {
|
||||
Some(id) => id,
|
||||
None => return Err("No focused WebViewId yet."),
|
||||
};
|
||||
Ok(webview_id)
|
||||
}
|
||||
|
||||
/// Request shutdown. Will call on_shutdown_complete.
|
||||
pub fn request_shutdown(&mut self) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::Quit)
|
||||
}
|
||||
|
||||
/// Call after on_shutdown_complete
|
||||
pub fn deinit(self) {
|
||||
self.servo.deinit();
|
||||
}
|
||||
|
||||
/// Returns the webrender surface management integration interface.
|
||||
/// This provides the embedder access to the current front buffer.
|
||||
pub fn surfman(&self) -> RenderingContext {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
|
||||
/// This is the Servo heartbeat. This needs to be called
|
||||
/// everytime wakeup is called or when embedder wants Servo
|
||||
/// to act on its pending events.
|
||||
pub fn perform_updates(&mut self) -> Result<(), &'static str> {
|
||||
debug!("perform_updates");
|
||||
let events = mem::replace(&mut self.events, Vec::new());
|
||||
self.servo.handle_events(events);
|
||||
let r = self.handle_servo_events();
|
||||
debug!("done perform_updates");
|
||||
r
|
||||
}
|
||||
|
||||
/// In batch mode, Servo won't call perform_updates automatically.
|
||||
/// This can be useful when the embedder wants to control when Servo
|
||||
/// acts on its pending events. For example, if the embedder wants Servo
|
||||
/// to act on the scroll events only at a certain time, not everytime
|
||||
/// scroll() is called.
|
||||
pub fn set_batch_mode(&mut self, batch: bool) -> Result<(), &'static str> {
|
||||
debug!("set_batch_mode");
|
||||
self.batch_mode = batch;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an URL.
|
||||
pub fn load_uri(&mut self, url: &str) -> Result<(), &'static str> {
|
||||
info!("load_uri: {}", url);
|
||||
crate::parser::location_bar_input_to_url(url)
|
||||
.ok_or("Can't parse URL")
|
||||
.and_then(|url| {
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::LoadUrl(browser_id, url);
|
||||
self.process_event(event)
|
||||
})
|
||||
}
|
||||
|
||||
/// Reload the page.
|
||||
pub fn clear_cache(&mut self) -> Result<(), &'static str> {
|
||||
info!("clear_cache");
|
||||
let event = EmbedderEvent::ClearCache;
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Reload the page.
|
||||
pub fn reload(&mut self) -> Result<(), &'static str> {
|
||||
info!("reload");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Reload(browser_id);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Redraw the page.
|
||||
pub fn refresh(&mut self) -> Result<(), &'static str> {
|
||||
info!("refresh");
|
||||
self.process_event(EmbedderEvent::Refresh)
|
||||
}
|
||||
|
||||
/// Stop loading the page.
|
||||
pub fn stop(&mut self) -> Result<(), &'static str> {
|
||||
warn!("TODO can't stop won't stop");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Go back in history.
|
||||
pub fn go_back(&mut self) -> Result<(), &'static str> {
|
||||
info!("go_back");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Back(1));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Go forward in history.
|
||||
pub fn go_forward(&mut self) -> Result<(), &'static str> {
|
||||
info!("go_forward");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Forward(1));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Let Servo know that the window has been resized.
|
||||
pub fn resize(&mut self, coordinates: Coordinates) -> Result<(), &'static str> {
|
||||
info!("resize");
|
||||
*self.callbacks.coordinates.borrow_mut() = coordinates;
|
||||
self.process_event(EmbedderEvent::WindowResize)
|
||||
}
|
||||
|
||||
/// Start scrolling.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
pub fn scroll_start(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event =
|
||||
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Down);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Scroll.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event =
|
||||
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Move);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// End scrolling.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
pub fn scroll_end(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event = EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Up);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: press down
|
||||
pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Down,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: move touching finger
|
||||
pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Move,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: Lift touching finger
|
||||
pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Up,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Cancel touch event
|
||||
pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Cancel,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse movement.
|
||||
pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event = EmbedderEvent::MouseWindowMoveEventClass(point);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse button press.
|
||||
pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event =
|
||||
EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseDown(button, point));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse button release.
|
||||
pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event = EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(button, point));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Start pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// Pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// End pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// Perform a click.
|
||||
pub fn click(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
|
||||
let mouse_event = MouseWindowEvent::Click(MouseButton::Left, Point2D::new(x, y));
|
||||
let event = EmbedderEvent::MouseWindowEventClass(mouse_event);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
pub fn key_down(&mut self, key: Key) -> Result<(), &'static str> {
|
||||
let key_event = KeyboardEvent {
|
||||
state: KeyState::Down,
|
||||
key,
|
||||
..KeyboardEvent::default()
|
||||
};
|
||||
self.process_event(EmbedderEvent::Keyboard(key_event))
|
||||
}
|
||||
|
||||
pub fn key_up(&mut self, key: Key) -> Result<(), &'static str> {
|
||||
let key_event = KeyboardEvent {
|
||||
state: KeyState::Up,
|
||||
key,
|
||||
..KeyboardEvent::default()
|
||||
};
|
||||
self.process_event(EmbedderEvent::Keyboard(key_event))
|
||||
}
|
||||
|
||||
pub fn pause_compositor(&mut self) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::InvalidateNativeSurface)
|
||||
}
|
||||
|
||||
pub fn resume_compositor(
|
||||
&mut self,
|
||||
native_surface: *mut c_void,
|
||||
coords: Coordinates,
|
||||
) -> Result<(), &'static str> {
|
||||
if native_surface.is_null() {
|
||||
panic!("null passed for native_surface");
|
||||
}
|
||||
self.process_event(EmbedderEvent::ReplaceNativeSurface(
|
||||
native_surface,
|
||||
coords.framebuffer,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn media_session_action(
|
||||
&mut self,
|
||||
action: MediaSessionActionType,
|
||||
) -> Result<(), &'static str> {
|
||||
info!("Media session action {:?}", action);
|
||||
self.process_event(EmbedderEvent::MediaSessionAction(action))
|
||||
}
|
||||
|
||||
pub fn set_throttled(&mut self, throttled: bool) -> Result<(), &'static str> {
|
||||
info!("set_throttled");
|
||||
if let Ok(id) = self.get_browser_id() {
|
||||
let event = EmbedderEvent::SetWebViewThrottled(id, throttled);
|
||||
self.process_event(event)
|
||||
} else {
|
||||
// Ignore visibility change if no browser has been created yet.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ime_dismissed(&mut self) -> Result<(), &'static str> {
|
||||
info!("ime_dismissed");
|
||||
self.process_event(EmbedderEvent::IMEDismissed)
|
||||
}
|
||||
|
||||
pub fn on_context_menu_closed(
|
||||
&mut self,
|
||||
result: ContextMenuResult,
|
||||
) -> Result<(), &'static str> {
|
||||
if let Some(sender) = self.context_menu_sender.take() {
|
||||
let _ = sender.send(result);
|
||||
} else {
|
||||
warn!("Trying to close a context menu when no context menu is active");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn process_event(&mut self, event: EmbedderEvent) -> Result<(), &'static str> {
|
||||
self.events.push(event);
|
||||
if !self.batch_mode {
|
||||
self.perform_updates()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_servo_events(&mut self) -> Result<(), &'static str> {
|
||||
let mut need_update = false;
|
||||
let mut need_present = false;
|
||||
for (browser_id, event) in self.servo.get_events() {
|
||||
match event {
|
||||
EmbedderMsg::ChangePageTitle(title) => {
|
||||
self.callbacks.host_callbacks.on_title_changed(title);
|
||||
},
|
||||
EmbedderMsg::AllowNavigationRequest(pipeline_id, url) => {
|
||||
if let Some(_browser_id) = browser_id {
|
||||
let data: bool = self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_allow_navigation(url.to_string());
|
||||
let window_event =
|
||||
EmbedderEvent::AllowNavigationResponse(pipeline_id, data);
|
||||
self.events.push(window_event);
|
||||
need_update = true;
|
||||
}
|
||||
},
|
||||
EmbedderMsg::HistoryChanged(entries, current) => {
|
||||
let can_go_back = current > 0;
|
||||
let can_go_forward = current < entries.len() - 1;
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_history_changed(can_go_back, can_go_forward);
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_url_changed(entries[current].clone().to_string());
|
||||
},
|
||||
EmbedderMsg::LoadStart => {
|
||||
self.callbacks.host_callbacks.on_load_started();
|
||||
},
|
||||
EmbedderMsg::LoadComplete => {
|
||||
self.callbacks.host_callbacks.on_load_ended();
|
||||
},
|
||||
EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => {
|
||||
let _ = sender.send(None);
|
||||
},
|
||||
EmbedderMsg::AllowUnload(sender) => {
|
||||
let _ = sender.send(true);
|
||||
},
|
||||
EmbedderMsg::ShowContextMenu(sender, title, items) => {
|
||||
if self.context_menu_sender.is_some() {
|
||||
warn!(
|
||||
"Trying to show a context menu when a context menu is already active"
|
||||
);
|
||||
let _ = sender.send(ContextMenuResult::Ignored);
|
||||
} else {
|
||||
self.context_menu_sender = Some(sender);
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.show_context_menu(title, items);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::Prompt(definition, origin) => {
|
||||
let cb = &self.callbacks.host_callbacks;
|
||||
let trusted = origin == PromptOrigin::Trusted;
|
||||
let res = match definition {
|
||||
PromptDefinition::Alert(message, sender) => {
|
||||
sender.send(cb.prompt_alert(message, trusted))
|
||||
},
|
||||
PromptDefinition::OkCancel(message, sender) => {
|
||||
sender.send(cb.prompt_ok_cancel(message, trusted))
|
||||
},
|
||||
PromptDefinition::YesNo(message, sender) => {
|
||||
sender.send(cb.prompt_yes_no(message, trusted))
|
||||
},
|
||||
PromptDefinition::Input(message, default, sender) => {
|
||||
sender.send(cb.prompt_input(message, default, trusted))
|
||||
},
|
||||
};
|
||||
if let Err(e) = res {
|
||||
let reason = format!("Failed to send Prompt response: {}", e);
|
||||
self.events
|
||||
.push(EmbedderEvent::SendError(browser_id, reason));
|
||||
}
|
||||
},
|
||||
EmbedderMsg::AllowOpeningWebView(response_chan) => {
|
||||
// Note: would be a place to handle pop-ups config.
|
||||
// see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
|
||||
if let Err(e) = response_chan.send(true) {
|
||||
warn!("Failed to send AllowOpeningBrowser response: {}", e);
|
||||
};
|
||||
},
|
||||
EmbedderMsg::WebViewOpened(new_webview_id) => {
|
||||
self.webviews.insert(new_webview_id, WebView {});
|
||||
self.creation_order.push(new_webview_id);
|
||||
self.events
|
||||
.push(EmbedderEvent::FocusWebView(new_webview_id));
|
||||
},
|
||||
EmbedderMsg::WebViewClosed(webview_id) => {
|
||||
self.webviews.retain(|&id, _| id != webview_id);
|
||||
self.creation_order.retain(|&id| id != webview_id);
|
||||
self.focused_webview_id = None;
|
||||
if let Some(&newest_webview_id) = self.creation_order.last() {
|
||||
self.events
|
||||
.push(EmbedderEvent::FocusWebView(newest_webview_id));
|
||||
} else {
|
||||
self.events.push(EmbedderEvent::Quit);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::WebViewFocused(webview_id) => {
|
||||
self.focused_webview_id = Some(webview_id);
|
||||
},
|
||||
EmbedderMsg::WebViewBlurred => {
|
||||
self.focused_webview_id = None;
|
||||
},
|
||||
EmbedderMsg::GetClipboardContents(sender) => {
|
||||
let contents = self.callbacks.host_callbacks.get_clipboard_contents();
|
||||
let _ = sender.send(contents.unwrap_or("".to_owned()));
|
||||
},
|
||||
EmbedderMsg::SetClipboardContents(text) => {
|
||||
self.callbacks.host_callbacks.set_clipboard_contents(text);
|
||||
},
|
||||
EmbedderMsg::Shutdown => {
|
||||
self.callbacks.host_callbacks.on_shutdown_complete();
|
||||
},
|
||||
EmbedderMsg::PromptPermission(prompt, sender) => {
|
||||
let message = match prompt {
|
||||
PermissionPrompt::Request(permission_name) => {
|
||||
format!("Do you want to grant permission for {:?}?", permission_name)
|
||||
},
|
||||
PermissionPrompt::Insecure(permission_name) => {
|
||||
format!(
|
||||
"The {:?} feature is only safe to use in secure context, but servo can't guarantee\n\
|
||||
that the current context is secure. Do you want to proceed and grant permission?",
|
||||
permission_name
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let result = match self.callbacks.host_callbacks.prompt_yes_no(message, true) {
|
||||
PromptResult::Primary => PermissionRequest::Granted,
|
||||
PromptResult::Secondary | PromptResult::Dismissed => {
|
||||
PermissionRequest::Denied
|
||||
},
|
||||
};
|
||||
|
||||
let _ = sender.send(result);
|
||||
},
|
||||
EmbedderMsg::ShowIME(kind, text, multiline, bounds) => {
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_ime_show(kind, text, multiline, bounds);
|
||||
},
|
||||
EmbedderMsg::HideIME => {
|
||||
self.callbacks.host_callbacks.on_ime_hide();
|
||||
},
|
||||
EmbedderMsg::MediaSessionEvent(event) => {
|
||||
match event {
|
||||
MediaSessionEvent::SetMetadata(metadata) => {
|
||||
self.callbacks.host_callbacks.on_media_session_metadata(
|
||||
metadata.title,
|
||||
metadata.artist,
|
||||
metadata.album,
|
||||
)
|
||||
},
|
||||
MediaSessionEvent::PlaybackStateChange(state) => self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_media_session_playback_state_change(state),
|
||||
MediaSessionEvent::SetPositionState(position_state) => self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_media_session_set_position_state(
|
||||
position_state.duration,
|
||||
position_state.position,
|
||||
position_state.playback_rate,
|
||||
),
|
||||
};
|
||||
},
|
||||
EmbedderMsg::OnDevtoolsStarted(port, token) => {
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_devtools_started(port, token);
|
||||
},
|
||||
EmbedderMsg::Panic(reason, backtrace) => {
|
||||
self.callbacks.host_callbacks.on_panic(reason, backtrace);
|
||||
},
|
||||
EmbedderMsg::ReadyToPresent(_webview_ids) => {
|
||||
need_present = true;
|
||||
},
|
||||
EmbedderMsg::Status(..) |
|
||||
EmbedderMsg::SelectFiles(..) |
|
||||
EmbedderMsg::MoveTo(..) |
|
||||
EmbedderMsg::ResizeTo(..) |
|
||||
EmbedderMsg::Keyboard(..) |
|
||||
EmbedderMsg::SetCursor(..) |
|
||||
EmbedderMsg::NewFavicon(..) |
|
||||
EmbedderMsg::HeadParsed |
|
||||
EmbedderMsg::SetFullscreenState(..) |
|
||||
EmbedderMsg::ReportProfile(..) |
|
||||
EmbedderMsg::EventDelivered(..) => {},
|
||||
}
|
||||
}
|
||||
|
||||
if need_update {
|
||||
let _ = self.perform_updates();
|
||||
}
|
||||
if need_present {
|
||||
self.servo.present();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
struct ServoEmbedderCallbacks {
|
||||
waker: Box<dyn EventLoopWaker>,
|
||||
xr_discovery: Option<webxr::Discovery>,
|
||||
#[allow(unused)]
|
||||
gl: Rc<dyn gl::Gl>,
|
||||
}
|
||||
|
||||
struct ServoWindowCallbacks {
|
||||
host_callbacks: Box<dyn HostTrait>,
|
||||
coordinates: RefCell<Coordinates>,
|
||||
density: f32,
|
||||
rendering_context: RenderingContext,
|
||||
}
|
||||
|
||||
impl EmbedderMethods for ServoEmbedderCallbacks {
|
||||
fn register_webxr(
|
||||
&mut self,
|
||||
registry: &mut webxr::MainThreadRegistry,
|
||||
_embedder_proxy: EmbedderProxy,
|
||||
) {
|
||||
debug!("EmbedderMethods::register_xr");
|
||||
if let Some(discovery) = self.xr_discovery.take() {
|
||||
registry.register(discovery);
|
||||
}
|
||||
}
|
||||
|
||||
fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
|
||||
debug!("EmbedderMethods::create_event_loop_waker");
|
||||
self.waker.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowMethods for ServoWindowCallbacks {
|
||||
fn rendering_context(&self) -> RenderingContext {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
|
||||
fn set_animation_state(&self, state: AnimationState) {
|
||||
debug!("WindowMethods::set_animation_state: {:?}", state);
|
||||
self.host_callbacks
|
||||
.on_animating_changed(state == AnimationState::Animating);
|
||||
}
|
||||
|
||||
fn get_coordinates(&self) -> EmbedderCoordinates {
|
||||
let coords = self.coordinates.borrow();
|
||||
EmbedderCoordinates {
|
||||
viewport: coords.viewport.to_box2d(),
|
||||
framebuffer: coords.framebuffer,
|
||||
window: (coords.viewport.size, Point2D::new(0, 0)),
|
||||
screen: coords.viewport.size,
|
||||
screen_avail: coords.viewport.size,
|
||||
hidpi_factor: Scale::new(self.density),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct ResourceReaderInstance;
|
||||
|
||||
impl ResourceReaderInstance {
|
||||
fn new() -> ResourceReaderInstance {
|
||||
ResourceReaderInstance
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceReaderMethods for ResourceReaderInstance {
|
||||
fn read(&self, res: Resource) -> Vec<u8> {
|
||||
Vec::from(match res {
|
||||
Resource::Preferences => &include_bytes!("../../../../resources/prefs.json")[..],
|
||||
Resource::HstsPreloadList => {
|
||||
&include_bytes!("../../../../resources/hsts_preload.json")[..]
|
||||
},
|
||||
Resource::BadCertHTML => &include_bytes!("../../../../resources/badcert.html")[..],
|
||||
Resource::NetErrorHTML => &include_bytes!("../../../../resources/neterror.html")[..],
|
||||
Resource::UserAgentCSS => &include_bytes!("../../../../resources/user-agent.css")[..],
|
||||
Resource::ServoCSS => &include_bytes!("../../../../resources/servo.css")[..],
|
||||
Resource::PresentationalHintsCSS => {
|
||||
&include_bytes!("../../../../resources/presentational-hints.css")[..]
|
||||
},
|
||||
Resource::QuirksModeCSS => &include_bytes!("../../../../resources/quirks-mode.css")[..],
|
||||
Resource::RippyPNG => &include_bytes!("../../../../resources/rippy.png")[..],
|
||||
Resource::DomainList => &include_bytes!("../../../../resources/public_domains.txt")[..],
|
||||
Resource::BluetoothBlocklist => {
|
||||
&include_bytes!("../../../../resources/gatt_blocklist.txt")[..]
|
||||
},
|
||||
Resource::MediaControlsCSS => {
|
||||
&include_bytes!("../../../../resources/media-controls.css")[..]
|
||||
},
|
||||
Resource::MediaControlsJS => {
|
||||
&include_bytes!("../../../../resources/media-controls.js")[..]
|
||||
},
|
||||
Resource::CrashHTML => &include_bytes!("../../../../resources/crash.html")[..],
|
||||
Resource::DirectoryListingHTML => {
|
||||
&include_bytes!("../../../../resources/directory-listing.html")[..]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn sandbox_access_files(&self) -> Vec<PathBuf> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
|
||||
vec![]
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
* 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/. */
|
||||
#![allow(non_camel_case_types)]
|
||||
#![allow(unused_imports)]
|
||||
|
||||
pub type ServoGl = std::rc::Rc<dyn servo::gl::Gl>;
|
||||
|
||||
|
|
75
ports/servoshell/egl/host_trait.rs
Normal file
75
ports/servoshell/egl/host_trait.rs
Normal file
|
@ -0,0 +1,75 @@
|
|||
/* 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 servo::embedder_traits::{InputMethodType, MediaSessionPlaybackState, PromptResult};
|
||||
use servo::webrender_api::units::DeviceIntRect;
|
||||
|
||||
/// Callbacks. Implemented by embedder. Called by Servo.
|
||||
pub trait HostTrait {
|
||||
/// Show alert.
|
||||
fn prompt_alert(&self, msg: String, trusted: bool);
|
||||
/// Ask Yes/No question.
|
||||
fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult;
|
||||
/// Ask Ok/Cancel question.
|
||||
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult;
|
||||
/// Ask for string
|
||||
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String>;
|
||||
/// Show context menu
|
||||
fn show_context_menu(&self, title: Option<String>, items: Vec<String>);
|
||||
/// Page starts loading.
|
||||
/// "Reload button" should be disabled.
|
||||
/// "Stop button" should be enabled.
|
||||
/// Throbber starts spinning.
|
||||
fn on_load_started(&self);
|
||||
/// Page has loaded.
|
||||
/// "Reload button" should be enabled.
|
||||
/// "Stop button" should be disabled.
|
||||
/// Throbber stops spinning.
|
||||
fn on_load_ended(&self);
|
||||
/// Page title has changed.
|
||||
fn on_title_changed(&self, title: Option<String>);
|
||||
/// Allow Navigation.
|
||||
fn on_allow_navigation(&self, url: String) -> bool;
|
||||
/// Page URL has changed.
|
||||
fn on_url_changed(&self, url: String);
|
||||
/// Back/forward state has changed.
|
||||
/// Back/forward buttons need to be disabled/enabled.
|
||||
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool);
|
||||
/// Page animation state has changed. If animating, it's recommended
|
||||
/// that the embedder doesn't wait for the wake function to be called
|
||||
/// to call perform_updates. Usually, it means doing:
|
||||
/// while true { servo.perform_updates() }. This will end up calling flush
|
||||
/// which will call swap_buffer which will be blocking long enough to limit
|
||||
/// drawing at 60 FPS.
|
||||
/// If not animating, call perform_updates only when needed (when the embedder
|
||||
/// has events for Servo, or Servo has woken up the embedder event loop via
|
||||
/// EventLoopWaker).
|
||||
fn on_animating_changed(&self, animating: bool);
|
||||
/// Servo finished shutting down.
|
||||
fn on_shutdown_complete(&self);
|
||||
/// A text input is focused.
|
||||
fn on_ime_show(
|
||||
&self,
|
||||
input_type: InputMethodType,
|
||||
text: Option<(String, i32)>,
|
||||
multiline: bool,
|
||||
bounds: DeviceIntRect,
|
||||
);
|
||||
/// Input lost focus
|
||||
fn on_ime_hide(&self);
|
||||
/// Gets sytem clipboard contents.
|
||||
fn get_clipboard_contents(&self) -> Option<String>;
|
||||
/// Sets system clipboard contents.
|
||||
fn set_clipboard_contents(&self, contents: String);
|
||||
/// Called when we get the media session metadata/
|
||||
fn on_media_session_metadata(&self, title: String, artist: String, album: String);
|
||||
/// Called when the media session playback state changes.
|
||||
fn on_media_session_playback_state_change(&self, state: MediaSessionPlaybackState);
|
||||
/// Called when the media session position state is set.
|
||||
fn on_media_session_set_position_state(&self, duration: f64, position: f64, playback_rate: f64);
|
||||
/// Called when devtools server is started
|
||||
fn on_devtools_started(&self, port: Result<u16, ()>, token: String);
|
||||
/// Called when we get a panic message from constellation
|
||||
fn on_panic(&self, reason: String, backtrace: Option<String>);
|
||||
}
|
|
@ -2,8 +2,15 @@
|
|||
* 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/. */
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
#[cfg(any(target_os = "android", target_env = "ohos"))]
|
||||
pub mod gl_glue;
|
||||
|
||||
#[cfg(target_os = "android")]
|
||||
mod android;
|
||||
|
||||
#[cfg(target_env = "ohos")]
|
||||
mod ohos;
|
||||
|
||||
mod host_trait;
|
||||
mod resources;
|
||||
mod servo_glue;
|
||||
|
|
619
ports/servoshell/egl/ohos.rs
Normal file
619
ports/servoshell/egl/ohos.rs
Normal file
|
@ -0,0 +1,619 @@
|
|||
/* 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/. */
|
||||
#![allow(non_snake_case)]
|
||||
|
||||
use std::mem::MaybeUninit;
|
||||
use std::os::raw::c_void;
|
||||
use std::sync::mpsc::{Receiver, Sender};
|
||||
use std::sync::{mpsc, Once, OnceLock};
|
||||
use std::thread;
|
||||
use std::thread::sleep;
|
||||
use std::time::Duration;
|
||||
|
||||
use log::{debug, error, info, warn, LevelFilter};
|
||||
use napi_derive_ohos::{module_exports, napi};
|
||||
use napi_ohos::bindgen_prelude::Undefined;
|
||||
use napi_ohos::threadsafe_function::{
|
||||
ErrorStrategy, ThreadsafeFunction, ThreadsafeFunctionCallMode,
|
||||
};
|
||||
use napi_ohos::{Env, JsFunction, JsObject, JsString, NapiRaw};
|
||||
use ohos_sys::ace::xcomponent::native_interface_xcomponent::{
|
||||
OH_NativeXComponent, OH_NativeXComponent_Callback, OH_NativeXComponent_GetTouchEvent,
|
||||
OH_NativeXComponent_TouchEvent, OH_NativeXComponent_TouchEventType,
|
||||
};
|
||||
use servo::embedder_traits::PromptResult;
|
||||
use servo::euclid::Point2D;
|
||||
use servo::style::Zero;
|
||||
use simpleservo::EventLoopWaker;
|
||||
|
||||
use super::gl_glue;
|
||||
use super::host_trait::HostTrait;
|
||||
use super::servo_glue::ServoGlue;
|
||||
|
||||
mod simpleservo;
|
||||
|
||||
// Todo: in the future these libraries should be added by Rust sys-crates
|
||||
#[link(name = "ace_napi.z")]
|
||||
#[link(name = "ace_ndk.z")]
|
||||
#[link(name = "hilog_ndk.z")]
|
||||
#[link(name = "native_window")]
|
||||
#[link(name = "clang_rt.builtins", kind = "static")]
|
||||
extern "C" {}
|
||||
|
||||
#[napi(object)]
|
||||
#[derive(Debug)]
|
||||
pub struct InitOpts {
|
||||
pub url: String,
|
||||
pub device_type: String,
|
||||
pub os_full_name: String,
|
||||
pub display_density: f64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum CallError {
|
||||
ChannelNotInitialized,
|
||||
ChannelDied,
|
||||
}
|
||||
|
||||
fn call(action: ServoAction) -> Result<(), CallError> {
|
||||
let tx = SERVO_CHANNEL
|
||||
.get()
|
||||
.ok_or(CallError::ChannelNotInitialized)?;
|
||||
tx.send(action).map_err(|_| CallError::ChannelDied)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[repr(transparent)]
|
||||
struct XComponentWrapper(*mut OH_NativeXComponent);
|
||||
#[repr(transparent)]
|
||||
struct WindowWrapper(*mut c_void);
|
||||
unsafe impl Send for XComponentWrapper {}
|
||||
unsafe impl Send for WindowWrapper {}
|
||||
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
enum TouchEventType {
|
||||
Down,
|
||||
Up,
|
||||
Move,
|
||||
Scroll { dx: f32, dy: f32 },
|
||||
Cancel,
|
||||
Unknown,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ServoAction {
|
||||
WakeUp,
|
||||
LoadUrl(String),
|
||||
TouchEvent {
|
||||
kind: TouchEventType,
|
||||
x: f32,
|
||||
y: f32,
|
||||
pointer_id: i32,
|
||||
},
|
||||
Initialize(Box<InitOpts>),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default)]
|
||||
enum Direction2D {
|
||||
Horizontal,
|
||||
Vertical,
|
||||
#[default]
|
||||
Free,
|
||||
}
|
||||
#[derive(Clone, Debug)]
|
||||
struct TouchTracker {
|
||||
last_position: Point2D<f32, f32>,
|
||||
}
|
||||
|
||||
impl TouchTracker {
|
||||
fn new(first_point: Point2D<f32, f32>) -> Self {
|
||||
TouchTracker {
|
||||
last_position: first_point,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Todo: Need to check if OnceLock is suitable, or if the TS function can be destroyed, e.g.
|
||||
// if the activity gets suspended.
|
||||
static SET_URL_BAR_CB: OnceLock<ThreadsafeFunction<String, ErrorStrategy::Fatal>> = OnceLock::new();
|
||||
|
||||
struct TsThreadState {
|
||||
// last_touch_event: Option<OH_NativeXComponent_TouchEvent>,
|
||||
velocity_tracker: Option<TouchTracker>,
|
||||
}
|
||||
|
||||
impl TsThreadState {
|
||||
const fn new() -> Self {
|
||||
Self {
|
||||
velocity_tracker: None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static mut TS_THREAD_STATE: TsThreadState = TsThreadState::new();
|
||||
|
||||
impl ServoAction {
|
||||
fn dispatch_touch_event(
|
||||
servo: &mut ServoGlue,
|
||||
kind: TouchEventType,
|
||||
x: f32,
|
||||
y: f32,
|
||||
pointer_id: i32,
|
||||
) -> Result<(), &'static str> {
|
||||
match kind {
|
||||
TouchEventType::Down => servo.touch_down(x, y, pointer_id),
|
||||
TouchEventType::Up => servo.touch_up(x, y, pointer_id),
|
||||
TouchEventType::Scroll { dx, dy } => servo.scroll(dx, dy, x as i32, y as i32),
|
||||
TouchEventType::Move => servo.touch_move(x, y, pointer_id),
|
||||
TouchEventType::Cancel => servo.touch_cancel(x, y, pointer_id),
|
||||
TouchEventType::Unknown => Err("Can't dispatch Unknown Touch Event"),
|
||||
}
|
||||
}
|
||||
|
||||
fn do_action(&self, servo: &mut ServoGlue) {
|
||||
use ServoAction::*;
|
||||
let res = match self {
|
||||
WakeUp => servo.perform_updates(),
|
||||
LoadUrl(url) => servo.load_uri(url.as_str()),
|
||||
TouchEvent {
|
||||
kind,
|
||||
x,
|
||||
y,
|
||||
pointer_id,
|
||||
} => Self::dispatch_touch_event(servo, *kind, *x, *y, *pointer_id),
|
||||
Initialize(_init_opts) => {
|
||||
panic!("Received Initialize event, even though servo is already initialized")
|
||||
},
|
||||
};
|
||||
if let Err(e) = res {
|
||||
error!("Failed to do {self:?} with error {e}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static SERVO_CHANNEL: OnceLock<Sender<ServoAction>> = OnceLock::new();
|
||||
|
||||
#[no_mangle]
|
||||
pub extern "C" fn on_surface_created_cb(xcomponent: *mut OH_NativeXComponent, window: *mut c_void) {
|
||||
info!("on_surface_created_cb");
|
||||
|
||||
let xc_wrapper = XComponentWrapper(xcomponent);
|
||||
let window_wrapper = WindowWrapper(window);
|
||||
|
||||
// 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");
|
||||
|
||||
let wakeup = Box::new(WakeupCallback::new(tx));
|
||||
let callbacks = Box::new(HostCallbacks::new());
|
||||
|
||||
let egl_init = gl_glue::init().expect("egl::init() failed");
|
||||
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 mut servo = simpleservo::init(
|
||||
*init_opts,
|
||||
window.0,
|
||||
xc.0,
|
||||
egl_init.gl_wrapper,
|
||||
wakeup,
|
||||
callbacks,
|
||||
)
|
||||
.expect("Servo initialization failed");
|
||||
|
||||
info!("Surface created!");
|
||||
|
||||
while let Ok(action) = rx.recv() {
|
||||
info!("Wakeup message received!");
|
||||
action.do_action(&mut servo);
|
||||
}
|
||||
|
||||
info!("Sender disconnected - Terminating main surface thread");
|
||||
});
|
||||
|
||||
info!("Returning from on_surface_created_cb");
|
||||
}
|
||||
|
||||
// Todo: Probably we need to block here, until the main thread has processed the change.
|
||||
pub extern "C" fn on_surface_changed_cb(
|
||||
_component: *mut OH_NativeXComponent,
|
||||
_window: *mut c_void,
|
||||
) {
|
||||
error!("on_surface_changed_cb is currently not implemented!");
|
||||
}
|
||||
|
||||
pub extern "C" fn on_surface_destroyed_cb(
|
||||
_component: *mut OH_NativeXComponent,
|
||||
_window: *mut c_void,
|
||||
) {
|
||||
error!("on_surface_destroyed_cb is currently not implemented");
|
||||
}
|
||||
|
||||
pub extern "C" fn on_dispatch_touch_event_cb(
|
||||
component: *mut OH_NativeXComponent,
|
||||
window: *mut c_void,
|
||||
) {
|
||||
info!("DispatchTouchEvent");
|
||||
let mut touch_event: MaybeUninit<OH_NativeXComponent_TouchEvent> = MaybeUninit::uninit();
|
||||
let res =
|
||||
unsafe { OH_NativeXComponent_GetTouchEvent(component, window, touch_event.as_mut_ptr()) };
|
||||
if res != 0 {
|
||||
error!("OH_NativeXComponent_GetTouchEvent failed with {res}");
|
||||
return;
|
||||
}
|
||||
let touch_event = unsafe { touch_event.assume_init() };
|
||||
let kind: TouchEventType =
|
||||
match touch_event.type_ {
|
||||
OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_DOWN => {
|
||||
if touch_event.id == 0 {
|
||||
unsafe {
|
||||
let old = TS_THREAD_STATE.velocity_tracker.replace(TouchTracker::new(
|
||||
Point2D::new(touch_event.x, touch_event.y),
|
||||
));
|
||||
assert!(old.is_none());
|
||||
}
|
||||
}
|
||||
TouchEventType::Down
|
||||
},
|
||||
OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_UP => {
|
||||
if touch_event.id == 0 {
|
||||
unsafe {
|
||||
let old = TS_THREAD_STATE.velocity_tracker.take();
|
||||
assert!(old.is_some());
|
||||
}
|
||||
}
|
||||
TouchEventType::Up
|
||||
},
|
||||
OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_MOVE => {
|
||||
// SAFETY: We only access TS_THREAD_STATE from the main TS thread.
|
||||
if touch_event.id == 0 {
|
||||
let (lastX, lastY) = unsafe {
|
||||
if let Some(last_event) = &mut TS_THREAD_STATE.velocity_tracker {
|
||||
let touch_point = last_event.last_position;
|
||||
last_event.last_position = Point2D::new(touch_event.x, touch_event.y);
|
||||
(touch_point.x, touch_point.y)
|
||||
} else {
|
||||
error!("Move Event received, but no previous touch event was stored!");
|
||||
// todo: handle this error case
|
||||
panic!("Move Event received, but no previous touch event was stored!");
|
||||
}
|
||||
};
|
||||
let dx = touch_event.x - lastX;
|
||||
let dy = touch_event.y - lastY;
|
||||
TouchEventType::Scroll { dx, dy }
|
||||
} else {
|
||||
TouchEventType::Move
|
||||
}
|
||||
},
|
||||
OH_NativeXComponent_TouchEventType::OH_NATIVEXCOMPONENT_CANCEL => {
|
||||
if touch_event.id == 0 {
|
||||
unsafe {
|
||||
let old = TS_THREAD_STATE.velocity_tracker.take();
|
||||
assert!(old.is_some());
|
||||
}
|
||||
}
|
||||
TouchEventType::Cancel
|
||||
},
|
||||
_ => {
|
||||
error!(
|
||||
"Failed to dispatch call for touch Event {:?}",
|
||||
touch_event.type_
|
||||
);
|
||||
TouchEventType::Unknown
|
||||
},
|
||||
};
|
||||
if let Err(e) = call(ServoAction::TouchEvent {
|
||||
kind,
|
||||
x: touch_event.x,
|
||||
y: touch_event.y,
|
||||
pointer_id: touch_event.id,
|
||||
}) {
|
||||
error!("Failed to dispatch call for touch Event {kind:?}: {e:?}");
|
||||
}
|
||||
}
|
||||
|
||||
fn initialize_logging_once() {
|
||||
static ONCE: Once = Once::new();
|
||||
ONCE.call_once(|| {
|
||||
let mut builder = hilog::Builder::new();
|
||||
builder.filter_level(LevelFilter::Debug).init();
|
||||
|
||||
info!("Servo Register callback called!");
|
||||
|
||||
std::panic::set_hook(Box::new(|info| {
|
||||
error!("Panic in Rust code");
|
||||
error!("PanicInfo: {info}");
|
||||
let msg = match info.payload().downcast_ref::<&'static str>() {
|
||||
Some(s) => *s,
|
||||
None => match info.payload().downcast_ref::<String>() {
|
||||
Some(s) => &**s,
|
||||
None => "Box<Any>",
|
||||
},
|
||||
};
|
||||
let current_thread = thread::current();
|
||||
let name = current_thread.name().unwrap_or("<unnamed>");
|
||||
if let Some(location) = info.location() {
|
||||
let _ = error!(
|
||||
"{} (thread {}, at {}:{})",
|
||||
msg,
|
||||
name,
|
||||
location.file(),
|
||||
location.line()
|
||||
);
|
||||
} else {
|
||||
let _ = error!("{} (thread {})", msg, name);
|
||||
}
|
||||
|
||||
let _ = crate::backtrace::print_ohos();
|
||||
}));
|
||||
})
|
||||
}
|
||||
|
||||
fn register_xcomponent_callbacks(env: &Env, xcomponent: &JsObject) -> napi_ohos::Result<()> {
|
||||
info!("napi_get_named_property call successfull");
|
||||
let raw = unsafe { xcomponent.raw() };
|
||||
let raw_env = env.raw();
|
||||
let mut nativeXComponent: *mut OH_NativeXComponent = core::ptr::null_mut();
|
||||
unsafe {
|
||||
let res = napi_ohos::sys::napi_unwrap(
|
||||
raw_env,
|
||||
raw,
|
||||
&mut nativeXComponent as *mut *mut OH_NativeXComponent as *mut *mut c_void,
|
||||
);
|
||||
assert!(res.is_zero());
|
||||
}
|
||||
info!("Got nativeXComponent!");
|
||||
let cbs = Box::new(OH_NativeXComponent_Callback {
|
||||
OnSurfaceCreated: Some(on_surface_created_cb),
|
||||
OnSurfaceChanged: Some(on_surface_changed_cb),
|
||||
OnSurfaceDestroyed: Some(on_surface_destroyed_cb),
|
||||
DispatchTouchEvent: Some(on_dispatch_touch_event_cb),
|
||||
});
|
||||
use ohos_sys::ace::xcomponent::native_interface_xcomponent::OH_NativeXComponent_RegisterCallback;
|
||||
let res =
|
||||
unsafe { OH_NativeXComponent_RegisterCallback(nativeXComponent, Box::leak(cbs) as *mut _) };
|
||||
if res != 0 {
|
||||
error!("Failed to register callbacks");
|
||||
} else {
|
||||
info!("Registerd callbacks successfully");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
fn debug_jsobject(obj: &JsObject, obj_name: &str) -> napi_ohos::Result<()> {
|
||||
let names = obj.get_property_names()?;
|
||||
error!("Getting property names of object {obj_name}");
|
||||
let len = names.get_array_length()?;
|
||||
error!("{obj_name} has {len} elements");
|
||||
for i in 0..len {
|
||||
let name: JsString = names.get_element(i)?;
|
||||
let name = name.into_utf8()?;
|
||||
error!("{obj_name} property {i}: {}", name.as_str()?)
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[module_exports]
|
||||
fn init(exports: JsObject, env: Env) -> napi_ohos::Result<()> {
|
||||
initialize_logging_once();
|
||||
info!("simpleservo init function called");
|
||||
if let Ok(xcomponent) = exports.get_named_property::<JsObject>("__NATIVE_XCOMPONENT_OBJ__") {
|
||||
register_xcomponent_callbacks(&env, &xcomponent)?;
|
||||
}
|
||||
|
||||
info!("Finished init");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi(js_name = "loadURL")]
|
||||
pub fn load_url(url: String) -> Undefined {
|
||||
debug!("load url");
|
||||
call(ServoAction::LoadUrl(url)).expect("Failed to load url");
|
||||
}
|
||||
|
||||
#[napi(js_name = "registerURLcallback")]
|
||||
pub fn register_url_callback(cb: JsFunction) -> napi_ohos::Result<()> {
|
||||
info!("register_url_callback called!");
|
||||
let tsfn: ThreadsafeFunction<String, ErrorStrategy::Fatal> =
|
||||
cb.create_threadsafe_function(1, |ctx| {
|
||||
debug!(
|
||||
"url callback argument transformer called with arg {}",
|
||||
ctx.value
|
||||
);
|
||||
let s = ctx
|
||||
.env
|
||||
.create_string_from_std(ctx.value)
|
||||
.inspect_err(|e| error!("Failed to create JsString: {e:?}"))?;
|
||||
Ok(vec![s])
|
||||
})?;
|
||||
// We ignore any error for now - but probably we should propagate it back to the TS layer.
|
||||
let _ = SET_URL_BAR_CB
|
||||
.set(tsfn)
|
||||
.inspect_err(|_| warn!("Failed to set URL callback - register_url_callback called twice?"));
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[napi]
|
||||
pub fn init_servo(init_opts: InitOpts) -> napi_ohos::Result<()> {
|
||||
info!("Servo is being initialised with the following Options: ");
|
||||
info!(
|
||||
"Device Type: {}, DisplayDensity: {}",
|
||||
init_opts.device_type, init_opts.display_density
|
||||
);
|
||||
info!("Initial URL: {}", init_opts.url);
|
||||
let channel = if let Some(channel) = SERVO_CHANNEL.get() {
|
||||
channel
|
||||
} else {
|
||||
warn!("Servo GL thread has not initialized yet. Retrying.....");
|
||||
let mut iter_count = 0;
|
||||
loop {
|
||||
if let Some(channel) = SERVO_CHANNEL.get() {
|
||||
break channel;
|
||||
} else {
|
||||
iter_count += 1;
|
||||
if iter_count > 10 {
|
||||
error!("Servo GL thread not reachable");
|
||||
panic!("Servo GL thread not reachable");
|
||||
}
|
||||
sleep(Duration::from_millis(100));
|
||||
}
|
||||
}
|
||||
};
|
||||
channel
|
||||
.send(ServoAction::Initialize(Box::new(init_opts)))
|
||||
.expect("Failed to connect to servo GL thread");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct WakeupCallback {
|
||||
chan: Sender<ServoAction>,
|
||||
}
|
||||
|
||||
impl WakeupCallback {
|
||||
pub(crate) fn new(chan: Sender<ServoAction>) -> Self {
|
||||
WakeupCallback { chan }
|
||||
}
|
||||
}
|
||||
|
||||
impl EventLoopWaker for WakeupCallback {
|
||||
fn clone_box(&self) -> Box<dyn EventLoopWaker> {
|
||||
Box::new(self.clone())
|
||||
}
|
||||
|
||||
fn wake(&self) {
|
||||
info!("wake called!");
|
||||
self.chan.send(ServoAction::WakeUp).unwrap_or_else(|e| {
|
||||
error!("Failed to send wake message with: {e}");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
struct HostCallbacks {}
|
||||
|
||||
impl HostCallbacks {
|
||||
pub fn new() -> Self {
|
||||
HostCallbacks {}
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl HostTrait for HostCallbacks {
|
||||
fn prompt_alert(&self, msg: String, trusted: bool) {
|
||||
warn!("prompt_alert not implemented. Cancelled. {}", msg);
|
||||
}
|
||||
|
||||
fn prompt_yes_no(&self, msg: String, trusted: bool) -> PromptResult {
|
||||
warn!("Prompt not implemented. Cancelled. {}", msg);
|
||||
PromptResult::Secondary
|
||||
}
|
||||
|
||||
fn prompt_ok_cancel(&self, msg: String, trusted: bool) -> PromptResult {
|
||||
warn!("Prompt not implemented. Cancelled. {}", msg);
|
||||
PromptResult::Secondary
|
||||
}
|
||||
|
||||
fn prompt_input(&self, msg: String, default: String, trusted: bool) -> Option<String> {
|
||||
warn!("Input prompt not implemented. Cancelled. {}", msg);
|
||||
Some(default)
|
||||
}
|
||||
|
||||
fn show_context_menu(&self, title: Option<String>, items: Vec<String>) {
|
||||
warn!("show_context_menu not implemented")
|
||||
}
|
||||
|
||||
fn on_load_started(&self) {
|
||||
warn!("on_load_started not implemented")
|
||||
}
|
||||
|
||||
fn on_load_ended(&self) {
|
||||
warn!("on_load_ended not implemented")
|
||||
}
|
||||
|
||||
fn on_title_changed(&self, title: Option<String>) {
|
||||
warn!("on_title_changed not implemented")
|
||||
}
|
||||
|
||||
fn on_allow_navigation(&self, url: String) -> bool {
|
||||
true
|
||||
}
|
||||
|
||||
fn on_url_changed(&self, url: String) {
|
||||
debug!("Hosttrait `on_url_changed` called with new url: {url}");
|
||||
if let Some(cb) = SET_URL_BAR_CB.get() {
|
||||
cb.call(url, ThreadsafeFunctionCallMode::Blocking);
|
||||
} else {
|
||||
warn!("`on_url_changed` called without a registered callback")
|
||||
}
|
||||
}
|
||||
|
||||
fn on_history_changed(&self, can_go_back: bool, can_go_forward: bool) {}
|
||||
|
||||
fn on_animating_changed(&self, animating: bool) {}
|
||||
|
||||
fn on_shutdown_complete(&self) {}
|
||||
|
||||
fn on_ime_show(
|
||||
&self,
|
||||
input_type: servo::embedder_traits::InputMethodType,
|
||||
text: Option<(String, i32)>,
|
||||
multiline: bool,
|
||||
bounds: servo::webrender_api::units::DeviceIntRect,
|
||||
) {
|
||||
warn!("on_title_changed not implemented")
|
||||
}
|
||||
|
||||
fn on_ime_hide(&self) {
|
||||
warn!("on_title_changed not implemented")
|
||||
}
|
||||
|
||||
fn get_clipboard_contents(&self) -> Option<String> {
|
||||
warn!("get_clipboard_contents not implemented");
|
||||
None
|
||||
}
|
||||
|
||||
fn set_clipboard_contents(&self, contents: String) {
|
||||
warn!("set_clipboard_contents not implemented");
|
||||
}
|
||||
|
||||
fn on_media_session_metadata(&self, title: String, artist: String, album: String) {
|
||||
warn!("on_media_session_metadata not implemented");
|
||||
}
|
||||
|
||||
fn on_media_session_playback_state_change(
|
||||
&self,
|
||||
state: servo::embedder_traits::MediaSessionPlaybackState,
|
||||
) {
|
||||
warn!("on_media_session_playback_state_change not implemented");
|
||||
}
|
||||
|
||||
fn on_media_session_set_position_state(
|
||||
&self,
|
||||
duration: f64,
|
||||
position: f64,
|
||||
playback_rate: f64,
|
||||
) {
|
||||
warn!("on_media_session_set_position_state not implemented");
|
||||
}
|
||||
|
||||
fn on_devtools_started(&self, port: Result<u16, ()>, token: String) {
|
||||
warn!("on_devtools_started not implemented");
|
||||
}
|
||||
|
||||
fn on_panic(&self, reason: String, backtrace: Option<String>) {
|
||||
error!("Panic: {reason},");
|
||||
if let Some(bt) = backtrace {
|
||||
error!("Backtrace: {bt:?}")
|
||||
}
|
||||
}
|
||||
}
|
110
ports/servoshell/egl/ohos/simpleservo.rs
Normal file
110
ports/servoshell/egl/ohos/simpleservo.rs
Normal file
|
@ -0,0 +1,110 @@
|
|||
/* 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::cell::RefCell;
|
||||
use std::convert::TryInto;
|
||||
use std::os::raw::c_void;
|
||||
use std::path::PathBuf;
|
||||
use std::rc::Rc;
|
||||
|
||||
use log::{debug, error, info};
|
||||
use ohos_sys::ace::xcomponent::native_interface_xcomponent::{
|
||||
OH_NativeXComponent, OH_NativeXComponent_GetXComponentSize,
|
||||
};
|
||||
use servo::compositing::windowing::EmbedderEvent;
|
||||
use servo::compositing::CompositeTarget;
|
||||
use servo::embedder_traits::resources;
|
||||
/// The EventLoopWaker::wake function will be called from any thread.
|
||||
/// It will be called to notify embedder that some events are available,
|
||||
/// and that perform_updates need to be called
|
||||
pub use servo::embedder_traits::EventLoopWaker;
|
||||
use servo::euclid::Size2D;
|
||||
use servo::servo_url::ServoUrl;
|
||||
use servo::webrender_traits::RenderingContext;
|
||||
use servo::{self, gl, Servo};
|
||||
use surfman::{Connection, SurfaceType};
|
||||
|
||||
use crate::egl::host_trait::HostTrait;
|
||||
use crate::egl::ohos::InitOpts;
|
||||
use crate::egl::resources::ResourceReaderInstance;
|
||||
use crate::egl::servo_glue::{
|
||||
Coordinates, ServoEmbedderCallbacks, ServoGlue, ServoWindowCallbacks,
|
||||
};
|
||||
|
||||
/// Initialize Servo. At that point, we need a valid GL context.
|
||||
/// In the future, this will be done in multiple steps.
|
||||
pub fn init(
|
||||
options: InitOpts,
|
||||
native_window: *mut c_void,
|
||||
xcomponent: *mut OH_NativeXComponent,
|
||||
gl: Rc<dyn gl::Gl>,
|
||||
waker: Box<dyn EventLoopWaker>,
|
||||
callbacks: Box<dyn HostTrait>,
|
||||
) -> Result<ServoGlue, &'static str> {
|
||||
info!("Entered simpleservo init function");
|
||||
resources::set(Box::new(ResourceReaderInstance::new()));
|
||||
|
||||
gl.clear_color(1.0, 1.0, 1.0, 1.0);
|
||||
gl.clear(gl::COLOR_BUFFER_BIT);
|
||||
gl.finish();
|
||||
|
||||
// Initialize surfman
|
||||
let connection = Connection::new().or(Err("Failed to create connection"))?;
|
||||
let adapter = connection
|
||||
.create_adapter()
|
||||
.or(Err("Failed to create adapter"))?;
|
||||
|
||||
let mut width: u64 = 0;
|
||||
let mut height: u64 = 0;
|
||||
let res = unsafe {
|
||||
OH_NativeXComponent_GetXComponentSize(
|
||||
xcomponent,
|
||||
native_window,
|
||||
&mut width as *mut _,
|
||||
&mut height as *mut _,
|
||||
)
|
||||
};
|
||||
assert_eq!(res, 0, "OH_NativeXComponent_GetXComponentSize failed");
|
||||
let width: i32 = width.try_into().expect("Width too large");
|
||||
let height: i32 = height.try_into().expect("Height too large");
|
||||
|
||||
debug!("Creating surfman widget with width {width} and height {height}");
|
||||
let native_widget = unsafe {
|
||||
connection.create_native_widget_from_ptr(native_window, Size2D::new(width, height))
|
||||
};
|
||||
let surface_type = SurfaceType::Widget { native_widget };
|
||||
|
||||
info!("Creating rendering context");
|
||||
let rendering_context = RenderingContext::create(&connection, &adapter, surface_type)
|
||||
.or(Err("Failed to create surface manager"))?;
|
||||
|
||||
info!("before ServoWindowCallbacks...");
|
||||
|
||||
let window_callbacks = Rc::new(ServoWindowCallbacks::new(
|
||||
callbacks,
|
||||
RefCell::new(Coordinates::new(0, 0, width, height, width, height)),
|
||||
options.display_density as f32,
|
||||
rendering_context.clone(),
|
||||
));
|
||||
|
||||
let embedder_callbacks = Box::new(ServoEmbedderCallbacks::new(waker, None, gl.clone()));
|
||||
|
||||
let servo = Servo::new(
|
||||
embedder_callbacks,
|
||||
window_callbacks.clone(),
|
||||
// User agent: Mozilla/5.0 (<Phone|PC|Tablet>; HarmonyOS 5.0) bla bla
|
||||
None,
|
||||
CompositeTarget::Window,
|
||||
);
|
||||
|
||||
let mut servo_glue = ServoGlue::new(rendering_context, servo.servo, window_callbacks);
|
||||
|
||||
let initial_url = ServoUrl::parse(options.url.as_str())
|
||||
.inspect_err(|e| error!("Invalid initial Servo URL `{}`. Error: {e:?}", options.url))
|
||||
.ok()
|
||||
.unwrap_or_else(|| ServoUrl::parse("about:blank").expect("Infallible"));
|
||||
|
||||
let _ = servo_glue.process_event(EmbedderEvent::NewWebView(initial_url, servo.browser_id));
|
||||
|
||||
Ok(servo_glue)
|
||||
}
|
56
ports/servoshell/egl/resources.rs
Normal file
56
ports/servoshell/egl/resources.rs
Normal file
|
@ -0,0 +1,56 @@
|
|||
/* 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::path::PathBuf;
|
||||
|
||||
use servo::embedder_traits::resources::{Resource, ResourceReaderMethods};
|
||||
|
||||
pub(crate) struct ResourceReaderInstance;
|
||||
|
||||
impl ResourceReaderInstance {
|
||||
pub(crate) fn new() -> ResourceReaderInstance {
|
||||
ResourceReaderInstance
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceReaderMethods for ResourceReaderInstance {
|
||||
fn read(&self, res: Resource) -> Vec<u8> {
|
||||
Vec::from(match res {
|
||||
Resource::Preferences => &include_bytes!("../../../resources/prefs.json")[..],
|
||||
Resource::HstsPreloadList => {
|
||||
&include_bytes!("../../../resources/hsts_preload.json")[..]
|
||||
},
|
||||
Resource::BadCertHTML => &include_bytes!("../../../resources/badcert.html")[..],
|
||||
Resource::NetErrorHTML => &include_bytes!("../../../resources/neterror.html")[..],
|
||||
Resource::UserAgentCSS => &include_bytes!("../../../resources/user-agent.css")[..],
|
||||
Resource::ServoCSS => &include_bytes!("../../../resources/servo.css")[..],
|
||||
Resource::PresentationalHintsCSS => {
|
||||
&include_bytes!("../../../resources/presentational-hints.css")[..]
|
||||
},
|
||||
Resource::QuirksModeCSS => &include_bytes!("../../../resources/quirks-mode.css")[..],
|
||||
Resource::RippyPNG => &include_bytes!("../../../resources/rippy.png")[..],
|
||||
Resource::DomainList => &include_bytes!("../../../resources/public_domains.txt")[..],
|
||||
Resource::BluetoothBlocklist => {
|
||||
&include_bytes!("../../../resources/gatt_blocklist.txt")[..]
|
||||
},
|
||||
Resource::MediaControlsCSS => {
|
||||
&include_bytes!("../../../resources/media-controls.css")[..]
|
||||
},
|
||||
Resource::MediaControlsJS => {
|
||||
&include_bytes!("../../../resources/media-controls.js")[..]
|
||||
},
|
||||
Resource::CrashHTML => &include_bytes!("../../../resources/crash.html")[..],
|
||||
Resource::DirectoryListingHTML => {
|
||||
&include_bytes!("../../../resources/directory-listing.html")[..]
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
fn sandbox_access_files(&self) -> Vec<PathBuf> {
|
||||
vec![]
|
||||
}
|
||||
|
||||
fn sandbox_access_files_dirs(&self) -> Vec<PathBuf> {
|
||||
vec![]
|
||||
}
|
||||
}
|
701
ports/servoshell/egl/servo_glue.rs
Normal file
701
ports/servoshell/egl/servo_glue.rs
Normal file
|
@ -0,0 +1,701 @@
|
|||
/* 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::cell::RefCell;
|
||||
use std::collections::HashMap;
|
||||
use std::mem;
|
||||
use std::os::raw::c_void;
|
||||
use std::rc::Rc;
|
||||
|
||||
use ipc_channel::ipc::IpcSender;
|
||||
use log::{debug, info, warn};
|
||||
use servo::base::id::WebViewId;
|
||||
use servo::compositing::windowing::{
|
||||
AnimationState, EmbedderCoordinates, EmbedderEvent, EmbedderMethods, MouseWindowEvent,
|
||||
WindowMethods,
|
||||
};
|
||||
use servo::embedder_traits::{
|
||||
ContextMenuResult, EmbedderMsg, EmbedderProxy, EventLoopWaker, MediaSessionEvent,
|
||||
PermissionPrompt, PermissionRequest, PromptDefinition, PromptOrigin, PromptResult,
|
||||
};
|
||||
use servo::euclid::{Point2D, Rect, Scale, Size2D, Vector2D};
|
||||
use servo::keyboard_types::{Key, KeyState, KeyboardEvent};
|
||||
use servo::script_traits::{
|
||||
MediaSessionActionType, MouseButton, TouchEventType, TouchId, TraversalDirection,
|
||||
};
|
||||
use servo::style_traits::DevicePixel;
|
||||
use servo::webrender_api::ScrollLocation;
|
||||
use servo::webrender_traits::RenderingContext;
|
||||
use servo::{gl, Servo, TopLevelBrowsingContextId};
|
||||
|
||||
use crate::egl::host_trait::HostTrait;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct Coordinates {
|
||||
pub viewport: Rect<i32, DevicePixel>,
|
||||
pub framebuffer: Size2D<i32, DevicePixel>,
|
||||
}
|
||||
|
||||
impl Coordinates {
|
||||
pub fn new(
|
||||
x: i32,
|
||||
y: i32,
|
||||
width: i32,
|
||||
height: i32,
|
||||
fb_width: i32,
|
||||
fb_height: i32,
|
||||
) -> Coordinates {
|
||||
Coordinates {
|
||||
viewport: Rect::new(Point2D::new(x, y), Size2D::new(width, height)),
|
||||
framebuffer: Size2D::new(fb_width, fb_height),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ServoWindowCallbacks {
|
||||
host_callbacks: Box<dyn HostTrait>,
|
||||
coordinates: RefCell<Coordinates>,
|
||||
density: f32,
|
||||
rendering_context: RenderingContext,
|
||||
}
|
||||
|
||||
impl ServoWindowCallbacks {
|
||||
pub(super) fn new(
|
||||
host_callbacks: Box<dyn HostTrait>,
|
||||
coordinates: RefCell<Coordinates>,
|
||||
density: f32,
|
||||
rendering_context: RenderingContext,
|
||||
) -> Self {
|
||||
Self {
|
||||
host_callbacks,
|
||||
coordinates,
|
||||
density,
|
||||
rendering_context,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct WebView {}
|
||||
|
||||
pub struct ServoGlue {
|
||||
rendering_context: RenderingContext,
|
||||
servo: Servo<ServoWindowCallbacks>,
|
||||
batch_mode: bool,
|
||||
callbacks: Rc<ServoWindowCallbacks>,
|
||||
events: Vec<EmbedderEvent>,
|
||||
context_menu_sender: Option<IpcSender<ContextMenuResult>>,
|
||||
|
||||
/// List of top-level browsing contexts.
|
||||
/// Modified by EmbedderMsg::WebViewOpened and EmbedderMsg::WebViewClosed,
|
||||
/// and we exit if it ever becomes empty.
|
||||
webviews: HashMap<WebViewId, WebView>,
|
||||
|
||||
/// The order in which the webviews were created.
|
||||
creation_order: Vec<WebViewId>,
|
||||
|
||||
/// The webview that is currently focused.
|
||||
/// Modified by EmbedderMsg::WebViewFocused and EmbedderMsg::WebViewBlurred.
|
||||
focused_webview_id: Option<WebViewId>,
|
||||
}
|
||||
|
||||
#[allow(unused)]
|
||||
impl ServoGlue {
|
||||
pub(super) fn new(
|
||||
rendering_context: RenderingContext,
|
||||
servo: Servo<ServoWindowCallbacks>,
|
||||
callbacks: Rc<ServoWindowCallbacks>,
|
||||
) -> Self {
|
||||
Self {
|
||||
rendering_context,
|
||||
servo,
|
||||
batch_mode: false,
|
||||
callbacks,
|
||||
events: vec![],
|
||||
context_menu_sender: None,
|
||||
webviews: HashMap::default(),
|
||||
creation_order: vec![],
|
||||
focused_webview_id: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn get_browser_id(&self) -> Result<TopLevelBrowsingContextId, &'static str> {
|
||||
let webview_id = match self.focused_webview_id {
|
||||
Some(id) => id,
|
||||
None => return Err("No focused WebViewId yet."),
|
||||
};
|
||||
Ok(webview_id)
|
||||
}
|
||||
|
||||
/// Request shutdown. Will call on_shutdown_complete.
|
||||
pub fn request_shutdown(&mut self) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::Quit)
|
||||
}
|
||||
|
||||
/// Call after on_shutdown_complete
|
||||
pub fn deinit(self) {
|
||||
self.servo.deinit();
|
||||
}
|
||||
|
||||
/// Returns the webrender surface management integration interface.
|
||||
/// This provides the embedder access to the current front buffer.
|
||||
pub fn surfman(&self) -> RenderingContext {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
|
||||
/// This is the Servo heartbeat. This needs to be called
|
||||
/// everytime wakeup is called or when embedder wants Servo
|
||||
/// to act on its pending events.
|
||||
pub fn perform_updates(&mut self) -> Result<(), &'static str> {
|
||||
debug!("perform_updates");
|
||||
let events = mem::replace(&mut self.events, Vec::new());
|
||||
self.servo.handle_events(events);
|
||||
let r = self.handle_servo_events();
|
||||
debug!("done perform_updates");
|
||||
r
|
||||
}
|
||||
|
||||
/// In batch mode, Servo won't call perform_updates automatically.
|
||||
/// This can be useful when the embedder wants to control when Servo
|
||||
/// acts on its pending events. For example, if the embedder wants Servo
|
||||
/// to act on the scroll events only at a certain time, not everytime
|
||||
/// scroll() is called.
|
||||
pub fn set_batch_mode(&mut self, batch: bool) -> Result<(), &'static str> {
|
||||
debug!("set_batch_mode");
|
||||
self.batch_mode = batch;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Load an URL.
|
||||
pub fn load_uri(&mut self, url: &str) -> Result<(), &'static str> {
|
||||
info!("load_uri: {}", url);
|
||||
crate::parser::location_bar_input_to_url(url)
|
||||
.ok_or("Can't parse URL")
|
||||
.and_then(|url| {
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::LoadUrl(browser_id, url);
|
||||
self.process_event(event)
|
||||
})
|
||||
}
|
||||
|
||||
/// Reload the page.
|
||||
pub fn clear_cache(&mut self) -> Result<(), &'static str> {
|
||||
info!("clear_cache");
|
||||
let event = EmbedderEvent::ClearCache;
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Reload the page.
|
||||
pub fn reload(&mut self) -> Result<(), &'static str> {
|
||||
info!("reload");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Reload(browser_id);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Redraw the page.
|
||||
pub fn refresh(&mut self) -> Result<(), &'static str> {
|
||||
info!("refresh");
|
||||
self.process_event(EmbedderEvent::Refresh)
|
||||
}
|
||||
|
||||
/// Stop loading the page.
|
||||
pub fn stop(&mut self) -> Result<(), &'static str> {
|
||||
warn!("TODO can't stop won't stop");
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Go back in history.
|
||||
pub fn go_back(&mut self) -> Result<(), &'static str> {
|
||||
info!("go_back");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Back(1));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Go forward in history.
|
||||
pub fn go_forward(&mut self) -> Result<(), &'static str> {
|
||||
info!("go_forward");
|
||||
let browser_id = self.get_browser_id()?;
|
||||
let event = EmbedderEvent::Navigation(browser_id, TraversalDirection::Forward(1));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Let Servo know that the window has been resized.
|
||||
pub fn resize(&mut self, coordinates: Coordinates) -> Result<(), &'static str> {
|
||||
info!("resize");
|
||||
*self.callbacks.coordinates.borrow_mut() = coordinates;
|
||||
self.process_event(EmbedderEvent::WindowResize)
|
||||
}
|
||||
|
||||
/// Start scrolling.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
pub fn scroll_start(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event =
|
||||
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Down);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Scroll.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
pub fn scroll(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event =
|
||||
EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Move);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// End scrolling.
|
||||
/// x/y are scroll coordinates.
|
||||
/// dx/dy are scroll deltas.
|
||||
#[cfg(not(target_env = "ohos"))]
|
||||
pub fn scroll_end(&mut self, dx: f32, dy: f32, x: i32, y: i32) -> Result<(), &'static str> {
|
||||
let delta = Vector2D::new(dx, dy);
|
||||
let scroll_location = ScrollLocation::Delta(delta);
|
||||
let event = EmbedderEvent::Scroll(scroll_location, Point2D::new(x, y), TouchEventType::Up);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: press down
|
||||
pub fn touch_down(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Down,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: move touching finger
|
||||
pub fn touch_move(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Move,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Touch event: Lift touching finger
|
||||
pub fn touch_up(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Up,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Cancel touch event
|
||||
pub fn touch_cancel(&mut self, x: f32, y: f32, pointer_id: i32) -> Result<(), &'static str> {
|
||||
let event = EmbedderEvent::Touch(
|
||||
TouchEventType::Cancel,
|
||||
TouchId(pointer_id),
|
||||
Point2D::new(x as f32, y as f32),
|
||||
);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse movement.
|
||||
pub fn mouse_move(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event = EmbedderEvent::MouseWindowMoveEventClass(point);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse button press.
|
||||
pub fn mouse_down(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event =
|
||||
EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseDown(button, point));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Register a mouse button release.
|
||||
pub fn mouse_up(&mut self, x: f32, y: f32, button: MouseButton) -> Result<(), &'static str> {
|
||||
let point = Point2D::new(x, y);
|
||||
let event = EmbedderEvent::MouseWindowEventClass(MouseWindowEvent::MouseUp(button, point));
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
/// Start pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom_start(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// Pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// End pinchzoom.
|
||||
/// x/y are pinch origin coordinates.
|
||||
pub fn pinchzoom_end(&mut self, factor: f32, _x: u32, _y: u32) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::PinchZoom(factor))
|
||||
}
|
||||
|
||||
/// Perform a click.
|
||||
pub fn click(&mut self, x: f32, y: f32) -> Result<(), &'static str> {
|
||||
let mouse_event = MouseWindowEvent::Click(MouseButton::Left, Point2D::new(x, y));
|
||||
let event = EmbedderEvent::MouseWindowEventClass(mouse_event);
|
||||
self.process_event(event)
|
||||
}
|
||||
|
||||
pub fn key_down(&mut self, key: Key) -> Result<(), &'static str> {
|
||||
let key_event = KeyboardEvent {
|
||||
state: KeyState::Down,
|
||||
key,
|
||||
..KeyboardEvent::default()
|
||||
};
|
||||
self.process_event(EmbedderEvent::Keyboard(key_event))
|
||||
}
|
||||
|
||||
pub fn key_up(&mut self, key: Key) -> Result<(), &'static str> {
|
||||
let key_event = KeyboardEvent {
|
||||
state: KeyState::Up,
|
||||
key,
|
||||
..KeyboardEvent::default()
|
||||
};
|
||||
self.process_event(EmbedderEvent::Keyboard(key_event))
|
||||
}
|
||||
|
||||
pub fn pause_compositor(&mut self) -> Result<(), &'static str> {
|
||||
self.process_event(EmbedderEvent::InvalidateNativeSurface)
|
||||
}
|
||||
|
||||
pub fn resume_compositor(
|
||||
&mut self,
|
||||
native_surface: *mut c_void,
|
||||
coords: Coordinates,
|
||||
) -> Result<(), &'static str> {
|
||||
if native_surface.is_null() {
|
||||
panic!("null passed for native_surface");
|
||||
}
|
||||
self.process_event(EmbedderEvent::ReplaceNativeSurface(
|
||||
native_surface,
|
||||
coords.framebuffer,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn media_session_action(
|
||||
&mut self,
|
||||
action: MediaSessionActionType,
|
||||
) -> Result<(), &'static str> {
|
||||
info!("Media session action {:?}", action);
|
||||
self.process_event(EmbedderEvent::MediaSessionAction(action))
|
||||
}
|
||||
|
||||
pub fn set_throttled(&mut self, throttled: bool) -> Result<(), &'static str> {
|
||||
info!("set_throttled");
|
||||
if let Ok(id) = self.get_browser_id() {
|
||||
let event = EmbedderEvent::SetWebViewThrottled(id, throttled);
|
||||
self.process_event(event)
|
||||
} else {
|
||||
// Ignore visibility change if no browser has been created yet.
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn ime_dismissed(&mut self) -> Result<(), &'static str> {
|
||||
info!("ime_dismissed");
|
||||
self.process_event(EmbedderEvent::IMEDismissed)
|
||||
}
|
||||
|
||||
pub fn on_context_menu_closed(
|
||||
&mut self,
|
||||
result: ContextMenuResult,
|
||||
) -> Result<(), &'static str> {
|
||||
if let Some(sender) = self.context_menu_sender.take() {
|
||||
let _ = sender.send(result);
|
||||
} else {
|
||||
warn!("Trying to close a context menu when no context menu is active");
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn process_event(&mut self, event: EmbedderEvent) -> Result<(), &'static str> {
|
||||
self.events.push(event);
|
||||
if !self.batch_mode {
|
||||
self.perform_updates()
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn handle_servo_events(&mut self) -> Result<(), &'static str> {
|
||||
let mut need_update = false;
|
||||
let mut need_present = false;
|
||||
for (browser_id, event) in self.servo.get_events() {
|
||||
match event {
|
||||
EmbedderMsg::ChangePageTitle(title) => {
|
||||
self.callbacks.host_callbacks.on_title_changed(title);
|
||||
},
|
||||
EmbedderMsg::AllowNavigationRequest(pipeline_id, url) => {
|
||||
if let Some(_browser_id) = browser_id {
|
||||
let data: bool = self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_allow_navigation(url.to_string());
|
||||
let window_event =
|
||||
EmbedderEvent::AllowNavigationResponse(pipeline_id, data);
|
||||
self.events.push(window_event);
|
||||
need_update = true;
|
||||
}
|
||||
},
|
||||
EmbedderMsg::HistoryChanged(entries, current) => {
|
||||
let can_go_back = current > 0;
|
||||
let can_go_forward = current < entries.len() - 1;
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_history_changed(can_go_back, can_go_forward);
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_url_changed(entries[current].clone().to_string());
|
||||
},
|
||||
EmbedderMsg::LoadStart => {
|
||||
self.callbacks.host_callbacks.on_load_started();
|
||||
},
|
||||
EmbedderMsg::LoadComplete => {
|
||||
self.callbacks.host_callbacks.on_load_ended();
|
||||
},
|
||||
EmbedderMsg::GetSelectedBluetoothDevice(_, sender) => {
|
||||
let _ = sender.send(None);
|
||||
},
|
||||
EmbedderMsg::AllowUnload(sender) => {
|
||||
let _ = sender.send(true);
|
||||
},
|
||||
EmbedderMsg::ShowContextMenu(sender, title, items) => {
|
||||
if self.context_menu_sender.is_some() {
|
||||
warn!(
|
||||
"Trying to show a context menu when a context menu is already active"
|
||||
);
|
||||
let _ = sender.send(ContextMenuResult::Ignored);
|
||||
} else {
|
||||
self.context_menu_sender = Some(sender);
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.show_context_menu(title, items);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::Prompt(definition, origin) => {
|
||||
let cb = &self.callbacks.host_callbacks;
|
||||
let trusted = origin == PromptOrigin::Trusted;
|
||||
let res = match definition {
|
||||
PromptDefinition::Alert(message, sender) => {
|
||||
sender.send(cb.prompt_alert(message, trusted))
|
||||
},
|
||||
PromptDefinition::OkCancel(message, sender) => {
|
||||
sender.send(cb.prompt_ok_cancel(message, trusted))
|
||||
},
|
||||
PromptDefinition::YesNo(message, sender) => {
|
||||
sender.send(cb.prompt_yes_no(message, trusted))
|
||||
},
|
||||
PromptDefinition::Input(message, default, sender) => {
|
||||
sender.send(cb.prompt_input(message, default, trusted))
|
||||
},
|
||||
};
|
||||
if let Err(e) = res {
|
||||
let reason = format!("Failed to send Prompt response: {}", e);
|
||||
self.events
|
||||
.push(EmbedderEvent::SendError(browser_id, reason));
|
||||
}
|
||||
},
|
||||
EmbedderMsg::AllowOpeningWebView(response_chan) => {
|
||||
// Note: would be a place to handle pop-ups config.
|
||||
// see Step 7 of #the-rules-for-choosing-a-browsing-context-given-a-browsing-context-name
|
||||
if let Err(e) = response_chan.send(true) {
|
||||
warn!("Failed to send AllowOpeningBrowser response: {}", e);
|
||||
};
|
||||
},
|
||||
EmbedderMsg::WebViewOpened(new_webview_id) => {
|
||||
self.webviews.insert(new_webview_id, WebView {});
|
||||
self.creation_order.push(new_webview_id);
|
||||
self.events
|
||||
.push(EmbedderEvent::FocusWebView(new_webview_id));
|
||||
},
|
||||
EmbedderMsg::WebViewClosed(webview_id) => {
|
||||
self.webviews.retain(|&id, _| id != webview_id);
|
||||
self.creation_order.retain(|&id| id != webview_id);
|
||||
self.focused_webview_id = None;
|
||||
if let Some(&newest_webview_id) = self.creation_order.last() {
|
||||
self.events
|
||||
.push(EmbedderEvent::FocusWebView(newest_webview_id));
|
||||
} else {
|
||||
self.events.push(EmbedderEvent::Quit);
|
||||
}
|
||||
},
|
||||
EmbedderMsg::WebViewFocused(webview_id) => {
|
||||
self.focused_webview_id = Some(webview_id);
|
||||
},
|
||||
EmbedderMsg::WebViewBlurred => {
|
||||
self.focused_webview_id = None;
|
||||
},
|
||||
EmbedderMsg::GetClipboardContents(sender) => {
|
||||
let contents = self.callbacks.host_callbacks.get_clipboard_contents();
|
||||
let _ = sender.send(contents.unwrap_or("".to_owned()));
|
||||
},
|
||||
EmbedderMsg::SetClipboardContents(text) => {
|
||||
self.callbacks.host_callbacks.set_clipboard_contents(text);
|
||||
},
|
||||
EmbedderMsg::Shutdown => {
|
||||
self.callbacks.host_callbacks.on_shutdown_complete();
|
||||
},
|
||||
EmbedderMsg::PromptPermission(prompt, sender) => {
|
||||
let message = match prompt {
|
||||
PermissionPrompt::Request(permission_name) => {
|
||||
format!("Do you want to grant permission for {:?}?", permission_name)
|
||||
},
|
||||
PermissionPrompt::Insecure(permission_name) => {
|
||||
format!(
|
||||
"The {:?} feature is only safe to use in secure context, but servo can't guarantee\n\
|
||||
that the current context is secure. Do you want to proceed and grant permission?",
|
||||
permission_name
|
||||
)
|
||||
},
|
||||
};
|
||||
|
||||
let result = match self.callbacks.host_callbacks.prompt_yes_no(message, true) {
|
||||
PromptResult::Primary => PermissionRequest::Granted,
|
||||
PromptResult::Secondary | PromptResult::Dismissed => {
|
||||
PermissionRequest::Denied
|
||||
},
|
||||
};
|
||||
|
||||
let _ = sender.send(result);
|
||||
},
|
||||
EmbedderMsg::ShowIME(kind, text, multiline, bounds) => {
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_ime_show(kind, text, multiline, bounds);
|
||||
},
|
||||
EmbedderMsg::HideIME => {
|
||||
self.callbacks.host_callbacks.on_ime_hide();
|
||||
},
|
||||
EmbedderMsg::MediaSessionEvent(event) => {
|
||||
match event {
|
||||
MediaSessionEvent::SetMetadata(metadata) => {
|
||||
self.callbacks.host_callbacks.on_media_session_metadata(
|
||||
metadata.title,
|
||||
metadata.artist,
|
||||
metadata.album,
|
||||
)
|
||||
},
|
||||
MediaSessionEvent::PlaybackStateChange(state) => self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_media_session_playback_state_change(state),
|
||||
MediaSessionEvent::SetPositionState(position_state) => self
|
||||
.callbacks
|
||||
.host_callbacks
|
||||
.on_media_session_set_position_state(
|
||||
position_state.duration,
|
||||
position_state.position,
|
||||
position_state.playback_rate,
|
||||
),
|
||||
};
|
||||
},
|
||||
EmbedderMsg::OnDevtoolsStarted(port, token) => {
|
||||
self.callbacks
|
||||
.host_callbacks
|
||||
.on_devtools_started(port, token);
|
||||
},
|
||||
EmbedderMsg::Panic(reason, backtrace) => {
|
||||
self.callbacks.host_callbacks.on_panic(reason, backtrace);
|
||||
},
|
||||
EmbedderMsg::ReadyToPresent(_webview_ids) => {
|
||||
need_present = true;
|
||||
},
|
||||
EmbedderMsg::Status(..) |
|
||||
EmbedderMsg::SelectFiles(..) |
|
||||
EmbedderMsg::MoveTo(..) |
|
||||
EmbedderMsg::ResizeTo(..) |
|
||||
EmbedderMsg::Keyboard(..) |
|
||||
EmbedderMsg::SetCursor(..) |
|
||||
EmbedderMsg::NewFavicon(..) |
|
||||
EmbedderMsg::HeadParsed |
|
||||
EmbedderMsg::SetFullscreenState(..) |
|
||||
EmbedderMsg::ReportProfile(..) |
|
||||
EmbedderMsg::EventDelivered(..) => {},
|
||||
}
|
||||
}
|
||||
|
||||
if need_update {
|
||||
let _ = self.perform_updates();
|
||||
}
|
||||
if need_present {
|
||||
self.servo.present();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) struct ServoEmbedderCallbacks {
|
||||
waker: Box<dyn EventLoopWaker>,
|
||||
xr_discovery: Option<webxr::Discovery>,
|
||||
#[allow(unused)]
|
||||
gl: Rc<dyn gl::Gl>,
|
||||
}
|
||||
|
||||
impl ServoEmbedderCallbacks {
|
||||
pub(super) fn new(
|
||||
waker: Box<dyn EventLoopWaker>,
|
||||
xr_discovery: Option<webxr::Discovery>,
|
||||
gl: Rc<dyn gl::Gl>,
|
||||
) -> Self {
|
||||
Self {
|
||||
waker,
|
||||
xr_discovery,
|
||||
gl,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl EmbedderMethods for ServoEmbedderCallbacks {
|
||||
fn create_event_loop_waker(&mut self) -> Box<dyn EventLoopWaker> {
|
||||
debug!("EmbedderMethods::create_event_loop_waker");
|
||||
self.waker.clone()
|
||||
}
|
||||
|
||||
fn register_webxr(
|
||||
&mut self,
|
||||
registry: &mut webxr::MainThreadRegistry,
|
||||
_embedder_proxy: EmbedderProxy,
|
||||
) {
|
||||
debug!("EmbedderMethods::register_xr");
|
||||
if let Some(discovery) = self.xr_discovery.take() {
|
||||
registry.register(discovery);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl WindowMethods for ServoWindowCallbacks {
|
||||
fn get_coordinates(&self) -> EmbedderCoordinates {
|
||||
let coords = self.coordinates.borrow();
|
||||
EmbedderCoordinates {
|
||||
viewport: coords.viewport.to_box2d(),
|
||||
framebuffer: coords.framebuffer,
|
||||
window: (coords.viewport.size, Point2D::new(0, 0)),
|
||||
screen: coords.viewport.size,
|
||||
screen_avail: coords.viewport.size,
|
||||
hidpi_factor: Scale::new(self.density),
|
||||
}
|
||||
}
|
||||
|
||||
fn set_animation_state(&self, state: AnimationState) {
|
||||
debug!("WindowMethods::set_animation_state: {:?}", state);
|
||||
self.host_callbacks
|
||||
.on_animating_changed(state == AnimationState::Animating);
|
||||
}
|
||||
|
||||
fn rendering_context(&self) -> RenderingContext {
|
||||
self.rendering_context.clone()
|
||||
}
|
||||
}
|
|
@ -14,14 +14,15 @@ mod backtrace;
|
|||
mod crash_handler;
|
||||
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
|
||||
pub(crate) mod desktop;
|
||||
#[cfg(any(target_os = "android", target_env = "ohos"))]
|
||||
mod egl;
|
||||
#[cfg(not(target_os = "android"))]
|
||||
mod panic_hook;
|
||||
mod parser;
|
||||
mod prefs;
|
||||
#[cfg(not(any(target_os = "android", target_env = "ohos")))]
|
||||
mod resources;
|
||||
|
||||
mod egl;
|
||||
|
||||
pub mod platform {
|
||||
#[cfg(target_os = "macos")]
|
||||
pub use crate::platform::macos::deinit;
|
||||
|
|
|
@ -22,12 +22,15 @@
|
|||
|
||||
fn main() {
|
||||
cfg_if::cfg_if! {
|
||||
if #[cfg(not(target_os = "android"))] {
|
||||
if #[cfg(not(any(target_os = "android", target_env = "ohos")))] {
|
||||
servoshell::main()
|
||||
} else {
|
||||
println!(
|
||||
"Cannot start /ports/servoshell/ on Android. \
|
||||
Use /support/android/apk/ + /ports/jniapi/ instead"
|
||||
"Cannot run the servoshell `bin` executable on platforms such as \
|
||||
Android or OpenHarmony. On these platforms you need to compile \
|
||||
the servoshell library as a `cdylib` and integrate it with the \
|
||||
platform app code into an `apk` (android) or `hap` (OpenHarmony).\
|
||||
For Android `mach build` will do these steps automatically for you."
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue