Skip to main content

leptos_dom/
helpers.rs

1//! A variety of DOM utility functions.
2
3use or_poisoned::OrPoisoned;
4#[cfg(debug_assertions)]
5use reactive_graph::diagnostics::SpecialNonReactiveZone;
6use reactive_graph::owner::Owner;
7use send_wrapper::SendWrapper;
8use std::time::Duration;
9use tachys::html::event::EventDescriptor;
10#[cfg(feature = "tracing")]
11use tracing::instrument;
12use wasm_bindgen::{prelude::Closure, JsCast, JsValue, UnwrapThrowExt};
13
14thread_local! {
15    pub(crate) static WINDOW: web_sys::Window = web_sys::window().unwrap_throw();
16
17    pub(crate) static DOCUMENT: web_sys::Document = web_sys::window().unwrap_throw().document().unwrap_throw();
18}
19
20/// Returns the [`Window`](https://developer.mozilla.org/en-US/docs/Web/API/Window).
21///
22/// This is cached as a thread-local variable, so calling `window()` multiple times
23/// requires only one call out to JavaScript.
24pub fn window() -> web_sys::Window {
25    WINDOW.with(Clone::clone)
26}
27
28/// Returns the [`Document`](https://developer.mozilla.org/en-US/docs/Web/API/Document).
29///
30/// This is cached as a thread-local variable, so calling `document()` multiple times
31/// requires only one call out to JavaScript.
32pub fn document() -> web_sys::Document {
33    DOCUMENT.with(Clone::clone)
34}
35
36/// Sets a property on a DOM element.
37pub fn set_property(
38    el: &web_sys::Element,
39    prop_name: &str,
40    value: &Option<JsValue>,
41) {
42    let key = JsValue::from_str(prop_name);
43    match value {
44        Some(value) => _ = js_sys::Reflect::set(el, &key, value),
45        None => _ = js_sys::Reflect::delete_property(el, &key),
46    };
47}
48
49/// Gets the value of a property set on a DOM element.
50#[doc(hidden)]
51pub fn get_property(
52    el: &web_sys::Element,
53    prop_name: &str,
54) -> Result<JsValue, JsValue> {
55    let key = JsValue::from_str(prop_name);
56    js_sys::Reflect::get(el, &key)
57}
58
59/// Returns the current [`window.location`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location).
60pub fn location() -> web_sys::Location {
61    window().location()
62}
63
64/// Current [`window.location.hash`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location)
65/// without the beginning #.
66pub fn location_hash() -> Option<String> {
67    if is_server() {
68        None
69    } else {
70        location()
71            .hash()
72            .ok()
73            .map(|hash| match hash.chars().next() {
74                Some('#') => hash[1..].to_string(),
75                _ => hash,
76            })
77    }
78}
79
80/// Current [`window.location.pathname`](https://developer.mozilla.org/en-US/docs/Web/API/Window/location).
81pub fn location_pathname() -> Option<String> {
82    location().pathname().ok()
83}
84
85/// Helper function to extract [`Event.target`](https://developer.mozilla.org/en-US/docs/Web/API/Event/target)
86/// from any event.
87pub fn event_target<T>(event: &web_sys::Event) -> T
88where
89    T: JsCast,
90{
91    event.target().unwrap_throw().unchecked_into::<T>()
92}
93
94/// Helper function to extract `event.target.value` from an event.
95///
96/// This is useful in the `on:input` or `on:change` listeners for an `<input>` element.
97pub fn event_target_value<T>(event: &T) -> String
98where
99    T: JsCast,
100{
101    event
102        .unchecked_ref::<web_sys::Event>()
103        .target()
104        .unwrap_throw()
105        .unchecked_into::<web_sys::HtmlInputElement>()
106        .value()
107}
108
109/// Helper function to extract `event.target.checked` from an event.
110///
111/// This is useful in the `on:change` listeners for an `<input type="checkbox">` element.
112pub fn event_target_checked(ev: &web_sys::Event) -> bool {
113    ev.target()
114        .unwrap()
115        .unchecked_into::<web_sys::HtmlInputElement>()
116        .checked()
117}
118
119/// Handle that is generated by [request_animation_frame_with_handle] and can
120/// be used to cancel the animation frame request.
121#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
122pub struct AnimationFrameRequestHandle(i32);
123
124impl AnimationFrameRequestHandle {
125    /// Cancels the animation frame request to which this refers.
126    /// See [`cancelAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelAnimationFrame)
127    pub fn cancel(&self) {
128        _ = window().cancel_animation_frame(self.0);
129    }
130}
131
132/// Runs the given function between the next repaint using
133/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame).
134///
135/// ### Note about Context
136///
137/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
138#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
139#[inline(always)]
140pub fn request_animation_frame(cb: impl FnOnce() + 'static) {
141    _ = request_animation_frame_with_handle(cb);
142}
143
144// Closure::once_into_js only frees the callback when it's actually
145// called, so this instead uses into_js_value, which can be freed by
146// the host JS engine's GC if it supports weak references (which all
147// modern browser engines do).  The way this works is that the provided
148// callback's captured data is dropped immediately after being called,
149// as before, but it leaves behind a small stub closure rust-side that
150// will be freed "eventually" by the JS GC.  If the function is never
151// called (e.g., it's a cancelled timeout or animation frame callback)
152// then it will also be freed eventually.
153fn closure_once(cb: impl FnOnce() + 'static) -> JsValue {
154    let mut wrapped_cb: Option<Box<dyn FnOnce()>> = Some(Box::new(cb));
155    let closure = Closure::new(move || {
156        if let Some(cb) = wrapped_cb.take() {
157            cb()
158        }
159    });
160    closure.into_js_value()
161}
162
163/// Runs the given function between the next repaint using
164/// [`Window.requestAnimationFrame`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestAnimationFrame),
165/// returning a cancelable handle.
166///
167/// ### Note about Context
168///
169/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
170#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
171#[inline(always)]
172pub fn request_animation_frame_with_handle(
173    cb: impl FnOnce() + 'static,
174) -> Result<AnimationFrameRequestHandle, JsValue> {
175    #[cfg(feature = "tracing")]
176    let span = ::tracing::Span::current();
177    #[cfg(feature = "tracing")]
178    let cb = move || {
179        let _guard = span.enter();
180        cb();
181    };
182
183    #[inline(never)]
184    fn raf(cb: JsValue) -> Result<AnimationFrameRequestHandle, JsValue> {
185        window()
186            .request_animation_frame(cb.as_ref().unchecked_ref())
187            .map(AnimationFrameRequestHandle)
188    }
189
190    raf(closure_once(cb))
191}
192
193/// Handle that is generated by [request_idle_callback_with_handle] and can be
194/// used to cancel the idle callback.
195#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
196pub struct IdleCallbackHandle(u32);
197
198impl IdleCallbackHandle {
199    /// Cancels the idle callback to which this refers.
200    /// See [`cancelAnimationFrame()`](https://developer.mozilla.org/en-US/docs/Web/API/Window/cancelIdleCallback)
201    pub fn cancel(&self) {
202        window().cancel_idle_callback(self.0);
203    }
204}
205
206/// Queues the given function during an idle period using
207/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback).
208///
209/// ### Note about Context
210///
211/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
212#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
213#[inline(always)]
214pub fn request_idle_callback(cb: impl Fn() + 'static) {
215    _ = request_idle_callback_with_handle(cb);
216}
217
218/// Queues the given function during an idle period using
219/// [`Window.requestIdleCallback`](https://developer.mozilla.org/en-US/docs/Web/API/window/requestIdleCallback),
220/// returning a cancelable handle.
221///
222/// ### Note about Context
223///
224/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
225#[cfg_attr(feature = "tracing", instrument(level = "trace", skip_all))]
226#[inline(always)]
227pub fn request_idle_callback_with_handle(
228    cb: impl Fn() + 'static,
229) -> Result<IdleCallbackHandle, JsValue> {
230    #[cfg(feature = "tracing")]
231    let span = ::tracing::Span::current();
232    #[cfg(feature = "tracing")]
233    let cb = move || {
234        let _guard = span.enter();
235        cb();
236    };
237
238    #[inline(never)]
239    fn ric(cb: Box<dyn Fn()>) -> Result<IdleCallbackHandle, JsValue> {
240        let cb = Closure::wrap(cb).into_js_value();
241
242        window()
243            .request_idle_callback(cb.as_ref().unchecked_ref())
244            .map(IdleCallbackHandle)
245    }
246
247    ric(Box::new(cb))
248}
249
250/// A microtask is a short function which will run after the current task has
251/// completed its work and when there is no other code waiting to be run before
252/// control of the execution context is returned to the browser's event loop.
253///
254/// Microtasks are especially useful for libraries and frameworks that need
255/// to perform final cleanup or other just-before-rendering tasks.
256///
257/// [MDN queueMicrotask](https://developer.mozilla.org/en-US/docs/Web/API/queueMicrotask)
258///
259/// <div class="warning">The task is called outside of the ownership tree, this means that if you want to access for example the context you need to reestablish the owner.</div>
260pub fn queue_microtask(task: impl FnOnce() + 'static) {
261    tachys::renderer::dom::queue_microtask(task);
262}
263
264/// Handle that is generated by [set_timeout_with_handle] and can be used to clear the timeout.
265#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
266pub struct TimeoutHandle(i32);
267
268impl TimeoutHandle {
269    /// Cancels the timeout to which this refers.
270    /// See [`clearTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/clearTimeout)
271    pub fn clear(&self) {
272        window().clear_timeout_with_handle(self.0);
273    }
274}
275
276/// Executes the given function after the given duration of time has passed.
277/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
278///
279/// ### Note about Context
280///
281/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
282#[cfg_attr(
283  feature = "tracing",
284  instrument(level = "trace", skip_all, fields(duration = ?duration))
285)]
286pub fn set_timeout(cb: impl FnOnce() + 'static, duration: Duration) {
287    _ = set_timeout_with_handle(cb, duration);
288}
289
290/// Executes the given function after the given duration of time has passed, returning a cancelable handle.
291/// [`setTimeout()`](https://developer.mozilla.org/en-US/docs/Web/API/setTimeout).
292///
293/// ### Note about Context
294///
295/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
296#[cfg_attr(
297  feature = "tracing",
298  instrument(level = "trace", skip_all, fields(duration = ?duration))
299)]
300#[inline(always)]
301pub fn set_timeout_with_handle(
302    cb: impl FnOnce() + 'static,
303    duration: Duration,
304) -> Result<TimeoutHandle, JsValue> {
305    #[cfg(debug_assertions)]
306    let cb = || {
307        let _z = SpecialNonReactiveZone::enter();
308        cb();
309    };
310
311    #[cfg(feature = "tracing")]
312    let span = ::tracing::Span::current();
313    #[cfg(feature = "tracing")]
314    let cb = move || {
315        let _guard = span.enter();
316        cb();
317    };
318
319    #[inline(never)]
320    fn st(cb: JsValue, duration: Duration) -> Result<TimeoutHandle, JsValue> {
321        window()
322            .set_timeout_with_callback_and_timeout_and_arguments_0(
323                cb.as_ref().unchecked_ref(),
324                duration.as_millis().try_into().unwrap_throw(),
325            )
326            .map(TimeoutHandle)
327    }
328
329    st(closure_once(cb), duration)
330}
331
332/// "Debounce" a callback function. This will cause it to wait for a period of `delay`
333/// after it is called. If it is called again during that period, it will wait
334/// `delay` before running, and so on. This can be used, for example, to wrap event
335/// listeners to prevent them from firing constantly as you type.
336///
337/// ```
338/// use leptos::{leptos_dom::helpers::debounce, logging::log, prelude::*, *};
339///
340/// #[component]
341/// fn DebouncedButton() -> impl IntoView {
342///     let delay = std::time::Duration::from_millis(250);
343///     let on_click = debounce(delay, move |_| {
344///         log!("...so many clicks!");
345///     });
346///
347///     view! {
348///       <button on:click=on_click>"Click me"</button>
349///     }
350/// }
351/// ```
352///
353/// ### Note about Context
354///
355/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
356pub fn debounce<T: 'static>(
357    delay: Duration,
358    mut cb: impl FnMut(T) + 'static,
359) -> impl FnMut(T) {
360    use std::sync::{Arc, RwLock};
361
362    #[cfg(debug_assertions)]
363    #[allow(unused_mut)]
364    let mut cb = move |value| {
365        let _z = SpecialNonReactiveZone::enter();
366        cb(value);
367    };
368
369    #[cfg(feature = "tracing")]
370    let span = ::tracing::Span::current();
371    #[cfg(feature = "tracing")]
372    #[allow(unused_mut)]
373    let mut cb = move |value| {
374        let _guard = span.enter();
375        cb(value);
376    };
377
378    let cb = Arc::new(RwLock::new(cb));
379    let timer = Arc::new(RwLock::new(None::<TimeoutHandle>));
380
381    Owner::on_cleanup({
382        let timer = Arc::clone(&timer);
383        move || {
384            if let Some(timer) = timer.write().or_poisoned().take() {
385                timer.clear();
386            }
387        }
388    });
389
390    move |arg| {
391        if let Some(timer) = timer.write().unwrap().take() {
392            timer.clear();
393        }
394        let handle = set_timeout_with_handle(
395            {
396                let cb = Arc::clone(&cb);
397                move || {
398                    cb.write().unwrap()(arg);
399                }
400            },
401            delay,
402        );
403        if let Ok(handle) = handle {
404            *timer.write().or_poisoned() = Some(handle);
405        }
406    }
407}
408
409/// Handle that is generated by [set_interval] and can be used to clear the interval.
410#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
411pub struct IntervalHandle(i32);
412
413impl IntervalHandle {
414    /// Cancels the repeating event to which this refers.
415    /// See [`clearInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/clearInterval)
416    pub fn clear(&self) {
417        window().clear_interval_with_handle(self.0);
418    }
419}
420
421/// Repeatedly calls the given function, with a delay of the given duration between calls.
422/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
423///
424/// ### Note about Context
425///
426/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
427#[cfg_attr(
428  feature = "tracing",
429  instrument(level = "trace", skip_all, fields(duration = ?duration))
430)]
431pub fn set_interval(cb: impl Fn() + 'static, duration: Duration) {
432    _ = set_interval_with_handle(cb, duration);
433}
434
435/// Repeatedly calls the given function, with a delay of the given duration between calls,
436/// returning a cancelable handle.
437/// See [`setInterval()`](https://developer.mozilla.org/en-US/docs/Web/API/setInterval).
438///
439/// ### Note about Context
440///
441/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
442#[cfg_attr(
443  feature = "tracing",
444  instrument(level = "trace", skip_all, fields(duration = ?duration))
445)]
446#[inline(always)]
447pub fn set_interval_with_handle(
448    cb: impl Fn() + 'static,
449    duration: Duration,
450) -> Result<IntervalHandle, JsValue> {
451    #[cfg(debug_assertions)]
452    let cb = move || {
453        let _z = SpecialNonReactiveZone::enter();
454        cb();
455    };
456    #[cfg(feature = "tracing")]
457    let span = ::tracing::Span::current();
458    #[cfg(feature = "tracing")]
459    let cb = move || {
460        let _guard = span.enter();
461        cb();
462    };
463
464    #[inline(never)]
465    fn si(
466        cb: Box<dyn FnMut()>,
467        duration: Duration,
468    ) -> Result<IntervalHandle, JsValue> {
469        let cb = Closure::wrap(cb).into_js_value();
470
471        window()
472            .set_interval_with_callback_and_timeout_and_arguments_0(
473                cb.as_ref().unchecked_ref(),
474                duration.as_millis().try_into().unwrap_throw(),
475            )
476            .map(IntervalHandle)
477    }
478
479    si(Box::new(cb), duration)
480}
481
482/// Adds an event listener to the `Window`, typed as a generic `Event`,
483/// returning a cancelable handle.
484///
485/// ### Note about Context
486///
487/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
488#[cfg_attr(
489  feature = "tracing",
490  instrument(level = "trace", skip_all, fields(event_name = %event_name))
491)]
492#[inline(always)]
493pub fn window_event_listener_untyped(
494    event_name: &str,
495    cb: impl Fn(web_sys::Event) + 'static,
496) -> WindowListenerHandle {
497    #[cfg(debug_assertions)]
498    let cb = move |e| {
499        let _z = SpecialNonReactiveZone::enter();
500        cb(e);
501    };
502    #[cfg(feature = "tracing")]
503    let span = ::tracing::Span::current();
504    #[cfg(feature = "tracing")]
505    let cb = move |e| {
506        let _guard = span.enter();
507        cb(e);
508    };
509
510    if !is_server() {
511        #[inline(never)]
512        fn wel(
513            cb: Box<dyn FnMut(web_sys::Event)>,
514            event_name: &str,
515        ) -> WindowListenerHandle {
516            let cb = Closure::wrap(cb).into_js_value();
517            _ = window().add_event_listener_with_callback(
518                event_name,
519                cb.unchecked_ref(),
520            );
521            let event_name = event_name.to_string();
522            let cb = SendWrapper::new(cb);
523            WindowListenerHandle(Box::new(move || {
524                _ = window().remove_event_listener_with_callback(
525                    &event_name,
526                    cb.unchecked_ref(),
527                );
528            }))
529        }
530
531        wel(Box::new(cb), event_name)
532    } else {
533        WindowListenerHandle(Box::new(|| ()))
534    }
535}
536
537/// Creates a window event listener from a typed event, returning a
538/// cancelable handle.
539/// ```
540/// use leptos::{
541///     ev, leptos_dom::helpers::window_event_listener, logging::log,
542///     prelude::*,
543/// };
544///
545/// #[component]
546/// fn App() -> impl IntoView {
547///     let handle = window_event_listener(ev::keypress, |ev| {
548///         // ev is typed as KeyboardEvent automatically,
549///         // so .code() can be called
550///         let code = ev.code();
551///         log!("code = {code:?}");
552///     });
553///     on_cleanup(move || handle.remove());
554/// }
555/// ```
556///
557/// ### Note about Context
558///
559/// The callback is called outside of the reactive ownership tree. This means that it does not have access to context via [`use_context`](reactive_graph::owner::use_context). If you want to use context inside the callback, you should either call `use_context` in the body of the component, and move the value into the callback, or access the current owner inside the component body using [`Owner::current`](reactive_graph::owner::Owner::current) and reestablish it in the callback with [`Owner::with`](reactive_graph::owner::Owner::with).
560pub fn window_event_listener<E: EventDescriptor + 'static>(
561    event: E,
562    cb: impl Fn(E::EventType) + 'static,
563) -> WindowListenerHandle
564where
565    E::EventType: JsCast,
566{
567    window_event_listener_untyped(&event.name(), move |e| {
568        cb(e.unchecked_into::<E::EventType>())
569    })
570}
571
572/// A handle that can be called to remove a global event listener.
573pub struct WindowListenerHandle(Box<dyn FnOnce() + Send + Sync>);
574
575impl core::fmt::Debug for WindowListenerHandle {
576    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
577        f.debug_tuple("WindowListenerHandle").finish()
578    }
579}
580
581impl WindowListenerHandle {
582    /// Removes the event listener.
583    pub fn remove(self) {
584        (self.0)()
585    }
586}
587
588/// Returns `true` if the current environment is a server.
589pub fn is_server() -> bool {
590    #[cfg(feature = "hydration")]
591    {
592        Owner::current_shared_context()
593            .map(|sc| !sc.is_browser())
594            .unwrap_or(false)
595    }
596    #[cfg(not(feature = "hydration"))]
597    {
598        false
599    }
600}
601
602/// Returns `true` if the current environment is a browser.
603pub fn is_browser() -> bool {
604    !is_server()
605}