at main 108 lines 3.1 kB view raw
1//! Platform detection for browser-specific workarounds. 2//! 3//! Based on patterns from ProseMirror's input handling, adapted for Rust/wasm. 4 5use std::sync::OnceLock; 6 7/// Cached platform detection results. 8#[derive(Debug, Clone)] 9pub struct Platform { 10 pub ios: bool, 11 pub mac: bool, 12 pub android: bool, 13 pub chrome: bool, 14 pub safari: bool, 15 pub gecko: bool, 16 pub webkit_version: Option<u32>, 17 pub chrome_version: Option<u32>, 18 pub mobile: bool, 19} 20 21impl Default for Platform { 22 fn default() -> Self { 23 Self { 24 ios: false, 25 mac: false, 26 android: false, 27 chrome: false, 28 safari: false, 29 gecko: false, 30 webkit_version: None, 31 chrome_version: None, 32 mobile: false, 33 } 34 } 35} 36 37static PLATFORM: OnceLock<Platform> = OnceLock::new(); 38 39/// Get cached platform info. Detection runs once on first call. 40pub fn platform() -> &'static Platform { 41 PLATFORM.get_or_init(detect_platform) 42} 43 44fn detect_platform() -> Platform { 45 let window = match web_sys::window() { 46 Some(w) => w, 47 None => return Platform::default(), 48 }; 49 50 let navigator = window.navigator(); 51 let user_agent = navigator.user_agent().unwrap_or_default().to_lowercase(); 52 let platform_str = navigator.platform().unwrap_or_default().to_lowercase(); 53 54 // iOS detection: iPhone/iPad/iPod in UA, or Mac platform with touch. 55 let ios = user_agent.contains("iphone") 56 || user_agent.contains("ipad") 57 || user_agent.contains("ipod") 58 || (platform_str.contains("mac") && has_touch_support(&navigator)); 59 60 // macOS (but not iOS). 61 let mac = platform_str.contains("mac") && !ios; 62 63 // Android. 64 let android = user_agent.contains("android"); 65 66 // Chrome (but not Edge, which also contains Chrome). 67 let chrome = user_agent.contains("chrome") && !user_agent.contains("edg"); 68 69 // Safari (WebKit but not Chrome). 70 let safari = user_agent.contains("safari") && !user_agent.contains("chrome"); 71 72 // Firefox/Gecko. 73 let gecko = user_agent.contains("gecko/") && !user_agent.contains("like gecko"); 74 75 // WebKit version extraction. 76 let webkit_version = extract_version(&user_agent, "applewebkit/"); 77 78 // Chrome version extraction. 79 let chrome_version = extract_version(&user_agent, "chrome/"); 80 81 // Mobile detection. 82 let mobile = 83 ios || android || user_agent.contains("mobile") || user_agent.contains("iemobile"); 84 85 Platform { 86 ios, 87 mac, 88 android, 89 chrome, 90 safari, 91 gecko, 92 webkit_version, 93 chrome_version, 94 mobile, 95 } 96} 97 98fn has_touch_support(navigator: &web_sys::Navigator) -> bool { 99 navigator.max_touch_points() > 0 100} 101 102fn extract_version(ua: &str, prefix: &str) -> Option<u32> { 103 ua.find(prefix).and_then(|idx| { 104 let after = &ua[idx + prefix.len()..]; 105 let version_str: String = after.chars().take_while(|c| c.is_ascii_digit()).collect(); 106 version_str.parse().ok() 107 }) 108}