Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
75 changes: 56 additions & 19 deletions crates/ability/src/helper/webview.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
use std::{
borrow::Cow,
cell::RefCell,
collections::HashMap,
rc::Rc,
sync::{Arc, Mutex},
Expand Down Expand Up @@ -59,22 +60,37 @@ pub struct WebViewInitData<'a> {
#[derive(Clone)]
pub struct Webview {
tag: String,
inner: Rc<ObjectRef>,
/// N-API reference to the ArkTS WebView controller. `None` once the
/// reference has been released (dispose or last clone drop).
inner: Rc<RefCell<Option<ObjectRef>>>,
web_view_native: Rc<Web>,
}

impl Webview {
pub fn new(tag: String, inner: ObjectRef) -> Result<Self> {
let native_instance = Web::new(tag.clone());
Ok(Self {
inner: Rc::new(inner),
inner: Rc::new(RefCell::new(Some(inner))),
web_view_native: Rc::new(native_instance),
tag,
})
}

pub fn inner(&self) -> Rc<ObjectRef> {
self.inner.clone()
/// Borrow the live N-API object reference.
fn object_ref(&self) -> Result<std::cell::Ref<'_, ObjectRef>> {
std::cell::Ref::filter_map(self.inner.borrow(), |inner| inner.as_ref())
.map_err(|_| Error::from_reason("webview reference already released"))
}

/// Release the N-API reference. Only the last clone may actually unref;
/// earlier clones simply mark the shared slot empty so a later drop is a
/// no-op.
fn release_native_ref(&self) {
if let Some(object_ref) = self.inner.borrow_mut().take() {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let _ = object_ref.unref(env);
}
}
}

pub fn tag(&self) -> String {
Expand All @@ -85,7 +101,7 @@ impl Webview {
pub fn url(&self) -> Result<String> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let url_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, (), String>>("getUrl")?;
url_js_function.call(())
Expand All @@ -97,7 +113,7 @@ impl Webview {
/// Load a url in the webview
pub fn load_url(&self, url: &str) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let load_url_js_function = self.inner.get_value(env)?.get_named_property::<Function<
let load_url_js_function = self.object_ref()?.get_value(env)?.get_named_property::<Function<
'_,
FnArgs<(String, Option<HashMap<String, String>>)>,
(),
Expand All @@ -114,7 +130,7 @@ impl Webview {
pub fn load_url_with_headers(&self, url: &str, headers: http::HeaderMap) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let load_url_with_headers_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, FnArgs<(String, HashMap<String, String>)>, ()>>(
"loadUrl",
Expand All @@ -135,7 +151,7 @@ impl Webview {
pub fn load_html(&self, html: &str) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let load_html_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, String, ()>>("loadHtml")?;
load_html_js_function.call(html.to_string())?;
Expand All @@ -149,7 +165,7 @@ impl Webview {
pub fn set_zoom(&self, zoom: f64) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let set_zoom_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, f64, ()>>("zoom")?;
set_zoom_js_function.call(zoom)?;
Expand All @@ -163,7 +179,7 @@ impl Webview {
pub fn reload(&self) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let reload_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, (), ()>>("refresh")?;
reload_js_function.call(())?;
Expand All @@ -177,7 +193,7 @@ impl Webview {
pub fn focus(&self) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let focus_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, (), ()>>("requestFocus")?;
focus_js_function.call(())?;
Expand All @@ -198,7 +214,7 @@ impl Webview {
) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let evaluate_js_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, FnArgs<(String, Function<'_, String, ()>)>, ()>>(
"runJavaScript",
Expand Down Expand Up @@ -226,7 +242,7 @@ impl Webview {
pub fn cookies_with_url(&self, url: &str) -> Result<String> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let cookies_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, String, String>>("getCookies")?;
cookies_js_function.call(url.to_string())
Expand All @@ -238,7 +254,7 @@ impl Webview {
pub fn set_background_color(&self, color: &str) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let set_background_color_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, String, ()>>("setBackgroundColor")?;
set_background_color_js_function.call(color.to_string())?;
Expand All @@ -251,7 +267,7 @@ impl Webview {
pub fn set_visible(&self, visible: bool) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let set_visible_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, bool, ()>>("setVisible")?;
set_visible_js_function.call(visible)?;
Expand All @@ -262,12 +278,20 @@ impl Webview {
}

pub fn dispose(&self) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let disposed = if let Some(env) = get_main_thread_env().borrow().as_ref() {
let dispose_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, (), ()>>("dispose")?;
dispose_js_function.call(())?;
dispose_js_function.call(()).is_ok()
} else {
false
};
// Release the N-API reference regardless of ArkTS dispose outcome:
// an unreachable or half-disposed controller must not stay pinned
// forever. Subsequent calls are no-ops.
self.release_native_ref();
if disposed {
Ok(())
} else {
Err(Error::from_reason("Failed to get main thread env"))
Expand All @@ -277,7 +301,7 @@ impl Webview {
pub fn clear_all_browsing_data(&self) -> Result<()> {
if let Some(env) = get_main_thread_env().borrow().as_ref() {
let clear_all_browsing_data_js_function = self
.inner
.object_ref()?
.get_value(env)?
.get_named_property::<Function<'_, (), ()>>("clearAllBrowsingData")?;
clear_all_browsing_data_js_function.call(())?;
Expand Down Expand Up @@ -481,3 +505,16 @@ impl CustomProtocolResponder {
(self.responder)(Response::from_parts(parts, body.into()))
}
}

impl Drop for Webview {
fn drop(&mut self) {
// Last-resort N-API reference release for clones that were never
// explicitly disposed. Only the last clone may release the shared
// slot: ephemeral clones are created for every native call (snapshot
// pattern) and dropped immediately, so releasing from any clone would
// invalidate the controller after the first call.
if Rc::strong_count(&self.inner) == 1 {
self.release_native_ref();
}
}
}
46 changes: 16 additions & 30 deletions rust_example/demo_native/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,10 @@ use std::{
};

use napi_derive_ohos::napi;
use napi_ohos::{
bindgen_prelude::{Function, JsObjectValue, Object},
Env, Error, Result,
};
use napi_ohos::{Env, Error, Result};
use ohos_hilog_binding::hilog_info;
use openharmony_ability::{
native_web::WebProxyBuilder, Event, InputEvent, OpenHarmonyApp, WebViewBuilder,
native_web::WebProxyBuilder, Event, InputEvent, OpenHarmonyApp, WebViewBuilder, Webview,
};
use openharmony_ability_derive::ability;

Expand All @@ -26,7 +23,7 @@ static BACK_PRESS_INTERCEPT_ENABLED: AtomicBool = AtomicBool::new(true);

thread_local! {
#[allow(clippy::missing_const_for_thread_local)]
static WEBVIEW_ID: RefCell<Option<Object<'static>>> = RefCell::new(None);
static WEBVIEW: RefCell<Option<Webview>> = RefCell::new(None);
}

const WEB_TAG: &str = "demo_webview";
Expand Down Expand Up @@ -70,7 +67,7 @@ pub fn toggle_back_press_intercept() -> bool {
}

#[napi]
pub fn handle_change(env: &Env) -> napi_ohos::Result<()> {
pub fn handle_change(_env: &Env) -> napi_ohos::Result<()> {
let web_tag = String::from(WEB_TAG);

let webview = WebViewBuilder::new()
Expand Down Expand Up @@ -105,40 +102,29 @@ pub fn handle_change(env: &Env) -> napi_ohos::Result<()> {
hilog_info!("ohos-rs macro on_page_end");
});

let ret: Object<'static> = unsafe {
std::mem::transmute::<Object<'_>, Object<'static>>(webview.inner().get_value(env)?)
};
WEBVIEW_ID.with(|w| {
w.replace(Some(ret));
WEBVIEW.with(|slot| {
slot.replace(Some(webview));
});

Ok(())
}

#[napi]
pub fn set_background_color(_env: &Env, color: String) -> napi_ohos::Result<()> {
WEBVIEW_ID.with(|w| {
if let Some(webview) = w.borrow().as_ref() {
let set_background_color_js_function = webview
.get_named_property::<Function<'_, String, ()>>("setBackgroundColor")
.unwrap();
set_background_color_js_function.call(color).unwrap();
}
});
Ok(())
WEBVIEW.with(|slot| {
slot.borrow()
.as_ref()
.map_or(Ok(()), |webview| webview.set_background_color(&color))
})
}

#[napi]
pub fn set_visible(_env: &Env, visible: bool) -> napi_ohos::Result<()> {
WEBVIEW_ID.with(|w| {
if let Some(webview) = w.borrow().as_ref() {
let set_visible_js_function = webview
.get_named_property::<Function<'_, bool, ()>>("setVisible")
.unwrap();
set_visible_js_function.call(visible).unwrap();
}
});
Ok(())
WEBVIEW.with(|slot| {
slot.borrow()
.as_ref()
.map_or(Ok(()), |webview| webview.set_visible(visible))
})
}

#[ability(webview, protocol = "wry,custom,other")]
Expand Down
Loading