mirror of
https://github.com/lexmount/moli.git
synced 2026-11-13 08:08:10 +00:00
fix(storage): use temporary disk backing without a profile
This commit is contained in:
Generated
+1
@@ -2781,6 +2781,7 @@ dependencies = [
|
||||
"serde_json",
|
||||
"static_assertions",
|
||||
"strip-ansi-escapes",
|
||||
"tempfile",
|
||||
"thiserror",
|
||||
"tokio",
|
||||
"tracing",
|
||||
|
||||
@@ -31,6 +31,7 @@ moli-fetch = { path = "../moli-fetch" }
|
||||
parking_lot = "0.12"
|
||||
serde = { version = "1.0.228", features = ["derive"] }
|
||||
serde_json = "1.0.145"
|
||||
tempfile = "3.27.0"
|
||||
tokio = { version = "1.51.0", features = ["macros", "net", "rt-multi-thread", "signal", "sync", "time"] }
|
||||
thiserror = "2.0.17"
|
||||
tracing = "0.1.41"
|
||||
|
||||
@@ -31,7 +31,7 @@ use crate::{
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) enum StoragePartitionPersistence {
|
||||
Memory,
|
||||
Ephemeral,
|
||||
ProfileBacked,
|
||||
}
|
||||
|
||||
@@ -47,6 +47,9 @@ pub struct StoragePartitionState {
|
||||
service_worker_resource_store: SharedServiceWorkerResourceStore,
|
||||
http_cache_root: Option<PathBuf>,
|
||||
profile: Option<Arc<BrowserProfile>>,
|
||||
// Drop after storage handles: temporary disk backing has partition scope,
|
||||
// while an explicit profile keeps its files after the partition closes.
|
||||
temporary_directory: Option<tempfile::TempDir>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -90,6 +93,13 @@ impl fmt::Debug for StoragePartitionState {
|
||||
)
|
||||
.field("http_cache_root", &self.http_cache_root)
|
||||
.field("profile_backed", &self.profile.is_some())
|
||||
.field(
|
||||
"temporary_root",
|
||||
&self
|
||||
.temporary_directory
|
||||
.as_ref()
|
||||
.map(tempfile::TempDir::path),
|
||||
)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
@@ -104,6 +114,16 @@ impl StoragePartitionState {
|
||||
}
|
||||
|
||||
fn from_profile(profile: Option<Arc<BrowserProfile>>) -> Result<Self> {
|
||||
let temporary_directory = if profile.is_none() {
|
||||
Some(
|
||||
tempfile::Builder::new()
|
||||
.prefix("moli-storage-")
|
||||
.tempdir()
|
||||
.context("failed to create temporary browser storage directory")?,
|
||||
)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let cookie_store = new_shared_browser_cookie_store();
|
||||
let session_storage_store = new_shared_web_storage_store();
|
||||
let (
|
||||
@@ -193,18 +213,26 @@ impl StoragePartitionState {
|
||||
Some(partition.http_cache_root().to_path_buf()),
|
||||
)
|
||||
} else {
|
||||
let indexed_db_manager =
|
||||
moli_renderer_v8::new_sqlite_indexed_db_manager(None, Default::default())
|
||||
.map_err(|error| anyhow!(error))
|
||||
.context("failed to initialize in-memory IndexedDB")?;
|
||||
let storage_service = StorageService::in_memory();
|
||||
let root = temporary_directory
|
||||
.as_ref()
|
||||
.expect("an ephemeral partition owns its temporary directory")
|
||||
.path();
|
||||
let indexed_db_manager = moli_renderer_v8::new_sqlite_indexed_db_manager(
|
||||
Some(root.join("idb-lite")),
|
||||
Default::default(),
|
||||
)
|
||||
.map_err(|error| anyhow!(error))
|
||||
.context("failed to initialize temporary IndexedDB")?;
|
||||
let storage_service = StorageService::on_disk(root.join("opfs"))
|
||||
.map_err(|error| anyhow!(error))
|
||||
.context("failed to initialize temporary OPFS")?;
|
||||
let storage_bucket_store =
|
||||
new_shared_storage_bucket_store_with_storage_service_and_indexed_db_manager(
|
||||
storage_service.clone(),
|
||||
&indexed_db_manager,
|
||||
);
|
||||
(
|
||||
StoragePartitionPersistence::Memory,
|
||||
StoragePartitionPersistence::Ephemeral,
|
||||
"default",
|
||||
new_shared_web_storage_store(),
|
||||
indexed_db_manager,
|
||||
@@ -226,6 +254,7 @@ impl StoragePartitionState {
|
||||
service_worker_resource_store,
|
||||
http_cache_root,
|
||||
profile,
|
||||
temporary_directory,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -503,22 +532,72 @@ mod tests {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_partition_owns_ephemeral_stores() -> Result<()> {
|
||||
fn ephemeral_partition_owns_temporary_stores() -> Result<()> {
|
||||
let partition = StoragePartitionState::open(None)?;
|
||||
let root = partition
|
||||
.temporary_directory
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.path()
|
||||
.to_path_buf();
|
||||
|
||||
assert_eq!(partition.persistence(), StoragePartitionPersistence::Memory);
|
||||
assert_eq!(
|
||||
partition.persistence(),
|
||||
StoragePartitionPersistence::Ephemeral
|
||||
);
|
||||
assert_eq!(partition.id(), "default");
|
||||
assert!(partition.http_cache_root().is_none());
|
||||
assert!(partition.profile_cookie_cache_paths().is_empty());
|
||||
let service = partition.storage_service.clone();
|
||||
let facade_service = partition.storage_bucket_store().lock().storage_service();
|
||||
assert!(Arc::ptr_eq(&service, &facade_service));
|
||||
assert!(root.join("idb-lite/format.json").is_file());
|
||||
assert!(!root.join("opfs").exists(), "OPFS remains lazy");
|
||||
|
||||
drop(partition);
|
||||
assert!(
|
||||
!root.exists(),
|
||||
"normal partition teardown removes temporary data"
|
||||
);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn memory_partition_flush_is_noop() -> Result<()> {
|
||||
fn ephemeral_partition_opfs_is_disk_backed_and_partition_isolated() -> Result<()> {
|
||||
let first = StoragePartitionState::open(None)?;
|
||||
let second = StoragePartitionState::open(None)?;
|
||||
let root = first.temporary_directory.as_ref().unwrap().path();
|
||||
assert_ne!(root, second.temporary_directory.as_ref().unwrap().path());
|
||||
let locator = StorageBucketLocator::default_bucket("https://temporary-opfs.test");
|
||||
let key = moli_storage_service::StorageService::opfs_bucket_key(&locator)?;
|
||||
let directory = first.storage_service.ensure_opfs_root(&locator)?;
|
||||
let file = first.storage_service.with_opfs(|opfs| {
|
||||
let file = opfs.get_file(&key, &directory, "entry.txt", true)?;
|
||||
opfs.write_file(&key, &file, b"temporary bytes", None)?;
|
||||
Ok::<_, moli_storage_service::OpfsError>(file)
|
||||
})?;
|
||||
// A separate backend must see the bytes through the host filesystem,
|
||||
// without sharing the first service's in-memory namespace.
|
||||
let reopened = moli_storage_service::StorageService::on_disk(root.join("opfs"))?;
|
||||
assert_eq!(
|
||||
reopened
|
||||
.with_opfs(|opfs| opfs.read_file(&key, &file))?
|
||||
.bytes,
|
||||
b"temporary bytes",
|
||||
);
|
||||
let other_directory = second.storage_service.ensure_opfs_root(&locator)?;
|
||||
assert!(
|
||||
second
|
||||
.storage_service
|
||||
.with_opfs(|opfs| { opfs.get_file(&key, &other_directory, "entry.txt", false) })
|
||||
.is_err()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn ephemeral_partition_cookie_flush_is_noop() -> Result<()> {
|
||||
let partition = StoragePartitionState::open(None)?;
|
||||
|
||||
partition.import_cookies(vec![stored_cookie("sid", "memory")])?;
|
||||
|
||||
@@ -218,8 +218,14 @@ fn partition_shutdown_finishes_accepted_bucket_cleanup_before_closing_sql() {
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn default_memory_partition_uses_sql_and_retained_clients_cannot_keep_its_data_alive() {
|
||||
fn temporary_partition_stores_sql_on_disk_and_retires_retained_clients_before_cleanup() {
|
||||
let partition = StoragePartitionState::open(None).unwrap();
|
||||
let root = partition
|
||||
.temporary_directory
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.path()
|
||||
.to_path_buf();
|
||||
let service = partition.indexed_db_manager.lock().sqlite_service();
|
||||
let client = database(&service, b"default-memory");
|
||||
upgrade(&client, 1, "kept");
|
||||
@@ -230,9 +236,34 @@ fn default_memory_partition_uses_sql_and_retained_clients_cannot_keep_its_data_a
|
||||
quota: None,
|
||||
},
|
||||
);
|
||||
let mut directories = vec![root.join("idb-lite")];
|
||||
let mut databases = Vec::new();
|
||||
while let Some(directory) = directories.pop() {
|
||||
for entry in fs::read_dir(directory).unwrap() {
|
||||
let path = entry.unwrap().path();
|
||||
if path.is_dir() {
|
||||
directories.push(path);
|
||||
} else if path
|
||||
.extension()
|
||||
.is_some_and(|extension| extension == "sqlite")
|
||||
{
|
||||
databases.push(path);
|
||||
}
|
||||
}
|
||||
}
|
||||
assert_eq!(databases.len(), 1);
|
||||
assert!(
|
||||
fs::read(&databases[0])
|
||||
.unwrap()
|
||||
.starts_with(b"SQLite format 3\0")
|
||||
);
|
||||
drop(partition);
|
||||
assert!(service.is_closed());
|
||||
assert!(client.is_retired());
|
||||
assert!(
|
||||
!root.exists(),
|
||||
"retained SQL clients must not pin temporary files"
|
||||
);
|
||||
let partition = StoragePartitionState::open(None).unwrap();
|
||||
let service = partition.indexed_db_manager.lock().sqlite_service();
|
||||
assert_eq!(
|
||||
|
||||
@@ -82,11 +82,11 @@ impl ProtocolServer {
|
||||
initial_cookies: Vec<StoredCookie>,
|
||||
) -> Self {
|
||||
let storage_partition = Arc::new(
|
||||
StoragePartitionState::open(None).expect("in-memory storage partition should open"),
|
||||
StoragePartitionState::open(None).expect("temporary storage partition should open"),
|
||||
);
|
||||
storage_partition
|
||||
.import_cookies(initial_cookies)
|
||||
.expect("in-memory cookie import should succeed");
|
||||
.expect("initial cookie import should succeed");
|
||||
Self::new_with_storage_partition(config, storage_partition)
|
||||
}
|
||||
|
||||
|
||||
@@ -1002,10 +1002,17 @@ pub struct CdpInitialStoragePartition {
|
||||
}
|
||||
|
||||
impl CdpInitialStoragePartition {
|
||||
/// Explicit in-memory backing for embedders and backend-focused tests.
|
||||
pub fn memory() -> Self {
|
||||
Self::new(BrowserContextStoragePartitionHandles::memory())
|
||||
}
|
||||
|
||||
pub fn temporary() -> anyhow::Result<Self> {
|
||||
Ok(Self::new(
|
||||
BrowserContextStoragePartitionHandles::temporary()?
|
||||
))
|
||||
}
|
||||
|
||||
pub fn with_cookies(cookies: Vec<StoredCookie>) -> Self {
|
||||
Self::new(BrowserContextStoragePartitionHandles::with_initial_cookies(
|
||||
cookies,
|
||||
@@ -1383,6 +1390,10 @@ impl Drop for CdpConnection {
|
||||
contexts.push(context);
|
||||
}
|
||||
contexts.append(&mut self.inactive_browser_contexts);
|
||||
let storage_partitions = contexts
|
||||
.iter()
|
||||
.map(BrowserContext::retain_storage_for_teardown)
|
||||
.collect::<Vec<_>>();
|
||||
let roots = contexts
|
||||
.iter_mut()
|
||||
.filter_map(BrowserContext::take_renderer_runtime_owner_for_teardown)
|
||||
@@ -1407,12 +1418,16 @@ impl Drop for CdpConnection {
|
||||
root.shutdown_network_and_join();
|
||||
}
|
||||
drop(roots);
|
||||
drop(storage_partitions);
|
||||
}
|
||||
}
|
||||
|
||||
impl CdpConnection {
|
||||
pub fn new() -> Self {
|
||||
Self::new_with_initial_storage_partition(CdpInitialStoragePartition::memory())
|
||||
Self::new_with_initial_storage_partition(
|
||||
CdpInitialStoragePartition::temporary()
|
||||
.expect("temporary browser storage should initialize"),
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn layout_policy(&self) -> LayoutPolicy {
|
||||
@@ -1493,7 +1508,8 @@ impl CdpConnection {
|
||||
|
||||
pub fn new_with_fetch_config(fetch_config: FetchConfig) -> Self {
|
||||
Self::new_with_initial_storage_partition_and_fetch_config(
|
||||
CdpInitialStoragePartition::memory(),
|
||||
CdpInitialStoragePartition::temporary()
|
||||
.expect("temporary browser storage should initialize"),
|
||||
fetch_config,
|
||||
)
|
||||
}
|
||||
@@ -3195,7 +3211,10 @@ impl CdpConnection {
|
||||
browser_context.global_geolocation_override = self.global_geolocation_override.clone();
|
||||
}
|
||||
|
||||
pub(crate) fn new_ephemeral_browser_context(&self, id: String) -> BrowserContext {
|
||||
pub(crate) fn new_ephemeral_browser_context(
|
||||
&self,
|
||||
id: String,
|
||||
) -> Result<BrowserContext, String> {
|
||||
let mut browser_context = BrowserContext::new_ephemeral_with_http_cache(
|
||||
id,
|
||||
self.fetch_config().http_cache_dir().map(PathBuf::from),
|
||||
@@ -3203,9 +3222,9 @@ impl CdpConnection {
|
||||
self.standalone_navigation_engine
|
||||
.runtime_config()
|
||||
.debugger_enabled(),
|
||||
);
|
||||
)?;
|
||||
self.apply_global_browser_context_state(&mut browser_context);
|
||||
browser_context
|
||||
Ok(browser_context)
|
||||
}
|
||||
|
||||
pub fn snapshot_cookies(&mut self) -> Vec<StoredCookie> {
|
||||
|
||||
@@ -40,7 +40,6 @@ use super::{
|
||||
pub struct BrowserContext {
|
||||
pub id: String,
|
||||
pub(crate) closing: bool,
|
||||
storage_partition: BrowserContextStoragePartition,
|
||||
pub(crate) page_targets: PageTargetRegistry,
|
||||
/// Browser focus is independent of which context is selected for routing.
|
||||
pub(crate) window_is_focused: bool,
|
||||
@@ -86,9 +85,10 @@ pub struct BrowserContext {
|
||||
pub(crate) http_cache_max_bytes: Option<u64>,
|
||||
pub(crate) page_navigation_runtime_config: Option<NavigationRuntimeConfig>,
|
||||
renderer_output_transport_sender: Option<moli_core::RendererOutputTransportSender>,
|
||||
// Keep the sole strong per-context network owner last. Protocol teardown
|
||||
// must drop pages and NavigationEngines before this field is released.
|
||||
// Release pages and NavigationEngines before joining the network owner,
|
||||
// then close storage before deleting its temporary disk backing.
|
||||
pub(crate) renderer_runtime_owner: Option<RendererBrowserContextRuntimeOwner>,
|
||||
storage_partition: BrowserContextStoragePartition,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -115,6 +115,7 @@ pub(crate) struct BrowserContextStoragePartitionHandles {
|
||||
web_storage_store: SharedWebStorageStore,
|
||||
indexed_db_manager: SharedIndexedDbManager,
|
||||
storage_bucket_store: SharedStorageBucketStore,
|
||||
_owner: Option<std::sync::Arc<StoragePartitionState>>,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -226,18 +227,12 @@ impl BrowserContextStoragePartitionHandles {
|
||||
web_storage_store,
|
||||
indexed_db_manager,
|
||||
storage_bucket_store,
|
||||
_owner: None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn memory() -> Self {
|
||||
Self::with_initial_cookies(Vec::new())
|
||||
}
|
||||
|
||||
pub(crate) fn with_initial_cookies(
|
||||
initial_cookies: impl IntoIterator<Item = StoredCookie>,
|
||||
) -> Self {
|
||||
let cookie_store = new_shared_browser_cookie_store();
|
||||
seed_initial_cookies(&cookie_store, initial_cookies);
|
||||
let indexed_db_manager = moli_core::storage::new_indexed_db_manager(None)
|
||||
.expect("in-memory IndexedDB manager should initialize");
|
||||
let storage_bucket_store =
|
||||
@@ -250,6 +245,21 @@ impl BrowserContextStoragePartitionHandles {
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn temporary() -> anyhow::Result<Self> {
|
||||
let owner = std::sync::Arc::new(StoragePartitionState::open(None)?);
|
||||
let mut handles = Self::from_storage_partition(Vec::new(), &owner);
|
||||
handles._owner = Some(owner);
|
||||
Ok(handles)
|
||||
}
|
||||
|
||||
pub(crate) fn with_initial_cookies(
|
||||
initial_cookies: impl IntoIterator<Item = StoredCookie>,
|
||||
) -> Self {
|
||||
let handles = Self::temporary().expect("temporary browser storage should initialize");
|
||||
seed_initial_cookies(&handles.cookie_store, initial_cookies);
|
||||
handles
|
||||
}
|
||||
|
||||
fn from_initial_storage_partition(
|
||||
cookie_store: SharedBrowserCookieStore,
|
||||
local_storage_store: SharedWebStorageStore,
|
||||
@@ -457,14 +467,16 @@ impl BrowserContext {
|
||||
http_cache_root: Option<PathBuf>,
|
||||
http_cache_max_bytes: Option<u64>,
|
||||
debugger_enabled: bool,
|
||||
) -> Self {
|
||||
Self::new_ephemeral_with_storage_partition_handles(
|
||||
) -> Result<Self, String> {
|
||||
let partition = BrowserContextStoragePartitionHandles::temporary()
|
||||
.map_err(|error| format!("{error:#}"))?;
|
||||
Ok(Self::new_ephemeral_with_storage_partition_handles(
|
||||
id,
|
||||
BrowserContextStoragePartitionHandles::memory(),
|
||||
partition,
|
||||
http_cache_root,
|
||||
http_cache_max_bytes,
|
||||
debugger_enabled,
|
||||
)
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_storage_partition_and_http_cache(
|
||||
@@ -819,6 +831,10 @@ impl BrowserContext {
|
||||
self.renderer_runtime_owner.take()
|
||||
}
|
||||
|
||||
pub(crate) fn retain_storage_for_teardown(&self) -> BrowserContextStoragePartitionHandles {
|
||||
self.storage_partition.handles.clone()
|
||||
}
|
||||
|
||||
pub(crate) fn routes_renderer_browser_context_runtime(
|
||||
&self,
|
||||
runtime_id: moli_core::RendererBrowserContextRuntimeId,
|
||||
@@ -1803,3 +1819,27 @@ pub(crate) fn seed_initial_cookies(
|
||||
let _ = store.upsert_with_request_url_report(cookie, None, CookieSource::Cdp);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod temporary_storage_tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn temporary_storage_handles_keep_the_partition_until_the_last_clone_drops() {
|
||||
let handles = BrowserContextStoragePartitionHandles::temporary().unwrap();
|
||||
let owner = std::sync::Arc::downgrade(handles._owner.as_ref().unwrap());
|
||||
let sql = handles.indexed_db_manager.lock().sqlite_service();
|
||||
let retained = handles.clone();
|
||||
|
||||
drop(handles);
|
||||
assert!(owner.upgrade().is_some());
|
||||
assert!(!sql.is_closed());
|
||||
|
||||
drop(retained);
|
||||
assert!(owner.upgrade().is_none());
|
||||
assert!(
|
||||
sql.is_closed(),
|
||||
"retained SQL clients must not own the partition"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -284,7 +284,9 @@ fn default_browser_contexts_reuse_partition_with_distinct_target_session_storage
|
||||
fn connection_ephemeral_browser_context_uses_isolated_storage_partition() {
|
||||
let conn = CdpConnection::new_with_initial_cookies(vec![stored_cookie("sid", "seeded")]);
|
||||
|
||||
let bc = conn.new_ephemeral_browser_context("BID-ephemeral".to_owned());
|
||||
let bc = conn
|
||||
.new_ephemeral_browser_context("BID-ephemeral".to_owned())
|
||||
.expect("temporary context");
|
||||
|
||||
assert!(!bc.is_profile_backed_storage_partition());
|
||||
assert_eq!(bc.storage_partition_kind_label(), "ephemeral");
|
||||
@@ -300,7 +302,9 @@ fn connection_default_and_ephemeral_context_creation_use_named_partition_paths()
|
||||
let conn = CdpConnection::new_with_initial_storage_partition(initial_storage_partition);
|
||||
|
||||
let profile_backed = conn.new_browser_context("BID-profile".to_owned());
|
||||
let ephemeral = conn.new_ephemeral_browser_context("BID-ephemeral".to_owned());
|
||||
let ephemeral = conn
|
||||
.new_ephemeral_browser_context("BID-ephemeral".to_owned())
|
||||
.expect("temporary context");
|
||||
let expected_web_storage_store = storage_partition
|
||||
.shared_storage_handles()
|
||||
.web_storage_store();
|
||||
@@ -324,7 +328,9 @@ fn connection_default_and_ephemeral_context_creation_use_named_partition_paths()
|
||||
fn browser_context_memory_diagnostics_include_storage_partition_identity() {
|
||||
let conn = CdpConnection::new();
|
||||
let profile_backed = conn.new_browser_context("BID-profile".to_owned());
|
||||
let ephemeral = conn.new_ephemeral_browser_context("BID-ephemeral".to_owned());
|
||||
let ephemeral = conn
|
||||
.new_ephemeral_browser_context("BID-ephemeral".to_owned())
|
||||
.expect("temporary context");
|
||||
|
||||
assert_eq!(
|
||||
profile_backed.storage_partition_kind_label(),
|
||||
@@ -568,7 +574,9 @@ fn connection_profile_backed_cookie_snapshot_ignores_ephemeral_contexts() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let profile_backed = conn.new_browser_context("BID-profile".to_owned());
|
||||
profile_backed.upsert_cookie_for_test(stored_cookie("profile", "1"));
|
||||
let ephemeral = conn.new_ephemeral_browser_context("BID-ephemeral".to_owned());
|
||||
let ephemeral = conn
|
||||
.new_ephemeral_browser_context("BID-ephemeral".to_owned())
|
||||
.expect("temporary context");
|
||||
ephemeral.upsert_cookie_for_test(stored_cookie("ephemeral", "1"));
|
||||
conn.install_browser_context_fixture_for_test(ephemeral);
|
||||
conn.push_inactive_browser_context_fixture_for_test(profile_backed);
|
||||
@@ -584,7 +592,9 @@ fn connection_profile_backed_cookie_snapshot_ignores_ephemeral_contexts() {
|
||||
#[test]
|
||||
fn connection_profile_backed_cookie_snapshot_is_none_without_profile_backed_context() {
|
||||
let mut conn = CdpConnection::new_with_initial_cookies(vec![stored_cookie("sid", "seeded")]);
|
||||
let ephemeral = conn.new_ephemeral_browser_context("BID-ephemeral".to_owned());
|
||||
let ephemeral = conn
|
||||
.new_ephemeral_browser_context("BID-ephemeral".to_owned())
|
||||
.expect("temporary context");
|
||||
assert!(ephemeral.snapshot_cookies().is_empty());
|
||||
conn.install_browser_context_fixture_for_test(ephemeral);
|
||||
|
||||
@@ -896,8 +906,12 @@ async fn browser_context_storage_does_not_cross_context_switches() {
|
||||
#[tokio::test]
|
||||
async fn browser_context_storage_buckets_reuse_within_context_and_isolate_between_contexts() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let context_a = conn.new_ephemeral_browser_context("BID-1".into());
|
||||
let context_b = conn.new_ephemeral_browser_context("BID-2".into());
|
||||
let context_a = conn
|
||||
.new_ephemeral_browser_context("BID-1".into())
|
||||
.expect("temporary context");
|
||||
let context_b = conn
|
||||
.new_ephemeral_browser_context("BID-2".into())
|
||||
.expect("temporary context");
|
||||
conn.install_browser_context_fixture_for_test(context_a);
|
||||
conn.push_inactive_browser_context_fixture_for_test(context_b);
|
||||
let first_url = Url::parse("https://context-storage-buckets.example/app/one").unwrap();
|
||||
|
||||
@@ -606,7 +606,9 @@ fn new_ephemeral_browser_context_inherits_effective_http_cache_owner() {
|
||||
fetch_config.set_http_cache_max_bytes(Some(77));
|
||||
|
||||
let conn = crate::conn::CdpConnection::new_with_fetch_config(fetch_config);
|
||||
let browser_context = conn.new_ephemeral_browser_context("BID-ephemeral".to_owned());
|
||||
let browser_context = conn
|
||||
.new_ephemeral_browser_context("BID-ephemeral".to_owned())
|
||||
.expect("temporary context");
|
||||
|
||||
assert_eq!(
|
||||
browser_context.http_cache_root.as_deref(),
|
||||
|
||||
@@ -292,7 +292,9 @@ pub(super) fn execute_devtools_create_browser_context_command(
|
||||
));
|
||||
}
|
||||
|
||||
let mut browser_context = conn.new_ephemeral_browser_context(id.clone());
|
||||
let mut browser_context = conn
|
||||
.new_ephemeral_browser_context(id.clone())
|
||||
.map_err(|error| DevToolsError::new(DevToolsErrorKind::Internal, error))?;
|
||||
browser_context.default_tls_verify_host_override =
|
||||
command.accept_insecure_certs.map(|accept| !accept);
|
||||
browser_context.default_http_proxy_override = command.proxy_server;
|
||||
@@ -505,7 +507,10 @@ pub(super) fn create_browser_context(conn: &mut CdpConnection, cmd: &Cmd<'_>) ->
|
||||
return CommandOutputPlan::error_without_session(-32602, message);
|
||||
}
|
||||
let id = conn.gen_bc_id();
|
||||
let mut browser_context = conn.new_ephemeral_browser_context(id.clone());
|
||||
let mut browser_context = match conn.new_ephemeral_browser_context(id.clone()) {
|
||||
Ok(context) => context,
|
||||
Err(error) => return CommandOutputPlan::error_without_session(-32000, &error),
|
||||
};
|
||||
browser_context.default_http_proxy_override = params.proxy_server;
|
||||
browser_context.default_http_no_proxy_override =
|
||||
normalize_proxy_bypass_list_for_loader(params.proxy_bypass_list.as_deref());
|
||||
|
||||
@@ -558,6 +558,7 @@ pub struct CommonArgs {
|
||||
#[arg(long)]
|
||||
pub http_cache_dir: Option<String>,
|
||||
|
||||
/// Persist browser storage in this directory. Without it, IndexedDB and OPFS use temporary files.
|
||||
#[arg(short = 'P', long)]
|
||||
pub profile_dir: Option<String>,
|
||||
|
||||
|
||||
Reference in New Issue
Block a user