Original commit: a185215c71
This commit is contained in:
Michał Wawrzyniec Urbańczyk 2021-05-10 09:42:59 +02:00 committed by GitHub
parent 99b0ae7ecb
commit 93c54c777b
12 changed files with 482 additions and 79 deletions

View File

@ -62,8 +62,9 @@ features = [
'CloseEvent',
'Document',
'Element',
"ErrorEvent",
"MessageEvent",
'ErrorEvent',
'EventTarget',
'MessageEvent',
'HtmlElement',
'Node',
'WebSocket',

View File

@ -42,5 +42,6 @@ pub enum TransportEvent {
/// A socket has been opened.
Opened,
/// A socket has been closed by the peer.
/// This event may be also emitted when reconnecting has failed.
Closed,
}

View File

@ -29,6 +29,11 @@ impl TestWithLocalPoolExecutor {
Self {executor,running_task_count}
}
/// Check if there are any uncompleted tasks in the pool.
pub fn has_ongoing_task(&self) -> bool {
self.running_task_count.get() > 0
}
/// Spawn new task in executor.
pub fn run_task<Task>(&mut self, task: Task)
where Task : Future<Output=()> + 'static {
@ -47,7 +52,7 @@ impl TestWithLocalPoolExecutor {
pub fn when_stalled<Callback>(&mut self, callback:Callback)
where Callback : FnOnce() {
self.run_until_stalled();
if self.running_task_count.get() > 0 {
if self.has_ongoing_task() {
callback();
}
}
@ -59,7 +64,7 @@ impl TestWithLocalPoolExecutor {
pub fn when_stalled_run_task<Task>(&mut self, task : Task)
where Task : Future<Output=()> + 'static {
self.run_until_stalled();
if self.running_task_count.get() > 0 {
if self.has_ongoing_task() {
self.run_task(task);
}
}

View File

@ -1,6 +1,8 @@
//! Module defining `JsExecutor` - an executor that tries running until stalled
//! on each animation frame callback call.
pub mod test;
use crate::prelude::*;
use ensogl::control::callback;

View File

@ -0,0 +1,8 @@
//! Utilities for tests related to the web-based executors.
/// Set up a global animation-frame-based executor.
/// Leaks it handle so it will run indefinitely.
/// To be used in asynchronous wasm tests.
pub fn setup_and_forget() {
std::mem::forget(crate::initializer::setup_global_executor());
}

View File

@ -279,8 +279,8 @@ async fn create_project_model
) -> FallibleResult<model::Project> {
info!(logger, "Establishing Language Server connection.");
let client_id = Uuid::new_v4();
let json_ws = WebSocket::new_opened(logger,json_endpoint).await?;
let binary_ws = WebSocket::new_opened(logger,binary_endpoint).await?;
let json_ws = WebSocket::new_opened(logger,&json_endpoint).await?;
let binary_ws = WebSocket::new_opened(logger,&binary_endpoint).await?;
let client_json = language_server::Client::new(json_ws);
let client_binary = binary::Client::new(logger,binary_ws);
crate::executor::global::spawn(client_json.runner());

View File

@ -2,8 +2,8 @@
use crate::prelude::*;
use ensogl_system_web::closure::storage::OptionalFmMutClosure;
use ensogl_system_web::js_to_string;
use ensogl_system_web::event::listener::Slot;
use failure::Error;
use futures::channel::mpsc;
use json_rpc::Transport;
@ -11,9 +11,6 @@ use json_rpc::TransportEvent;
use utils::channel;
use wasm_bindgen::JsCast;
use web_sys::BinaryType;
use web_sys::CloseEvent;
use web_sys::Event;
use web_sys::MessageEvent;
@ -36,6 +33,14 @@ pub enum ConnectingError {
FailedToConnect,
}
impl ConnectingError {
/// Create a `ConstructionError` value from a JS value describing an error.
pub fn construction_error(js_val:impl AsRef<JsValue>) -> Self {
let text = js_to_string(js_val);
ConnectingError::ConstructionError(text)
}
}
/// Error that may occur when attempting to send the data over WebSocket
/// transport.
#[derive(Clone,Debug,Fail)]
@ -51,7 +56,7 @@ pub enum SendingError {
impl SendingError {
/// Constructs from the error yielded by one of the JS's WebSocket sending functions.
pub fn from_send_error(error:JsValue) -> SendingError {
SendingError::FailedToSend(js_to_string(error))
SendingError::FailedToSend(js_to_string(&error))
}
}
@ -97,54 +102,204 @@ impl State {
// =================
// === JS Events ===
// =================
/// Description of events that can be emitted by JS WebSocket.
pub mod event {
use super::*;
use ensogl_system_web::event::Type;
/// Represents WebSocket.open event.
#[derive(Clone,Copy,Debug)]
pub enum Open{}
impl Type for Open {
type Interface = web_sys::Event;
type Target = web_sys::WebSocket;
const NAME:&'static str = "open";
}
/// Represents WebSocket.close event.
#[derive(Clone,Copy,Debug)]
pub enum Close{}
impl Type for Close {
type Interface = web_sys::CloseEvent;
type Target = web_sys::WebSocket;
const NAME:&'static str = "close";
}
/// Represents WebSocket.message event.
#[derive(Clone,Copy,Debug)]
pub enum Message{}
impl Type for Message {
type Interface = web_sys::MessageEvent;
type Target = web_sys::WebSocket;
const NAME:&'static str = "message";
}
/// Represents WebSocket.error event.
#[derive(Clone,Copy,Debug)]
pub enum Error{}
impl Type for Error {
type Interface = web_sys::Event;
type Target = web_sys::WebSocket;
const NAME:&'static str = "error";
}
}
// =============
// === Model ===
// =============
/// An owning wrapper over JS `WebSocket` object and callbacks to its signals.
#[derive(Derivative)]
#[derivative(Debug)]
#[allow(missing_docs)]
struct Model {
// === User-provided callbacks ===
pub on_close : Slot<event::Close>,
pub on_message : Slot<event::Message>,
pub on_open : Slot<event::Open>,
pub on_error : Slot<event::Error>,
// === Internal ===
pub logger : Logger,
pub socket : web_sys::WebSocket,
/// Special callback on "close" event. As it must be invoked after `on_close`, care should be
/// taken to keep it registered as an event listener *after* `on_close` registration.
/// By default `Model` takes care of it by itself.
pub on_close_internal : Slot<event::Close>,
/// When enabled, the WS will try to automatically reconnect whenever connection is lost.
pub auto_reconnect : bool,
}
impl Model {
/// Wraps given WebSocket object.
pub fn new(socket:web_sys::WebSocket, logger:Logger) -> Model {
socket.set_binary_type(BinaryType::Arraybuffer);
Model {
on_close : Slot::new(&socket, &logger),
on_message : Slot::new(&socket, &logger),
on_open : Slot::new(&socket, &logger),
on_error : Slot::new(&socket, &logger),
on_close_internal : Slot::new(&socket, &logger),
auto_reconnect : true,
logger,
socket,
}
}
/// Close the socket.
pub fn close(&mut self, reason:&str) -> Result<(),JsValue> {
// If socket was manually requested to close, it should not try to reconnect then.
self.auto_reconnect = false;
let normal_closure = 1000;
self.socket.close_with_code_and_reason(normal_closure,reason)?;
self.clear_callbacks();
Ok(())
}
/// Clear all the available callbacks.
pub fn clear_callbacks(&mut self) {
// We list explicitly all the fields, to get a compiler error when a new slot as added
// but not handled here.
#[allow(clippy::unneeded_field_pattern)]
let Self{
// Callback slots to be cleared.
on_close, on_error, on_message, on_open, on_close_internal,
// Explicitly ignored non-slot fields.
auto_reconnect:_, logger:_, socket:_
} = self;
// We don't care if removing actually removed anything.
// If callbacks were not set, then they are clear from the start.
on_close.clear_callback();
on_error.clear_callback();
on_message.clear_callback();
on_open.clear_callback();
on_close_internal.clear_callback()
}
/// Establish a new WS connection, using the same URL as the previous one.
/// All callbacks will be transferred to the new connection.
pub fn reconnect(&mut self) -> Result<(),JsValue> {
if !self.auto_reconnect {
return Err(js_sys::Error::new("Reconnecting has been disabled").into());
}
let url = self.socket.url();
info!(self.logger, "Reconnecting WS to {url}.");
let new_ws = web_sys::WebSocket::new(&url)?;
self.on_close. set_target(&new_ws);
self.on_error. set_target(&new_ws);
self.on_message. set_target(&new_ws);
self.on_open. set_target(&new_ws);
self.on_close_internal.set_target(&new_ws);
self.socket = new_ws;
Ok(())
}
}
impl Drop for Model {
fn drop(&mut self) {
info!(self.logger, "Dropping WS model.");
if let Err(e) = self.close("Rust Value has been dropped.") {
error!(self.logger,"Error when closing socket due to being dropped: {js_to_string(&e)}")
}
}
}
// =================
// === WebSocket ===
// =================
/// Wrapper over JS `WebSocket` object and callbacks to its signals.
#[derive(Debug)]
/// Wrapper over JS `WebSocket` meant for general use.
#[derive(Clone,CloneRef,Debug)]
pub struct WebSocket {
#[allow(missing_docs)]
pub logger : Logger,
/// Handle to the JS `WebSocket` object.
pub ws : web_sys::WebSocket,
/// Handle to a closure connected to `WebSocket.onmessage`.
pub on_message : OptionalFmMutClosure<MessageEvent>,
/// Handle to a closure connected to `WebSocket.onclose`.
pub on_close : OptionalFmMutClosure<CloseEvent>,
/// Handle to a closure connected to `WebSocket.onopen`.
pub on_open : OptionalFmMutClosure<Event>,
/// Handle to a closure connected to `WebSocket.onerror`.
pub on_error : OptionalFmMutClosure<Event>,
pub logger : Logger,
model : Rc<RefCell<Model>>,
}
impl WebSocket {
/// Wraps given WebSocket object.
pub fn new
(ws:web_sys::WebSocket, parent:impl AnyLogger, name:impl AsRef<str>) -> WebSocket {
ws.set_binary_type(BinaryType::Arraybuffer);
WebSocket {
ws,
logger : Logger::sub(parent,name),
on_message : default(),
on_close : default(),
on_open : default(),
on_error : default(),
}
/// Wrap given raw JS WebSocket object.
pub fn new(ws:web_sys::WebSocket, parent:impl AnyLogger) -> WebSocket {
let logger = Logger::sub(parent,ws.url());
let model = Rc::new(RefCell::new(Model::new(ws,logger.clone())));
WebSocket {logger,model}
}
/// Establish connection with endpoint defined by the given URL and wrap it.
/// Asynchronous, because it waits until connection is established.
pub async fn new_opened
(parent:impl AnyLogger, url:impl Str) -> Result<WebSocket,ConnectingError> {
let ws = web_sys::WebSocket::new(url.as_ref()).map_err(|e| {
ConnectingError::ConstructionError(js_to_string(e))
})?;
let mut wst = WebSocket::new(ws,&parent,url.into());
(parent:impl AnyLogger, url:&str) -> Result<WebSocket,ConnectingError> {
let ws = web_sys::WebSocket::new(url).map_err(ConnectingError::construction_error)?;
let mut wst = WebSocket::new(ws,&parent);
wst.wait_until_open().await?;
Ok(wst)
}
/// Generate a callback to be invoked when socket needs reconnecting.
fn reconnect_trigger(&self) -> impl FnMut(web_sys::CloseEvent) {
let model = Rc::downgrade(&self.model);
let logger = self.logger.clone();
move |_| {
if let Some(model) = model.upgrade() {
if let Err(e) = model.borrow_mut().reconnect() {
error!(logger,"Failed to reconnect: {js_to_string(&e)}");
}
}
}
}
/// Awaits until `open` signal has been emitted. Clears any callbacks on
/// this `WebSocket`, if any has been set.
async fn wait_until_open(&mut self) -> Result<(),ConnectingError> {
@ -152,6 +307,7 @@ impl WebSocket {
// We shall wait for whatever comes first.
let (transmitter, mut receiver) = mpsc::unbounded::<Result<(),()>>();
let transmitter_clone = transmitter.clone();
self.set_on_close(move |_| {
// Note [mwu] Ignore argument, `CloseEvent` here contains rubbish
// anyway, nothing useful to pass to caller. Error code or reason
@ -164,7 +320,8 @@ impl WebSocket {
match receiver.next().await {
Some(Ok(())) => {
self.clear_callbacks();
self.model.borrow_mut().clear_callbacks();
self.model.borrow_mut().on_close_internal.set_callback(self.reconnect_trigger());
info!(self.logger, "Connection opened.");
Ok(())
}
@ -174,43 +331,35 @@ impl WebSocket {
/// Checks the current state of the connection.
pub fn state(&self) -> State {
State::query_ws(&self.ws)
State::query_ws(&self.model.borrow().socket)
}
fn with_borrow_mut_model<R>(&mut self, f:impl FnOnce(&mut Model) -> R) -> R {
with(self.model.borrow_mut(), |mut model| f(model.deref_mut()))
}
/// Sets callback for the `close` event.
pub fn set_on_close(&mut self, f:impl FnMut(CloseEvent) + 'static) {
self.on_close.wrap(f);
self.ws.set_onclose(self.on_close.js_ref());
pub fn set_on_close(&mut self, f:impl FnMut(web_sys::CloseEvent) + 'static) {
self.with_borrow_mut_model(move |model| {
model.on_close.set_callback(f);
// Force internal callback to be after the user-defined one.
model.on_close_internal.reattach();
});
}
/// Sets callback for the `error` event.
pub fn set_on_error(&mut self, f:impl FnMut(Event) + 'static) {
self.on_error.wrap(f);
self.ws.set_onerror(self.on_error.js_ref());
pub fn set_on_error(&mut self, f:impl FnMut(web_sys::Event) + 'static) {
self.with_borrow_mut_model(move |model| model.on_error.set_callback(f))
}
/// Sets callback for the `message` event.
pub fn set_on_message(&mut self, f:impl FnMut(MessageEvent) + 'static) {
self.on_message.wrap(f);
self.ws.set_onmessage(self.on_message.js_ref());
pub fn set_on_message(&mut self, f:impl FnMut(web_sys::MessageEvent) + 'static) {
self.with_borrow_mut_model(move |model| model.on_message.set_callback(f))
}
/// Sets callback for the `open` event.
pub fn set_on_open(&mut self, f:impl FnMut(Event) + 'static) {
self.on_open.wrap(f);
self.ws.set_onopen(self.on_open.js_ref());
}
/// Clears all the available callbacks.
pub fn clear_callbacks(&mut self) {
self.on_close .clear();
self.on_error .clear();
self.on_message.clear();
self.on_open .clear();
self.ws.set_onclose(None);
self.ws.set_onerror(None);
self.ws.set_onmessage(None);
self.ws.set_onopen(None);
pub fn set_on_open(&mut self, f:impl FnMut(web_sys::Event) + 'static) {
self.with_borrow_mut_model(move |model| model.on_open.set_callback(f))
}
/// Executes a given function with a mutable reference to the socket.
@ -218,7 +367,9 @@ impl WebSocket {
///
/// Fails if the socket is not opened or if the sending function failed.
/// The error from `F` shall be translated into `SendingError`.
pub fn send_with_open_socket<F,R>(&mut self, f:F) -> Result<R,Error>
///
/// WARNING: `f` works under borrow_mut and must not give away control.
fn send_with_open_socket<F,R>(&mut self, f:F) -> Result<R,Error>
where F : FnOnce(&mut web_sys::WebSocket) -> Result<R,JsValue> {
// Sending through the closed WebSocket can return Ok() with error only
// appearing in the log. We explicitly check for this to get failure as
@ -230,7 +381,7 @@ impl WebSocket {
if state != State::Open {
Err(SendingError::NotOpen(state).into())
} else {
let result = f(&mut self.ws);
let result = f(&mut self.model.borrow_mut().socket);
result.map_err(|e| SendingError::from_send_error(e).into())
}
}
@ -238,13 +389,13 @@ impl WebSocket {
impl Transport for WebSocket {
fn send_text(&mut self, message:&str) -> Result<(), Error> {
info!(self.logger, "Sending text message of length {message.len()}");
info!(self.logger, "Sending text message of length {message.len()}.");
debug!(self.logger, "Message contents: {message}");
self.send_with_open_socket(|ws| ws.send_with_str(message))
}
fn send_binary(&mut self, message:&[u8]) -> Result<(), Error> {
info!(self.logger, "Sending binary message of length {message.len()}");
info!(self.logger, "Sending binary message of length {message.len()}.");
debug!(self.logger,|| format!("Message contents: {:x?}", message));
// TODO [mwu]
// Here we workaround issue from wasm-bindgen 0.2.58:
@ -291,3 +442,43 @@ impl Transport for WebSocket {
});
}
}
#[cfg(test)]
mod tests {
use super::*;
use ensogl::system::web;
use std::time::Duration;
/// Provisional code allowing testing WS behavior and its events.
/// Keeping it for future debug purposes.
/// To run uncomment attribute line and invoke:
/// `cargo watch -- wasm-pack test .\ide\ --chrome -- websocket_test`
//#[wasm_bindgen_test::wasm_bindgen_test]
#[allow(dead_code)]
async fn websocket_tests() {
web::set_stdout();
executor::web::test::setup_and_forget();
let logger = DefaultTraceLogger::new("Test");
info!(logger,"Started");
// Create WebSocket
let ws = WebSocket::new_opened(&logger,"ws://localhost:30445").await;
let mut ws = ws.expect("Couldn't connect to WebSocket server.");
info!(logger,"WebSocket opened: {ws:?}");
// Log events
let handler = ws.establish_event_stream().for_each(f!([logger](event) {
info!(logger,"Socket emitted event: {event:?}");
futures::future::ready(())
}));
// Spawn task to process events stream.
executor::global::spawn(handler);
// Close socket after some delay.
web::sleep(Duration::from_secs(20)).await;
info!(logger,"Finished");
}
}

View File

@ -277,7 +277,7 @@ impl Instance {
use enso_frp::web::js_to_string;
let logger = self.model.logger.clone();
error!(logger,"Failed to trigger initial preprocessor update from JS: \
{js_to_string(js_error)}");
{js_to_string(&js_error)}");
}
self
}

View File

@ -32,8 +32,12 @@ impl <Arg> OptionalFmMutClosure<Arg> {
}
/// Stores the given closure.
pub fn store(&mut self, closure:Closure<dyn FnMut(Arg)>) {
pub fn store(&mut self, closure:Closure<dyn FnMut(Arg)>) -> &Function {
self.closure = Some(closure);
// TODO [mwu]: `insert` should be used when we bump rustc - and then get rid of unwrap.
// Blocked by https://github.com/enso-org/ide/issues/1028
// The `unwrap` call is safe, because the line above set closure to `Some`.
self.js_ref().unwrap()
}
/// Obtain JS reference to the closure (that can be passed e.g. as a callback
@ -43,12 +47,12 @@ impl <Arg> OptionalFmMutClosure<Arg> {
}
/// Wraps given function into a Closure.
pub fn wrap(&mut self, f:impl ClosureFn<Arg>) {
pub fn wrap(&mut self, f:impl ClosureFn<Arg>) -> &Function {
let boxed = Box::new(f);
// Note: [mwu] Not sure exactly why, but compiler sometimes require this
// explicit type below and sometimes does not.
let wrapped:Closure<dyn FnMut(Arg)> = Closure::wrap(boxed);
self.store(wrapped);
self.store(wrapped)
}
/// Clears the current closure.
@ -57,4 +61,20 @@ impl <Arg> OptionalFmMutClosure<Arg> {
pub fn clear(&mut self) {
self.closure = None;
}
/// Register this closure as an event handler.
/// No action is taken if there is no closure stored.
pub fn add_listener<EventType:crate::event::Type>(&self, target:&EventType::Target) {
if let Some(function) = self.js_ref() {
EventType::add_listener(target, function)
}
}
/// Unregister this closure as an event handler. The closure must be the same as when it was
/// registered.
pub fn remove_listener<EventType:crate::event::Type>(&self, target:&EventType::Target) {
if let Some(function) = self.js_ref() {
EventType::remove_listener(target, function)
}
}
}

View File

@ -0,0 +1,51 @@
//! Utilities for DOM events.
pub mod listener;
use crate::prelude::*;
use js_sys::Function;
use web_sys::EventTarget;
// =============
// === Event ===
// =============
/// This trait represents a type of event that may fire from some specific JS `EventTarget`.
///
/// For example, `WebSocket.close` is such an event, where `close` is event type and `WebSocket` is
/// the `EventTarget`.
///
/// The purpose is to increase type safety by grouping event type name, event target type and event
/// value type together.
///
/// Typically this trait is to be implemented for uncreatable types, created for the sole
/// purpose of denoting a particular event type within a context of an event target.
pub trait Type {
/// The event value -- i.e. the Rust type of a value that will be passed as an argument
/// to the listener.
/// For example `web_sys::CloseEvent`.
type Interface : AsRef<web_sys::Event>;
/// The type of the EventTarget object that fires this type of event, e.g. `web_sys::WebSocket`.
type Target : AsRef<EventTarget> + AsRef<JsValue> + Clone + PartialEq;
/// The type of the event as a string. For example `"close"`.
const NAME:&'static str;
/// Add a given function to the event's target as an event listener. It will be called each
/// time event fires until listener is removed through `remove_listener`.
fn add_listener(target:&Self::Target, listener:&Function) {
// The unwrap here is safe, as the `addEventListener` never throws.
EventTarget::add_event_listener_with_callback(target.as_ref(), Self::NAME, listener).unwrap()
}
/// Remove the event listener. The `add_listener` method should have been called before with
/// the very same function argument.
fn remove_listener(target:&Self::Target, listener:&Function) {
// The unwrap here is safe, as the `addEventListener` never throws.
EventTarget::remove_event_listener_with_callback(target.as_ref(), Self::NAME, listener).unwrap()
}
}

View File

@ -0,0 +1,117 @@
use crate::prelude::*;
use crate::closure::storage::ClosureFn;
use crate::closure::storage::OptionalFmMutClosure;
// ============
// === Slot ===
// ============
/// A Slot stores a callback and manages its connection with JS `EventTarget`.
///
/// Both callback and the target can be set independently using `set_target` and `set_callback`.
/// Additionally, callback can be cleared at any point by `clear_callback`.
///
/// When both target and callback are set, slot ensures that the callback is registered as an
/// event listener in the target.
///
/// When changing target, `Slot` reattaches callback.
///
/// `Slot` owns callback and wraps it into JS closure. `Slot` also keeps reference to the target,
/// so it must not be leaked.
#[derive(Derivative)]
#[derivative(Debug(bound="EventType::Interface: Debug"))]
pub struct Slot<EventType:crate::event::Type> {
logger : Logger,
#[derivative(Debug="ignore")]
target : Option<EventType::Target>,
js_closure : OptionalFmMutClosure<EventType::Interface>,
}
impl<EventType:crate::event::Type> Slot<EventType> {
/// Create a new `Slot`. As the initial target is provided, the listener will register once it
/// gets a callback (see [[set_callback]]).
pub fn new(target:&EventType::Target, logger:impl AnyLogger) -> Self {
Self {
logger : Logger::sub(logger, EventType::NAME),
target : Some(target.clone()),
js_closure : default(),
}
}
/// Register the event listener if both target and callback are set.
fn add_if_active(&mut self) {
if let (Some(target), Some(function)) = (self.target.as_ref(), self.js_closure.js_ref()) {
debug!(self.logger,"Attaching the callback.");
EventType::add_listener(target, function)
}
}
/// Unregister the event listener if both target and callback are set.
fn remove_if_active(&mut self) {
if let (Some(target), Some(function)) = (self.target.as_ref(), self.js_closure.js_ref()) {
debug!(self.logger,"Detaching the callback.");
EventType::remove_listener(target, function)
}
}
/// Set a new target.
///
/// If callback is set, it will be reattached as a listener to a newly set target.
pub fn set_target(&mut self, target:&EventType::Target) {
// Prevent spurious reattaching that could affect listeners order.
if Some(target) != self.target.as_ref() {
self.remove_if_active();
self.target = Some(target.clone());
self.add_if_active()
}
}
/// Clear event target.
///
/// If callback is set, it will be unregistered.
pub fn clear_target(&mut self, target:&EventType::Target) {
// Prevent spurious reattaching that could affect listeners order.
if Some(target) != self.target.as_ref() {
self.remove_if_active();
self.target = None;
}
}
/// Assign a new event callback closure and register it in the target.
///
/// If the listener was registered with the previous closure, it will unregister first.
///
/// Caveat: using this method will move the event listener to the end of the registered
/// callbacks. This will affect the order of callback calls.
pub fn set_callback(&mut self, f:impl ClosureFn<EventType::Interface>) {
self.remove_if_active();
self.js_closure.wrap(f);
self.add_if_active()
}
/// Erase the callback.
///
/// The stored closure will be dropped and event listener unregistered.
pub fn clear_callback(&mut self) {
self.remove_if_active();
self.js_closure.clear();
}
/// Detach and attach the listener to the target.
///
/// The purpose is to move this slot to the end of the listeners list.
pub fn reattach(&mut self) {
self.remove_if_active();
self.add_if_active();
}
}
/// Unregister listener on drop.
impl<EventType:crate::event::Type> Drop for Slot<EventType> {
fn drop(&mut self) {
self.remove_if_active();
}
}

View File

@ -7,11 +7,14 @@
pub mod clipboard;
pub mod closure;
pub mod event;
pub mod resize_observer;
pub mod platform;
/// Common types that should be visible across the whole crate.
pub mod prelude {
pub use enso_logger::*;
pub use enso_logger::DefaultInfoLogger as Logger;
pub use enso_prelude::*;
pub use wasm_bindgen::prelude::*;
}
@ -77,11 +80,15 @@ impl From<JsValue> for Error {
#[wasm_bindgen]
extern "C" {
/// Converts given `JsValue` into a `String`. Uses JS's `String` function,
/// see: https://www.w3schools.com/jsref/jsref_string.asp
#[allow(unsafe_code)]
#[wasm_bindgen(js_name="String")]
pub fn js_to_string(s: JsValue) -> String;
fn js_to_string_inner(s:&JsValue) -> String;
}
/// Converts given `JsValue` into a `String`. Uses JS's `String` function,
/// see: https://www.w3schools.com/jsref/jsref_string.asp
pub fn js_to_string(s:impl AsRef<JsValue>) -> String {
js_to_string_inner(s.as_ref())
}
@ -651,7 +658,7 @@ pub fn reflect_get_nested_string(target:&JsValue, keys:&[&str]) -> Result<String
if tgt.is_undefined() {
Err(Error("Key was not present in the target."))
} else {
Ok(js_to_string(tgt))
Ok(js_to_string(&tgt))
}
}