///|
fn RuntimeSession::new(
runtime : @native.Runtime,
definitions : Array[PreparedWindow],
windows : Array[RunningWindow],
command_host : CommandHostRuntime?,
wakeup : RuntimeWakeup,
forward_menu_events : Bool,
monitor_bridge : Bool,
launch_input_handlers : Array[async (RuntimeLaunchInput) -> Unit noraise],
window_event_handlers : Array[
async (WindowHandle, WindowEvent) -> Unit noraise,
],
window_tasks : @async.TaskGroup[Unit],
view_event_handlers : Array[async (ViewHandle, ViewEvent) -> Unit noraise],
window_close_handler : (async (WindowHandle) -> WindowCloseDecision noraise)?,
window_lifecycle_hooks : Array[WindowLifecycleHook],
lifecycle_failures : Array[AppCleanupError],
bridge_startup_timeout_ms : Int,
navigation_handler : (async (BrowserHandle, NavigationRequest) -> NavigationDecision noraise)?,
popup_handler : (async (BrowserHandle, PopupRequest) -> PopupDecision noraise)?,
download_handler : (async (BrowserHandle, DownloadRequest) -> DownloadDecision noraise)?,
certificate_handler : (async (BrowserHandle, CertificateError) -> BrowserPermissionDecision noraise)?,
media_handler : (async (BrowserHandle, MediaPermissionRequest) -> BrowserPermissionDecision noraise)?,
download_event_handlers : Array[
async (BrowserHandle, DownloadEvent) -> Unit noraise,
],
) -> RuntimeSession {
RuntimeSession::{
runtime,
definitions,
windows,
command_host,
pending_bridge: [],
wakeup,
forward_menu_events,
monitor_bridge,
launch_input_handlers,
window_event_handlers,
view_event_handlers,
window_close_handler,
window_tasks,
window_lifecycle_hooks,
lifecycle_failures,
bridge_startup_timeout_ms,
window_commands: [],
pending_window_opens: [],
pending_window_closes: [],
pending_browser_requests: [],
navigation_handler,
popup_handler,
download_handler,
certificate_handler,
media_handler,
download_event_handlers,
}
}
///|
fn RuntimeSession::window_manager(self : RuntimeSession) -> WindowManager {
WindowManager::{
open_window: id => self.request_open_window(id),
find_window: id => {
self.find_active_window(id).map(window => self.window_handle(window))
},
}
}
///|
fn RuntimeSession::window_handle(
self : RuntimeSession,
window : RunningWindow,
) -> WindowHandle {
let id = window.id
let native_id = window.window.id()
WindowHandle::{
id,
native_id,
show_window: () => {
self.control_window(id, native_id, "show", window => window.show())
},
hide_window: () => {
self.control_window(id, native_id, "hide", window => window.hide())
},
close_window: () => self.close_window(id, native_id),
focus_window: () => {
self.control_window(id, native_id, "focus", window => window.focus())
},
set_window_title: title => {
self.control_window(id, native_id, "set title", window => {
window.set_title(title)
})
},
set_window_size: (width, height) => {
self.control_window(id, native_id, "set size", window => {
window.set_size(width, height)
})
},
minimize_window: () => {
self.control_window(id, native_id, "minimize", window => window.minimize())
},
maximize_window: () => {
self.control_window(id, native_id, "maximize", window => window.maximize())
},
restore_window: () => {
self.control_window(id, native_id, "restore", window => window.restore())
},
set_window_fullscreen: fullscreen => {
self.control_window(id, native_id, "set fullscreen", window => {
window.set_fullscreen(fullscreen)
})
},
set_window_position: (x, y) => {
self.control_window(id, native_id, "set position", window => {
window.set_position(x, y)
})
},
set_window_always_on_top: always_on_top => {
self.control_window(id, native_id, "set always on top", window => {
window.set_always_on_top(always_on_top)
})
},
set_window_zoom_percent: zoom_percent => {
self.control_window(id, native_id, "set zoom", window => {
window.set_zoom_percent(zoom_percent)
})
},
read_window_state: () => self.read_window_state(id, native_id),
browser: self.browser_handle(window),
add_view: (view_id, config) => {
self.add_window_view(id, native_id, view_id, config)
},
remove_view: view_id => self.remove_window_view(id, native_id, view_id),
list_views: () => self.list_window_views(id, native_id),
find_view: view_id => self.find_window_view(id, native_id, view_id),
}
}
///|
fn RuntimeSession::view_handle(
self : RuntimeSession,
window : RunningWindow,
view : RunningView,
) -> ViewHandle {
let window_id = window.id
let window_native_id = window.window.id()
let id = view.id
let native_id = view.view.id()
ViewHandle::{
id,
native_id,
set_view_bounds: (x, y, width, height) => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"set bounds of",
view => view.set_bounds(x~, y~, width~, height~),
)
},
set_view_visible: visible => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"set visibility of",
view => view.set_visible(visible),
)
},
set_view_z_order: z_order => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"set z-order of",
view => view.set_z_order(z_order),
)
},
load_view_url: url => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"load URL in",
view => view.load_url(url),
)
},
load_view_html: (html, base_url) => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"load HTML in",
view => view.load_html(html, base_url),
)
},
eval_view_script: script => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"evaluate script in",
view => view.eval(script),
)
},
send_view_command: (command, download_id) => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
command + " in",
view => view.browser_command(command, download_id?),
)
},
read_view_state: () => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
running.view.state() catch {
error =>
raise OperationFailed(
action="read view " + id + " state in window " + window_id,
error~,
)
}
},
close_view: () => self.remove_window_view(window_id, window_native_id, id),
}
}
///|
/// Resolves a live view instance by window id, view id, and both native
/// instance ids. The pair of ids keeps stale handles from targeting a later
/// view or window that reuses the same declarative id.
fn RuntimeSession::resolve_view(
self : RuntimeSession,
window_id : String,
window_native_id : Int64,
view_id : String,
view_native_id : Int64,
) -> RunningView raise WindowSessionError {
let running = match self.find_active_window(window_id) {
Some(running) if running.window.id() == window_native_id => running
_ => raise StaleWindow(id=window_id)
}
for view in running.views {
if view.id == view_id {
if view.view.id() != view_native_id {
raise StaleView(id=view_id)
}
return view
}
}
raise UnknownView(id=view_id)
}
///|
fn RuntimeSession::control_view(
self : RuntimeSession,
window_id : String,
window_native_id : Int64,
view_id : String,
view_native_id : Int64,
action : String,
operation : (@native.View) -> Unit raise @native.NativeError,
) -> Unit raise WindowSessionError {
let running = self.resolve_view(
window_id, window_native_id, view_id, view_native_id,
)
operation(running.view) catch {
error =>
raise OperationFailed(
action=action + " view " + view_id + " in window " + window_id,
error~,
)
}
}
///|
fn RuntimeSession::add_window_view(
self : RuntimeSession,
window_id : String,
window_native_id : Int64,
view_id : String,
config : @native.ViewConfig,
) -> ViewHandle raise WindowSessionError {
let running = match self.find_active_window(window_id) {
Some(running) if running.window.id() == window_native_id => running
_ => raise StaleWindow(id=window_id)
}
if view_id.is_empty() {
raise OperationFailed(
action="add view to window " + window_id,
error=@native.NativeError::InvalidArgument(
message="view id must not be empty",
),
)
}
for view in running.views {
if view.id == view_id {
raise AlreadyExists(id=view_id)
}
}
let created = @native.View::new(running.window, config) catch {
error =>
raise OperationFailed(
action="add view " + view_id + " to window " + window_id,
error~,
)
}
let running_view = RunningView::{ id: view_id, view: created }
running.views.push(running_view)
self.view_handle(running, running_view)
}
///|
fn RuntimeSession::remove_window_view(
self : RuntimeSession,
window_id : String,
window_native_id : Int64,
view_id : String,
) -> Unit raise WindowSessionError {
let running = match self.find_active_window(window_id) {
Some(running) if running.window.id() == window_native_id => running
_ => raise StaleWindow(id=window_id)
}
let mut index = -1
for i, view in running.views {
if view.id == view_id {
index = i
break
}
}
if index < 0 {
raise UnknownView(id=view_id)
}
let view = running.views[index]
view.view.destroy() catch {
error =>
raise OperationFailed(
action="remove view " + view_id + " from window " + window_id,
error~,
)
}
ignore(running.views.remove(index))
}
///|
fn RuntimeSession::list_window_views(
self : RuntimeSession,
window_id : String,
window_native_id : Int64,
) -> Array[ViewHandle] {
match self.find_active_window(window_id) {
Some(running) if running.window.id() == window_native_id =>
running.views.map(view => self.view_handle(running, view))
_ => []
}
}
///|
fn RuntimeSession::find_window_view(
self : RuntimeSession,
window_id : String,
window_native_id : Int64,
view_id : String,
) -> ViewHandle? {
match self.find_active_window(window_id) {
Some(running) if running.window.id() == window_native_id =>
for view in running.views {
if view.id == view_id {
return Some(self.view_handle(running, view))
}
} nobreak {
None
}
_ => None
}
}
///|
fn RuntimeSession::browser_handle(
self : RuntimeSession,
window : RunningWindow,
) -> BrowserHandle {
let id = window.id
let native_id = window.window.id()
BrowserHandle::{
id,
native_id,
load_browser_url: url => {
self.control_window(id, native_id, "load URL in", window => {
window.load_url(url)
})
},
load_browser_html: (html, base_url) => {
self.control_window(id, native_id, "load HTML in", window => {
window.load_html(html, base_url)
})
},
eval_browser_script: script => {
self.control_window(id, native_id, "evaluate script in", window => {
window.eval(script)
})
},
send_browser_command: (command, download_id) => {
self.control_window(id, native_id, command + " in", window => {
window.browser_command(command, download_id?)
})
},
}
}
///|
fn RunningWindow::is_active(self : RunningWindow) -> Bool {
match self.state {
WindowStarting | WindowOpen => true
WindowCloseRequested | WindowClosed => false
}
}
///|
fn RunningWindow::is_closed(self : RunningWindow) -> Bool {
self.state is WindowClosed
}
///|
fn RuntimeSession::close_window(
self : RuntimeSession,
id : String,
native_id : Int64,
) -> Unit raise WindowSessionError {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.close() catch {
error => raise OperationFailed(action="close window " + id, error~)
}
if self.window_close_handler is None {
running.state = WindowCloseRequested
}
}
///|
fn RuntimeSession::control_window(
self : RuntimeSession,
id : String,
native_id : Int64,
action : String,
operation : (@native.Window) -> Unit raise @native.NativeError,
) -> Unit raise WindowSessionError {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
operation(running.window) catch {
error => raise OperationFailed(action=action + " window " + id, error~)
}
}
///|
fn RuntimeSession::read_window_state(
self : RuntimeSession,
id : String,
native_id : Int64,
) -> @native.WindowState raise WindowSessionError {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.state() catch {
error =>
raise OperationFailed(action="read window " + id + " state", error~)
}
}
///|
fn RuntimeSession::find_definition(
self : RuntimeSession,
id : String,
) -> PreparedWindow? {
for definition in self.definitions {
if definition.plan.id == id {
return Some(definition)
}
}
None
}
///|
fn RuntimeSession::find_active_window(
self : RuntimeSession,
id : String,
) -> RunningWindow? {
for running in self.windows {
if running.id == id && running.is_active() {
return Some(running)
}
}
None
}
///|
fn RuntimeSession::create_window(
self : RuntimeSession,
definition : PreparedWindow,
) -> RunningWindow raise AppRunError {
let plan = definition.plan
if self.find_active_window(plan.id) is Some(_) {
raise ConfigurationError(
InvalidSetting(
name="window.id",
message="window is already open: " + plan.id,
),
)
}
let created_window = @native.Window::new(
self.runtime,
config=native_window_config(
plan.window,
definition.bridge,
definition.browser_policy,
),
) catch {
error => raise native_run_error("create window " + plan.id, error)
}
if self.window_close_handler is Some(_) {
created_window.set_close_interception(true) catch {
error =>
raise native_run_error(
"enable close interception for " + plan.id,
error,
)
}
}
let running = RunningWindow::{
id: plan.id,
window: created_window,
permissions: definition.permissions,
lifetime: WindowLifetime::new(),
views: [],
bridge_ready: !self.monitor_bridge,
state: WindowStarting,
}
self.windows.push(running)
load_entry_after_create(created_window, plan.entry) catch {
error => raise EntryLoadError(error)
}
for view_plan in definition.views {
let (view_id, view_config) = view_plan
ignore(
self.add_window_view(
running.id,
created_window.id(),
view_id,
view_config,
) catch {
error =>
raise native_run_error(
"add view " + view_id + " to " + plan.id,
match error {
OperationFailed(error~, ..) => error
other =>
@native.NativeError::InvalidArgument(message=other.message())
},
)
},
)
}
running
}
///|
async fn RuntimeSession::activate_window(
self : RuntimeSession,
running : RunningWindow,
) -> Unit raise AppRunError {
if running.lifetime.ready {
running.lifetime.wait_until_ready()
return
}
let window_events = WindowEventEmitter::new(
typed_event_sender(running.window, None),
)
let manager = self.window_manager()
let handle = self.window_handle(running)
ignore(
self.window_tasks.spawn(
() => {
run_window_lifecycle_scope(
running,
handle,
manager,
window_events,
self.window_lifecycle_hooks,
self.lifecycle_failures,
)
},
no_wait=true,
allow_failure=true,
),
)
running.lifetime.wait_until_ready()
if running.is_closed() {
return
}
running.window.show() catch {
error => raise native_run_error("show window " + running.id, error)
}
running.state = WindowOpen
}
///|
async fn RuntimeSession::request_open_window(
self : RuntimeSession,
id : String,
) -> WindowHandle raise WindowSessionError {
let completion = WindowOpenCompletion::new()
self.window_commands.push(Open(id, completion))
self.wakeup.signal.notify()
completion.wait()
}
///|
fn WindowOpenCompletion::new() -> WindowOpenCompletion {
WindowOpenCompletion::{
state: WindowOpenPending,
changed: @async.CondVar::Cond(),
}
}
///|
fn WindowOpenCompletion::succeed(
self : WindowOpenCompletion,
handle : WindowHandle,
) -> Unit {
if self.state is WindowOpenPending {
self.state = WindowOpenSucceeded(handle)
self.changed.broadcast()
}
}
///|
fn WindowOpenCompletion::fail(
self : WindowOpenCompletion,
error : WindowSessionError,
) -> Unit {
if self.state is WindowOpenPending {
self.state = WindowOpenFailed(error)
self.changed.broadcast()
}
}
///|
async fn WindowOpenCompletion::wait(
self : WindowOpenCompletion,
) -> WindowHandle raise WindowSessionError {
while self.state is WindowOpenPending {
self.changed.wait() catch {
_ => raise Cancelled
}
}
match self.state {
WindowOpenSucceeded(handle) => handle
WindowOpenFailed(error) => raise error
WindowOpenPending => abort("unreachable pending window completion")
}
}
///|
fn RuntimeSession::process_window_commands(self : RuntimeSession) -> Bool {
guard self.window_commands.length() > 0 else { return false }
let commands = self.window_commands.copy()
self.window_commands.clear()
for command in commands {
let Open(id, completion) = command
let definition = match self.find_definition(id) {
Some(definition) => definition
None => {
completion.fail(UnknownWindow(id~))
continue
}
}
if self.find_active_window(id) is Some(_) {
completion.fail(AlreadyOpen(id~))
continue
}
let running = self.create_window(definition) catch {
error => {
completion.fail(StartupFailed(id~, error~))
continue
}
}
let timeout = self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
@async.sleep(self.bridge_startup_timeout_ms) catch {
_ => ()
}
},
no_wait=true,
allow_failure=true,
)
self.pending_window_opens.push(PendingWindowOpen::{
id,
running,
completion,
activation: None,
timeout,
})
}
true
}
///|
fn RuntimeSession::advance_window_opens(self : RuntimeSession) -> Bool {
let remaining : Array[PendingWindowOpen] = []
let mut did_work = false
for pending in self.pending_window_opens {
let timed_out = pending.timeout.try_wait() catch { _ => Some(()) }
if timed_out is Some(_) {
did_work = true
let state = Some(pending.running.window.bridge_lifecycle_state()) catch {
_ => None
}
let error = match state {
Some(state) =>
BridgeStartupError(
bridge_startup_timeout_diagnostic(
state,
self.bridge_startup_timeout_ms,
),
)
None =>
NativeRuntimeError(
action="open window " + pending.id,
error=@native.NativeError::InvalidPayload(
context="bridge startup",
message="window startup timed out",
),
)
}
self.fail_window_open(pending, error)
continue
}
match pending.activation {
Some(task) => {
let completed = task.try_wait() catch {
error => {
did_work = true
self.fail_window_open(pending, normalize_async_run_error(error))
None
}
}
if pending.completion.state is WindowOpenFailed(_) {
continue
}
match completed {
Some(_) => {
did_work = true
pending.timeout.cancel()
if pending.running.is_closed() {
pending.completion.fail(Cancelled)
} else {
pending.completion.succeed(self.window_handle(pending.running))
}
}
None => remaining.push(pending)
}
}
None => {
let ready = if self.monitor_bridge {
refresh_window_bridge_startup_state(pending.running) catch {
error => {
did_work = true
self.fail_window_open(pending, error)
false
}
}
} else {
true
}
if pending.completion.state is WindowOpenFailed(_) {
continue
}
if ready {
did_work = true
pending.activation = Some(
self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
self.activate_window(pending.running)
},
no_wait=true,
allow_failure=true,
),
)
}
remaining.push(pending)
}
}
}
self.pending_window_opens.clear()
for pending in remaining {
self.pending_window_opens.push(pending)
}
did_work
}
///|
fn RuntimeSession::fail_window_open(
_self : RuntimeSession,
pending : PendingWindowOpen,
error : AppRunError,
) -> Unit {
pending.timeout.cancel()
match pending.activation {
Some(task) => task.cancel()
None => ()
}
pending.running.window.destroy() catch {
_ => ()
}
pending.running.state = WindowClosed
pending.running.lifetime.close()
pending.completion.fail(StartupFailed(id=pending.id, error~))
}
///|
/// Opens one declared window that is not currently active.
pub async fn WindowManager::open(
self : WindowManager,
id : String,
) -> WindowHandle raise WindowSessionError {
(self.open_window)(id)
}
///|
/// Returns the active instance for a declared window id.
pub fn WindowManager::find(self : WindowManager, id : String) -> WindowHandle? {
(self.find_window)(id)
}
///|
/// Returns the declarative id of this window.
pub fn WindowHandle::id(self : WindowHandle) -> String {
self.id
}
///|
/// Returns a low-level non-owning reference for APIs such as dialogs.
pub fn WindowHandle::as_native_ref(self : WindowHandle) -> @native.WindowRef {
@native.WindowRef::unsafe_from_handle(self.native_id)
}
///|
pub fn WindowHandle::show(self : WindowHandle) -> Unit raise WindowSessionError {
(self.show_window)()
}
///|
pub fn WindowHandle::hide(self : WindowHandle) -> Unit raise WindowSessionError {
(self.hide_window)()
}
///|
pub fn WindowHandle::close(
self : WindowHandle,
) -> Unit raise WindowSessionError {
(self.close_window)()
}
///|
pub fn WindowHandle::focus(
self : WindowHandle,
) -> Unit raise WindowSessionError {
(self.focus_window)()
}
///|
pub fn WindowHandle::set_title(
self : WindowHandle,
title : String,
) -> Unit raise WindowSessionError {
(self.set_window_title)(title)
}
///|
pub fn WindowHandle::set_size(
self : WindowHandle,
width : Int,
height : Int,
) -> Unit raise WindowSessionError {
(self.set_window_size)(width, height)
}
///|
pub fn WindowHandle::minimize(
self : WindowHandle,
) -> Unit raise WindowSessionError {
(self.minimize_window)()
}
///|
pub fn WindowHandle::maximize(
self : WindowHandle,
) -> Unit raise WindowSessionError {
(self.maximize_window)()
}
///|
pub fn WindowHandle::restore(
self : WindowHandle,
) -> Unit raise WindowSessionError {
(self.restore_window)()
}
///|
pub fn WindowHandle::set_fullscreen(
self : WindowHandle,
fullscreen : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_fullscreen)(fullscreen)
}
///|
pub fn WindowHandle::set_position(
self : WindowHandle,
x : Int,
y : Int,
) -> Unit raise WindowSessionError {
(self.set_window_position)(x, y)
}
///|
pub fn WindowHandle::set_always_on_top(
self : WindowHandle,
always_on_top : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_always_on_top)(always_on_top)
}
///|
pub fn WindowHandle::set_zoom_percent(
self : WindowHandle,
zoom_percent : Int,
) -> Unit raise WindowSessionError {
(self.set_window_zoom_percent)(zoom_percent)
}
///|
pub fn WindowHandle::state(
self : WindowHandle,
) -> @native.WindowState raise WindowSessionError {
(self.read_window_state)()
}
///|
pub fn WindowHandle::browser(self : WindowHandle) -> BrowserHandle {
self.browser
}
///|
/// Adds a web contents view to this window, following the Electron
/// `WebContentsView` model: the view renders its own page above the window's
/// main browser content at explicit bounds. `id` is unique within the window
/// and lets the session reject stale handles.
pub fn WindowHandle::add_view(
self : WindowHandle,
id : String,
config : @native.ViewConfig,
) -> ViewHandle raise WindowSessionError {
(self.add_view)(id, config)
}
///|
/// Removes and destroys a web contents view previously added with `add_view`.
pub fn WindowHandle::remove_view(
self : WindowHandle,
id : String,
) -> Unit raise WindowSessionError {
(self.remove_view)(id)
}
///|
/// Lists the live web contents views of this window.
pub fn WindowHandle::views(self : WindowHandle) -> Array[ViewHandle] {
(self.list_views)()
}
///|
/// Returns the live view with the given declarative id, if one exists.
pub fn WindowHandle::view(self : WindowHandle, id : String) -> ViewHandle? {
(self.find_view)(id)
}
///|
/// Returns the declarative id of this view.
pub fn ViewHandle::id(self : ViewHandle) -> String {
self.id
}
///|
/// Returns a low-level non-owning reference for native APIs.
pub fn ViewHandle::as_native_ref(self : ViewHandle) -> @native.ViewRef {
@native.ViewRef::unsafe_from_handle(self.native_id)
}
///|
/// Moves and resizes the view. `x`/`y` use a top-left origin in the owning
/// window's content coordinate space, matching Electron's `setBounds`.
pub fn ViewHandle::set_bounds(
self : ViewHandle,
x~ : Int,
y~ : Int,
width~ : Int,
height~ : Int,
) -> Unit raise WindowSessionError {
(self.set_view_bounds)(x, y, width, height)
}
///|
pub fn ViewHandle::set_visible(
self : ViewHandle,
visible : Bool,
) -> Unit raise WindowSessionError {
(self.set_view_visible)(visible)
}
///|
/// Stacks the view relative to the window's other views; higher `z_order`
/// renders above lower values.
pub fn ViewHandle::set_z_order(
self : ViewHandle,
z_order : Int,
) -> Unit raise WindowSessionError {
(self.set_view_z_order)(z_order)
}
///|
/// Navigates the view's page, the Electron `view.webContents.loadURL`
/// equivalent.
pub fn ViewHandle::load_url(
self : ViewHandle,
url : String,
) -> Unit raise WindowSessionError {
(self.load_view_url)(url)
}
///|
/// Loads inline HTML into the view, served from `base_url` on the
/// `proton://` scheme, mirroring `WindowHandle::load_html`.
pub fn ViewHandle::load_html(
self : ViewHandle,
html : String,
base_url : String,
) -> Unit raise WindowSessionError {
(self.load_view_html)(html, base_url)
}
///|
/// Executes JavaScript in the view's main frame without awaiting a result.
pub fn ViewHandle::eval(
self : ViewHandle,
script : String,
) -> Unit raise WindowSessionError {
(self.eval_view_script)(script)
}
///|
pub fn ViewHandle::back(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("back", None)
}
///|
pub fn ViewHandle::forward(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("forward", None)
}
///|
pub fn ViewHandle::reload(
self : ViewHandle,
ignore_cache? : Bool = false,
) -> Unit raise WindowSessionError {
(self.send_view_command)(
if ignore_cache {
"reload_ignore_cache"
} else {
"reload"
},
None,
)
}
///|
pub fn ViewHandle::stop(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("stop", None)
}
///|
pub fn ViewHandle::open_devtools(
self : ViewHandle,
) -> Unit raise WindowSessionError {
(self.send_view_command)("open_devtools", None)
}
///|
pub fn ViewHandle::close_devtools(
self : ViewHandle,
) -> Unit raise WindowSessionError {
(self.send_view_command)("close_devtools", None)
}
///|
/// Reads the current view state from the native runtime.
pub fn ViewHandle::state(
self : ViewHandle,
) -> @native.ViewState raise WindowSessionError {
(self.read_view_state)()
}
///|
/// Removes and destroys the view, the Electron `removeChildView` equivalent.
pub fn ViewHandle::close(self : ViewHandle) -> Unit raise WindowSessionError {
(self.close_view)()
}
///|
pub fn BrowserHandle::window_id(self : BrowserHandle) -> String {
self.id
}
///|
pub fn BrowserHandle::load_url(
self : BrowserHandle,
url : String,
) -> Unit raise WindowSessionError {
(self.load_browser_url)(url)
}
///|
pub fn BrowserHandle::load_html(
self : BrowserHandle,
html : String,
base_url : String,
) -> Unit raise WindowSessionError {
(self.load_browser_html)(html, base_url)
}
///|
pub fn BrowserHandle::eval(
self : BrowserHandle,
script : String,
) -> Unit raise WindowSessionError {
(self.eval_browser_script)(script)
}
///|
pub fn BrowserHandle::back(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("back", None)
}
///|
pub fn BrowserHandle::forward(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("forward", None)
}
///|
pub fn BrowserHandle::reload(
self : BrowserHandle,
ignore_cache? : Bool = false,
) -> Unit raise WindowSessionError {
(self.send_browser_command)(
if ignore_cache {
"reload_ignore_cache"
} else {
"reload"
},
None,
)
}
///|
pub fn BrowserHandle::stop(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("stop", None)
}
///|
pub fn BrowserHandle::open_devtools(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("open_devtools", None)
}
///|
pub fn BrowserHandle::close_devtools(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("close_devtools", None)
}
///|
pub fn BrowserHandle::cancel_download(
self : BrowserHandle,
download_id : Int,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("cancel_download", Some(download_id))
}
///|
fn RuntimeSession::command_host(self : RuntimeSession) -> @core.AppCommandHost? {
self.command_host.map(host => host.host)
}
///|
async fn RuntimeSession::wait_for_bridge_startup(
self : RuntimeSession,
timeout_ms : Int,
) -> Unit raise AppRunError {
let completed = @async.with_timeout_opt(timeout_ms, () => {
while true {
let revision = self.wakeup.revision()
let did_work = self.pump_once(monitor_bridge=false)
match refresh_bridge_startup_states(self.windows) {
None => return
Some(_) =>
if did_work {
@async.sleep(0)
} else {
self.wakeup.wait_after(revision)
}
}
}
}) catch {
error => raise normalize_async_run_error(error)
}
match completed {
Some(_) => ()
None => {
ignore(self.pump_once(monitor_bridge=false))
match refresh_bridge_startup_states(self.windows) {
None => return
Some(state) =>
raise BridgeStartupError(
bridge_startup_timeout_diagnostic(state, timeout_ms),
)
}
}
}
}
///|
async fn RuntimeSession::run(self : RuntimeSession) -> Unit raise AppRunError {
while !self.all_windows_closed() ||
self.window_commands.length() > 0 ||
self.pending_window_opens.length() > 0 {
let revision = self.wakeup.revision()
let did_work = self.pump_once(monitor_bridge=self.monitor_bridge)
if did_work {
@async.sleep(0) catch {
error => raise normalize_async_run_error(error)
}
} else {
self.wakeup.wait_after(revision)
}
}
self.cancel_all_bridge_tasks()
self.cancel_all_window_close_requests()
self.cancel_all_browser_requests()
}
///|
async fn RuntimeSession::drive_until_complete(
self : RuntimeSession,
task : @async.Task[Unit],
) -> Unit raise AppRunError {
while true {
let revision = self.wakeup.revision()
let completed = task.try_wait() catch {
error => raise normalize_async_run_error(error)
}
match completed {
Some(_) => return
None => {
let did_work = self.pump_once(monitor_bridge=self.monitor_bridge)
if did_work {
@async.sleep(0) catch {
error => raise normalize_async_run_error(error)
}
} else {
self.wakeup.wait_after(revision)
}
}
}
}
}
///|
fn RuntimeSession::pump_once(
self : RuntimeSession,
monitor_bridge? : Bool = true,
) -> Bool raise AppRunError {
let mut did_work = self.process_window_commands()
if self.drain_runtime_events() {
did_work = true
}
if monitor_bridge {
self.check_bridge_failures()
}
if self.drain_bridge_requests() {
did_work = true
}
if self.complete_bridge_requests() {
did_work = true
}
if self.advance_window_opens() {
did_work = true
}
if self.complete_window_close_requests() {
did_work = true
}
if self.complete_browser_requests() {
did_work = true
}
did_work
}
///|
fn RuntimeSession::drain_runtime_events(
self : RuntimeSession,
) -> Bool raise AppRunError {
let mut did_work = false
let mut polling = true
while polling {
let event = self.runtime.poll_event() catch {
error => raise native_run_error("poll event", error)
}
match event {
None => polling = false
Some(event) => {
did_work = true
self.dispatch_runtime_event(event)
}
}
}
did_work
}
///|
fn RuntimeSession::dispatch_runtime_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit raise AppRunError {
self.dispatch_window_event(event)
self.dispatch_view_event(event)
self.dispatch_browser_event(event)
self.dispatch_notification_event(event)
self.dispatch_launch_input_event(event)
self.dispatch_menu_event(event)
self.cancel_bridge_request(event.bridge_request_cancellation())
}
///|
fn RuntimeSession::dispatch_window_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit {
runtime_session_dispatch_window_event(
self.windows,
self.pending_bridge,
event,
)
if event.is_window_closed() {
self.cancel_window_close_requests(event.window_id())
self.cancel_browser_requests(event.window_id())
}
match (event.window_id(), event.window_state_change()) {
(Some(window_id), Some(state)) =>
match self.find_window_by_native_id(window_id) {
Some(window) => {
let handle = self.window_handle(window)
for handler in self.window_event_handlers {
ignore(
self.window_tasks.spawn(
() => handler(handle, StateChanged(state)),
no_wait=true,
),
)
}
}
None => ()
}
_ => ()
}
match (event.window_id(), event.window_close_request()) {
(Some(window_id), Some(request_id)) =>
self.start_window_close_request(window_id, request_id)
_ => ()
}
}
///|
fn RuntimeSession::dispatch_view_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit {
if self.view_event_handlers.is_empty() {
return
}
let info = match event.view_event() {
Some(info) => info
None => return
}
let (window_native_id, view_native_id) = match
(event.window_id(), event.view_id()) {
(Some(window_id), Some(view_id)) => (window_id, view_id)
_ => return
}
let running = match self.find_window_by_native_id(window_native_id) {
Some(running) => running
None => return
}
for view in running.views {
if view.view.id() == view_native_id {
let handle = self.view_handle(running, view)
let view_event = match event.event_type() {
"view_loading_changed" =>
LoadingChanged(is_loading=info.is_loading.unwrap_or(false))
"view_navigated" => Navigated(url=info.url.unwrap_or(""))
"view_title_updated" => TitleUpdated(title=info.title.unwrap_or(""))
"view_load_failed" =>
LoadFailed(
url=info.url.unwrap_or(""),
error_code=info.error_code.unwrap_or(0),
error_text=info.error_text.unwrap_or(""),
)
_ => return
}
for handler in self.view_event_handlers {
ignore(
self.window_tasks.spawn(
() => handler(handle, view_event),
no_wait=true,
),
)
}
return
}
}
}
///|
fn RuntimeSession::dispatch_browser_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit {
match event.browser_request() {
Some(request) => self.start_browser_request(event.window_id(), request)
None => ()
}
match (event.window_id(), event.browser_download_update()) {
(Some(window_id), Some(update)) =>
match self.find_window_by_native_id(window_id) {
Some(window) => {
let browser = self.browser_handle(window)
let observed = DownloadEvent::{
id: update.download_id,
state: update.state,
received_bytes: update.received_bytes,
total_bytes: update.total_bytes,
percent: update.percent,
}
for handler in self.download_event_handlers {
ignore(
self.window_tasks.spawn(
() => handler(browser, observed),
no_wait=true,
),
)
}
}
None => ()
}
_ => ()
}
}
///|
fn RuntimeSession::start_browser_request(
self : RuntimeSession,
window_id : Int64?,
request : @native.NativeBrowserRequest,
) -> Unit {
guard window_id is Some(window_id) else { return }
guard self.find_window_by_native_id(window_id) is Some(window) else { return }
let browser = self.browser_handle(window)
let request_id = match request {
Navigation(request_id~, ..)
| Popup(request_id~, ..)
| Download(request_id~, ..)
| Certificate(request_id~, ..)
| Media(request_id~, ..) => request_id
}
let task = self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
match request {
Navigation(url~, http_method~, user_gesture~, redirect~, ..) =>
match self.navigation_handler {
Some(handler) =>
match
handler(browser, { url, http_method, user_gesture, redirect }) {
NavigationDecision::Allow =>
BrowserResponse::{ action: "allow", path: None }
NavigationDecision::Deny =>
BrowserResponse::{ action: "deny", path: None }
}
None => BrowserResponse::{ action: "deny", path: None }
}
Popup(url~, disposition~, user_gesture~, ..) => {
let decision = match self.popup_handler {
Some(handler) =>
handler(browser, { url, disposition, user_gesture })
None => PopupDecision::Deny
}
match decision {
PopupDecision::Deny => ()
PopupDecision::OpenInCurrent =>
browser.load_url(url) catch {
_ => ()
}
PopupDecision::OpenInWindow(id) => {
let opened = Some(self.window_manager().open(id)) catch {
_ => None
}
match opened {
Some(window) => window.browser().load_url(url) catch { _ => () }
None => ()
}
}
}
BrowserResponse::{ action: "deny", path: None }
}
Download(download_id~, url~, suggested_name~, ..) =>
match self.download_handler {
Some(handler) =>
match handler(browser, { id: download_id, url, suggested_name }) {
DownloadDecision::Deny =>
BrowserResponse::{ action: "deny", path: None }
DownloadDecision::ShowSaveDialog =>
BrowserResponse::{ action: "allow", path: None }
DownloadDecision::SaveTo(path) =>
BrowserResponse::{ action: "allow", path: Some(path) }
}
None => BrowserResponse::{ action: "deny", path: None }
}
Certificate(url~, error_code~, ..) =>
match self.certificate_handler {
Some(handler) =>
match handler(browser, { url, error_code }) {
BrowserPermissionDecision::Allow =>
BrowserResponse::{ action: "allow", path: None }
BrowserPermissionDecision::Deny =>
BrowserResponse::{ action: "deny", path: None }
}
None => BrowserResponse::{ action: "deny", path: None }
}
Media(origin~, permissions~, ..) =>
match self.media_handler {
Some(handler) =>
match handler(browser, { origin, permissions }) {
BrowserPermissionDecision::Allow =>
BrowserResponse::{ action: "allow", path: None }
BrowserPermissionDecision::Deny =>
BrowserResponse::{ action: "deny", path: None }
}
None => BrowserResponse::{ action: "deny", path: None }
}
}
},
no_wait=true,
)
self.pending_browser_requests.push({ window: window_id, request_id, task })
}
///|
fn RuntimeSession::complete_browser_requests(
self : RuntimeSession,
) -> Bool raise AppRunError {
let remaining : Array[PendingBrowserRequest] = []
let mut did_work = false
for pending in self.pending_browser_requests {
let completed = pending.task.try_wait() catch {
_ => Some(BrowserResponse::{ action: "deny", path: None })
}
match completed {
None => remaining.push(pending)
Some(response) => {
did_work = true
match self.find_window_by_native_id(pending.window) {
Some(window) if !window.is_closed() => {
let path = response.path
window.window.respond_browser_request(
pending.request_id,
response.action,
path?,
) catch {
error =>
if !error.is_stale_browser_request() &&
!error.is_stale_window_request() {
raise native_run_error("respond to browser request", error)
}
}
}
_ => ()
}
}
}
}
self.pending_browser_requests.clear()
for pending in remaining {
self.pending_browser_requests.push(pending)
}
did_work
}
///|
fn RuntimeSession::cancel_browser_requests(
self : RuntimeSession,
window_id : Int64?,
) -> Unit {
let remaining : Array[PendingBrowserRequest] = []
for pending in self.pending_browser_requests {
if window_id == Some(pending.window) {
pending.task.cancel()
} else {
remaining.push(pending)
}
}
self.pending_browser_requests.clear()
for pending in remaining {
self.pending_browser_requests.push(pending)
}
}
///|
fn RuntimeSession::cancel_all_browser_requests(self : RuntimeSession) -> Unit {
for pending in self.pending_browser_requests {
pending.task.cancel()
}
self.pending_browser_requests.clear()
}
///|
fn RuntimeSession::start_window_close_request(
self : RuntimeSession,
window_id : Int64,
request_id : Int64,
) -> Unit {
guard self.window_close_handler is Some(handler) else { return }
guard self.find_window_by_native_id(window_id) is Some(window) else { return }
let handle = self.window_handle(window)
let task = self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
handler(handle)
},
no_wait=true,
)
self.pending_window_closes.push(PendingWindowClose::{
window: window_id,
request_id,
task,
})
}
///|
fn RuntimeSession::complete_window_close_requests(
self : RuntimeSession,
) -> Bool raise AppRunError {
let remaining : Array[PendingWindowClose] = []
let mut did_work = false
for pending in self.pending_window_closes {
let completed = pending.task.try_wait() catch { _ => Some(Allow) }
match completed {
None => remaining.push(pending)
Some(decision) => {
did_work = true
match self.find_window_by_native_id(pending.window) {
Some(window) if !window.is_closed() => {
window.window.respond_close_request(
pending.request_id,
decision == Allow,
) catch {
error =>
if !error.is_stale_window_request() {
raise native_run_error(
"respond to window close request", error,
)
}
}
if decision == Allow {
window.state = WindowCloseRequested
}
}
_ => ()
}
}
}
}
self.pending_window_closes.clear()
for pending in remaining {
self.pending_window_closes.push(pending)
}
did_work
}
///|
fn RuntimeSession::cancel_window_close_requests(
self : RuntimeSession,
window_id : Int64?,
) -> Unit {
let remaining : Array[PendingWindowClose] = []
for pending in self.pending_window_closes {
if window_id == Some(pending.window) {
pending.task.cancel()
} else {
remaining.push(pending)
}
}
self.pending_window_closes.clear()
for pending in remaining {
self.pending_window_closes.push(pending)
}
}
///|
fn RuntimeSession::cancel_all_window_close_requests(
self : RuntimeSession,
) -> Unit {
for pending in self.pending_window_closes {
pending.task.cancel()
}
self.pending_window_closes.clear()
}
///|
fn runtime_session_dispatch_window_event(
windows : Array[RunningWindow],
pending_bridge : Array[BridgeDispatchTask],
event : @native.RuntimeEvent,
) -> Unit {
if event.is_window_closed() {
runtime_session_mark_window_closed(windows, event.window_id())
runtime_session_cancel_window_bridge_tasks(
pending_bridge,
event.window_id(),
)
}
}
///|
fn RuntimeSession::dispatch_notification_event(
_self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit {
match event.notification_result() {
Some(result) => publish_notification_result(result)
None => ()
}
}
///|
fn RuntimeSession::dispatch_launch_input_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit raise AppRunError {
match event.launch_input() {
Some(input) => {
self.activate_for_launch_input()
for handler in self.launch_input_handlers {
ignore(self.window_tasks.spawn(() => handler(input), no_wait=true))
}
}
None => ()
}
}
///|
fn RuntimeSession::activate_for_launch_input(
self : RuntimeSession,
) -> Unit raise AppRunError {
let mut target : RunningWindow? = None
for running in self.windows {
if !running.is_closed() && running.id == "main" {
target = Some(running)
break
}
}
if target is None {
for running in self.windows {
if !running.is_closed() {
target = Some(running)
break
}
}
}
match target {
Some(running) => {
let state = running.window.state() catch {
error => raise native_run_error("read activation window state", error)
}
if state.minimized {
running.window.restore() catch {
error => raise native_run_error("restore activation window", error)
}
}
if !state.visible {
running.window.show() catch {
error => raise native_run_error("show activation window", error)
}
}
running.window.focus() catch {
error => raise native_run_error("focus activation window", error)
}
}
None => ()
}
}
///|
fn RuntimeSession::dispatch_menu_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit raise AppRunError {
guard self.forward_menu_events else { return }
match event.menu_command_id() {
Some(command_id) => self.forward_menu_command(command_id, event.window_id())
None => ()
}
}
///|
fn RuntimeSession::check_bridge_failures(
self : RuntimeSession,
) -> Unit raise AppRunError {
for running in self.windows {
if running.is_closed() {
continue
}
let failure = take_runtime_bridge_failure(running.window) catch {
error =>
raise native_run_error("read bridge failure for " + running.id, error)
}
match failure {
Some(diagnostic) => raise BridgeRuntimeError(diagnostic)
None => ()
}
}
}
///|
fn RuntimeSession::drain_bridge_requests(
self : RuntimeSession,
) -> Bool raise AppRunError {
let mut did_work = false
let mut polling = true
while polling {
let request = self.runtime.poll_bridge_request() catch {
error => raise native_run_error("poll bridge request", error)
}
match request {
None => polling = false
Some(request) => {
did_work = true
self.start_bridge_request(request)
}
}
}
did_work
}
///|
fn RuntimeSession::start_bridge_request(
self : RuntimeSession,
request : @native.BridgeRequest,
) -> Unit raise AppRunError {
let request_id = request.request_id()
let target = match self.find_window_by_native_id(request.window()) {
Some(window) if window.is_active() => window
_ => {
self.respond_bridge_request(
@native.BridgeResponse::Err(
request_id~,
code="window_closed",
message="the issuing window is no longer available",
),
"reject bridge request",
)
return
}
}
match self.command_host() {
None =>
self.respond_bridge_request(
@native.BridgeResponse::Err(
request_id~,
code="command_host_unavailable",
message="the application command host is not available",
),
"reject bridge request",
)
Some(host) => {
let task = self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
dispatch_bridge_request(
target.window,
target.id,
target.permissions,
host,
request,
self.wakeup.signal,
)
},
no_wait=true,
allow_failure=true,
)
self.pending_bridge.push(BridgeDispatchTask::{
request_id,
window: target.window.id(),
task,
state: Running,
})
}
}
}
///|
fn RuntimeSession::respond_bridge_request(
self : RuntimeSession,
response : @native.BridgeResponse,
action : String,
) -> Unit raise AppRunError {
self.runtime.respond_bridge_request(response) catch {
error =>
if !is_stale_bridge_response_error(error) {
raise native_run_error(action, error)
}
}
}
///|
fn RuntimeSession::complete_bridge_requests(
self : RuntimeSession,
) -> Bool raise AppRunError {
let remaining : Array[BridgeDispatchTask] = []
let mut did_work = false
for item in self.pending_bridge {
if item.state == Cancelled {
continue
}
let completed = item.task.try_wait() catch {
_ =>
Some(
@native.BridgeResponse::Err(
request_id=item.request_id,
code="handler_failed",
message="bridge handler task failed",
),
)
}
match completed {
Some(response) => {
did_work = true
item.state = Responding
self.runtime.respond_bridge_request(response) catch {
error =>
if is_stale_bridge_response_error(error) {
item.state = Stale
} else {
raise native_run_error("respond bridge request", error)
}
}
if item.state == Responding {
item.state = Completed
}
}
None => remaining.push(item)
}
}
self.pending_bridge.clear()
for item in remaining {
self.pending_bridge.push(item)
}
did_work
}
///|
fn RuntimeSession::find_window_by_native_id(
self : RuntimeSession,
window_id : Int64,
) -> RunningWindow? {
for running in self.windows {
if running.window.id() == window_id {
return Some(running)
}
}
None
}
///|
fn runtime_session_mark_window_closed(
windows : Array[RunningWindow],
window_id : Int64?,
) -> Unit {
match window_id {
Some(window_id) =>
match runtime_session_find_window_by_native_id(windows, window_id) {
Some(window) => {
window.state = WindowClosed
window.lifetime.close()
}
None => ()
}
None => ()
}
}
///|
fn runtime_session_find_window_by_native_id(
windows : Array[RunningWindow],
window_id : Int64,
) -> RunningWindow? {
for running in windows {
if running.window.id() == window_id {
return Some(running)
}
}
None
}
///|
fn RuntimeSession::all_windows_closed(self : RuntimeSession) -> Bool {
self.windows.length() > 0 &&
self.windows.all(fn(window) { window.is_closed() })
}
///|
fn RuntimeSession::cancel_all_bridge_tasks(self : RuntimeSession) -> Unit {
runtime_session_cancel_all_bridge_tasks(self.pending_bridge)
}
///|
fn runtime_session_cancel_all_bridge_tasks(
pending_bridge : Array[BridgeDispatchTask],
) -> Unit {
for item in pending_bridge {
if item.state == Running {
item.task.cancel()
item.state = Cancelled
}
}
pending_bridge.clear()
}
///|
fn runtime_session_cancel_window_bridge_tasks(
pending_bridge : Array[BridgeDispatchTask],
window_id : Int64?,
) -> Unit {
match window_id {
Some(window_id) =>
for item in pending_bridge {
if item.window == window_id && item.state == Running {
item.task.cancel()
item.state = Cancelled
}
}
None => ()
}
}
///|
fn RuntimeSession::cancel_bridge_request(
self : RuntimeSession,
request_id : Int64?,
) -> Unit {
runtime_session_cancel_bridge_request(self.pending_bridge, request_id)
}
///|
fn runtime_session_cancel_bridge_request(
pending_bridge : Array[BridgeDispatchTask],
request_id : Int64?,
) -> Unit {
match request_id {
Some(request_id) =>
for item in pending_bridge {
if item.request_id == request_id && item.state == Running {
item.task.cancel()
item.state = Cancelled
}
}
None => ()
}
}
///|
fn RuntimeSession::forward_menu_command(
self : RuntimeSession,
command_id : String,
focused_window : Int64?,
) -> Unit raise AppRunError {
let target = match focused_window {
Some(window_id) => self.find_window_by_native_id(window_id)
None => None
}
let target = match target {
Some(window) => Some(window)
None => self.windows.get(0)
}
match target {
Some(window) if window.is_active() =>
forward_menu_command(window.window, command_id, focused_window)
_ => ()
}
}