ltk_webkit/wpe/
popups.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! `window.open()` and `window.close()`: WebKit's `create` and `close` signals.

use std::ffi::c_char;
use std::ptr;
use std::sync::{ Mutex, Weak };

use glib_sys::gpointer;
use gobject_sys::{ g_signal_connect_data, GClosure, GObject };
use wpe_platform_sys::WPEDisplay;
use wpe_webkit_sys::{ WebKitNavigationAction, WebKitWebView };

use super::{ Inner, WebView };

/// The view a new one is opened from.
pub( crate ) struct Opener
{
	pub( crate ) display: *mut WPEDisplay,
	pub( crate ) webview: *mut WebKitWebView,
}

type CreateFn = unsafe extern "C" fn( *mut WebKitWebView, *mut WebKitNavigationAction, gpointer ) -> *mut WebKitWebView;
type CloseFn  = unsafe extern "C" fn( *mut WebKitWebView, gpointer );

impl WebView
{
	/// Views the page opened with `window.open()`, related to this one, for the host to show.
	pub fn take_created_views( &self ) -> Vec<WebView>
	{
		self.inner.lock().map( |mut g| std::mem::take( &mut g.created_views ) ).unwrap_or_default()
	}

	/// Whether the page called `window.close()` since the previous call.
	pub fn take_close_request( &self ) -> bool
	{
		self.inner.lock().map( |mut g| std::mem::take( &mut g.close_requested ) ).unwrap_or( false )
	}
}

pub( crate ) unsafe fn connect( webview: *mut WebKitWebView, inner: Weak<Mutex<Inner>> )
{
	// SAFETY: each handler matches its signal's signature; each closure owns its boxed `Weak`.
	unsafe
	{
		let create = std::mem::transmute::<CreateFn, unsafe extern "C" fn()>( on_create );
		let close  = std::mem::transmute::<CloseFn,  unsafe extern "C" fn()>( on_close );
		for ( signal, handler ) in [ ( &b"create\0"[ .. ], create ), ( &b"close\0"[ .. ], close ) ]
		{
			let data = Box::into_raw( Box::new( inner.clone() ) ) as gpointer;
			g_signal_connect_data( webview as *mut GObject, signal.as_ptr() as *const c_char, Some( handler ), data, Some( drop_weak ), 0 );
		}
	}
}

pub( crate ) unsafe extern "C" fn drop_weak( data: gpointer, _closure: *mut GClosure )
{
	if data.is_null() { return; }
	// SAFETY: produced by `Box::into_raw` in `connect`, reclaimed exactly once.
	drop( unsafe { Box::from_raw( data as *mut Weak<Mutex<Inner>> ) } );
}

/// WebKit keeps no reference on the view it gets back; the queued `WebView` owns it.
unsafe extern "C" fn on_create( _webview: *mut WebKitWebView, _action: *mut WebKitNavigationAction, user_data: gpointer ) -> *mut WebKitWebView
{
	// SAFETY: `user_data` is the boxed `Weak` owned by the closure.
	let Some( inner ) = unsafe { &*( user_data as *const Weak<Mutex<Inner>> ) }.upgrade() else { return ptr::null_mut() };
	let ( opener, width, height, scale ) = match inner.lock()
	{
		Ok( g )  => ( Opener { display: g.display, webview: g.webview }, g.width, g.height, g.scale ),
		Err( _ ) => return ptr::null_mut(),
	};
	// The lock is released: building a view runs WebKit code that can re-enter.
	let Ok( created ) = WebView::build( width, height, Some( opener ) ) else { return ptr::null_mut() };
	created.set_scale( scale );
	let raw = created.inner.lock().map( |g| g.webview ).unwrap_or( ptr::null_mut() );
	if let Ok( mut g ) = inner.lock() { g.created_views.push( created ); }
	raw
}

unsafe extern "C" fn on_close( _webview: *mut WebKitWebView, user_data: gpointer )
{
	// SAFETY: `user_data` is the boxed `Weak` owned by the closure.
	let Some( inner ) = unsafe { &*( user_data as *const Weak<Mutex<Inner>> ) }.upgrade() else { return };
	if let Ok( mut g ) = inner.lock() { g.close_requested = true; };
}