ltk_webkit/wpe/
features.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
// SPDX-License-Identifier: LGPL-2.1-only
// Copyright (C) 2026 Liberux Labs, S. L. <info@liberux.net>

//! WebKit's runtime features, such as WebGPU, turned on or off for every view created afterwards.

use std::ffi::CStr;
use std::sync::Mutex;

use wpe_webkit_sys::*;

static CHOSEN: Mutex<Vec<( String, bool )>> = Mutex::new( Vec::new() );

/// Turn the feature named `identifier` (as `webkit_feature_get_identifier` gives it) on or off in the views created from now on.
/// An identifier this WebKit does not know is reported once on stderr, with the ones that resemble it.
pub fn set_feature( identifier: &str, enabled: bool )
{
	let Ok( mut chosen ) = CHOSEN.lock() else { return };
	chosen.retain( |( id, _ )| id != identifier );
	chosen.push( ( identifier.to_string(), enabled ) );
}

/// Apply the chosen features to a view just built, before its first load.
pub( crate ) unsafe fn apply( webview: *mut WebKitWebView )
{
	let Ok( mut chosen ) = CHOSEN.lock() else { return };
	if chosen.is_empty() { return; }
	let mut unknown = Vec::new();
	// SAFETY: the caller passes a live view; the feature list is ours until unref'd, its features live as long as it does.
	unsafe
	{
		let settings = webkit_web_view_get_settings( webview );
		let list     = webkit_settings_get_all_features();
		if settings.is_null() || list.is_null() { return; }
		let features: Vec<*mut WebKitFeature> = ( 0..webkit_feature_list_get_length( list ) ).map( |i| webkit_feature_list_get( list, i ) ).collect();
		for ( identifier, enabled ) in chosen.iter()
		{
			match features.iter().find( |&&f| feature_id( f ) == *identifier )
			{
				Some( &feature ) => webkit_settings_set_feature_enabled( settings, feature, *enabled as glib_sys::gboolean ),
				None             =>
				{
					report_unknown( identifier, &features );
					unknown.push( identifier.clone() );
				}
			}
		}
		webkit_feature_list_unref( list );
	}
	chosen.retain( |( id, _ )| !unknown.contains( id ) );
}

unsafe fn feature_id( feature: *mut WebKitFeature ) -> String
{
	// SAFETY: a feature of a live list; WebKit owns the string.
	let id = unsafe { webkit_feature_get_identifier( feature ) };
	if id.is_null() { String::new() } else { unsafe { CStr::from_ptr( id ) }.to_string_lossy().into_owned() }
}

unsafe fn report_unknown( identifier: &str, features: &[ *mut WebKitFeature ] )
{
	let wanted  = identifier.trim_end_matches( "Enabled" ).to_lowercase();
	// SAFETY: features of a live list.
	let similar: Vec<String> = features.iter()
		.map( |&f| ( unsafe { feature_id( f ) }, unsafe { webkit_feature_get_status( f ) } ) )
		.filter( |( id, _ )| id.to_lowercase().contains( &wanted ) )
		.map( |( id, status )| format!( "{id} ({})", status_name( status ) ) )
		.collect();
	eprintln!( "ltk-webkit: no WebKit feature {identifier}; similar: {}", if similar.is_empty() { "none".to_string() } else { similar.join( ", " ) } );
}

fn status_name( status: WebKitFeatureStatus ) -> &'static str
{
	match status
	{
		WEBKIT_FEATURE_STATUS_EMBEDDER  => "embedder",
		WEBKIT_FEATURE_STATUS_UNSTABLE  => "unstable",
		WEBKIT_FEATURE_STATUS_INTERNAL  => "internal",
		WEBKIT_FEATURE_STATUS_DEVELOPER => "developer",
		WEBKIT_FEATURE_STATUS_TESTABLE  => "testable",
		WEBKIT_FEATURE_STATUS_PREVIEW   => "preview",
		WEBKIT_FEATURE_STATUS_STABLE    => "stable",
		WEBKIT_FEATURE_STATUS_MATURE    => "mature",
		_                               => "unknown",
	}
}