1use ahash::AHashMap;
2use std::{borrow::Cow, sync::LazyLock};
3
4pub mod features;
5pub mod region;
6
7use crate::utils::{BinMap, PooledStr};
8
9pub const ANDROID_EVERGREEN_FIRST: f32 = 37.0;
10pub const OP_MOB_BLINK_FIRST: u32 = 14;
11
12#[derive(Clone, Debug)]
13pub struct BrowserStat(u32, u32);
14
15#[derive(Clone, Debug)]
16pub struct VersionDetail {
17 version: PooledStr,
18 pub release_date: i64,
19 pub released: bool,
21 pub global_usage: f32,
22}
23
24include!("generated/caniuse-browsers.rs");
25
26static CANIUSE_BROWSERS: BinMap<PooledStr, BrowserStat> = BinMap(BROWSERS_STATS);
27
28static CANIUSE_GLOBAL_USAGE: &[(PooledStr, PooledStr, f32)] =
29 include!("generated/caniuse-global-usage.rs");
30
31static BROWSER_VERSION_ALIASES: LazyLock<
32 AHashMap<&'static str, AHashMap<&'static str, &'static str>>,
33> = LazyLock::new(|| {
34 let mut aliases = CANIUSE_BROWSERS
35 .iter()
36 .filter_map(|(name, stat)| {
37 let name = name.as_str();
38 let aliases = stat
39 .version_list()
40 .iter()
41 .filter_map(|version| {
42 version
43 .version
44 .as_str()
45 .split_once('-')
46 .map(|(bottom, top)| (bottom, top, version.version))
47 })
48 .fold(
49 AHashMap::<&str, &str>::new(),
50 move |mut aliases, (bottom, top, version)| {
51 let _ = aliases.insert(bottom, version.as_str());
52 let _ = aliases.insert(top, version.as_str());
53 aliases
54 },
55 );
56 if aliases.is_empty() {
57 None
58 } else {
59 Some((name, aliases))
60 }
61 })
62 .collect::<AHashMap<&'static str, _>>();
63 let _ = aliases.insert("op_mob", {
64 let mut aliases = AHashMap::new();
65 let _ = aliases.insert("59", "58");
66 aliases
67 });
68 aliases
69});
70
71static ANDROID_TO_DESKTOP: LazyLock<Vec<VersionDetail>> = LazyLock::new(|| {
72 let chrome = CANIUSE_BROWSERS.get("chrome").unwrap();
73 let android = CANIUSE_BROWSERS.get("android").unwrap();
74
75 let chrome_point = chrome
76 .version_list()
77 .binary_search_by_key(&(ANDROID_EVERGREEN_FIRST as usize), |probe| {
78 probe.version.as_str().parse::<usize>().unwrap()
79 })
80 .unwrap();
81
82 android
83 .version_list()
84 .iter()
85 .filter(|version| {
86 let version = version.version.as_str();
87 version.starts_with("2.")
88 || version.starts_with("3.")
89 || version.starts_with("4.")
90 || version == "3"
91 || version == "4"
92 })
93 .chain(chrome.version_list().iter().skip(chrome_point))
94 .cloned()
95 .collect()
96});
97
98pub fn get_browser_stat(
99 name: &str,
100 mobile_to_desktop: bool,
101) -> Option<(&'static str, &'static [VersionDetail])> {
102 let name = if name.bytes().all(|b| b.is_ascii_lowercase()) {
103 Cow::from(name)
104 } else {
105 Cow::from(name.to_ascii_lowercase())
106 };
107 let name = get_browser_alias(&name);
108
109 if mobile_to_desktop {
110 if let Some(desktop_name) = to_desktop_name(name) {
111 match name {
112 "android" => Some(("android", &*ANDROID_TO_DESKTOP)),
113 "op_mob" => {
114 let stat = CANIUSE_BROWSERS.get("opera").unwrap();
115 Some(("op_mob", stat.version_list()))
116 }
117 _ => CANIUSE_BROWSERS.get(desktop_name).map(|stat| {
118 (
119 get_mobile_by_desktop_name(desktop_name),
120 stat.version_list(),
121 )
122 }),
123 }
124 } else {
125 CANIUSE_BROWSERS
126 .get_key_value(name)
127 .map(|(k, v)| (k.as_str(), v.version_list()))
128 }
129 } else {
130 CANIUSE_BROWSERS
131 .get_key_value(name)
132 .map(|(k, v)| (k.as_str(), v.version_list()))
133 }
134}
135
136pub fn iter_browser_stat(
137 mobile_to_desktop: bool,
138) -> impl Iterator<Item = (&'static str, &'static [VersionDetail])> {
139 CANIUSE_BROWSERS.iter().filter_map(move |(name, stat)| {
140 match (
141 mobile_to_desktop,
142 to_desktop_name(name.as_str()),
143 name.as_str(),
144 ) {
145 (false, _, _) | (true, None, _) => Some((name.as_str(), stat.version_list())),
146 (true, Some(_), "android") => Some(("android", &*ANDROID_TO_DESKTOP)),
147 (true, Some(_), "op_mob") => {
148 let stat = CANIUSE_BROWSERS.get("opera").unwrap();
149 Some(("op_mob", stat.version_list()))
150 }
151 (true, Some(desktop_name), _) => CANIUSE_BROWSERS.get(desktop_name).map(|stat| {
152 (
153 get_mobile_by_desktop_name(desktop_name),
154 stat.version_list(),
155 )
156 }),
157 }
158 })
159}
160
161pub fn iter_global_usage() -> impl ExactSizeIterator<Item = (&'static str, &'static str, f32)> {
162 CANIUSE_GLOBAL_USAGE
163 .iter()
164 .copied()
165 .map(|(name, version, usage)| (name.as_str(), version.as_str(), usage))
166}
167
168pub fn get_browser_version_alias(name: &str, version: &str) -> Option<&'static str> {
169 BROWSER_VERSION_ALIASES.get(name)?.get(version).copied()
170}
171
172fn get_browser_alias(name: &str) -> &str {
173 match name {
174 "fx" | "ff" => "firefox",
175 "ios" => "ios_saf",
176 "explorer" => "ie",
177 "blackberry" => "bb",
178 "explorermobile" => "ie_mob",
179 "operamini" => "op_mini",
180 "operamobile" => "op_mob",
181 "chromeandroid" => "and_chr",
182 "firefoxandroid" => "and_ff",
183 "ucandroid" => "and_uc",
184 "qqandroid" => "and_qq",
185 _ => name,
186 }
187}
188
189pub fn to_desktop_name(name: &str) -> Option<&'static str> {
190 match name {
191 "and_chr" | "android" => Some("chrome"),
192 "and_ff" => Some("firefox"),
193 "ie_mob" => Some("ie"),
194 _ => None,
195 }
196}
197
198fn get_mobile_by_desktop_name(name: &str) -> &'static str {
199 match name {
200 "chrome" => "and_chr", "firefox" => "and_ff",
202 "ie" => "ie_mob",
203 "opera" => "op_mob",
204 _ => unreachable!(),
205 }
206}
207
208pub fn normalize_version<'a>(
209 name: &'a str,
210 version_list: &'static [VersionDetail],
211 version: &'a str,
212) -> Option<&'a str> {
213 if version_list.iter().any(|v| v.version.as_str() == version) {
214 Some(version)
215 } else if let Some(version) = get_browser_version_alias(name, version) {
216 Some(version)
217 } else if version_list.len() == 1 {
218 version_list.first().map(|s| s.version.as_str())
219 } else {
220 None
221 }
222}
223
224impl BrowserStat {
225 pub fn version_list(&self) -> &'static [VersionDetail] {
226 let range = (self.0 as usize)..(self.1 as usize);
227 &VERSION_LIST[range]
228 }
229}
230
231impl VersionDetail {
232 pub fn version(&self) -> &'static str {
233 self.version.as_str()
234 }
235}