///|
fn RuntimeSession::RuntimeSession(
runtime : @native.Runtime,
event_pump : RuntimeEventPump,
locale_preferences : @locale.LocalePreferences,
definitions : Array[PreparedWindow],
windows : Array[RunningWindow],
command_host : CommandHostRuntime?,
wakeup : RuntimeWakeup,
forward_menu_events : Bool,
monitor_bridge : Bool,
launch_input_handlers : Array[
async (ApplicationContext, RuntimeLaunchInput) -> Unit noraise,
],
window_event_handlers : Array[
async (WindowHandle, WindowEvent) -> Unit noraise,
],
application_tasks : @async.TaskGroup[Unit],
last_window_closed_policy : LastWindowClosedPolicy,
window_tasks : @async.TaskGroup[Unit],
view_event_handlers : Array[async (ViewHandle, ViewEvent) -> Unit noraise],
browser_event_handlers : Array[
async (BrowserHandle, BrowserEvent) -> 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,
],
application_identifier? : String = "",
application_executable? : String = "",
application_arguments? : Array[String] = [],
url_schemes? : Array[String] = [],
) -> RuntimeSession {
RuntimeSession::{
runtime,
event_pump,
locale_preferences,
definitions,
windows,
has_created_window: windows.length() > 0,
command_host,
pending_bridge: [],
wakeup,
forward_menu_events,
monitor_bridge,
application_tasks,
last_window_closed_policy,
launch_input_handlers,
window_event_handlers,
view_event_handlers,
browser_event_handlers,
window_close_handler,
window_tasks,
window_lifecycle_hooks,
lifecycle_failures,
bridge_startup_timeout_ms,
window_commands: [],
pending_window_opens: [],
pending_browser_requests: [],
pending_resource_requests: [],
navigation_handler,
popup_handler,
download_handler,
certificate_handler,
media_handler,
download_event_handlers,
close_coordinator: CloseCoordinator(),
application_identifier,
application_executable,
application_arguments,
url_schemes,
}
}
///|
fn CloseCoordinator::CloseCoordinator() -> CloseCoordinator {
CloseCoordinator::{ state: CloseRunning, pending: [], }
}
///|
fn CloseCoordinator::request_quit(self : CloseCoordinator) -> Bool {
guard self.state == CloseRunning else { return false }
self.state = CloseQuitRequested
true
}
///|
fn CloseCoordinator::is_quitting(self : CloseCoordinator) -> Bool {
self.state != CloseRunning
}
///|
fn CloseCoordinator::find(
self : CloseCoordinator,
window_id : Int64,
) -> PendingCloseDecision? {
for pending in self.pending {
if pending.window == window_id {
return Some(pending)
}
}
None
}
///|
fn CloseCoordinator::cancel(
self : CloseCoordinator,
window_id : Int64?,
) -> Unit {
let remaining : Array[PendingCloseDecision] = []
for pending in self.pending {
if window_id == Some(pending.window) {
pending.task.cancel()
} else {
remaining.push(pending)
}
}
self.pending.clear()
for pending in remaining {
self.pending.push(pending)
}
}
///|
fn CloseCoordinator::cancel_all(self : CloseCoordinator) -> Unit {
for pending in self.pending {
pending.task.cancel()
}
self.pending.clear()
}
///|
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::application_context(
self : RuntimeSession,
) -> ApplicationContext {
ApplicationContext(
self.application_tasks,
self.window_manager(),
self.locale_preferences,
request_quit=() => self.request_quit(),
application_identifier=self.application_identifier,
application_executable=self.application_executable,
application_arguments=self.application_arguments,
url_schemes=self.url_schemes,
)
}
///|
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())
},
show_window_inactive: () => {
self.control_window(id, native_id, "show inactive", window => {
window.show_inactive()
})
},
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_icon: path => {
self.control_window(id, native_id, "set icon", window => {
window.set_icon(path)
})
},
set_window_parent: (parent, modal) => {
let parent_native_id = parent.map(parent => parent.native_id)
self.set_window_parent(id, native_id, parent_native_id, modal)
},
set_window_size: (width, height) => {
self.control_window(id, native_id, "set size", window => {
window.set_size(width, height)
})
},
set_window_content_size: (width, height) => {
self.control_window(id, native_id, "set content size", window => {
window.set_content_size(width, height)
})
},
read_window_content_size: () => self.read_window_content_size(id, native_id),
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_kiosk: kiosk => {
self.control_window(id, native_id, "set kiosk", window => {
window.set_kiosk(kiosk)
})
},
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_resizable: resizable => {
self.control_window(id, native_id, "set resizable", window => {
window.set_resizable(resizable)
})
},
set_window_minimum_size: (width, height) => {
self.control_window(id, native_id, "set minimum size", window => {
window.set_minimum_size(width, height)
})
},
set_window_maximum_size: (width, height) => {
self.control_window(id, native_id, "set maximum size", window => {
window.set_maximum_size(width, height)
})
},
set_window_aspect_ratio: aspect_ratio => {
self.control_window(id, native_id, "set aspect ratio", window => {
window.set_aspect_ratio(aspect_ratio)
})
},
set_window_movable: movable => {
self.control_window(id, native_id, "set movable", window => {
window.set_movable(movable)
})
},
set_window_opacity: opacity => {
self.control_window(id, native_id, "set opacity", window => {
window.set_opacity(opacity)
})
},
set_window_skip_taskbar: skip => {
self.control_window(id, native_id, "set taskbar visibility", window => {
window.set_skip_taskbar(skip)
})
},
set_window_content_protection: enabled => {
self.control_window(id, native_id, "set content protection", window => {
window.set_content_protection(enabled)
})
},
set_window_minimizable: minimizable => {
self.control_window(id, native_id, "set minimizable", window => {
window.set_minimizable(minimizable)
})
},
set_window_maximizable: maximizable => {
self.control_window(id, native_id, "set maximizable", window => {
window.set_maximizable(maximizable)
})
},
set_window_closable: closable => {
self.control_window(id, native_id, "set closable", window => {
window.set_closable(closable)
})
},
set_window_button_visibility: visible => {
self.control_window(id, native_id, "set window button visibility", window => {
window.set_button_visibility(visible)
})
},
set_window_focusable: focusable => {
self.control_window(id, native_id, "set focusable", window => {
window.set_focusable(focusable)
})
},
set_window_fullscreenable: fullscreenable => {
self.control_window(id, native_id, "set fullscreenable", window => {
window.set_fullscreenable(fullscreenable)
})
},
set_window_has_shadow: has_shadow => {
self.control_window(id, native_id, "set window shadow", window => {
window.set_has_shadow(has_shadow)
})
},
set_window_ignore_mouse_events: (ignore, forward) => {
self.control_window(id, native_id, "set ignore mouse events", window => {
window.set_ignore_mouse_events(ignore, forward)
})
},
set_window_background_color: color => {
self.control_window(id, native_id, "set background color", window => {
window.set_background_color(color)
})
},
set_window_visible_on_all_workspaces: visible => {
self.control_window(id, native_id, "set workspace visibility", window => {
window.set_visible_on_all_workspaces(visible)
})
},
set_window_enabled: enabled => {
self.control_window(id, native_id, "set enabled", window => {
window.set_enabled(enabled)
})
},
set_window_menu: menu => {
try {
match menu {
Some(menu) => self.runtime.set_menu(menu.to_native())
None => self.runtime.set_menu(@native.MenuBar(menus=[]))
}
} catch {
error =>
raise window_operation_failed("set menu in window " + id, error)
}
},
set_window_zoom_percent: zoom_percent => {
self.control_window(id, native_id, "set zoom", window => {
window.set_zoom_percent(zoom_percent)
})
},
set_window_progress_bar: progress => {
self.control_window(id, native_id, "set progress bar", window => {
window.set_progress_bar(progress)
})
},
flash_window_frame: flash => {
self.control_window(id, native_id, "flash frame", window => {
window.flash_frame(flash)
})
},
popup_window_menu: (menu, x, y) => {
self.control_window(id, native_id, "popup menu in", window => {
window.popup_menu(@native.MenuBar(menus=[menu.to_native()]), x, y)
})
},
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.to_native())
},
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),
)
},
set_view_zoom_percent: zoom_percent => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"set zoom of",
view => view.set_zoom_percent(zoom_percent),
)
},
read_view_zoom_percent: () => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
running.view.zoom_percent() catch {
error =>
raise window_operation_failed(
"read zoom of view " + id + " in window " + window_id,
error,
)
}
},
set_view_audio_muted: muted => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"set audio mute of",
view => view.set_audio_muted(muted),
)
},
read_view_audio_muted: () => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
running.view.is_audio_muted() catch {
error =>
raise window_operation_failed(
"read audio mute of view " + id + " in window " + window_id,
error,
)
}
},
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.load_view_html(
window_id, window_native_id, id, native_id, 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),
)
},
focus_view: () => {
self.control_window(
window_id,
window_native_id,
"focus for view " + id + " in",
window => window.focus(),
)
self.control_view(window_id, window_native_id, id, native_id, "focus", view => {
view.browser_command("focus")
})
},
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?),
)
},
find_view_in_page: (text, forward, match_case, find_next) => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
running.view.find_in_page(text, forward, match_case, find_next) catch {
error =>
raise window_operation_failed(
"find in view " + id + " in window " + window_id,
error,
)
}
},
stop_view_find: clear_selection => {
self.control_view(
window_id,
window_native_id,
id,
native_id,
"stop find in",
view => view.stop_find_in_page(clear_selection),
)
},
read_view_navigation_state: () => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
running.view.navigation_state() catch {
error =>
raise window_operation_failed(
"read navigation state of view " + id + " in window " + window_id,
error,
)
}
},
read_view_focused: () => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
running.view.browser_is_focused() catch {
error =>
raise window_operation_failed(
"read focus of view " + id + " in window " + window_id,
error,
)
}
},
read_view_devtools_opened: () => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
running.view.is_devtools_opened() catch {
error =>
raise window_operation_failed(
"read DevTools state of view " + id + " in window " + window_id,
error,
)
}
},
read_view_state: () => {
let running = self.resolve_view(
window_id, window_native_id, id, native_id,
)
ViewState::from_native(running.view.state()) catch {
error =>
raise window_operation_failed(
"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 window_operation_failed(
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)
}
let running_view = add_view_to_running_window(running, view_id, config)
self.view_handle(running, running_view)
}
///|
fn add_view_to_running_window(
running : RunningWindow,
view_id : String,
config : @native.ViewConfig,
) -> RunningView raise WindowSessionError {
if view_id.is_empty() {
raise window_operation_failed(
"add view to window " + running.id,
@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::View(running.window, config) catch {
error =>
raise window_operation_failed(
"add view " + view_id + " to window " + running.id,
error,
)
}
let running_view = RunningView::{
id: view_id,
view: created,
document: None,
}
running.views.push(running_view)
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 window_operation_failed(
"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()
let session_handle = self.session_handle(window)
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.load_window_html(id, native_id, html, base_url)
},
eval_browser_script: script => {
self.control_window(id, native_id, "evaluate script in", window => {
window.eval(script)
})
},
focus_browser: () => {
self.control_window(id, native_id, "focus browser in", window => {
window.focus()
window.browser_command("focus")
})
},
send_browser_command: (command, download_id) => {
self.control_window(id, native_id, command + " in", window => {
window.browser_command(command, download_id?)
})
},
download_browser_url: url => {
self.control_window(id, native_id, "download URL in", window => {
window.download_url(url)
})
},
print_browser: () => {
self.control_window(id, native_id, "print browser in", window => {
window.print()
})
},
print_browser_to_pdf: (path, options) => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.print_to_pdf(path, options.to_native()) catch {
error =>
raise window_operation_failed(
"print browser to PDF in window " + id,
error,
)
}
},
find_browser_in_page: (text, forward, match_case, find_next) => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.find_in_page(text, forward, match_case, find_next) catch {
error =>
raise window_operation_failed(
"find in browser for window " + id,
error,
)
}
},
stop_browser_find: clear_selection => {
self.control_window(id, native_id, "stop browser find in", window => {
window.stop_find_in_page(clear_selection)
})
},
set_browser_zoom_percent: zoom_percent => {
self.control_window(id, native_id, "set browser zoom", window => {
window.set_zoom_percent(zoom_percent)
})
},
read_browser_zoom_percent: () => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.state().zoom_percent catch {
error =>
raise window_operation_failed(
"read browser zoom in window " + id,
error,
)
}
},
set_browser_audio_muted: muted => {
self.control_window(id, native_id, "set browser audio mute", window => {
window.set_audio_muted(muted)
})
},
read_browser_audio_muted: () => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.is_audio_muted() catch {
error =>
raise window_operation_failed(
"read browser audio mute in window " + id,
error,
)
}
},
read_browser_navigation_state: () => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.navigation_state() catch {
error =>
raise window_operation_failed(
"read browser navigation state in window " + id,
error,
)
}
},
read_browser_focused: () => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.browser_is_focused() catch {
error =>
raise window_operation_failed(
"read browser focus in window " + id,
error,
)
}
},
read_browser_devtools_opened: () => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.is_devtools_opened() catch {
error =>
raise window_operation_failed(
"read browser DevTools state in window " + id,
error,
)
}
},
read_browser_state: () => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
BrowserState::from_native(running.window.browser_state()) catch {
error =>
raise window_operation_failed(
"read browser state in window " + id,
error,
)
}
},
session_handle,
}
}
///|
fn PdfPrintOptions::to_native(
self : PdfPrintOptions,
) -> @native.PdfPrintSettings {
let (margin_type, margin_top, margin_right, margin_bottom, margin_left) = match
self.margins {
PdfPrintMargins::Default => (0, 0.0, 0.0, 0.0, 0.0)
PdfPrintMargins::NoMargins => (1, 0.0, 0.0, 0.0, 0.0)
PdfPrintMargins::Custom(top~, right~, bottom~, left~) =>
(2, top, right, bottom, left)
}
{
landscape: self.landscape,
print_background: self.print_background,
scale: self.scale,
paper_width: self.paper_width,
paper_height: self.paper_height,
prefer_css_page_size: self.prefer_css_page_size,
margin_type,
margin_top,
margin_right,
margin_bottom,
margin_left,
page_ranges: self.page_ranges,
display_header_footer: self.display_header_footer,
header_template: self.header_template,
footer_template: self.footer_template,
generate_tagged_pdf: self.generate_tagged_pdf,
generate_document_outline: self.generate_document_outline,
}
}
///|
fn RuntimeSession::session_handle(
self : RuntimeSession,
window : RunningWindow,
) -> SessionHandle {
let id = window.id
let native_id = window.window.id()
SessionHandle::{
id,
native_id,
read_cookies: (url, include_http_only) => {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
let request_id = running.window.cookie_begin_get(url, include_http_only) catch {
error =>
raise window_operation_failed("read cookies in window " + id, error)
}
let payload = self.event_pump.cookie_completions.wait(request_id)
decode_cookies(payload) catch {
CookieDecodeError(detail) =>
raise OperationFailed(
action="decode cookies in window " + id,
status=-1,
detail~,
)
}
},
write_cookie: (url, name, value, domain, path, secure, http_only, same_site) => {
self.control_window(id, native_id, "set cookie in", window => {
window.cookie_set(
url,
name,
value,
domain?,
path?,
secure~,
http_only~,
same_site=same_site.to_native(),
)
})
},
remove_cookies: (url, name) => {
self.control_window(id, native_id, "delete cookies in", window => {
window.cookie_delete(url, name)
})
},
flush_cookie_store: () => {
self.control_window(id, native_id, "flush cookies in", window => {
window.cookie_flush()
})
},
clear_http_cache: () => {
self.control_window(id, native_id, "clear cache in", window => {
window.clear_cache()
})
},
}
}
///|
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 preferred_active_window(windows : Array[RunningWindow]) -> RunningWindow? {
preferred_active_windows(windows).get(0)
}
///|
fn preferred_active_windows(
windows : Array[RunningWindow],
) -> Array[RunningWindow] {
let candidates : Array[RunningWindow] = []
let fallback : Array[RunningWindow] = []
for running in windows {
if running.is_active() {
if running.id == "main" {
candidates.push(running)
} else {
fallback.push(running)
}
}
}
for running in fallback {
candidates.push(running)
}
candidates
}
///|
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 window_operation_failed("close window " + id, error)
}
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 window_operation_failed(action + " window " + id, error)
}
}
///|
fn RuntimeSession::load_window_html(
self : RuntimeSession,
id : String,
native_id : Int64,
html : String,
base_url : String,
) -> Unit raise WindowSessionError {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
guard resource_document_url_is_supported(base_url) else {
raise window_operation_failed(
"load HTML in window " + id,
@native.NativeError::InvalidArgument(
message="base_url must use a Proton application origin",
),
)
}
let previous = running.document
running.document = Some({ url: base_url, content: @utf8.encode(html), })
running.window.load_url(base_url) catch {
error => {
running.document = previous
raise window_operation_failed("load HTML in window " + id, error)
}
}
}
///|
fn RuntimeSession::load_view_html(
self : RuntimeSession,
window_id : String,
window_native_id : Int64,
view_id : String,
view_native_id : Int64,
html : String,
base_url : String,
) -> Unit raise WindowSessionError {
let running = self.resolve_view(
window_id, window_native_id, view_id, view_native_id,
)
guard resource_document_url_is_supported(base_url) else {
raise window_operation_failed(
"load HTML in view " + view_id + " in window " + window_id,
@native.NativeError::InvalidArgument(
message="base_url must use a Proton application origin",
),
)
}
let previous = running.document
running.document = Some({ url: base_url, content: @utf8.encode(html), })
running.view.load_url(base_url) catch {
error => {
running.document = previous
raise window_operation_failed(
"load HTML in view " + view_id + " in window " + window_id,
error,
)
}
}
}
///|
fn RuntimeSession::read_window_state(
self : RuntimeSession,
id : String,
native_id : Int64,
) -> WindowState raise WindowSessionError {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
WindowState::from_native(running.window.state()) catch {
error =>
raise window_operation_failed("read window " + id + " state", error)
}
}
///|
fn RuntimeSession::read_window_content_size(
self : RuntimeSession,
id : String,
native_id : Int64,
) -> (Int, Int) raise WindowSessionError {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
running.window.content_size() catch {
error =>
raise window_operation_failed("read content size of window " + id, error)
}
}
///|
fn RuntimeSession::set_window_parent(
self : RuntimeSession,
id : String,
native_id : Int64,
parent_native_id : Int64?,
modal : Bool,
) -> Unit raise WindowSessionError {
let running = match self.find_active_window(id) {
Some(running) if running.window.id() == native_id => running
_ => raise StaleWindow(id~)
}
let parent = match parent_native_id {
Some(parent_id) => {
let mut found : RunningWindow? = None
for candidate in self.windows {
if candidate.is_active() && candidate.window.id() == parent_id {
found = Some(candidate)
break
}
}
match found {
Some(parent) => Some(parent.window.as_ref())
None => raise StaleWindow(id="parent")
}
}
None => None
}
running.window.set_parent(parent, modal) catch {
error => raise window_operation_failed("set parent of window " + id, 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::Window(
self.runtime,
config=native_window_config(
plan.window,
definition.bridge,
definition.browser_policy,
self.locale_preferences,
),
) catch {
error => raise native_run_error("create window " + plan.id, error)
}
let (document, asset_root) = match definition.entry {
RemoteEntry(_) => (None, None)
DocumentEntry(document, asset_root) => (Some(document), asset_root)
}
let running = RunningWindow::{
id: plan.id,
window: created_window,
permissions: definition.permissions,
lifetime: WindowLifetime(),
views: [],
document,
asset_root,
bridge_ready: !self.monitor_bridge,
state: WindowStarting,
}
errdefer (created_window.destroy() catch {
error =>
self.lifecycle_failures.push(
WindowDestroy(status=error.status(), detail=error.message()),
)
})
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,
)
}
}
load_prepared_entry(created_window, definition.entry) catch {
error => raise EntryLoadError(error)
}
for view_plan in definition.views {
let (view_id, view_config) = view_plan
ignore(
add_view_to_running_window(running, view_id, view_config) catch {
error =>
raise match error {
OperationFailed(action~, status~, detail~) =>
RuntimeOperationFailed(action~, status~, detail~)
other => UnexpectedTaskFailure(detail=other.message())
}
},
)
}
self.windows.push(running)
self.has_created_window = true
@xlog.info(category="proton.window")
{ "message": "window created", "window": running.id }
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(
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,
self.locale_preferences,
)
},
no_wait=true,
allow_failure=true,
),
)
running.lifetime.wait_until_ready()
if !running.is_active() {
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 {
if self.is_quitting() {
raise ApplicationQuitting
}
let completion = WindowOpenCompletion()
self.window_commands.push(Open(id, completion))
self.wakeup.signal.notify()
completion.wait()
}
///|
fn WindowOpenCompletion::WindowOpenCompletion() -> 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
if self.is_quitting() {
completion.fail(ApplicationQuitting)
continue
}
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(
BridgeDiagnostic::from_native(
bridge_startup_timeout_diagnostic(
state,
self.bridge_startup_timeout_ms,
),
),
)
None =>
RuntimeOperationFailed(
action="open window " + pending.id,
status=-1,
detail="bridge startup: 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_active() {
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
}
///|
pub fn WindowHandle::show(self : WindowHandle) -> Unit raise WindowSessionError {
(self.show_window)()
}
///|
/// Shows the window without activating it.
pub fn WindowHandle::show_inactive(
self : WindowHandle,
) -> Unit raise WindowSessionError {
(self.show_window_inactive)()
}
///|
pub fn WindowHandle::is_visible(
self : WindowHandle,
) -> Bool raise WindowSessionError {
(self.read_window_state)().visible
}
///|
pub fn WindowHandle::is_focused(
self : WindowHandle,
) -> Bool raise WindowSessionError {
(self.read_window_state)().focused
}
///|
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)
}
///|
/// Sets the native window icon from an image file path.
pub fn WindowHandle::set_icon(
self : WindowHandle,
path : String,
) -> Unit raise WindowSessionError {
(self.set_window_icon)(path)
}
///|
/// Establishes or clears this window's native parent relationship.
pub fn WindowHandle::set_parent(
self : WindowHandle,
parent : WindowHandle?,
modal? : Bool = false,
) -> Unit raise WindowSessionError {
(self.set_window_parent)(parent, modal)
}
///|
pub fn WindowHandle::set_size(
self : WindowHandle,
width : Int,
height : Int,
) -> Unit raise WindowSessionError {
(self.set_window_size)(width, height)
}
///|
/// Sets the renderer content area in logical pixels.
pub fn WindowHandle::set_content_size(
self : WindowHandle,
width : Int,
height : Int,
) -> Unit raise WindowSessionError {
(self.set_window_content_size)(width, height)
}
///|
/// Returns the renderer content area in logical pixels.
pub fn WindowHandle::content_size(
self : WindowHandle,
) -> (Int, Int) raise WindowSessionError {
(self.read_window_content_size)()
}
///|
/// Returns `(x, y, width, height)` in logical screen pixels, matching
/// Electron's `getBounds` coordinate order.
pub fn WindowHandle::bounds(
self : WindowHandle,
) -> (Int, Int, Int, Int) raise WindowSessionError {
let state = (self.read_window_state)()
(state.x, state.y, state.width, state.height)
}
///|
/// Sets `(x, y, width, height)` in logical screen pixels, matching Electron's
/// `setBounds` coordinate order.
pub fn WindowHandle::set_bounds(
self : WindowHandle,
x~ : Int,
y~ : Int,
width~ : Int,
height~ : Int,
) -> Unit raise WindowSessionError {
self.set_position(x, y)
self.set_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)
}
///|
/// Enters or leaves kiosk mode.
///
/// Kiosk uses the full display and hides system chrome where the platform
/// supports it. Call with `false` to provide a programmatic exit path.
pub fn WindowHandle::set_kiosk(
self : WindowHandle,
kiosk : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_kiosk)(kiosk)
}
///|
pub fn WindowHandle::set_position(
self : WindowHandle,
x : Int,
y : Int,
) -> Unit raise WindowSessionError {
(self.set_window_position)(x, y)
}
///|
/// Centers the window in the work area of the monitor containing it.
///
/// The current native frame size is preserved. Work-area coordinates exclude
/// taskbars, docks, and other reserved desktop regions, and use Proton's
/// top-left coordinate convention on every platform.
pub fn WindowHandle::center(
self : WindowHandle,
) -> Unit raise WindowSessionError {
let state = self.state()
let monitor = state.monitor
let x = monitor.work_x + (monitor.work_width - state.width) / 2
let y = monitor.work_y + (monitor.work_height - state.height) / 2
self.set_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)
}
///|
/// Sets whether the user can manually resize this window.
///
/// This changes the live native window on macOS, Windows, and Linux. Existing
/// minimum or maximum size hints remain in effect. Headless runtimes raise
/// `WindowSessionError`.
pub fn WindowHandle::set_resizable(
self : WindowHandle,
resizable : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_resizable)(resizable)
}
///|
/// Sets the minimum live native window size.
///
/// Both dimensions must be positive, or both must be zero to clear the
/// constraint. Existing maximum size and resizable state remain unchanged.
/// Headless runtimes raise `WindowSessionError`.
pub fn WindowHandle::set_minimum_size(
self : WindowHandle,
width : Int,
height : Int,
) -> Unit raise WindowSessionError {
(self.set_window_minimum_size)(width, height)
}
///|
/// Sets the maximum live native window size.
///
/// Both dimensions must be positive, or both must be zero to clear the
/// constraint. Existing minimum size and resizable state remain unchanged.
/// Headless runtimes raise `WindowSessionError`.
pub fn WindowHandle::set_maximum_size(
self : WindowHandle,
width : Int,
height : Int,
) -> Unit raise WindowSessionError {
(self.set_window_maximum_size)(width, height)
}
///|
/// Sets the live native window aspect ratio used while the user resizes it.
/// Pass `0.0` to clear the constraint. Programmatic `set_size` calls are not
/// constrained by the ratio, matching Electron's `setAspectRatio` behavior.
/// Headless runtimes raise `WindowSessionError`.
pub fn WindowHandle::set_aspect_ratio(
self : WindowHandle,
aspect_ratio : Double,
) -> Unit raise WindowSessionError {
(self.set_window_aspect_ratio)(aspect_ratio)
}
///|
/// Sets whether the user can move this live native window.
///
/// macOS and Windows prevent manual frame movement when `movable` is false;
/// programmatic `set_position` and `center` calls remain available. Linux
/// follows Electron and treats this as a no-op. Headless runtimes raise
/// `WindowSessionError` because they have no native frame.
pub fn WindowHandle::set_movable(
self : WindowHandle,
movable : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_movable)(movable)
}
///|
/// Sets the live native window opacity.
///
/// Values are clamped to the Electron-compatible `0.0..1.0` range, where
/// `0.0` is fully transparent and `1.0` is fully opaque. Headless runtimes
/// raise `WindowSessionError` because they have no native frame.
pub fn WindowHandle::set_opacity(
self : WindowHandle,
opacity : Double,
) -> Unit raise WindowSessionError {
(self.set_window_opacity)(opacity)
}
///|
/// Sets whether this live native window is omitted from the taskbar or dock.
///
/// Windows removes or restores the taskbar tab. macOS and Linux follow
/// Electron and treat this as a no-op. Headless runtimes raise
/// `WindowSessionError` because they have no native window shell.
pub fn WindowHandle::set_skip_taskbar(
self : WindowHandle,
skip : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_skip_taskbar)(skip)
}
///|
/// Sets whether other applications should be prevented from capturing this
/// live native window.
///
/// macOS applies `NSWindowSharingNone`, although ScreenCaptureKit-based apps
/// on recent macOS releases may still capture the window. Windows uses display
/// affinity to exclude the window from capture. Linux follows Electron and
/// treats this as a no-op. Headless runtimes raise `WindowSessionError`.
pub fn WindowHandle::set_content_protection(
self : WindowHandle,
enabled : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_content_protection)(enabled)
}
///|
/// Sets whether the user can manually minimize this live native window.
/// macOS and Windows update the native minimize control; Linux follows
/// Electron and treats this as a successful no-op. Headless runtimes raise
/// `WindowSessionError`.
pub fn WindowHandle::set_minimizable(
self : WindowHandle,
minimizable : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_minimizable)(minimizable)
}
///|
/// Sets whether the user can manually maximize this live native window.
/// macOS and Windows update the native maximize/zoom control; Linux follows
/// Electron and treats this as a successful no-op. Headless runtimes raise
/// `WindowSessionError`.
pub fn WindowHandle::set_maximizable(
self : WindowHandle,
maximizable : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_maximizable)(maximizable)
}
///|
/// Sets whether the user can manually close this live native window.
///
/// macOS and Windows update the native close control; programmatic `close`
/// calls remain available. Linux follows Electron and treats this as a
/// successful no-op. Headless runtimes raise `WindowSessionError`.
pub fn WindowHandle::set_closable(
self : WindowHandle,
closable : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_closable)(closable)
}
///|
/// Sets visibility of the standard close, minimize, and maximize buttons.
/// On macOS this updates the native traffic-light controls. Other platforms
/// apply the equivalent live capability controls where available.
pub fn WindowHandle::set_window_button_visibility(
self : WindowHandle,
visible : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_button_visibility)(visible)
}
///|
/// Sets whether this live native window can receive focus.
///
/// macOS and Windows update the native window behavior. On macOS, disabling
/// focus does not remove focus from a window that is already focused, matching
/// Electron. Linux follows Electron and treats this as a successful no-op.
/// Headless runtimes raise `WindowSessionError`.
pub fn WindowHandle::set_focusable(
self : WindowHandle,
focusable : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_focusable)(focusable)
}
///|
/// Sets whether this live native window can enter fullscreen mode.
///
/// When disabled, attempts to enter fullscreen are ignored. An already
/// fullscreen window can still exit fullscreen.
pub fn WindowHandle::set_fullscreenable(
self : WindowHandle,
fullscreenable : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_fullscreenable)(fullscreenable)
}
///|
/// Sets whether this live native window draws a frame shadow.
///
/// macOS applies this directly to the native window. Linux and Windows accept
/// the setting as a successful no-op when the platform frame owns the shadow.
/// Headless runtimes raise `WindowSessionError`.
pub fn WindowHandle::set_has_shadow(
self : WindowHandle,
has_shadow : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_has_shadow)(has_shadow)
}
///|
/// Sets whether this live native window ignores mouse events.
///
/// When `ignore` is true, mouse input passes through to the window below while
/// keyboard input remains available to the focused window. `forward` requests
/// mouse-move forwarding to Chromium where the platform supports it. Forwarding
/// is disabled automatically when `ignore` is false.
pub fn WindowHandle::set_ignore_mouse_events(
self : WindowHandle,
ignore : Bool,
forward : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_ignore_mouse_events)(ignore, forward)
}
///|
/// Sets the live native window background color.
///
/// Accepts `#RRGGBB` or `#AARRGGBB`, matching Proton view configuration.
pub fn WindowHandle::set_background_color(
self : WindowHandle,
color : String,
) -> Unit raise WindowSessionError {
(self.set_window_background_color)(color)
}
///|
/// Sets whether the live window appears on every workspace.
///
/// This maps to Spaces on macOS and sticky windows on Linux. It is a
/// successful no-op on Windows, matching Electron.
pub fn WindowHandle::set_visible_on_all_workspaces(
self : WindowHandle,
visible : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_visible_on_all_workspaces)(visible)
}
///|
/// Enables or disables user interaction with the live native window.
pub fn WindowHandle::set_enabled(
self : WindowHandle,
enabled : Bool,
) -> Unit raise WindowSessionError {
(self.set_window_enabled)(enabled)
}
///|
/// Replaces the native menu for the application runtime containing this window.
/// Passing `None` clears the menu.
pub fn WindowHandle::set_menu(
self : WindowHandle,
menu : MenuBar?,
) -> Unit raise WindowSessionError {
(self.set_window_menu)(menu)
}
///|
pub fn WindowHandle::set_zoom_percent(
self : WindowHandle,
zoom_percent : Int,
) -> Unit raise WindowSessionError {
(self.set_window_zoom_percent)(zoom_percent)
}
///|
/// Returns the main browser zoom percentage.
pub fn WindowHandle::zoom_percent(
self : WindowHandle,
) -> Int raise WindowSessionError {
(self.read_window_state)().zoom_percent
}
///|
/// Sets the platform progress indicator using Electron-compatible values.
///
/// Pass a negative value to clear the indicator, a value from `0.0` through
/// `1.0` for determinate progress, or a value above `1.0` for indeterminate
/// progress. The current native implementation displays this in the macOS
/// Dock as one application-level indicator; the most recent window call wins,
/// and any negative value clears it. Unsupported platforms raise
/// `WindowSessionError`.
pub fn WindowHandle::set_progress_bar(
self : WindowHandle,
progress : Double,
) -> Unit raise WindowSessionError {
(self.set_window_progress_bar)(progress)
}
///|
/// Starts or stops flashing the window to attract the user's attention.
///
/// On macOS, `true` continuously bounces the application Dock icon until the
/// application becomes active or `flash_frame(false)` cancels the request. On
/// Windows it flashes the taskbar button until the window becomes active, and
/// on Linux it sets the desktop window urgency hint. Headless runtimes raise
/// `WindowSessionError`.
pub fn WindowHandle::flash_frame(
self : WindowHandle,
flash : Bool,
) -> Unit raise WindowSessionError {
(self.flash_window_frame)(flash)
}
///|
pub fn WindowHandle::state(
self : WindowHandle,
) -> WindowState raise WindowSessionError {
(self.read_window_state)()
}
///|
fn WindowHandle::popup_menu(
self : WindowHandle,
menu : Menu,
x : Int,
y : Int,
) -> Unit raise WindowSessionError {
(self.popup_window_menu)(menu, x, y)
}
///|
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 : 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
}
///|
/// 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)
}
///|
/// Sets this view's browser zoom from 25% through 500%.
pub fn ViewHandle::set_zoom_percent(
self : ViewHandle,
zoom_percent : Int,
) -> Unit raise WindowSessionError {
(self.set_view_zoom_percent)(zoom_percent)
}
///|
/// Returns this view's browser zoom percentage.
pub fn ViewHandle::zoom_percent(
self : ViewHandle,
) -> Int raise WindowSessionError {
(self.read_view_zoom_percent)()
}
///|
/// Mutes or unmutes audio produced by this view's page.
pub fn ViewHandle::set_audio_muted(
self : ViewHandle,
muted : Bool,
) -> Unit raise WindowSessionError {
(self.set_view_audio_muted)(muted)
}
///|
/// Returns whether audio produced by this view's page is muted.
pub fn ViewHandle::is_audio_muted(
self : ViewHandle,
) -> Bool raise WindowSessionError {
(self.read_view_audio_muted)()
}
///|
/// 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)
}
///|
/// Starts or advances a page search and returns its request id. Set
/// `find_next=true` to begin a new search session, matching Electron's
/// `findInPage` option; leave it false to advance the current session.
pub fn ViewHandle::find_in_page(
self : ViewHandle,
text : String,
forward? : Bool = true,
match_case? : Bool = false,
find_next? : Bool = false,
) -> Int raise WindowSessionError {
(self.find_view_in_page)(text, forward, match_case, find_next)
}
///|
/// Stops the active page search. Set `clear_selection=false` to preserve the
/// current match highlight.
pub fn ViewHandle::stop_find_in_page(
self : ViewHandle,
clear_selection? : Bool = true,
) -> Unit raise WindowSessionError {
(self.stop_view_find)(clear_selection)
}
///|
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)
}
///|
/// Returns whether the view can navigate to a previous history entry.
pub fn ViewHandle::can_go_back(
self : ViewHandle,
) -> Bool raise WindowSessionError {
(self.read_view_navigation_state)().0
}
///|
/// Returns whether the view can navigate to a later history entry.
pub fn ViewHandle::can_go_forward(
self : ViewHandle,
) -> Bool raise WindowSessionError {
(self.read_view_navigation_state)().1
}
///|
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)
}
///|
/// Undoes the most recent edit in the focused frame.
pub fn ViewHandle::undo(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("undo", None)
}
///|
/// Redoes the most recently undone edit in the focused frame.
pub fn ViewHandle::redo(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("redo", None)
}
///|
/// Cuts the current selection in the focused frame.
pub fn ViewHandle::cut(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("cut", None)
}
///|
/// Copies the current selection in the focused frame.
pub fn ViewHandle::copy(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("copy", None)
}
///|
/// Pastes clipboard contents into the focused frame.
pub fn ViewHandle::paste(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("paste", None)
}
///|
/// Pastes clipboard contents using the destination's current style.
pub fn ViewHandle::paste_and_match_style(
self : ViewHandle,
) -> Unit raise WindowSessionError {
(self.send_view_command)("paste_and_match_style", None)
}
///|
/// Deletes the current selection in the focused frame.
pub fn ViewHandle::delete(self : ViewHandle) -> Unit raise WindowSessionError {
(self.send_view_command)("delete", None)
}
///|
/// Selects all editable content in the focused frame.
pub fn ViewHandle::select_all(
self : ViewHandle,
) -> Unit raise WindowSessionError {
(self.send_view_command)("select_all", None)
}
///|
/// Focuses this view's web page, matching Electron `webContents.focus()`.
pub fn ViewHandle::focus(self : ViewHandle) -> Unit raise WindowSessionError {
(self.focus_view)()
}
///|
/// Returns whether this view's web page currently owns focus.
pub fn ViewHandle::is_focused(
self : ViewHandle,
) -> Bool raise WindowSessionError {
(self.read_view_focused)()
}
///|
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)
}
///|
/// Toggles the DevTools view for this web contents.
pub fn ViewHandle::toggle_devtools(
self : ViewHandle,
) -> Unit raise WindowSessionError {
(self.send_view_command)("toggle_devtools", None)
}
///|
/// Returns whether this web contents currently has an open DevTools view.
pub fn ViewHandle::is_devtools_opened(
self : ViewHandle,
) -> Bool raise WindowSessionError {
(self.read_view_devtools_opened)()
}
///|
/// Reads the current view state from the native runtime.
pub fn ViewHandle::state(
self : ViewHandle,
) -> 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)
}
///|
/// Starts or advances a page search and returns its request id. Set
/// `find_next=true` to begin a new search session, matching Electron's
/// `findInPage` option; leave it false to advance the current session.
pub fn BrowserHandle::find_in_page(
self : BrowserHandle,
text : String,
forward? : Bool = true,
match_case? : Bool = false,
find_next? : Bool = false,
) -> Int raise WindowSessionError {
(self.find_browser_in_page)(text, forward, match_case, find_next)
}
///|
/// Stops the active page search. Set `clear_selection=false` to preserve the
/// current match highlight.
pub fn BrowserHandle::stop_find_in_page(
self : BrowserHandle,
clear_selection? : Bool = true,
) -> Unit raise WindowSessionError {
(self.stop_browser_find)(clear_selection)
}
///|
/// Starts downloading `url` without navigating the page. The request uses the
/// app's download approval and progress handlers. CEF does not support
/// Electron's optional custom request headers for this operation.
pub fn BrowserHandle::download_url(
self : BrowserHandle,
url : String,
) -> Unit raise WindowSessionError {
(self.download_browser_url)(url)
}
///|
/// Opens the platform print flow for the main browser contents.
pub fn BrowserHandle::print(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.print_browser)()
}
///|
/// Starts printing the main browser contents to `path` and returns a request
/// id. Completion is delivered as `BrowserEvent::PdfPrinted`.
pub fn BrowserHandle::print_to_pdf(
self : BrowserHandle,
path : String,
options? : PdfPrintOptions = PdfPrintOptions(),
) -> Int raise WindowSessionError {
(self.print_browser_to_pdf)(path, options)
}
///|
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)
}
///|
/// Sets the main browser zoom from 25% through 500%.
pub fn BrowserHandle::set_zoom_percent(
self : BrowserHandle,
zoom_percent : Int,
) -> Unit raise WindowSessionError {
(self.set_browser_zoom_percent)(zoom_percent)
}
///|
/// Returns the main browser zoom percentage.
pub fn BrowserHandle::zoom_percent(
self : BrowserHandle,
) -> Int raise WindowSessionError {
(self.read_browser_zoom_percent)()
}
///|
/// Mutes or unmutes audio produced by the main browser page.
pub fn BrowserHandle::set_audio_muted(
self : BrowserHandle,
muted : Bool,
) -> Unit raise WindowSessionError {
(self.set_browser_audio_muted)(muted)
}
///|
/// Returns whether audio produced by the main browser page is muted.
pub fn BrowserHandle::is_audio_muted(
self : BrowserHandle,
) -> Bool raise WindowSessionError {
(self.read_browser_audio_muted)()
}
///|
/// Returns whether the main browser can navigate to a previous history entry.
pub fn BrowserHandle::can_go_back(
self : BrowserHandle,
) -> Bool raise WindowSessionError {
(self.read_browser_navigation_state)().0
}
///|
/// Returns whether the main browser can navigate to a later history entry.
pub fn BrowserHandle::can_go_forward(
self : BrowserHandle,
) -> Bool raise WindowSessionError {
(self.read_browser_navigation_state)().1
}
///|
/// Reads the current main browser page state.
pub fn BrowserHandle::state(
self : BrowserHandle,
) -> BrowserState raise WindowSessionError {
(self.read_browser_state)()
}
///|
/// Returns the browser session used by this main page.
pub fn BrowserHandle::session(self : BrowserHandle) -> SessionHandle {
self.session_handle
}
///|
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)
}
///|
/// Undoes the most recent edit in the focused frame.
pub fn BrowserHandle::undo(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("undo", None)
}
///|
/// Redoes the most recently undone edit in the focused frame.
pub fn BrowserHandle::redo(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("redo", None)
}
///|
/// Cuts the current selection in the focused frame.
pub fn BrowserHandle::cut(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("cut", None)
}
///|
/// Copies the current selection in the focused frame.
pub fn BrowserHandle::copy(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("copy", None)
}
///|
/// Pastes clipboard contents into the focused frame.
pub fn BrowserHandle::paste(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("paste", None)
}
///|
/// Pastes clipboard contents using the destination's current style.
pub fn BrowserHandle::paste_and_match_style(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("paste_and_match_style", None)
}
///|
/// Deletes the current selection in the focused frame.
pub fn BrowserHandle::delete(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("delete", None)
}
///|
/// Selects all editable content in the focused frame.
pub fn BrowserHandle::select_all(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("select_all", None)
}
///|
/// Focuses the web page, matching Electron `webContents.focus()`.
pub fn BrowserHandle::focus(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.focus_browser)()
}
///|
/// Returns whether the web page currently owns focus.
pub fn BrowserHandle::is_focused(
self : BrowserHandle,
) -> Bool raise WindowSessionError {
(self.read_browser_focused)()
}
///|
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)
}
///|
/// Toggles the DevTools view for this web contents.
pub fn BrowserHandle::toggle_devtools(
self : BrowserHandle,
) -> Unit raise WindowSessionError {
(self.send_browser_command)("toggle_devtools", None)
}
///|
/// Returns whether this web contents currently has an open DevTools view.
pub fn BrowserHandle::is_devtools_opened(
self : BrowserHandle,
) -> Bool raise WindowSessionError {
(self.read_browser_devtools_opened)()
}
///|
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, () => {
drive_wakeup_until(
self.wakeup,
() => {
self.is_quitting() ||
refresh_bridge_startup_states(self.windows) is None
},
() => self.pump_once(monitor_bridge=false),
)
}) catch {
error => raise normalize_async_run_error(error)
}
match completed {
Some(_) => ()
None => {
ignore(self.pump_once(monitor_bridge=false))
if self.is_quitting() {
return
}
match refresh_bridge_startup_states(self.windows) {
None => return
Some(state) =>
raise BridgeStartupError(
BridgeDiagnostic::from_native(
bridge_startup_timeout_diagnostic(state, timeout_ms),
),
)
}
}
}
}
///|
async fn RuntimeSession::run(self : RuntimeSession) -> Unit raise AppRunError {
drive_wakeup_until(self.wakeup, () => self.quit_complete(), () => {
self.pump_once(monitor_bridge=self.monitor_bridge)
})
self.cancel_all_bridge_tasks()
self.close_coordinator.cancel_all()
self.cancel_all_browser_requests()
self.cancel_all_resource_requests()
}
///|
async fn RuntimeSession::drive_until_complete(
self : RuntimeSession,
task : @async.Task[Unit],
) -> Unit raise AppRunError {
drive_wakeup_until(
self.wakeup,
() => {
let completed = task.try_wait() catch {
error => raise normalize_async_run_error(error)
}
completed is Some(_)
},
() => self.pump_once(monitor_bridge=self.monitor_bridge),
)
}
///|
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 self.drain_extension_events() {
did_work = true
}
if monitor_bridge {
self.check_bridge_failures()
}
if self.complete_bridge_requests() {
did_work = true
}
if self.advance_window_opens() {
did_work = true
}
if self.complete_browser_requests() {
did_work = true
}
if self.complete_resource_requests() {
did_work = true
}
if self.advance_application_lifetime() {
did_work = true
}
did_work
}
///|
fn RuntimeSession::start_extension_event_sources(
self : RuntimeSession,
) -> Unit raise AppRunError {
match self.command_host {
None => ()
Some(host) =>
start_command_extension_event_sources(
host.event_sources,
@native.event_wakeup_callback(),
)
}
}
///|
fn start_command_extension_event_sources(
event_sources : Array[CommandExtensionEventSource],
wakeup : @proton_extension.EventWakeup,
) -> Unit raise AppRunError {
for event_source in event_sources {
guard !event_source.started else { continue }
event_source.started = true
event_source.source.start(wakeup) catch {
error => {
event_source.source.stop()
event_source.started = false
raise CommandExtensionLifecycleError(
EventSourceStartFailed(
extension_id=event_source.extension_id,
detail=@debug.render(Repr(error)),
),
)
}
}
}
}
///|
fn RuntimeSession::drain_extension_events(
self : RuntimeSession,
) -> Bool raise AppRunError {
let mut did_work = false
match self.command_host {
None => ()
Some(host) =>
for event_source in host.event_sources {
guard event_source.started else { continue }
let events = event_source.drain()
if !events.is_empty() {
did_work = true
}
for event in events {
guard event.extension_id() == event_source.extension_id &&
event.extension_namespace() == event_source.extension_namespace else {
event_source.disable(
"event descriptor belongs to " +
event.extension_id() +
" (" +
event.extension_namespace() +
")",
)
break
}
self.forward_extension_event_to_granted_windows(
event_source.extension_id,
event_source.extension_namespace,
event.name(),
event.payload(),
)
}
}
}
did_work
}
///|
fn CommandExtensionEventSource::drain(
self : CommandExtensionEventSource,
) -> Array[@proton_extension.Event] {
guard self.started else { return [] }
self.source.drain() catch {
error => {
self.disable(@debug.render(Repr(error)))
[]
}
}
}
///|
fn CommandExtensionEventSource::disable(
self : CommandExtensionEventSource,
detail : String,
) -> Unit {
self.stop()
@xlog.error(category="proton.extension")
{
"message": "event source disabled",
"extension_id": self.extension_id,
"detail": detail,
}
}
///|
fn RuntimeSession::forward_extension_event_to_granted_windows(
self : RuntimeSession,
extension_id : String,
extension_namespace : String,
name : String,
payload : Json,
) -> Unit raise AppRunError {
for running in self.windows {
guard running.is_active() &&
running.bridge_ready &&
running.permissions.grants_extension(extension_id) else {
continue
}
forward_extension_event(running.window, extension_namespace, name, payload)
}
}
///|
fn RuntimeSession::drain_runtime_events(
self : RuntimeSession,
) -> Bool raise AppRunError {
self.event_pump.drain_session_events(event => {
self.dispatch_runtime_event(event)
})
}
///|
fn RuntimeSession::dispatch_runtime_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit raise AppRunError {
if event.is_quit_requested() {
self.request_quit()
}
self.dispatch_window_event(event)
self.dispatch_view_event(event)
self.dispatch_browser_event(event)
self.dispatch_resource_event(event)
self.dispatch_notification_event(event)
self.dispatch_launch_input_event(event)
self.dispatch_menu_event(event)
match event.bridge_request() {
Some(request) => self.start_bridge_request(request)
None => ()
}
self.cancel_bridge_request(event.bridge_request_cancellation())
}
///|
fn RuntimeSession::dispatch_window_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit raise AppRunError {
if event.is_window_closed() {
let window_id = event.window_id()
runtime_session_cancel_window_bridge_tasks(self.pending_bridge, window_id)
self.close_coordinator.cancel(window_id)
self.cancel_browser_requests(window_id)
self.cancel_resource_requests_for_window(window_id)
self.retire_closed_window(window_id)
return
}
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(WindowState::from_native(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 raise AppRunError {
let closes_view = event.is_view_closed()
if self.view_event_handlers.is_empty() && !closes_view {
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 index, 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(), event.view_event(), event.find_in_page_result()) {
("view_loading_changed", Some(info), _) =>
ViewEvent::LoadingChanged(is_loading=info.is_loading.unwrap_or(false))
("view_navigated", Some(info), _) =>
ViewEvent::Navigated(url=info.url.unwrap_or(""))
("view_title_updated", Some(info), _) =>
ViewEvent::TitleUpdated(title=info.title.unwrap_or(""))
("view_load_failed", Some(info), _) =>
ViewEvent::LoadFailed(
url=info.url.unwrap_or(""),
error_code=info.error_code.unwrap_or(0),
error_text=info.error_text.unwrap_or(""),
)
("view_found_in_page", _, Some(result)) =>
ViewEvent::FoundInPage(result=FindInPageResult::from_native(result))
("view_closed", Some(_), _) => ViewEvent::Closed
_ => return
}
if closes_view {
view.view.destroy() catch {
error =>
raise native_run_error(
"destroy closed view " + view.id + " in window " + running.id,
error,
)
}
ignore(running.views.remove(index))
}
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.window_id(),
event.browser_event(),
event.find_in_page_result(),
event.pdf_print_result(),
) {
(Some(window_id), info, find_result, pdf_result) =>
match self.find_window_by_native_id(window_id) {
Some(window) => {
let browser = self.browser_handle(window)
let observed = match
(event.event_type(), info, find_result, pdf_result) {
("browser_loading_changed", Some(info), _, _) =>
Some(
BrowserEvent::LoadingChanged(
is_loading=info.is_loading.unwrap_or(false),
),
)
("browser_navigated", Some(info), _, _) =>
Some(BrowserEvent::Navigated(url=info.url.unwrap_or("")))
("browser_title_updated", Some(info), _, _) =>
Some(BrowserEvent::TitleUpdated(title=info.title.unwrap_or("")))
("browser_load_failed", Some(info), _, _) =>
Some(
BrowserEvent::LoadFailed(
url=info.url.unwrap_or(""),
error_code=info.error_code.unwrap_or(0),
error_text=info.error_text.unwrap_or(""),
),
)
("browser_found_in_page", _, Some(result), _) =>
Some(
BrowserEvent::FoundInPage(
result=FindInPageResult::from_native(result),
),
)
("browser_pdf_print_finished", _, _, Some(result)) =>
Some(
BrowserEvent::PdfPrinted(result={
request_id: result.request_id,
path: result.path,
success: result.success,
}),
)
_ => None
}
match observed {
Some(observed) =>
for handler in self.browser_event_handlers {
ignore(
self.window_tasks.spawn(
() => handler(browser, observed),
no_wait=true,
),
)
}
None => ()
}
}
None => ()
}
_ => ()
}
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_active() => {
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::dispatch_resource_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit {
match event.resource_request() {
Some(request) => self.start_resource_request(request)
None => ()
}
self.cancel_resource_request(event.resource_request_cancellation())
}
///|
fn RuntimeSession::start_resource_request(
self : RuntimeSession,
request : @native.NativeResourceRequest,
) -> Unit {
let (document, asset_root) = match
self.find_window_by_native_id(request.window) {
Some(window) if request.view is Some(view_id) => {
let mut document = None
for view in window.views {
if view.view.id() == view_id {
document = view.document
break
}
}
(document, None)
}
Some(window) => (window.document, window.asset_root)
None => (None, None)
}
let task = self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
resolve_resource_request(document, asset_root, request.url)
},
no_wait=true,
)
self.pending_resource_requests.push({
request_id: request.request_id,
window: request.window,
task,
})
}
///|
fn RuntimeSession::complete_resource_requests(
self : RuntimeSession,
) -> Bool raise AppRunError {
let remaining : Array[PendingResourceRequest] = []
let mut did_work = false
for pending in self.pending_resource_requests {
let completed = pending.task.try_wait() catch {
_ => Some(resource_text_response(500, "Resource request failed"))
}
match completed {
None => remaining.push(pending)
Some(response) => {
did_work = true
self.runtime.complete_resource_request(
pending.request_id,
response.status,
response.mime_type,
response.body,
) catch {
error =>
if !error.is_stale_resource_request() {
raise native_run_error("complete resource request", error)
}
}
}
}
}
self.pending_resource_requests.clear()
for pending in remaining {
self.pending_resource_requests.push(pending)
}
did_work
}
///|
fn RuntimeSession::cancel_resource_request(
self : RuntimeSession,
request_id : Int64?,
) -> Unit {
guard request_id is Some(request_id) else { return }
let remaining : Array[PendingResourceRequest] = []
for pending in self.pending_resource_requests {
if pending.request_id == request_id {
pending.task.cancel()
} else {
remaining.push(pending)
}
}
self.pending_resource_requests.clear()
for pending in remaining {
self.pending_resource_requests.push(pending)
}
}
///|
fn RuntimeSession::cancel_resource_requests_for_window(
self : RuntimeSession,
window_id : Int64?,
) -> Unit {
guard window_id is Some(window_id) else { return }
let remaining : Array[PendingResourceRequest] = []
for pending in self.pending_resource_requests {
if pending.window == window_id {
pending.task.cancel()
} else {
remaining.push(pending)
}
}
self.pending_resource_requests.clear()
for pending in remaining {
self.pending_resource_requests.push(pending)
}
}
///|
fn RuntimeSession::cancel_all_resource_requests(self : RuntimeSession) -> Unit {
for pending in self.pending_resource_requests {
pending.task.cancel()
}
self.pending_resource_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 }
match self.close_coordinator.find(window_id) {
Some(pending) => {
pending.native_request = Some(request_id)
return
}
None => ()
}
window.state = WindowCloseRequested
let handle = self.window_handle(window)
let task = self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
handler(handle)
},
no_wait=true,
)
self.close_coordinator.pending.push(PendingCloseDecision::{
window: window_id,
task,
decision: None,
native_request: Some(request_id),
})
}
///|
fn RuntimeSession::advance_individual_close_decisions(
self : RuntimeSession,
) -> Bool raise AppRunError {
let remaining : Array[PendingCloseDecision] = []
let mut did_work = false
for pending in self.close_coordinator.pending {
let completed = pending.task.try_wait() catch { _ => Some(Allow) }
match completed {
None => remaining.push(pending)
Some(decision) => {
did_work = true
self.respond_close_decision(pending, decision)
}
}
}
self.close_coordinator.pending.clear()
for pending in remaining {
self.close_coordinator.pending.push(pending)
}
did_work
}
///|
fn RuntimeSession::dispatch_notification_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit raise AppRunError {
match event.notification_result() {
Some(result) => publish_notification_result(result)
None => ()
}
match event.notification_click() {
Some(payload) =>
for running in preferred_active_windows(self.windows) {
guard running.is_active() &&
running.bridge_ready &&
running.permissions.grants_extension(notification_extension_id) else {
continue
}
forward_extension_event(
running.window,
notification_extension_namespace,
"click",
notification_click_payload(payload),
)
break
}
None => ()
}
}
///|
fn RuntimeSession::dispatch_launch_input_event(
self : RuntimeSession,
event : @native.RuntimeEvent,
) -> Unit raise AppRunError {
match event.launch_input() {
Some(native_input) => {
guard !self.is_quitting() else { return }
let input = match native_input {
@native.RuntimeLaunchInput::OpenUrls(urls) =>
RuntimeLaunchInput::OpenUrls(urls)
@native.RuntimeLaunchInput::OpenFiles(files) =>
RuntimeLaunchInput::OpenFiles(files)
@native.RuntimeLaunchInput::Reopen => RuntimeLaunchInput::Reopen
}
self.activate_for_launch_input()
let context = self.application_context()
for handler in self.launch_input_handlers {
ignore(
self.application_tasks.spawn(
() => handler(context, input),
no_wait=true,
),
)
}
}
None => ()
}
}
///|
fn RuntimeSession::activate_for_launch_input(
self : RuntimeSession,
) -> Unit raise AppRunError {
for running in preferred_active_windows(self.windows) {
if try_activate_window_for_launch_input(running) {
return
}
}
}
///|
fn try_activate_window_for_launch_input(
running : RunningWindow,
) -> Bool raise AppRunError {
try_activate_for_launch_input(
() => running.window.state(),
() => running.window.restore(),
() => running.window.show(),
() => running.window.focus(),
)
}
///|
fn try_activate_for_launch_input(
read_state : () -> @native.WindowState raise @native.NativeError,
restore : () -> Unit raise @native.NativeError,
show : () -> Unit raise @native.NativeError,
focus : () -> Unit raise @native.NativeError,
) -> Bool raise AppRunError {
let state = read_state() catch {
error => {
if error.is_invalid_state() || error.is_stale_window_request() {
return false
}
raise native_run_error("read activation window state", error)
}
}
if state.minimized {
restore() catch {
error => {
if error.is_invalid_state() || error.is_stale_window_request() {
return false
}
raise native_run_error("restore activation window", error)
}
}
}
if !state.visible {
show() catch {
error => {
if error.is_invalid_state() || error.is_stale_window_request() {
return false
}
raise native_run_error("show activation window", error)
}
}
}
focus() catch {
error => {
if error.is_invalid_state() || error.is_stale_window_request() {
return false
}
raise native_run_error("focus activation window", error)
}
}
true
}
///|
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 {
guard !self.is_quitting() else { return }
for running in self.windows {
if !running.is_active() {
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(BridgeDiagnostic::from_native(diagnostic))
None => ()
}
}
}
///|
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,
self.event_pump.dialog_completions,
self.locale_preferences,
)
},
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 RuntimeSession::retire_closed_window(
self : RuntimeSession,
window_id : Int64?,
) -> Unit raise AppRunError {
guard window_id is Some(window_id) else { return }
let mut index = -1
for i, running in self.windows {
if running.window.id() == window_id {
index = i
break
}
}
guard index >= 0 else { return }
let running = self.windows[index]
running.state = WindowClosed
running.lifetime.close()
running.window.destroy() catch {
error =>
raise native_run_error("destroy closed window " + running.id, error)
}
ignore(self.windows.remove(index))
@xlog.info(category="proton.window")
{ "message": "window closed", "window": running.id }
}
///|
fn RuntimeSession::all_windows_closed(self : RuntimeSession) -> Bool {
self.has_created_window && self.windows.all(fn(window) { window.is_closed() })
}
///|
fn should_quit_after_last_window(
policy : LastWindowClosedPolicy,
has_windows : Bool,
all_windows_closed : Bool,
) -> Bool {
policy == LastWindowClosedPolicy::Quit && has_windows && all_windows_closed
}
///|
fn should_commit_application_close(
decisions : Array[WindowCloseDecision],
) -> Bool {
decisions.all(fn(decision) { decision == WindowCloseDecision::Allow })
}
///|
fn RuntimeSession::request_quit(self : RuntimeSession) -> Unit {
if self.close_coordinator.request_quit() {
self.wakeup.signal.notify()
}
}
///|
fn RuntimeSession::is_quitting(self : RuntimeSession) -> Bool {
self.close_coordinator.is_quitting()
}
///|
fn RuntimeSession::advance_application_lifetime(
self : RuntimeSession,
) -> Bool raise AppRunError {
let mut did_work = false
if self.close_coordinator.state == CloseRunning &&
should_quit_after_last_window(
self.last_window_closed_policy,
self.has_created_window,
self.all_windows_closed(),
) {
self.request_quit()
did_work = true
}
match self.close_coordinator.state {
CloseRunning =>
if self.advance_individual_close_decisions() {
did_work = true
}
CloseQuitRequested => {
self.begin_application_close_decisions()
did_work = true
}
CloseCollecting =>
if self.advance_application_close_decisions() {
did_work = true
}
CloseCommitted => ()
}
did_work
}
///|
fn RuntimeSession::begin_application_close_decisions(
self : RuntimeSession,
) -> Unit raise AppRunError {
guard self.window_close_handler is Some(handler) else {
self.commit_application_close()
return
}
for running in self.windows {
guard running.is_active() else { continue }
let window_id = running.window.id()
guard self.close_coordinator.find(window_id) is None else { continue }
let handle = self.window_handle(running)
let task = self.window_tasks.spawn(
() => {
defer self.wakeup.signal.notify()
handler(handle)
},
no_wait=true,
)
self.close_coordinator.pending.push(PendingCloseDecision::{
window: window_id,
task,
decision: None,
native_request: None,
})
}
if self.close_coordinator.pending.is_empty() {
self.commit_application_close()
} else {
self.close_coordinator.state = CloseCollecting
}
}
///|
fn RuntimeSession::advance_application_close_decisions(
self : RuntimeSession,
) -> Bool raise AppRunError {
let mut did_work = false
let mut waiting = false
let decisions : Array[WindowCloseDecision] = []
for pending in self.close_coordinator.pending {
if pending.decision is None {
let completed = pending.task.try_wait() catch {
_ => Some(WindowCloseDecision::Allow)
}
match completed {
Some(decision) => {
pending.decision = Some(decision)
did_work = true
}
None => waiting = true
}
}
match pending.decision {
Some(decision) => decisions.push(decision)
None => waiting = true
}
}
if waiting {
return did_work
}
if !should_commit_application_close(decisions) {
for pending in self.close_coordinator.pending {
guard pending.decision is Some(decision) else { continue }
if pending.native_request is Some(_) {
self.respond_close_decision(pending, decision)
}
}
self.close_coordinator.pending.clear()
self.close_coordinator.state = CloseRunning
} else {
self.commit_application_close()
}
true
}
///|
fn RuntimeSession::respond_close_decision(
self : RuntimeSession,
pending : PendingCloseDecision,
decision : WindowCloseDecision,
) -> Unit raise AppRunError {
match self.find_window_by_native_id(pending.window) {
Some(window) if !window.is_closed() => {
match pending.native_request {
Some(request_id) =>
window.window.respond_close_request(
request_id,
decision == WindowCloseDecision::Allow,
) catch {
error =>
if !error.is_stale_window_request() {
raise native_run_error("respond to window close request", error)
}
}
None => ()
}
window.state = if decision == WindowCloseDecision::Allow {
WindowCloseRequested
} else {
WindowOpen
}
}
_ => ()
}
pending.native_request = None
}
///|
fn RuntimeSession::commit_application_close(
self : RuntimeSession,
) -> Unit raise AppRunError {
self.close_coordinator.state = CloseCommitted
self.close_coordinator.pending.clear()
for running in self.windows {
guard running.is_active() else { continue }
if self.window_close_handler is Some(_) {
running.window.set_close_interception(false) catch {
error =>
raise native_run_error(
"commit close interception for " + running.id,
error,
)
}
}
running.window.close() catch {
error => raise native_run_error("close window " + running.id, error)
}
running.state = WindowCloseRequested
}
}
///|
fn RuntimeSession::quit_complete(self : RuntimeSession) -> Bool {
self.close_coordinator.state == CloseCommitted &&
self.windows.all(fn(window) { window.is_closed() }) &&
self.window_commands.length() == 0 &&
self.pending_window_opens.length() == 0 &&
self.close_coordinator.pending.length() == 0
}
///|
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)
_ => ()
}
}