use super::*;
use std::cell::RefCell;
use std::collections::HashMap;
use std::rc::Rc;
use std::sync::atomic::{AtomicBool, AtomicU64, AtomicUsize, Ordering};
use std::sync::Arc;
use dpi::PhysicalSize;
use servo::{
AllowOrDenyRequest, ConsoleLogLevel, CreateNewWebViewRequest, DeviceIntPoint, DeviceIntRect,
DeviceIntSize, EmbedderControl, EmbedderControlId, InputEventId, InputEventResult, LoadStatus,
NavigationRequest, PermissionRequest, ScreenGeometry, ServoDelegate, ServoError, TraversalId,
WebView, WebViewDelegate,
};
use bao_cdp::servo_bridge::main_frame_id_for_target;
use bao_cdp::{BaoEvent, ConsoleMessage};
use bao_cdp_client::bridge::{ConsoleLevel, ServoEvent};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkerLocation {
pub href: String,
pub protocol: String,
pub host: String,
pub hostname: String,
pub port: String,
pub pathname: String,
pub search: String,
pub hash: String,
pub origin: String,
}
impl WorkerLocation {
pub fn from_url(url_str: &str) -> Option<Self> {
let parsed = url::Url::parse(url_str).ok()?;
let scheme = parsed.scheme();
let host = parsed.host_str().unwrap_or("");
let port = parsed.port();
let default_port_for_scheme = match scheme {
"http" => Some(80),
"https" => Some(443),
_ => None,
};
let is_default_port = port.map_or(true, |p| Some(p) == default_port_for_scheme);
let host_with_port = if is_default_port {
host.to_string()
} else {
format!("{}:{}", host, port.unwrap())
};
let origin = if scheme == "http" || scheme == "https" {
if is_default_port {
format!("{}://{}", scheme, host)
} else {
format!("{}://{}:{}", scheme, host, port.unwrap())
}
} else {
"null".to_string()
};
Some(WorkerLocation {
href: url_str.to_string(),
protocol: format!("{}:", scheme),
host: host_with_port,
hostname: host.to_string(),
port: port.map_or(String::new(), |p| p.to_string()),
pathname: parsed.path().to_string(),
search: parsed.query().map_or(String::new(), |q| format!("?{}", q)),
hash: parsed
.fragment()
.map_or(String::new(), |f| format!("#{}", f)),
origin,
})
}
pub fn from_url_value(url: url::Url) -> Self {
let scheme = url.scheme();
let host = url.host_str().unwrap_or("");
let port = url.port();
let default_port_for_scheme = match scheme {
"http" => Some(80),
"https" => Some(443),
_ => None,
};
let is_default_port = port.map_or(true, |p| Some(p) == default_port_for_scheme);
let host_with_port = if is_default_port {
host.to_string()
} else {
format!("{}:{}", host, port.unwrap())
};
let origin = if scheme == "http" || scheme == "https" {
if is_default_port {
format!("{}://{}", scheme, host)
} else {
format!("{}://{}:{}", scheme, host, port.unwrap())
}
} else {
"null".to_string()
};
let href = url.to_string();
WorkerLocation {
href,
protocol: format!("{}:", scheme),
host: host_with_port,
hostname: host.to_string(),
port: port.map_or(String::new(), |p| p.to_string()),
pathname: url.path().to_string(),
search: url.query().map_or(String::new(), |q| format!("?{}", q)),
hash: url.fragment().map_or(String::new(), |f| format!("#{}", f)),
origin,
}
}
}
#[derive(Debug, Clone)]
pub struct WorkerNavigator {
pub user_agent: String,
pub platform: String,
pub hardware_concurrency: usize,
pub language: String,
pub languages: Vec<String>,
pub connection: Option<WorkerNetworkInformation>,
pub cookie_enabled: bool,
pub max_touch_points: u32,
pub product: String,
pub app_code_name: String,
pub app_name: String,
pub app_version: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct WorkerNetworkInformation {
pub effective_type: String,
pub downlink: u64,
pub rtt: u64,
pub save_data: bool,
}
pub trait ScopeNavigatorConfig {
fn navigator_fields(&self) -> (&str, &str, usize, &str, &[String]);
}
macro_rules! impl_scope_navigator_config {
($($config:ty),* $(,)?) => {
$(
impl ScopeNavigatorConfig for $config {
fn navigator_fields(&self) -> (&str, &str, usize, &str, &[String]) {
(
&self.user_agent,
&self.platform,
self.hardware_concurrency,
&self.language,
&self.languages,
)
}
}
)*
};
}
impl_scope_navigator_config!(
WorkerScopeConfig,
SharedWorkerScopeConfig,
ServiceWorkerScopeConfig
);
impl WorkerNavigator {
fn from_scope_core(
user_agent: &str,
platform: &str,
hardware_concurrency: usize,
language: &str,
languages: &[String],
) -> Self {
WorkerNavigator {
user_agent: user_agent.to_string(),
platform: platform.to_string(),
hardware_concurrency,
language: language.to_string(),
languages: languages.to_vec(),
connection: None,
cookie_enabled: false,
max_touch_points: 0,
product: "Gecko".to_string(),
app_code_name: "Mozilla".to_string(),
app_name: "Netscape".to_string(),
app_version: user_agent.to_string(),
}
}
pub fn from_scope_config<C: ScopeNavigatorConfig>(config: &C) -> Self {
let (user_agent, platform, hardware_concurrency, language, languages) =
config.navigator_fields();
Self::from_scope_core(
user_agent,
platform,
hardware_concurrency,
language,
languages,
)
}
}
impl Default for WorkerNavigator {
fn default() -> Self {
WorkerNavigator {
user_agent: String::new(),
platform: String::new(),
hardware_concurrency: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1),
language: "en-US".to_string(),
languages: vec!["en-US".to_string(), "en".to_string()],
connection: None,
cookie_enabled: false,
max_touch_points: 0,
product: "Gecko".to_string(),
app_code_name: "Mozilla".to_string(),
app_name: "Netscape".to_string(),
app_version: String::new(),
}
}
}
#[derive(Debug, Clone)]
pub struct WorkerGlobalScopeState {
pub worker_url: String,
pub closing: bool,
pub location: Option<WorkerLocation>,
pub navigator: WorkerNavigator,
}
impl WorkerGlobalScopeState {
pub fn from_scope_config<C: ScopeNavigatorConfig>(worker_url: String, config: &C) -> Self {
WorkerGlobalScopeState {
location: WorkerLocation::from_url(&worker_url),
navigator: WorkerNavigator::from_scope_config(config),
worker_url,
closing: false,
}
}
pub fn new(worker_url: String, config: &WorkerScopeConfig) -> Self {
Self::from_scope_config(worker_url, config)
}
pub fn new_shared(worker_url: String, config: &SharedWorkerScopeConfig) -> Self {
Self::from_scope_config(worker_url, config)
}
}
#[derive(Debug, Clone)]
pub struct DedicatedWorkerGlobalScopeState {
pub scope: WorkerGlobalScopeState,
pub worker_id: WorkerId,
pub has_onmessage: bool,
pub has_onerror: bool,
}
impl DedicatedWorkerGlobalScopeState {
pub fn new(worker_id: WorkerId, config: &WorkerScopeConfig) -> Self {
let worker_url = worker_id.0.clone();
DedicatedWorkerGlobalScopeState {
scope: WorkerGlobalScopeState::new(worker_url, config),
worker_id,
has_onmessage: false,
has_onerror: false,
}
}
pub fn location(&self) -> Option<&WorkerLocation> {
self.scope.location.as_ref()
}
pub fn navigator(&self) -> &WorkerNavigator {
&self.scope.navigator
}
pub fn set_onmessage(&mut self) {
self.has_onmessage = true;
}
pub fn set_onerror(&mut self) {
self.has_onerror = true;
}
}
#[derive(Debug, Clone)]
pub struct WorkerScopeConfig {
pub stealth_profile: Option<bao_stealth::StealthProfile>,
pub user_agent: String,
pub platform: String,
pub hardware_concurrency: usize,
pub language: String,
pub languages: Vec<String>,
}
impl Default for WorkerScopeConfig {
fn default() -> Self {
WorkerScopeConfig {
stealth_profile: None,
user_agent: String::new(),
platform: String::new(),
hardware_concurrency: std::thread::available_parallelism()
.map(|n| n.get())
.unwrap_or(1),
language: "en-US".to_string(),
languages: vec!["en-US".to_string(), "en".to_string()],
}
}
}
impl From<&bao_stealth::StealthProfile> for WorkerScopeConfig {
fn from(profile: &bao_stealth::StealthProfile) -> Self {
WorkerScopeConfig {
stealth_profile: Some(profile.clone()),
user_agent: profile.navigator.user_agent.clone(),
platform: profile.navigator.platform.clone(),
hardware_concurrency: profile.navigator.hardware_concurrency as usize,
language: profile.navigator.language.clone(),
languages: profile.navigator.languages.clone(),
}
}
}
impl From<&bao_stealth::StealthProfile> for SharedWorkerScopeConfig {
fn from(profile: &bao_stealth::StealthProfile) -> Self {
SharedWorkerScopeConfig {
stealth_profile: Some(profile.clone()),
user_agent: profile.navigator.user_agent.clone(),
platform: profile.navigator.platform.clone(),
hardware_concurrency: profile.navigator.hardware_concurrency as usize,
language: profile.navigator.language.clone(),
languages: profile.navigator.languages.clone(),
}
}
}
pub struct AutoCloseWorker {
handle: WorkerHandle,
teardown_path: WorkerTeardownPath,
}
impl AutoCloseWorker {
pub fn new(handle: WorkerHandle) -> Self {
AutoCloseWorker {
handle,
teardown_path: WorkerTeardownPath::PageUnload,
}
}
pub fn lifecycle_state(&self) -> WorkerLifecycleState {
if self.handle.is_terminated() {
WorkerLifecycleState::Terminated(self.teardown_path.clone())
} else if self.handle.is_closing() {
WorkerLifecycleState::Closing(self.teardown_path.clone())
} else {
WorkerLifecycleState::Running
}
}
pub fn terminate_via(&mut self, path: WorkerTeardownPath) {
if !self.handle.is_closing() {
self.teardown_path = path;
self.handle.terminate();
}
}
pub fn handle(&self) -> &WorkerHandle {
&self.handle
}
}
impl Drop for AutoCloseWorker {
fn drop(&mut self) {
if !self.handle.is_closing() {
self.teardown_path = WorkerTeardownPath::PageUnload;
self.handle.terminate();
}
self.handle.unregister_stealth_profile();
self.handle.mark_terminated();
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_auto_close_worker_new_is_running() {
let handle = WorkerHandle::new("worker.js".to_string());
let guard = AutoCloseWorker::new(handle);
assert!(!guard.handle().is_closing());
assert!(!guard.handle().is_terminated());
}
#[test]
fn test_auto_close_worker_terminate_via() {
let handle = WorkerHandle::new("worker.js".to_string());
let mut guard = AutoCloseWorker::new(handle);
guard.terminate_via(WorkerTeardownPath::Terminate);
assert!(guard.handle().is_closing());
assert_eq!(
guard.lifecycle_state(),
WorkerLifecycleState::Closing(WorkerTeardownPath::Terminate)
);
}
#[test]
fn test_auto_close_worker_terminate_via_idempotent() {
let handle = WorkerHandle::new("worker.js".to_string());
let mut guard = AutoCloseWorker::new(handle);
guard.terminate_via(WorkerTeardownPath::Terminate);
guard.terminate_via(WorkerTeardownPath::SelfClose);
assert_eq!(
guard.lifecycle_state(),
WorkerLifecycleState::Closing(WorkerTeardownPath::Terminate)
);
}
#[test]
fn test_auto_close_worker_drop_terminates() {
let handle = WorkerHandle::new("worker.js".to_string());
let handle_clone = handle.clone();
let guard = AutoCloseWorker::new(handle);
assert!(!handle_clone.is_closing());
drop(guard);
assert!(handle_clone.is_closing());
}
#[test]
fn test_auto_close_worker_drop_already_closing() {
let handle = WorkerHandle::new("worker.js".to_string());
let handle_clone = handle.clone();
let mut guard = AutoCloseWorker::new(handle);
guard.terminate_via(WorkerTeardownPath::Terminate);
drop(guard);
assert!(handle_clone.is_closing());
assert!(handle_clone.is_terminated());
}
#[test]
fn test_worker_scope_config_default() {
let config = WorkerScopeConfig::default();
assert!(config.stealth_profile.is_none());
assert!(config.user_agent.is_empty());
assert!(config.platform.is_empty());
assert!(config.hardware_concurrency > 0);
assert_eq!(config.language, "en-US");
assert!(!config.languages.is_empty());
}
#[test]
fn test_worker_scope_config_set_on_state() {
let mut state = BaoWebViewState::default();
let config = WorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/1.0".to_string(),
platform: "Linux x86_64".to_string(),
hardware_concurrency: 8,
language: "zh-CN".to_string(),
languages: vec!["zh-CN".to_string(), "zh".to_string(), "en".to_string()],
};
state.set_worker_scope_config(config);
assert_eq!(state.worker_scope_config.user_agent, "Bao/1.0");
assert_eq!(state.worker_scope_config.platform, "Linux x86_64");
assert_eq!(state.worker_scope_config.hardware_concurrency, 8);
assert_eq!(state.worker_scope_config.language, "zh-CN");
assert_eq!(state.worker_scope_config.languages.len(), 3);
}
#[test]
fn test_webview_state_default_worker_scope_config() {
let state = BaoWebViewState::default();
assert!(state.worker_scope_config.stealth_profile.is_none());
assert!(state.worker_scope_config.hardware_concurrency > 0);
}
#[test]
fn test_worker_location_from_https_url() {
let loc = WorkerLocation::from_url("https://example.com:8080/path?q=1#hash").unwrap();
assert_eq!(loc.href, "https://example.com:8080/path?q=1#hash");
assert_eq!(loc.protocol, "https:");
assert_eq!(loc.host, "example.com:8080");
assert_eq!(loc.hostname, "example.com");
assert_eq!(loc.port, "8080");
assert_eq!(loc.pathname, "/path");
assert_eq!(loc.search, "?q=1");
assert_eq!(loc.hash, "#hash");
assert_eq!(loc.origin, "https://example.com:8080");
}
#[test]
fn test_worker_location_from_default_port() {
let loc = WorkerLocation::from_url("https://example.com/path").unwrap();
assert_eq!(loc.host, "example.com");
assert_eq!(loc.port, "");
assert_eq!(loc.origin, "https://example.com");
}
#[test]
fn test_worker_location_from_http_url() {
let loc = WorkerLocation::from_url("http://localhost:3000/worker.js").unwrap();
assert_eq!(loc.protocol, "http:");
assert_eq!(loc.hostname, "localhost");
assert_eq!(loc.port, "3000");
assert_eq!(loc.pathname, "/worker.js");
}
#[test]
fn test_worker_location_from_url_no_query_no_hash() {
let loc = WorkerLocation::from_url("https://example.com/worker.js").unwrap();
assert_eq!(loc.search, "");
assert_eq!(loc.hash, "");
}
#[test]
fn test_worker_location_from_invalid_url() {
assert!(WorkerLocation::from_url("not a url").is_none());
}
#[test]
fn test_worker_location_from_url_value() {
let url = url::Url::parse("https://example.com/worker.js").unwrap();
let loc = WorkerLocation::from_url_value(url);
assert_eq!(loc.protocol, "https:");
assert_eq!(loc.hostname, "example.com");
assert_eq!(loc.pathname, "/worker.js");
}
#[test]
fn test_worker_navigator_default() {
let nav = WorkerNavigator::default();
assert!(nav.user_agent.is_empty());
assert!(nav.platform.is_empty());
assert!(nav.hardware_concurrency > 0);
assert_eq!(nav.language, "en-US");
assert!(!nav.languages.is_empty());
assert!(nav.connection.is_none());
assert!(!nav.cookie_enabled);
assert_eq!(nav.max_touch_points, 0);
assert_eq!(nav.product, "Gecko");
assert_eq!(nav.app_code_name, "Mozilla");
assert_eq!(nav.app_name, "Netscape");
assert!(nav.app_version.is_empty());
}
#[test]
fn test_worker_navigator_from_scope_config() {
let config = WorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/1.0".to_string(),
platform: "Linux x86_64".to_string(),
hardware_concurrency: 8,
language: "zh-CN".to_string(),
languages: vec!["zh-CN".to_string(), "zh".to_string()],
};
let nav = WorkerNavigator::from_scope_config(&config);
assert_eq!(nav.user_agent, "Bao/1.0");
assert_eq!(nav.platform, "Linux x86_64");
assert_eq!(nav.hardware_concurrency, 8);
assert_eq!(nav.language, "zh-CN");
assert_eq!(nav.languages.len(), 2);
assert_eq!(nav.app_version, "Bao/1.0"); assert_eq!(nav.product, "Gecko");
assert_eq!(nav.app_code_name, "Mozilla");
assert_eq!(nav.app_name, "Netscape");
}
#[test]
fn test_worker_navigator_from_shared_scope_config() {
let config = SharedWorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/2.0".to_string(),
platform: "MacOS".to_string(),
hardware_concurrency: 4,
language: "ja".to_string(),
languages: vec!["ja".to_string(), "en".to_string()],
};
let nav = WorkerNavigator::from_scope_config(&config);
assert_eq!(nav.user_agent, "Bao/2.0");
assert_eq!(nav.platform, "MacOS");
assert_eq!(nav.hardware_concurrency, 4);
assert_eq!(nav.app_version, "Bao/2.0");
}
#[test]
fn test_worker_network_information() {
let info = WorkerNetworkInformation {
effective_type: "4g".to_string(),
downlink: 10,
rtt: 50,
save_data: false,
};
assert_eq!(info.effective_type, "4g");
assert_eq!(info.downlink, 10);
assert_eq!(info.rtt, 50);
assert!(!info.save_data);
}
#[test]
fn test_worker_global_scope_state_new() {
let config = WorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/1.0".to_string(),
platform: "Linux".to_string(),
hardware_concurrency: 8,
language: "en-US".to_string(),
languages: vec!["en-US".to_string()],
};
let scope =
WorkerGlobalScopeState::new("https://example.com/worker.js".to_string(), &config);
assert_eq!(scope.worker_url, "https://example.com/worker.js");
assert!(!scope.closing);
assert!(scope.location.is_some());
assert_eq!(scope.navigator.user_agent, "Bao/1.0");
}
#[test]
fn test_worker_global_scope_state_new_shared() {
let config = SharedWorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/2.0".to_string(),
platform: "MacOS".to_string(),
hardware_concurrency: 4,
language: "ja".to_string(),
languages: vec!["ja".to_string()],
};
let scope =
WorkerGlobalScopeState::new_shared("https://example.com/sw.js".to_string(), &config);
assert_eq!(scope.worker_url, "https://example.com/sw.js");
assert_eq!(scope.navigator.user_agent, "Bao/2.0");
}
#[test]
fn test_worker_global_scope_state_location_parsed() {
let config = WorkerScopeConfig::default();
let scope = WorkerGlobalScopeState::new(
"https://example.com:8080/app/worker.js?debug=true#section".to_string(),
&config,
);
let loc = scope.location.unwrap();
assert_eq!(loc.hostname, "example.com");
assert_eq!(loc.port, "8080");
assert_eq!(loc.pathname, "/app/worker.js");
assert_eq!(loc.search, "?debug=true");
assert_eq!(loc.hash, "#section");
}
#[test]
fn test_worker_global_scope_state_invalid_url_no_location() {
let config = WorkerScopeConfig::default();
let scope = WorkerGlobalScopeState::new("not-a-url".to_string(), &config);
assert!(scope.location.is_none());
}
#[test]
fn test_dedicated_worker_global_scope_state_new() {
let worker_id = WorkerId("https://example.com/worker.js".to_string());
let config = WorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/1.0".to_string(),
platform: "Linux".to_string(),
hardware_concurrency: 8,
language: "en-US".to_string(),
languages: vec!["en-US".to_string()],
};
let scope = DedicatedWorkerGlobalScopeState::new(worker_id.clone(), &config);
assert_eq!(scope.worker_id, worker_id);
assert!(!scope.has_onmessage);
assert!(!scope.has_onerror);
assert_eq!(scope.scope.navigator.user_agent, "Bao/1.0");
}
#[test]
fn test_dedicated_worker_global_scope_state_location() {
let worker_id = WorkerId("https://example.com/worker.js".to_string());
let config = WorkerScopeConfig::default();
let scope = DedicatedWorkerGlobalScopeState::new(worker_id, &config);
let loc = scope.location().unwrap();
assert_eq!(loc.hostname, "example.com");
assert_eq!(loc.pathname, "/worker.js");
}
#[test]
fn test_dedicated_worker_global_scope_state_navigator() {
let worker_id = WorkerId("worker.js".to_string());
let config = WorkerScopeConfig {
stealth_profile: None,
user_agent: "Bao/1.0".to_string(),
platform: "Linux".to_string(),
hardware_concurrency: 8,
language: "zh-CN".to_string(),
languages: vec!["zh-CN".to_string()],
};
let scope = DedicatedWorkerGlobalScopeState::new(worker_id, &config);
let nav = scope.navigator();
assert_eq!(nav.user_agent, "Bao/1.0");
assert_eq!(nav.hardware_concurrency, 8);
}
#[test]
fn test_dedicated_worker_global_scope_state_event_handlers() {
let worker_id = WorkerId("worker.js".to_string());
let config = WorkerScopeConfig::default();
let mut scope = DedicatedWorkerGlobalScopeState::new(worker_id, &config);
assert!(!scope.has_onmessage);
assert!(!scope.has_onerror);
scope.set_onmessage();
assert!(scope.has_onmessage);
assert!(!scope.has_onerror);
scope.set_onerror();
assert!(scope.has_onmessage);
assert!(scope.has_onerror);
}
#[test]
fn test_worker_scope_config_from_stealth_profile_chrome() {
let profile = bao_stealth::StealthProfile::chrome_default();
let config = WorkerScopeConfig::from(&profile);
assert!(
config.stealth_profile.is_some(),
"stealth_profile must be Some"
);
assert_eq!(config.user_agent, profile.navigator.user_agent);
assert_eq!(config.platform, profile.navigator.platform);
assert_eq!(
config.hardware_concurrency,
profile.navigator.hardware_concurrency as usize
);
assert_eq!(config.language, profile.navigator.language);
assert_eq!(config.languages, profile.navigator.languages);
assert!(
config.user_agent.contains("Chrome"),
"Chrome profile UA must contain Chrome"
);
}
#[test]
fn test_worker_scope_config_from_stealth_profile_firefox() {
let profile = bao_stealth::StealthProfile::firefox_default();
let config = WorkerScopeConfig::from(&profile);
assert!(
config.stealth_profile.is_some(),
"stealth_profile must be Some"
);
assert_eq!(config.user_agent, profile.navigator.user_agent);
assert_eq!(config.platform, profile.navigator.platform);
assert_eq!(
config.hardware_concurrency,
profile.navigator.hardware_concurrency as usize
);
assert_eq!(config.language, profile.navigator.language);
assert_eq!(config.languages, profile.navigator.languages);
assert!(
config.user_agent.contains("Firefox"),
"Firefox profile UA must contain Firefox"
);
}
#[test]
fn test_shared_worker_scope_config_from_stealth_profile() {
let profile = bao_stealth::StealthProfile::chrome_default();
let config = SharedWorkerScopeConfig::from(&profile);
assert!(
config.stealth_profile.is_some(),
"stealth_profile must be Some"
);
assert_eq!(config.user_agent, profile.navigator.user_agent);
assert_eq!(config.platform, profile.navigator.platform);
assert_eq!(
config.hardware_concurrency,
profile.navigator.hardware_concurrency as usize
);
assert_eq!(config.language, profile.navigator.language);
assert_eq!(config.languages, profile.navigator.languages);
}
#[test]
fn test_worker_scope_config_from_stealth_profile_carries_canvas_webgl_audio() {
let profile = bao_stealth::StealthProfile::chrome_default();
let config = WorkerScopeConfig::from(&profile);
let worker_profile = config.stealth_profile.unwrap();
assert_eq!(
worker_profile.canvas.seed(),
profile.canvas.seed(),
"Canvas seed must match"
);
assert!(
(worker_profile.canvas.noise_amplitude() - profile.canvas.noise_amplitude()).abs()
< f64::EPSILON,
"Canvas amplitude must match"
);
assert_eq!(
worker_profile.audio.seed(),
profile.audio.seed(),
"Audio seed must match"
);
assert_eq!(
worker_profile.webgl.vendor, profile.webgl.vendor,
"WebGL vendor must match"
);
assert_eq!(
worker_profile.webgl.renderer, profile.webgl.renderer,
"WebGL renderer must match"
);
}
#[test]
fn test_worker_scope_config_from_different_profiles_produces_different_configs() {
let chrome = bao_stealth::StealthProfile::chrome_default();
let firefox = bao_stealth::StealthProfile::firefox_default();
let chrome_config = WorkerScopeConfig::from(&chrome);
let firefox_config = WorkerScopeConfig::from(&firefox);
assert_ne!(chrome_config.user_agent, firefox_config.user_agent);
assert_ne!(
chrome_config.stealth_profile.unwrap().canvas.seed(),
firefox_config.stealth_profile.unwrap().canvas.seed()
);
}
}