use std::collections::HashMap;
use once_cell::sync::Lazy;
use regex::Regex;
use relay_event_schema::protocol::{
BrowserContext, Context, Cookies, OsContext, ResponseContext, RuntimeContext,
};
use relay_protocol::{Annotated, Empty, Value};
static OS_WINDOWS_REGEX1: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^(Microsoft )?Windows (NT )?(?P<version>\d+\.\d+\.(?P<build_number>\d+)).*$")
.unwrap()
});
static OS_WINDOWS_REGEX2: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^Windows\s+\d+\s+\((?P<version>\d+\.\d+\.(?P<build_number>\d+)).*$").unwrap()
});
static OS_ANDROID_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^Android (OS )?(?P<version>\d+(\.\d+){0,2}) / API-(?P<api>(\d+))").unwrap()
});
static OS_MACOS_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^Mac OS X (?P<version>\d+\.\d+\.\d+)( \((?P<build>[a-fA-F0-9]+)\))?$").unwrap()
});
static OS_IOS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^iOS (?P<version>\d+\.\d+\.\d+)").unwrap());
static OS_IPADOS_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^iPadOS (?P<version>\d+\.\d+\.\d+)").unwrap());
static OS_LINUX_DISTRO_UNAME_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^Linux (?P<kernel_version>\d+\.\d+(\.\d+(\.[1-9]+)?)?) (?P<name>[a-zA-Z]+) (?P<version>\d+(\.\d+){0,2})").unwrap()
});
static OS_UNAME_REGEX: Lazy<Regex> = Lazy::new(|| {
Regex::new(r"^(?P<name>[a-zA-Z]+) (?P<kernel_version>\d+\.\d+(\.\d+(\.[1-9]+)?)?)").unwrap()
});
static RUNTIME_DOTNET_REGEX: Lazy<Regex> =
Lazy::new(|| Regex::new(r"^(?P<name>.*) (?P<version>\d+\.\d+(\.\d+){0,2}).*$").unwrap());
static ANDROID_MODEL_NAMES: Lazy<HashMap<&'static str, &'static str>> = Lazy::new(|| {
let mut map = HashMap::new();
let android_str = include_str!("android_models.csv");
let mut lines = android_str.lines();
let header = lines.next().expect("CSV file should have a header");
let header_fields: Vec<&str> = header.split(',').collect();
let model_index = header_fields.iter().position(|&s| s.trim() == "Model");
let product_name_index = header_fields
.iter()
.position(|&s| s.trim() == "Marketing Name");
let (model_index, product_name_index) = match (model_index, product_name_index) {
(Some(model_index), Some(product_name_index)) => (model_index, product_name_index),
(_, _) => {
relay_log::error!(
"failed to find model and/or marketing name headers for android-model map",
);
return HashMap::new();
}
};
for line in lines {
let fields: Vec<&str> = line.split(',').collect();
if fields.len() > std::cmp::max(model_index, product_name_index) {
map.insert(
fields[model_index].trim(),
fields[product_name_index].trim(),
);
}
}
map
});
fn normalize_runtime_context(runtime: &mut RuntimeContext) {
if runtime.name.value().is_empty() && runtime.version.value().is_empty() {
if let Some(raw_description) = runtime.raw_description.as_str() {
if let Some(captures) = RUNTIME_DOTNET_REGEX.captures(raw_description) {
runtime.name = captures.name("name").map(|m| m.as_str().to_string()).into();
runtime.version = captures
.name("version")
.map(|m| m.as_str().to_string())
.into();
}
}
}
if let Some(name) = runtime.name.as_str() {
if let Some(build) = runtime.build.as_str() {
if name.starts_with(".NET Framework") {
let version = match build {
"378389" => Some("4.5".to_string()),
"378675" => Some("4.5.1".to_string()),
"378758" => Some("4.5.1".to_string()),
"379893" => Some("4.5.2".to_string()),
"393295" => Some("4.6".to_string()),
"393297" => Some("4.6".to_string()),
"394254" => Some("4.6.1".to_string()),
"394271" => Some("4.6.1".to_string()),
"394802" => Some("4.6.2".to_string()),
"394806" => Some("4.6.2".to_string()),
"460798" => Some("4.7".to_string()),
"460805" => Some("4.7".to_string()),
"461308" => Some("4.7.1".to_string()),
"461310" => Some("4.7.1".to_string()),
"461808" => Some("4.7.2".to_string()),
"461814" => Some("4.7.2".to_string()),
"528040" => Some("4.8".to_string()),
"528049" => Some("4.8".to_string()),
"528209" => Some("4.8".to_string()),
"528372" => Some("4.8".to_string()),
"528449" => Some("4.8".to_string()),
_ => None,
};
if let Some(version) = version {
runtime.version = version.into();
}
}
}
}
if runtime.runtime.value().is_none() {
if let (Some(name), Some(version)) = (runtime.name.value(), runtime.version.value()) {
runtime.runtime = Annotated::from(format!("{} {}", name, version));
}
}
}
fn get_windows_version(description: &str) -> Option<(&str, &str)> {
let captures = OS_WINDOWS_REGEX1
.captures(description)
.or_else(|| OS_WINDOWS_REGEX2.captures(description))?;
let full_version = captures.name("version")?.as_str();
let build_number_str = captures.name("build_number")?.as_str();
let build_number = build_number_str.parse::<u64>().ok()?;
let version_name = match build_number {
2600..=3790 => "XP",
6002 => "Vista",
7601 => "7",
9200 => "8",
9600 => "8.1",
10240..=19044 => "10",
22000..=22999 => "11",
_ => full_version,
};
Some((version_name, build_number_str))
}
#[allow(dead_code)]
pub fn get_android_api_version(description: &str) -> Option<&str> {
if let Some(captures) = OS_ANDROID_REGEX.captures(description) {
captures.name("api").map(|m| m.as_str())
} else {
None
}
}
fn normalize_os_context(os: &mut OsContext) {
if os.name.value().is_some() || os.version.value().is_some() {
compute_os_context(os);
return;
}
if let Some(raw_description) = os.raw_description.as_str() {
if let Some((version, build_number)) = get_windows_version(raw_description) {
os.name = "Windows".to_string().into();
os.version = version.to_string().into();
if os.build.is_empty() {
os.build.set_value(Some(build_number.to_string().into()));
}
} else if let Some(captures) = OS_MACOS_REGEX.captures(raw_description) {
os.name = "macOS".to_string().into();
os.version = captures
.name("version")
.map(|m| m.as_str().to_string())
.into();
os.build = captures
.name("build")
.map(|m| m.as_str().to_string().into())
.into();
} else if let Some(captures) = OS_IOS_REGEX.captures(raw_description) {
os.name = "iOS".to_string().into();
os.version = captures
.name("version")
.map(|m| m.as_str().to_string())
.into();
os.build = captures
.name("build")
.map(|m| m.as_str().to_string().into())
.into();
} else if let Some(captures) = OS_IPADOS_REGEX.captures(raw_description) {
os.name = "iPadOS".to_string().into();
os.version = captures
.name("version")
.map(|m| m.as_str().to_string())
.into();
os.build = captures
.name("build")
.map(|m| m.as_str().to_string().into())
.into();
} else if let Some(captures) = OS_LINUX_DISTRO_UNAME_REGEX.captures(raw_description) {
os.name = captures.name("name").map(|m| m.as_str().to_string()).into();
os.version = captures
.name("version")
.map(|m| m.as_str().to_string())
.into();
os.kernel_version = captures
.name("kernel_version")
.map(|m| m.as_str().to_string())
.into();
} else if let Some(captures) = OS_UNAME_REGEX.captures(raw_description) {
os.name = captures.name("name").map(|m| m.as_str().to_string()).into();
os.kernel_version = captures
.name("kernel_version")
.map(|m| m.as_str().to_string())
.into();
} else if let Some(captures) = OS_ANDROID_REGEX.captures(raw_description) {
os.name = "Android".to_string().into();
os.version = captures
.name("version")
.map(|m| m.as_str().to_string())
.into();
}
}
compute_os_context(os);
}
fn compute_os_context(os: &mut OsContext) {
if os.os.value().is_none() {
if let (Some(name), Some(version)) = (os.name.value(), os.version.value()) {
os.os = Annotated::from(format!("{} {}", name, version));
}
}
}
fn normalize_browser_context(browser: &mut BrowserContext) {
if browser.browser.value().is_none() {
if let (Some(name), Some(version)) = (browser.name.value(), browser.version.value()) {
browser.browser = Annotated::from(format!("{} {}", name, version));
}
}
}
fn parse_raw_response_data(response: &ResponseContext) -> Option<(&'static str, Value)> {
let raw = response.data.as_str()?;
serde_json::from_str(raw)
.ok()
.map(|value| ("application/json", value))
}
fn normalize_response_data(response: &mut ResponseContext) {
if let Some((content_type, parsed_data)) = parse_raw_response_data(response) {
response.data.set_value(Some(parsed_data));
response.inferred_content_type = Annotated::from(content_type.to_string());
} else {
response.inferred_content_type = response
.headers
.value()
.and_then(|headers| headers.get_header("Content-Type"))
.map(|value| value.split(';').next().unwrap_or(value).to_string())
.into();
}
}
fn normalize_response(response: &mut ResponseContext) {
normalize_response_data(response);
let headers = match response.headers.value_mut() {
Some(headers) => headers,
None => return,
};
if response.cookies.value().is_some() {
headers.remove("Set-Cookie");
return;
}
let cookie_header = match headers.get_header("Set-Cookie") {
Some(header) => header,
None => return,
};
if let Ok(new_cookies) = Cookies::parse(cookie_header) {
response.cookies = Annotated::from(new_cookies);
headers.remove("Set-Cookie");
}
}
pub fn normalize_context(context: &mut Context) {
match context {
Context::Runtime(runtime) => normalize_runtime_context(runtime),
Context::Os(os) => normalize_os_context(os),
Context::Browser(browser) => normalize_browser_context(browser),
Context::Response(response) => normalize_response(response),
Context::Device(device) => {
if let Some(product_name) = device
.as_ref()
.model
.value()
.and_then(|model| ANDROID_MODEL_NAMES.get(model.as_str()))
{
device.name.set_value(Some(product_name.to_string()))
}
}
_ => {}
}
}
#[cfg(test)]
mod tests {
use relay_event_schema::protocol::{Headers, LenientString, PairList};
use relay_protocol::Object;
use similar_asserts::assert_eq;
use super::*;
#[test]
fn test_get_product_name() {
assert_eq!(
ANDROID_MODEL_NAMES.get("NE2211").unwrap(),
&"OnePlus 10 Pro 5G"
);
assert_eq!(
ANDROID_MODEL_NAMES.get("MP04").unwrap(),
&"A13 Pro Max 5G EEA"
);
assert_eq!(ANDROID_MODEL_NAMES.get("ZT216_7").unwrap(), &"zyrex");
assert!(ANDROID_MODEL_NAMES.get("foobar").is_none());
}
#[test]
fn test_dotnet_framework_48_without_build_id() {
let mut runtime = RuntimeContext {
raw_description: ".NET Framework 4.8.4250.0".to_string().into(),
..RuntimeContext::default()
};
normalize_runtime_context(&mut runtime);
assert_eq!(Some(".NET Framework"), runtime.name.as_str());
assert_eq!(Some("4.8.4250.0"), runtime.version.as_str());
}
#[test]
fn test_dotnet_framework_472() {
let mut runtime = RuntimeContext {
raw_description: ".NET Framework 4.7.3056.0".to_string().into(),
build: LenientString("461814".to_string()).into(),
..RuntimeContext::default()
};
normalize_runtime_context(&mut runtime);
assert_eq!(Some(".NET Framework"), runtime.name.as_str());
assert_eq!(Some("4.7.2"), runtime.version.as_str());
}
#[test]
fn test_dotnet_framework_future_version() {
let mut runtime = RuntimeContext {
raw_description: ".NET Framework 200.0".to_string().into(),
build: LenientString("999999".to_string()).into(),
..RuntimeContext::default()
};
normalize_runtime_context(&mut runtime);
assert_eq!(Some(".NET Framework"), runtime.name.as_str());
assert_eq!(Some("200.0"), runtime.version.as_str());
}
#[test]
fn test_dotnet_native() {
let mut runtime = RuntimeContext {
raw_description: ".NET Native 2.0".to_string().into(),
..RuntimeContext::default()
};
normalize_runtime_context(&mut runtime);
assert_eq!(Some(".NET Native"), runtime.name.as_str());
assert_eq!(Some("2.0"), runtime.version.as_str());
}
#[test]
fn test_dotnet_core() {
let mut runtime = RuntimeContext {
raw_description: ".NET Core 2.0".to_string().into(),
..RuntimeContext::default()
};
normalize_runtime_context(&mut runtime);
assert_eq!(Some(".NET Core"), runtime.name.as_str());
assert_eq!(Some("2.0"), runtime.version.as_str());
}
#[test]
fn test_windows_7_or_server_2008() {
let mut os = OsContext {
raw_description: "Microsoft Windows NT 6.1.7601 Service Pack 1"
.to_string()
.into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows"), os.name.as_str());
assert_eq!(Some("7"), os.version.as_str());
}
#[test]
fn test_windows_8_or_server_2012_or_later() {
let mut os = OsContext {
raw_description: "Microsoft Windows NT 6.2.9200.0".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows"), os.name.as_str());
assert_eq!(Some("8"), os.version.as_str());
}
#[test]
fn test_windows_10() {
let mut os = OsContext {
raw_description: "Microsoft Windows 10.0.16299".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows"), os.name.as_str());
assert_eq!(Some("10"), os.version.as_str());
}
#[test]
fn test_windows_11() {
let mut os = OsContext {
raw_description: "Microsoft Windows 10.0.22000".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows"), os.name.as_str());
assert_eq!(Some("11"), os.version.as_str());
}
#[test]
fn test_windows_11_future1() {
let mut os = OsContext {
raw_description: "Microsoft Windows 10.0.22001".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows"), os.name.as_str());
assert_eq!(Some("11"), os.version.as_str());
}
#[test]
fn test_windows_11_future2() {
let mut os = OsContext {
raw_description: "Microsoft Windows 10.1.23456".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows"), os.name.as_str());
assert_eq!(Some("10.1.23456"), os.version.as_str());
}
#[test]
fn test_macos_os_version() {
let mut os = OsContext {
raw_description: "Unix 17.5.0.0".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Unix"), os.name.as_str());
assert_eq!(Some("17.5.0"), os.kernel_version.as_str());
}
#[test]
fn test_macos_runtime() {
let mut os = OsContext {
raw_description: "Darwin 17.5.0 Darwin Kernel Version 17.5.0: Mon Mar 5 22:24:32 PST 2018; root:xnu-4570.51.1~1/RELEASE_X86_64".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Darwin"), os.name.as_str());
assert_eq!(Some("17.5.0"), os.kernel_version.as_str());
}
#[test]
fn test_centos_os_version() {
let mut os = OsContext {
raw_description: "Unix 3.10.0.693".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Unix"), os.name.as_str());
assert_eq!(Some("3.10.0.693"), os.kernel_version.as_str());
}
#[test]
fn test_centos_runtime_info() {
let mut os = OsContext {
raw_description: "Linux 3.10.0-693.21.1.el7.x86_64 #1 SMP Wed Mar 7 19:03:37 UTC 2018"
.to_string()
.into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Linux"), os.name.as_str());
assert_eq!(Some("3.10.0"), os.kernel_version.as_str());
}
#[test]
fn test_wsl_ubuntu() {
let mut os = OsContext {
raw_description: "Linux 4.4.0-43-Microsoft #1-Microsoft Wed Dec 31 14:42:53 PST 2014"
.to_string()
.into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Linux"), os.name.as_str());
assert_eq!(Some("4.4.0"), os.kernel_version.as_str());
}
#[test]
fn test_macos_with_build() {
let mut os = OsContext {
raw_description: "Mac OS X 10.14.2 (18C54)".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("macOS"), os.name.as_str());
assert_eq!(Some("10.14.2"), os.version.as_str());
assert_eq!(Some("18C54"), os.build.as_str());
}
#[test]
fn test_macos_without_build() {
let mut os = OsContext {
raw_description: "Mac OS X 10.14.2".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("macOS"), os.name.as_str());
assert_eq!(Some("10.14.2"), os.version.as_str());
assert_eq!(None, os.build.value());
}
#[test]
fn test_name_not_overwritten() {
let mut os = OsContext {
name: "Properly defined name".to_string().into(),
raw_description: "Linux 4.4.0".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Properly defined name"), os.name.as_str());
}
#[test]
fn test_version_not_overwritten() {
let mut os = OsContext {
version: "Properly defined version".to_string().into(),
raw_description: "Linux 4.4.0".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Properly defined version"), os.version.as_str());
}
#[test]
fn test_no_name() {
let mut os = OsContext::default();
normalize_os_context(&mut os);
assert_eq!(None, os.name.value());
assert_eq!(None, os.version.value());
assert_eq!(None, os.kernel_version.value());
assert_eq!(None, os.raw_description.value());
}
#[test]
fn test_unity_mac_os() {
let mut os = OsContext {
raw_description: "Mac OS X 10.16.0".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("macOS"), os.name.as_str());
assert_eq!(Some("10.16.0"), os.version.as_str());
assert_eq!(None, os.build.value());
}
#[test]
fn test_unity_ios() {
let mut os = OsContext {
raw_description: "iOS 17.5.1".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("iOS"), os.name.as_str());
assert_eq!(Some("17.5.1"), os.version.as_str());
assert_eq!(None, os.build.value());
}
#[test]
fn test_unity_ipados() {
let mut os = OsContext {
raw_description: "iPadOS 17.5.1".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("iPadOS"), os.name.as_str());
assert_eq!(Some("17.5.1"), os.version.as_str());
assert_eq!(None, os.build.value());
}
#[test]
fn test_unity_windows_os() {
let mut os = OsContext {
raw_description: "Windows 10 (10.0.19042) 64bit".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows"), os.name.as_str());
assert_eq!(Some("10"), os.version.as_str());
assert_eq!(Some(&LenientString("19042".to_string())), os.build.value());
}
#[test]
fn test_unity_android_os() {
let mut os = OsContext {
raw_description: "Android OS 11 / API-30 (RP1A.201005.001/2107031736)"
.to_string()
.into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Android"), os.name.as_str());
assert_eq!(Some("11"), os.version.as_str());
assert_eq!(None, os.build.value());
}
#[test]
fn test_unity_android_api_version() {
let description = "Android OS 11 / API-30 (RP1A.201005.001/2107031736)";
assert_eq!(Some("30"), get_android_api_version(description));
}
#[test]
fn test_linux_5_11() {
let mut os = OsContext {
raw_description: "Linux 5.11 Ubuntu 20.04 64bit".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Ubuntu"), os.name.as_str());
assert_eq!(Some("20.04"), os.version.as_str());
assert_eq!(Some("5.11"), os.kernel_version.as_str());
assert_eq!(None, os.build.value());
}
#[test]
fn test_android_4_4_2() {
let mut os = OsContext {
raw_description: "Android OS 4.4.2 / API-19 (KOT49H/A536_S186_150813_ROW)"
.to_string()
.into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Android"), os.name.as_str());
assert_eq!(Some("4.4.2"), os.version.as_str());
assert_eq!(None, os.build.value());
}
#[test]
fn test_infer_json() {
let mut response = ResponseContext {
data: Annotated::from(Value::String(r#"{"foo":"bar"}"#.to_string())),
..ResponseContext::default()
};
let mut expected_value = Object::new();
expected_value.insert(
"foo".to_string(),
Annotated::from(Value::String("bar".into())),
);
normalize_response(&mut response);
assert_eq!(
response.inferred_content_type.as_str(),
Some("application/json")
);
assert_eq!(response.data.value(), Some(&Value::Object(expected_value)));
}
#[test]
fn test_broken_json_with_fallback() {
let mut response = ResponseContext {
data: Annotated::from(Value::String(r#"{"foo":"b"#.to_string())),
headers: Annotated::from(Headers(PairList(vec![Annotated::new((
Annotated::new("Content-Type".to_string().into()),
Annotated::new("text/plain; encoding=utf-8".to_string().into()),
))]))),
..ResponseContext::default()
};
normalize_response(&mut response);
assert_eq!(response.inferred_content_type.as_str(), Some("text/plain"));
assert_eq!(response.data.as_str(), Some(r#"{"foo":"b"#));
}
#[test]
fn test_broken_json_without_fallback() {
let mut response = ResponseContext {
data: Annotated::from(Value::String(r#"{"foo":"b"#.to_string())),
..ResponseContext::default()
};
normalize_response(&mut response);
assert_eq!(response.inferred_content_type.value(), None);
assert_eq!(response.data.as_str(), Some(r#"{"foo":"b"#));
}
#[test]
fn test_os_computed_context() {
let mut os = OsContext {
name: "Windows".to_string().into(),
version: "10".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(Some("Windows 10"), os.os.as_str());
}
#[test]
fn test_os_computed_context_missing_version() {
let mut os = OsContext {
name: "Windows".to_string().into(),
..OsContext::default()
};
normalize_os_context(&mut os);
assert_eq!(None, os.os.value());
}
#[test]
fn test_runtime_computed_context() {
let mut runtime = RuntimeContext {
name: "Python".to_string().into(),
version: "3.9.0".to_string().into(),
..RuntimeContext::default()
};
normalize_runtime_context(&mut runtime);
assert_eq!(Some("Python 3.9.0"), runtime.runtime.as_str());
}
#[test]
fn test_runtime_computed_context_missing_version() {
let mut runtime = RuntimeContext {
name: "Python".to_string().into(),
..RuntimeContext::default()
};
normalize_runtime_context(&mut runtime);
assert_eq!(None, runtime.runtime.value());
}
#[test]
fn test_browser_computed_context() {
let mut browser = BrowserContext {
name: "Firefox".to_string().into(),
version: "89.0".to_string().into(),
..BrowserContext::default()
};
normalize_browser_context(&mut browser);
assert_eq!(Some("Firefox 89.0"), browser.browser.as_str());
}
#[test]
fn test_browser_computed_context_missing_version() {
let mut browser = BrowserContext {
name: "Firefox".to_string().into(),
..BrowserContext::default()
};
normalize_browser_context(&mut browser);
assert_eq!(None, browser.browser.value());
}
}