ltk_webkit/wpe/
policy.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
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! `decide-policy` handling: new-window navigations are handed to the host
//! as plain URIs, and responses WebKit cannot display become downloads.

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

use glib_sys::*;
use gobject_sys::*;
use wpe_webkit_sys::*;

use super::Inner;

type DecidePolicyFn = unsafe extern "C" fn( *mut WebKitWebView, *mut WebKitPolicyDecision, WebKitPolicyDecisionType, gpointer ) -> gboolean;

/// Connect the policy handler to `webview`. The `Weak` rides as signal user
/// data and is dropped by GObject when the handler is disconnected.
pub( crate ) unsafe fn connect( webview: *mut WebKitWebView, inner: Weak<Mutex<Inner>> )
{
	let data = Box::into_raw( Box::new( inner ) ) as gpointer;
	// SAFETY: GObject invokes the handler with the `decide-policy` signature,
	// which is what `DecidePolicyFn` spells out.
	unsafe
	{
		let handler = std::mem::transmute::<DecidePolicyFn, unsafe extern "C" fn()>( on_decide_policy );
		g_signal_connect_data(
			webview as *mut GObject,
			b"decide-policy\0".as_ptr() as *const c_char,
			Some( handler ),
			data,
			Some( drop_weak ),
			0,
		);
	}
}

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>> ) } );
}

unsafe extern "C" fn on_decide_policy(
	_webview:  *mut WebKitWebView,
	decision:  *mut WebKitPolicyDecision,
	kind:      WebKitPolicyDecisionType,
	user_data: gpointer,
) -> gboolean
{
	if decision.is_null() { return 0; }
	match kind
	{
		WEBKIT_POLICY_DECISION_TYPE_NAVIGATION_ACTION =>
		{
			// 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 0 };
			// SAFETY: the decision is alive for the signal.
			unsafe { super::navigation::decide( decision, &inner ) }
		}
		WEBKIT_POLICY_DECISION_TYPE_NEW_WINDOW_ACTION =>
		{
			// SAFETY: for this decision type WebKit passes a navigation decision.
			let uri = unsafe { navigation_uri( decision as *mut WebKitNavigationPolicyDecision ) };
			if let Some( uri ) = uri
			{
				// SAFETY: `user_data` is the boxed `Weak` owned by the closure.
				let inner = unsafe { &*( user_data as *const Weak<Mutex<Inner>> ) }.upgrade();
				if let Some( inner ) = inner
				{
					if let Ok( mut g ) = inner.lock() { g.new_window_requests.push( uri ); }
				}
			}
			unsafe { webkit_policy_decision_ignore( decision ); }
			1
		}
		WEBKIT_POLICY_DECISION_TYPE_RESPONSE =>
		{
			let response = decision as *mut WebKitResponsePolicyDecision;
			// SAFETY: for this decision type WebKit passes a response decision.
			if unsafe { webkit_response_policy_decision_is_mime_type_supported( response ) } != 0
			{
				return 0;
			}
			unsafe { webkit_policy_decision_download( decision ); }
			1
		}
		_ => 0,
	}
}

unsafe fn navigation_uri( decision: *mut WebKitNavigationPolicyDecision ) -> Option<String>
{
	// SAFETY: the caller passes a live navigation decision; every accessor
	// below returns a pointer owned by it.
	unsafe
	{
		let action = webkit_navigation_policy_decision_get_navigation_action( decision );
		if action.is_null() { return None; }
		let request = webkit_navigation_action_get_request( action );
		if request.is_null() { return None; }
		let uri = webkit_uri_request_get_uri( request );
		if uri.is_null() { return None; }
		Some( CStr::from_ptr( uri ).to_string_lossy().into_owned() )
	}
}