Skip to main content

leyline/profile/
browser.rs

1use crate::profile::{BrowserProfile, ChromiumBrand, Platform, PlatformIdentity};
2use crate::{Error, Kind};
3
4mod digest;
5
6pub(crate) use digest::digest;
7
8include!(concat!(env!("OUT_DIR"), "/browser.rs"));
9
10struct Entry {
11    family: Family,
12    key: &'static str,
13    version: u32,
14    id: &'static str,
15    name: &'static str,
16    hello: Browser,
17    platforms: &'static [(&'static str, Browser)],
18    digest: u64,
19    source: &'static str,
20}
21
22impl Family {
23    #[must_use]
24    pub fn all() -> &'static [Family] {
25        FAMILY_ALL
26    }
27
28    #[must_use]
29    pub fn id(&self) -> &'static str {
30        FAMILY_IDS[*self as usize]
31    }
32
33    pub(crate) fn samesite_checks_redirect_chain(self) -> bool {
34        FAMILY_SAMESITE_REDIRECT_CHAIN[self as usize]
35    }
36}
37
38impl std::fmt::Display for Family {
39    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
40        f.write_str(FAMILY_LABELS[*self as usize])
41    }
42}
43
44impl std::str::FromStr for Family {
45    type Err = Error;
46
47    fn from_str(id: &str) -> Result<Self, Self::Err> {
48        FAMILY_ALL
49            .iter()
50            .copied()
51            .find(|family| family.id().eq_ignore_ascii_case(id))
52            .ok_or_else(|| unknown("family", id, FAMILY_IDS.iter().copied()))
53    }
54}
55
56string_id_serde!(Family);
57
58impl std::str::FromStr for Browser {
59    type Err = Error;
60
61    fn from_str(id: &str) -> Result<Self, Self::Err> {
62        ALL.iter()
63            .copied()
64            .find(|browser| browser.id().eq_ignore_ascii_case(id))
65            .ok_or_else(|| unknown("browser", id, ALL.iter().map(Browser::id)))
66    }
67}
68
69string_id_serde!(Browser);
70
71pub(crate) fn unknown<'a>(kind: &str, id: &str, known: impl Iterator<Item = &'a str>) -> Error {
72    Error::new(Kind::Config).with_message(format!(
73        "unknown {kind} {id:?}; expected one of {}",
74        known.collect::<Vec<_>>().join(", ")
75    ))
76}
77
78impl Browser {
79    pub fn all() -> &'static [Browser] {
80        ALL
81    }
82
83    #[must_use]
84    pub fn id(&self) -> &'static str {
85        self.entry().id
86    }
87
88    pub(crate) fn profile_digest(self) -> u64 {
89        self.entry().digest
90    }
91
92    fn entry(self) -> &'static Entry {
93        &ENTRIES[self as usize]
94    }
95
96    pub(crate) fn profile_key(&self) -> (&'static str, u32) {
97        let entry = self.entry();
98        (entry.key, entry.version)
99    }
100
101    #[must_use]
102    pub fn get(family: Family, version: u32) -> Option<Self> {
103        ALL.iter().copied().find(|browser| {
104            let entry = browser.entry();
105            entry.family == family && entry.version == version
106        })
107    }
108
109    #[must_use]
110    pub fn latest(family: Family) -> Self {
111        FAMILY_LATEST[family as usize]
112    }
113
114    #[must_use]
115    pub fn family(&self) -> Family {
116        self.entry().family
117    }
118
119    #[must_use]
120    pub fn version(&self) -> u32 {
121        self.entry().version
122    }
123
124    #[must_use]
125    pub(crate) fn hello_rep(self) -> Self {
126        self.entry().hello
127    }
128
129    #[must_use]
130    pub fn for_platform(self, platform: Platform) -> Self {
131        let key = platform.resolve().identity_key();
132        self.entry()
133            .platforms
134            .iter()
135            .find(|(name, _)| *name == key)
136            .map_or(self, |&(_, browser)| browser)
137    }
138
139    #[must_use]
140    pub fn identity(
141        self,
142        platform: Platform,
143        brand: Option<ChromiumBrand>,
144    ) -> Option<PlatformIdentity> {
145        self.platform_profile(platform)
146            .resolve_identity(platform, brand.unwrap_or_default())
147            .ok()
148    }
149
150    pub(crate) fn platform_profile(self, platform: Platform) -> &'static BrowserProfile {
151        self.for_platform(platform).profile()
152    }
153
154    pub(crate) fn profile_source(self) -> &'static str {
155        self.entry().source
156    }
157
158    pub(crate) fn matching_source(text: &str) -> Option<Self> {
159        let wanted = digest(&[text.as_bytes()]);
160        ALL.iter()
161            .copied()
162            .find(|browser| digest(&[browser.profile_source().as_bytes()]) == wanted)
163    }
164
165    pub(crate) fn default_browser() -> Self {
166        Self::latest(DEFAULT_FAMILY)
167    }
168
169    #[cfg(feature = "bench-internals")]
170    pub fn chromium_major(&self) -> Option<u32> {
171        self.profile().meta.chromium_major
172    }
173}
174
175impl Default for Browser {
176    fn default() -> Self {
177        Self::default_browser()
178    }
179}
180
181impl std::fmt::Display for Browser {
182    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
183        f.write_str(self.entry().name)
184    }
185}
186
187#[cfg(test)]
188mod tests;