mirror of
https://github.com/lexmount/moli.git
synced 2026-09-30 16:01:42 +00:00
refactor(browser): move physical state behind BrowserService handles
Bind document commands, resource work, Worker controls, downloads and retirement to exact native handles. Remove Context-wide mirrors and transfer physical ownership to the independent Core service.
This commit is contained in:
Generated
+3
@@ -2543,6 +2543,7 @@ version = "0.1.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"http",
|
||||
"indexmap",
|
||||
"moli-browser-profile",
|
||||
"moli-cookie-jar",
|
||||
"moli-crypto",
|
||||
@@ -2553,6 +2554,8 @@ dependencies = [
|
||||
"moli-parser",
|
||||
"moli-renderer-v8",
|
||||
"moli-selector",
|
||||
"moli-shared-worker",
|
||||
"moli-site",
|
||||
"moli-storage-key",
|
||||
"moli-storage-service",
|
||||
"moli-test-support",
|
||||
|
||||
@@ -769,8 +769,8 @@ fn insert_context<'a>(url: &'a Url, source: CookieSource) -> InsertContext<'a> {
|
||||
match source {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
CookieSource::Http => InsertContext::http(url),
|
||||
// CDP is a privileged browser-side API. Treat it closer to Chromium's manager-side set
|
||||
// path than to `document.cookie`, so fork-side HttpOnly guards do not reject it.
|
||||
CookieSource::Cdp => InsertContext::cdp(url),
|
||||
// Browser management is privileged. Treat it closer to Chromium's manager-side set path
|
||||
// than to `document.cookie`, so fork-side HttpOnly guards do not reject it.
|
||||
CookieSource::Management => InsertContext::cdp(url),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ use super::stored_cookie::StoredCookiePartitionKey;
|
||||
pub enum CookieSource {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
Http,
|
||||
Cdp,
|
||||
Management,
|
||||
}
|
||||
|
||||
/// SameSite request context computed at the network boundary.
|
||||
|
||||
@@ -1592,7 +1592,7 @@ fn cdp_upsert_updates_network_visible_cookie_header() {
|
||||
creation_index: 999,
|
||||
last_access_index: 999,
|
||||
},
|
||||
CookieSource::Cdp,
|
||||
CookieSource::Management,
|
||||
);
|
||||
|
||||
assert_eq!(store.cookie_header(&url), Some("sid=cdp".to_owned()));
|
||||
@@ -1622,7 +1622,7 @@ fn cdp_upsert_host_only_cookie_preserves_explicit_domain_without_request_hint()
|
||||
creation_index: 999,
|
||||
last_access_index: 999,
|
||||
},
|
||||
CookieSource::Cdp,
|
||||
CookieSource::Management,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -4072,7 +4072,7 @@ fn cdp_upsert_applies_prefix_and_same_site_guards() {
|
||||
creation_index: 999,
|
||||
last_access_index: 999,
|
||||
},
|
||||
CookieSource::Cdp,
|
||||
CookieSource::Management,
|
||||
);
|
||||
store.upsert(
|
||||
StoredCookie {
|
||||
@@ -4092,7 +4092,7 @@ fn cdp_upsert_applies_prefix_and_same_site_guards() {
|
||||
creation_index: 999,
|
||||
last_access_index: 999,
|
||||
},
|
||||
CookieSource::Cdp,
|
||||
CookieSource::Management,
|
||||
);
|
||||
store.upsert(
|
||||
StoredCookie {
|
||||
@@ -4112,7 +4112,7 @@ fn cdp_upsert_applies_prefix_and_same_site_guards() {
|
||||
creation_index: 999,
|
||||
last_access_index: 999,
|
||||
},
|
||||
CookieSource::Cdp,
|
||||
CookieSource::Management,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
@@ -4166,7 +4166,7 @@ fn cdp_upsert_accepts_partitioned_cookie() {
|
||||
creation_index: 999,
|
||||
last_access_index: 999,
|
||||
},
|
||||
CookieSource::Cdp,
|
||||
CookieSource::Management,
|
||||
);
|
||||
|
||||
assert_eq!(
|
||||
|
||||
@@ -16,6 +16,7 @@ wpt-extensions = ["moli-renderer-v8/wpt-extensions"]
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
http = "1"
|
||||
indexmap = "2.6"
|
||||
moli-crypto = { path = "../moli-crypto" }
|
||||
moli-header-field = { path = "../moli-header-field" }
|
||||
moli-browser-profile = { path = "../moli-browser-profile" }
|
||||
@@ -24,6 +25,8 @@ moli-parser = { path = "../moli-parser" }
|
||||
moli-page-types = { path = "../moli-page-types" }
|
||||
moli-renderer-v8 = { path = "../moli-renderer-v8" }
|
||||
moli-selector = { path = "../moli-selector" }
|
||||
moli-shared-worker = { path = "../moli-shared-worker" }
|
||||
moli-site = { path = "../moli-site" }
|
||||
moli-storage-key = { path = "../moli-storage-key" }
|
||||
moli-storage-service = { path = "../moli-storage-service" }
|
||||
moli-url = { path = "../moli-url" }
|
||||
|
||||
@@ -0,0 +1,795 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::{
|
||||
browser::{
|
||||
BrowserContextId, DocumentHandle, DownloadManager, DownloadPolicy, PermissionOverrides,
|
||||
WebContentsHandle, WebContentsId,
|
||||
},
|
||||
network::{SharedWebStorageStore, new_shared_web_storage_store},
|
||||
runtime::{
|
||||
NavigationEngine, NavigationPageStorageHandles, NavigationResourceStorageHandles,
|
||||
NavigationRuntimeConfig, RendererBrowserContextRuntime, RendererBrowserContextRuntimeOwner,
|
||||
RendererBrowserContextRuntimeOwnerAccess, storage_partition::StoragePartitionState,
|
||||
},
|
||||
storage::{
|
||||
SharedIndexedDbManager, SharedStorageBucketStore, WeakIndexedDbManager,
|
||||
new_shared_storage_bucket_store_with_indexed_db_manager,
|
||||
},
|
||||
};
|
||||
use indexmap::IndexMap;
|
||||
use moli_browser_profile::BrowserIdentityProfile;
|
||||
use moli_cookie_jar::{
|
||||
CookieSource, SharedBrowserCookieStore, StoredCookie, new_shared_browser_cookie_store,
|
||||
};
|
||||
|
||||
use super::web_contents::{ClosingWebContents, DocumentHost, WebContents};
|
||||
use crate::browser::{
|
||||
EmulatedDeviceMetrics, EmulatedGeolocationOverrideState, EmulatedNetworkConditions,
|
||||
};
|
||||
|
||||
mod dialogs;
|
||||
mod document_commands;
|
||||
mod document_policy;
|
||||
mod document_queries;
|
||||
mod fetch;
|
||||
mod input;
|
||||
mod navigation;
|
||||
mod permissions;
|
||||
mod residence;
|
||||
mod resource_commands;
|
||||
mod resource_runtime;
|
||||
mod settings;
|
||||
mod storage_partition;
|
||||
mod workers;
|
||||
pub use document_commands::*;
|
||||
pub use document_policy::*;
|
||||
pub use fetch::*;
|
||||
pub use input::*;
|
||||
pub use navigation::DocumentNavigationMetadata;
|
||||
pub use permissions::{CompletedContextPermissionUpdate, PendingContextPermissionUpdate};
|
||||
pub use resource_commands::*;
|
||||
use storage_partition::StoragePartition;
|
||||
pub use storage_partition::{OriginStorageUsage, SiteDataClearOptions, StoragePartitionKind};
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserContextStoragePartitionHandles {
|
||||
pub cookie_store: SharedBrowserCookieStore,
|
||||
pub web_storage_store: SharedWebStorageStore,
|
||||
pub indexed_db_manager: SharedIndexedDbManager,
|
||||
pub storage_bucket_store: SharedStorageBucketStore,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserContextResourceStorageHandles {
|
||||
pub cookie_store: SharedBrowserCookieStore,
|
||||
pub web_storage_store: SharedWebStorageStore,
|
||||
pub session_storage_store: SharedWebStorageStore,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
pub struct BrowserContextPageStorageHandles {
|
||||
pub cookie_store: SharedBrowserCookieStore,
|
||||
pub web_storage_store: SharedWebStorageStore,
|
||||
pub session_storage_store: SharedWebStorageStore,
|
||||
pub indexed_db_manager: Option<WeakIndexedDbManager>,
|
||||
pub storage_bucket_store: Option<SharedStorageBucketStore>,
|
||||
}
|
||||
|
||||
impl BrowserContextStoragePartitionHandles {
|
||||
fn from_stores(
|
||||
cookie_store: SharedBrowserCookieStore,
|
||||
web_storage_store: SharedWebStorageStore,
|
||||
indexed_db_manager: SharedIndexedDbManager,
|
||||
storage_bucket_store: SharedStorageBucketStore,
|
||||
) -> Self {
|
||||
Self {
|
||||
cookie_store,
|
||||
web_storage_store,
|
||||
indexed_db_manager,
|
||||
storage_bucket_store,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn memory() -> Self {
|
||||
Self::with_initial_cookies(Vec::new())
|
||||
}
|
||||
|
||||
pub 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 = crate::storage::new_indexed_db_manager(None)
|
||||
.expect("in-memory IndexedDB manager should initialize");
|
||||
let storage_bucket_store =
|
||||
new_shared_storage_bucket_store_with_indexed_db_manager(&indexed_db_manager);
|
||||
Self::from_stores(
|
||||
cookie_store,
|
||||
new_shared_web_storage_store(),
|
||||
indexed_db_manager,
|
||||
storage_bucket_store,
|
||||
)
|
||||
}
|
||||
|
||||
fn from_initial_storage_partition(
|
||||
cookie_store: SharedBrowserCookieStore,
|
||||
local_storage_store: SharedWebStorageStore,
|
||||
indexed_db_manager: SharedIndexedDbManager,
|
||||
storage_bucket_store: SharedStorageBucketStore,
|
||||
) -> Self {
|
||||
Self::from_stores(
|
||||
cookie_store,
|
||||
local_storage_store,
|
||||
indexed_db_manager,
|
||||
storage_bucket_store,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn from_storage_partition(
|
||||
initial_cookies: impl IntoIterator<Item = StoredCookie>,
|
||||
storage_partition: &StoragePartitionState,
|
||||
) -> Self {
|
||||
let cookie_store = new_shared_browser_cookie_store();
|
||||
seed_initial_cookies(&cookie_store, initial_cookies);
|
||||
let shared_storage = storage_partition.shared_storage_handles();
|
||||
Self::from_initial_storage_partition(
|
||||
cookie_store,
|
||||
shared_storage.web_storage_store(),
|
||||
shared_storage.indexed_db_manager(),
|
||||
shared_storage.storage_bucket_store(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn resource_storage_handles(
|
||||
&self,
|
||||
session_storage_store: SharedWebStorageStore,
|
||||
) -> BrowserContextResourceStorageHandles {
|
||||
BrowserContextResourceStorageHandles {
|
||||
cookie_store: self.cookie_store.clone(),
|
||||
web_storage_store: self.web_storage_store.clone(),
|
||||
session_storage_store,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn page_storage_handles(
|
||||
&self,
|
||||
session_storage_store: SharedWebStorageStore,
|
||||
) -> BrowserContextPageStorageHandles {
|
||||
BrowserContextPageStorageHandles {
|
||||
cookie_store: self.cookie_store.clone(),
|
||||
web_storage_store: self.web_storage_store.clone(),
|
||||
session_storage_store,
|
||||
indexed_db_manager: Some(crate::storage::downgrade_indexed_db_manager(
|
||||
&self.indexed_db_manager,
|
||||
)),
|
||||
storage_bucket_store: Some(self.storage_bucket_store.clone()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserContextResourceStorageHandles {
|
||||
pub fn into_navigation_storage(self) -> NavigationResourceStorageHandles {
|
||||
NavigationResourceStorageHandles::new(
|
||||
self.cookie_store,
|
||||
self.web_storage_store,
|
||||
self.session_storage_store,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserContextPageStorageHandles {
|
||||
pub fn into_navigation_storage(self) -> NavigationPageStorageHandles {
|
||||
NavigationPageStorageHandles::new(
|
||||
self.cookie_store,
|
||||
self.web_storage_store,
|
||||
self.session_storage_store,
|
||||
self.indexed_db_manager,
|
||||
self.storage_bucket_store,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fn seed_initial_cookies(
|
||||
cookie_store: &SharedBrowserCookieStore,
|
||||
initial_cookies: impl IntoIterator<Item = StoredCookie>,
|
||||
) {
|
||||
let mut store = cookie_store.lock();
|
||||
for cookie in initial_cookies {
|
||||
let _ = store.upsert_with_request_url_report(cookie, None, CookieSource::Management);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn seed_initial_cookies_for_test(
|
||||
cookie_store: &SharedBrowserCookieStore,
|
||||
initial_cookies: impl IntoIterator<Item = StoredCookie>,
|
||||
) {
|
||||
seed_initial_cookies(cookie_store, initial_cookies);
|
||||
}
|
||||
|
||||
/// Physical context ownership held only by the Browser owner sequence.
|
||||
/// No projection identity, output transport or session state belongs here.
|
||||
pub struct BrowserContext {
|
||||
id: BrowserContextId,
|
||||
page_navigation_runtime_config: Option<NavigationRuntimeConfig>,
|
||||
network_policy: ContextNetworkPolicy,
|
||||
emulation_defaults: ContextEmulationDefaults,
|
||||
environment_owner: crate::ProcessEnvironmentOwner,
|
||||
browser_identity_override: Option<BrowserIdentityProfile>,
|
||||
permission_overrides: PermissionOverrides,
|
||||
download_policy: Option<DownloadPolicy>,
|
||||
downloads: DownloadManager,
|
||||
// The Browser collection and its only selector have the same lifetime.
|
||||
// Keep insertion order when choosing a replacement foreground page.
|
||||
web_contents: IndexMap<WebContentsId, WebContents>,
|
||||
selected_web_contents: Option<WebContentsId>,
|
||||
// Drop Documents/engines before the runtime root and its storage handles.
|
||||
renderer_output_transport_sender: Option<crate::RendererOutputTransportSender>,
|
||||
renderer_runtime_owner: Option<RendererBrowserContextRuntimeOwner>,
|
||||
storage_partition: StoragePartition,
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn id(&self) -> BrowserContextId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub(crate) fn web_contents(&self, handle: WebContentsHandle) -> Result<&WebContents, String> {
|
||||
if handle.context() != self.id {
|
||||
return Err("WebContents belongs to a different BrowserContext".into());
|
||||
}
|
||||
self.web_contents
|
||||
.get(&handle.id())
|
||||
.ok_or_else(|| "WebContents unavailable".into())
|
||||
}
|
||||
|
||||
pub(crate) fn web_contents_mut(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<&mut WebContents, String> {
|
||||
if handle.context() != self.id {
|
||||
return Err("WebContents belongs to a different BrowserContext".into());
|
||||
}
|
||||
self.web_contents
|
||||
.get_mut(&handle.id())
|
||||
.ok_or_else(|| "WebContents unavailable".into())
|
||||
}
|
||||
|
||||
pub(crate) fn document(&self, handle: DocumentHandle) -> Result<&DocumentHost, String> {
|
||||
if handle.web_contents().context() != self.id {
|
||||
return Err("Document belongs to a different BrowserContext".into());
|
||||
}
|
||||
let document = self
|
||||
.web_contents
|
||||
.get(&handle.web_contents().id())
|
||||
.and_then(|contents| contents.main_frame.current_document.as_ref())
|
||||
.ok_or("NoDocumentLoaded")?;
|
||||
if document.id != handle.id() {
|
||||
return Err("Document changed".into());
|
||||
}
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
pub(crate) fn document_mut(
|
||||
&mut self,
|
||||
handle: DocumentHandle,
|
||||
) -> Result<&mut DocumentHost, String> {
|
||||
if handle.web_contents().context() != self.id {
|
||||
return Err("Document belongs to a different BrowserContext".into());
|
||||
}
|
||||
let document = self
|
||||
.web_contents
|
||||
.get_mut(&handle.web_contents().id())
|
||||
.and_then(|contents| contents.main_frame.current_document.as_mut())
|
||||
.ok_or("NoDocumentLoaded")?;
|
||||
if document.id != handle.id() {
|
||||
return Err("Document changed".into());
|
||||
}
|
||||
Ok(document)
|
||||
}
|
||||
|
||||
pub fn inherited_document_policy(
|
||||
&self,
|
||||
fetch_config: moli_fetch::FetchConfig,
|
||||
defaults: &crate::browser::PermissionDefaults,
|
||||
global_headers: &moli_fetch::RequestHeaders,
|
||||
global_network_conditions: Option<EmulatedNetworkConditions>,
|
||||
global_geolocation: Option<&EmulatedGeolocationOverrideState>,
|
||||
) -> super::web_contents::InheritedDocumentPolicy {
|
||||
let mut policy =
|
||||
self.inherited_resource_policy(fetch_config, global_headers, global_network_conditions);
|
||||
policy.permissions = self.permission_overrides.snapshot(defaults);
|
||||
policy.emulation.geolocation = policy
|
||||
.emulation
|
||||
.geolocation
|
||||
.or_else(|| global_geolocation.cloned());
|
||||
policy
|
||||
}
|
||||
|
||||
pub fn inherited_resource_policy(
|
||||
&self,
|
||||
mut fetch_config: moli_fetch::FetchConfig,
|
||||
global_headers: &moli_fetch::RequestHeaders,
|
||||
global_network_conditions: Option<EmulatedNetworkConditions>,
|
||||
) -> super::web_contents::InheritedDocumentPolicy {
|
||||
if let Some(identity) = &self.browser_identity_override {
|
||||
fetch_config.set_browser_identity(identity.clone());
|
||||
}
|
||||
if let Some(proxy) = &self.network_policy.http_proxy {
|
||||
fetch_config.set_http_proxy(Some(proxy.clone()));
|
||||
}
|
||||
if let Some(no_proxy) = &self.network_policy.http_no_proxy {
|
||||
fetch_config.set_http_no_proxy(Some(no_proxy.clone()));
|
||||
}
|
||||
if let Some(verify) = self.network_policy.tls_verify_host {
|
||||
fetch_config.set_tls_verify_host(verify);
|
||||
}
|
||||
let mut emulation = self.emulation_defaults.clone();
|
||||
emulation.network_conditions = emulation.network_conditions.or(global_network_conditions);
|
||||
super::web_contents::InheritedDocumentPolicy {
|
||||
selected_web_contents: self.selected_web_contents,
|
||||
navigator_queries: Default::default(),
|
||||
fetch_config,
|
||||
extra_headers: super::web_contents::merge_extra_header_layers(&[
|
||||
global_headers,
|
||||
&self.network_policy.extra_headers,
|
||||
]),
|
||||
emulation,
|
||||
permissions: Vec::new(),
|
||||
storage: self.storage_partition.handles.clone(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn new(
|
||||
handles: BrowserContextStoragePartitionHandles,
|
||||
kind: StoragePartitionKind,
|
||||
http_cache_root: Option<PathBuf>,
|
||||
http_cache_max_bytes: Option<u64>,
|
||||
) -> Self {
|
||||
Self {
|
||||
id: BrowserContextId::allocate(),
|
||||
page_navigation_runtime_config: None,
|
||||
network_policy: ContextNetworkPolicy::default(),
|
||||
emulation_defaults: ContextEmulationDefaults::default(),
|
||||
environment_owner: Default::default(),
|
||||
browser_identity_override: None,
|
||||
permission_overrides: PermissionOverrides::default(),
|
||||
download_policy: None,
|
||||
downloads: DownloadManager::default(),
|
||||
web_contents: IndexMap::new(),
|
||||
selected_web_contents: None,
|
||||
renderer_output_transport_sender: None,
|
||||
renderer_runtime_owner: Some(RendererBrowserContextRuntime::new()),
|
||||
storage_partition: StoragePartition::new(
|
||||
handles,
|
||||
kind,
|
||||
http_cache_root,
|
||||
http_cache_max_bytes,
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn selected_web_contents_id(&self) -> Option<WebContentsId> {
|
||||
self.selected_web_contents
|
||||
}
|
||||
|
||||
pub fn select_web_contents(&mut self, id: WebContentsId) -> bool {
|
||||
if !self.web_contents.contains_key(&id) {
|
||||
return false;
|
||||
}
|
||||
self.selected_web_contents = Some(id);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn close_web_contents(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<ClosingWebContents, String> {
|
||||
self.web_contents(handle)?;
|
||||
let id = handle.id();
|
||||
let removed = self
|
||||
.web_contents
|
||||
.shift_remove(&id)
|
||||
.expect("validated WebContents must remain resident until close");
|
||||
if self.selected_web_contents == Some(id) {
|
||||
self.selected_web_contents = None;
|
||||
}
|
||||
for contents in self.web_contents.values_mut() {
|
||||
if contents
|
||||
.window
|
||||
.opener
|
||||
.is_some_and(|opener| opener.web_contents_id == id)
|
||||
{
|
||||
contents.window.opener = None;
|
||||
}
|
||||
}
|
||||
Ok(removed.begin_close())
|
||||
}
|
||||
|
||||
pub fn close_all_web_contents(&mut self) -> Vec<ClosingWebContents> {
|
||||
self.selected_web_contents = None;
|
||||
std::mem::take(&mut self.web_contents)
|
||||
.into_values()
|
||||
.map(WebContents::begin_close)
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn new_page_navigation_engine(&self, config: NavigationRuntimeConfig) -> NavigationEngine {
|
||||
let engine = NavigationEngine::new_with_runtime_config_and_browser_context_access(
|
||||
config,
|
||||
self.renderer_runtime_owner_access(),
|
||||
)
|
||||
.expect("live BrowserContext owner must accept a page engine");
|
||||
if let Some(sender) = self.renderer_output_transport_sender.clone() {
|
||||
engine.set_renderer_output_transport_sender(sender);
|
||||
}
|
||||
engine
|
||||
}
|
||||
|
||||
fn renderer_runtime(&self) -> RendererBrowserContextRuntime {
|
||||
self.renderer_runtime_owner
|
||||
.as_ref()
|
||||
.expect("BrowserContext renderer owner was already taken for teardown")
|
||||
.handle()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn renderer_runtime_id_for_test(&self) -> crate::RendererBrowserContextRuntimeId {
|
||||
self.renderer_runtime().id()
|
||||
}
|
||||
|
||||
fn renderer_runtime_owner_access(&self) -> RendererBrowserContextRuntimeOwnerAccess {
|
||||
self.renderer_runtime_owner
|
||||
.as_ref()
|
||||
.expect("BrowserContext renderer owner was already taken for teardown")
|
||||
.owner_access()
|
||||
}
|
||||
|
||||
pub(crate) fn shutdown(mut self) {
|
||||
let mut runtime = self
|
||||
.renderer_runtime_owner
|
||||
.take()
|
||||
.expect("BrowserContext renderer owner must exist until teardown");
|
||||
runtime.terminate_renderer_producers_for_owner_shutdown();
|
||||
drop(self);
|
||||
runtime.shutdown_network_and_join();
|
||||
}
|
||||
}
|
||||
|
||||
/// Context-scoped request defaults, with no frontend or session attribution.
|
||||
/// A missing value inherits the process policy; an empty bypass list does not.
|
||||
#[derive(Debug, Clone, Default, PartialEq, Eq)]
|
||||
pub struct ContextNetworkPolicy {
|
||||
pub http_proxy: Option<String>,
|
||||
pub http_no_proxy: Option<String>,
|
||||
pub tls_verify_host: Option<bool>,
|
||||
pub extra_headers: moli_fetch::RequestHeaders,
|
||||
}
|
||||
|
||||
/// Installed context defaults; inherited process values are not copied here.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub struct ContextEmulationDefaults {
|
||||
pub network_conditions: Option<EmulatedNetworkConditions>,
|
||||
pub geolocation: Option<EmulatedGeolocationOverrideState>,
|
||||
pub device_metrics: Option<EmulatedDeviceMetrics>,
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use serde_json::json;
|
||||
|
||||
fn permission_rule(setting: &str) -> crate::page::PermissionOverrideRegistration {
|
||||
crate::page::PermissionOverrideRegistration {
|
||||
permission: json!({"name": "geolocation"}),
|
||||
setting: setting.into(),
|
||||
origin: None,
|
||||
embedded_origin: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn install_test_document(
|
||||
context: &mut BrowserContext,
|
||||
page: crate::page::Page,
|
||||
) -> WebContentsHandle {
|
||||
let (handle, _) = context
|
||||
.register_web_contents(WebContents::default())
|
||||
.expect("test WebContents");
|
||||
context
|
||||
.replace_document_for_test(
|
||||
handle,
|
||||
Some(DocumentHost::new(
|
||||
crate::browser::DocumentId::allocate(),
|
||||
page,
|
||||
)),
|
||||
)
|
||||
.expect("test Document");
|
||||
handle
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_refresh_visits_all_documents_without_protocol_projection() {
|
||||
let browser = crate::runtime::Browser::new(crate::runtime::BrowserConfig::default())
|
||||
.expect("test browser");
|
||||
let mut defaults = crate::browser::PermissionDefaults::default();
|
||||
defaults.set(permission_rule("denied"));
|
||||
let mut context = BrowserContext::new(
|
||||
BrowserContextStoragePartitionHandles::memory(),
|
||||
StoragePartitionKind::Ephemeral,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let mut handles = Vec::new();
|
||||
for title in ["first", "second"] {
|
||||
let page = browser
|
||||
.fetch(&format!("data:text/html,<title>{title}</title>"))
|
||||
.await
|
||||
.expect("test Page");
|
||||
handles.push(install_test_document(&mut context, page));
|
||||
}
|
||||
context.set_permission_override(&mut defaults, permission_rule("granted"));
|
||||
|
||||
assert_eq!(context.permission_override_count(), 1);
|
||||
for expected in ["granted", "denied"] {
|
||||
let pending = context
|
||||
.start_permission_update(&defaults)
|
||||
.expect("permission update")
|
||||
.expect("loaded Documents");
|
||||
assert_eq!(pending.page_count_for_test(), 2);
|
||||
let completed = pending.wait().await;
|
||||
context
|
||||
.finish_permission_update(completed)
|
||||
.expect("permission completion");
|
||||
for (handle, title) in handles.iter().copied().zip(["first", "second"]) {
|
||||
let document = context
|
||||
.document_handle_for_web_contents(handle)
|
||||
.expect("test WebContents")
|
||||
.expect("test Document");
|
||||
assert_eq!(context.document_title(document).unwrap(), title);
|
||||
let result = context
|
||||
.evaluate_document_expression_for_test(
|
||||
document,
|
||||
"navigator.permissions.query({name:'geolocation'}).then(status => status.state)",
|
||||
true,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
assert_eq!(result, json!({"type": "string", "value": expected}));
|
||||
}
|
||||
context.clear_permission_overrides();
|
||||
}
|
||||
assert_eq!(defaults.snapshot(), vec![permission_rule("denied")]);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn permission_completion_cannot_retarget_replacement_web_contents() {
|
||||
let browser = crate::runtime::Browser::new(crate::runtime::BrowserConfig::default())
|
||||
.expect("test browser");
|
||||
let mut context = BrowserContext::new(
|
||||
BrowserContextStoragePartitionHandles::memory(),
|
||||
StoragePartitionKind::Ephemeral,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let first = browser
|
||||
.fetch("data:text/html,<title>first</title>")
|
||||
.await
|
||||
.unwrap();
|
||||
let old_handle = install_test_document(&mut context, first);
|
||||
let mut defaults = crate::browser::PermissionDefaults::default();
|
||||
context.set_permission_override(&mut defaults, permission_rule("granted"));
|
||||
let completed = context
|
||||
.start_permission_update(&defaults)
|
||||
.unwrap()
|
||||
.unwrap()
|
||||
.wait()
|
||||
.await;
|
||||
|
||||
context
|
||||
.close_web_contents(old_handle)
|
||||
.unwrap()
|
||||
.close_async()
|
||||
.await;
|
||||
let replacement = browser
|
||||
.fetch("data:text/html,<title>replacement</title>")
|
||||
.await
|
||||
.unwrap();
|
||||
let replacement = install_test_document(&mut context, replacement);
|
||||
assert_ne!(replacement, old_handle);
|
||||
assert_eq!(
|
||||
context.finish_permission_update(completed),
|
||||
Err("NoDocumentLoaded".into())
|
||||
);
|
||||
let document = context
|
||||
.document_handle_for_web_contents(replacement)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(context.document_title(document).unwrap(), "replacement");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn resource_maintenance_never_adopts_a_peers_transport_or_policy() {
|
||||
let mut context = BrowserContext::new(
|
||||
BrowserContextStoragePartitionHandles::memory(),
|
||||
StoragePartitionKind::Ephemeral,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
let mut ids = Vec::new();
|
||||
for (agent, verify) in [("Native/first", false), ("Native/peer", true)] {
|
||||
let mut contents = WebContents::default();
|
||||
contents.install_navigation_engine(
|
||||
context.new_page_navigation_engine(NavigationRuntimeConfig::default()),
|
||||
);
|
||||
contents.browser_identity_override = Some(BrowserIdentityProfile::new(agent, "en"));
|
||||
contents.tls_verify_host_override = Some(verify);
|
||||
ids.push(contents.id());
|
||||
context.web_contents.insert(contents.id(), contents);
|
||||
}
|
||||
let inherited = context.inherited_resource_policy(
|
||||
moli_fetch::FetchConfig::default(),
|
||||
&Default::default(),
|
||||
None,
|
||||
);
|
||||
let first = context
|
||||
.web_contents
|
||||
.get_mut(&ids[0])
|
||||
.unwrap()
|
||||
.ensure_resource_request_client(&inherited)
|
||||
.unwrap();
|
||||
let peer = context
|
||||
.web_contents
|
||||
.get_mut(&ids[1])
|
||||
.unwrap()
|
||||
.ensure_resource_request_client(&inherited)
|
||||
.unwrap();
|
||||
assert!(!first.shares_resource_runtime_with(&peer));
|
||||
assert!(std::sync::Arc::ptr_eq(
|
||||
&first.cookie_store(),
|
||||
&peer.cookie_store()
|
||||
));
|
||||
let contents = context.web_contents.get_mut(&ids[0]).unwrap();
|
||||
contents.invalidate_resource_runtime();
|
||||
assert!(
|
||||
contents
|
||||
.start_resource_runtime_rebuild(&inherited)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
let rebuilt = contents.ensure_resource_request_client(&inherited).unwrap();
|
||||
assert!(!rebuilt.shares_resource_runtime_with(&peer));
|
||||
assert!(rebuilt.shares_page_network_policy_with(&first));
|
||||
assert!(
|
||||
rebuilt
|
||||
.browser_resource_runtime()
|
||||
.matches_fetch_config(contents.navigation_fetch_config().unwrap())
|
||||
);
|
||||
assert!(
|
||||
!contents
|
||||
.navigation_fetch_config()
|
||||
.unwrap()
|
||||
.tls_verify_host()
|
||||
);
|
||||
assert_eq!(
|
||||
rebuilt
|
||||
.browser_resource_runtime()
|
||||
.browser_identity()
|
||||
.user_agent(),
|
||||
"Native/first"
|
||||
);
|
||||
let contents = context.web_contents.get(&ids[1]).unwrap();
|
||||
assert!(
|
||||
contents
|
||||
.navigation_fetch_config()
|
||||
.unwrap()
|
||||
.tls_verify_host()
|
||||
);
|
||||
assert_eq!(
|
||||
peer.browser_resource_runtime()
|
||||
.browser_identity()
|
||||
.user_agent(),
|
||||
"Native/peer"
|
||||
);
|
||||
assert!(
|
||||
peer.browser_resource_runtime()
|
||||
.matches_fetch_config(contents.navigation_fetch_config().unwrap())
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn document_policy_capture_needs_no_projection_or_current_document() {
|
||||
let mut context = BrowserContext::new(
|
||||
BrowserContextStoragePartitionHandles::memory(),
|
||||
StoragePartitionKind::Ephemeral,
|
||||
None,
|
||||
None,
|
||||
);
|
||||
context.network_policy.extra_headers = vec![("X-Policy".into(), "context".into())].into();
|
||||
context.network_policy.tls_verify_host = Some(false);
|
||||
context
|
||||
.set_default_locale_override(Some("fr-FR".into()))
|
||||
.unwrap();
|
||||
let mut contents = WebContents::default();
|
||||
contents.install_navigation_engine(
|
||||
context.new_page_navigation_engine(NavigationRuntimeConfig::default()),
|
||||
);
|
||||
contents.network_request_policy.extra_headers =
|
||||
vec![("X-Policy".into(), "page".into())].into();
|
||||
contents.emulation_policy.script_execution_disabled = true;
|
||||
contents.emulation_policy.max_touch_points = 1;
|
||||
assert!(
|
||||
contents
|
||||
.start_fetch_interception_update(true, None)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
let id = contents.id();
|
||||
context.web_contents.insert(id, contents);
|
||||
let geolocation = EmulatedGeolocationOverrideState::Position(
|
||||
crate::browser::EmulatedGeolocationOverride {
|
||||
latitude: 48.85837,
|
||||
longitude: 2.294481,
|
||||
accuracy: 7.0,
|
||||
altitude: None,
|
||||
altitude_accuracy: None,
|
||||
heading: None,
|
||||
speed: None,
|
||||
},
|
||||
);
|
||||
let inherited = context.inherited_document_policy(
|
||||
moli_fetch::FetchConfig::default(),
|
||||
&crate::browser::PermissionDefaults::default(),
|
||||
&vec![
|
||||
("X-Global".into(), "global".into()),
|
||||
("X-Policy".into(), "global".into()),
|
||||
]
|
||||
.into(),
|
||||
Some(EmulatedNetworkConditions::offline()),
|
||||
Some(&geolocation),
|
||||
);
|
||||
let contents = context.web_contents.get_mut(&id).unwrap();
|
||||
let policy = contents
|
||||
.capture_document_policy(inherited, &url::Url::parse("about:blank").unwrap())
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
policy.extra_http_headers.to_byte_strings(),
|
||||
[
|
||||
("X-Global".into(), "global".into()),
|
||||
("X-Policy".into(), "page".into())
|
||||
]
|
||||
);
|
||||
assert_eq!(context.environment_owner.locale().as_deref(), Some("fr-FR"));
|
||||
assert!(policy.network_offline);
|
||||
assert_eq!(policy.navigator_overrides.online, Some(false));
|
||||
assert_eq!(policy.navigator_overrides.max_touch_points, 1);
|
||||
assert_eq!(
|
||||
policy
|
||||
.navigator_overrides
|
||||
.geolocation
|
||||
.as_ref()
|
||||
.map(|position| position.latitude),
|
||||
Some(48.85837),
|
||||
);
|
||||
assert!(policy.script_execution_disabled);
|
||||
assert!(policy.fetch_subresource_interception_enabled);
|
||||
assert!(
|
||||
!contents
|
||||
.navigation_engine_for_test()
|
||||
.unwrap()
|
||||
.fetch_config()
|
||||
.tls_verify_host()
|
||||
);
|
||||
assert!(contents.main_frame.current_document.is_none());
|
||||
assert!(
|
||||
contents
|
||||
.start_fetch_interception_update(false, None)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
assert_eq!(contents.fetch_subresource_interception(), (false, None));
|
||||
assert!(
|
||||
policy.fetch_subresource_interception_enabled,
|
||||
"capture is a value, not a live registration view"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
use crate::{
|
||||
browser::{
|
||||
DocumentHandle, WebContentsHandle,
|
||||
web_contents::{
|
||||
JavaScriptDialogClosed, JavaScriptDialogError, JavaScriptDialogKey,
|
||||
JavaScriptDialogSnapshot,
|
||||
},
|
||||
},
|
||||
page::RendererPendingJavaScriptDialog,
|
||||
};
|
||||
|
||||
use super::BrowserContext;
|
||||
|
||||
impl BrowserContext {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn clear_web_contents_javascript_dialogs_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?.javascript_dialogs.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn web_contents_has_pending_javascript_dialog(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<bool, String> {
|
||||
Ok(!self.web_contents(handle)?.javascript_dialogs.is_empty())
|
||||
}
|
||||
|
||||
pub fn install_document_javascript_dialog(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
dialog: RendererPendingJavaScriptDialog,
|
||||
) -> Result<JavaScriptDialogKey, String> {
|
||||
if let Err(error) = self.ensure_document_current(document) {
|
||||
let _ = dialog.finish(false, String::new());
|
||||
return Err(error);
|
||||
}
|
||||
Ok(self
|
||||
.web_contents_mut(document.web_contents())?
|
||||
.javascript_dialogs
|
||||
.install(document.id(), dialog))
|
||||
}
|
||||
|
||||
pub fn document_javascript_dialog_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
key: JavaScriptDialogKey,
|
||||
) -> Option<JavaScriptDialogSnapshot> {
|
||||
self.ensure_document_current(document).ok()?;
|
||||
self.web_contents(document.web_contents())
|
||||
.ok()?
|
||||
.javascript_dialogs
|
||||
.snapshot(key)
|
||||
}
|
||||
|
||||
pub fn set_document_javascript_dialog_prompt_text(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
key: JavaScriptDialogKey,
|
||||
prompt_text: String,
|
||||
) -> Result<(), JavaScriptDialogError> {
|
||||
self.ensure_document_current(document)
|
||||
.map_err(|_| JavaScriptDialogError::NotFound)?;
|
||||
self.web_contents_mut(document.web_contents())
|
||||
.map_err(|_| JavaScriptDialogError::NotFound)?
|
||||
.javascript_dialogs
|
||||
.set_prompt_text(key, prompt_text)
|
||||
}
|
||||
|
||||
pub fn finish_document_javascript_dialog(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
key: JavaScriptDialogKey,
|
||||
accepted: bool,
|
||||
prompt_text: Option<String>,
|
||||
) -> Option<JavaScriptDialogClosed> {
|
||||
self.ensure_document_current(document).ok()?;
|
||||
self.web_contents_mut(document.web_contents())
|
||||
.ok()?
|
||||
.javascript_dialogs
|
||||
.finish(key, accepted, prompt_text)
|
||||
}
|
||||
|
||||
pub fn dismiss_document_javascript_dialog(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
key: JavaScriptDialogKey,
|
||||
) {
|
||||
if self.ensure_document_current(document).is_ok()
|
||||
&& let Ok(contents) = self.web_contents_mut(document.web_contents())
|
||||
{
|
||||
contents.javascript_dialogs.dismiss(key);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,704 @@
|
||||
use super::BrowserContext;
|
||||
use crate::browser::{DocumentHandle, WebContentsHandle};
|
||||
use crate::page::{
|
||||
CompletedPageCommand, PendingPageCommand, RendererCaptureScreencastFrameReply,
|
||||
RendererCaptureScreencastFrameRequest, RendererCaptureScreenshotReply,
|
||||
RendererCaptureScreenshotRequest, RendererCommandTurnOutput, RendererPageDiagnosticsSnapshot,
|
||||
RendererResourceTextSearchOutcome, RendererSetDocumentContentResult, SubresourceNetworkRecord,
|
||||
};
|
||||
|
||||
struct PendingDocumentCommand {
|
||||
document: DocumentHandle,
|
||||
pending: PendingPageCommand,
|
||||
}
|
||||
|
||||
struct CompletedDocumentCommand {
|
||||
document: DocumentHandle,
|
||||
completed: Result<CompletedPageCommand, String>,
|
||||
}
|
||||
|
||||
impl PendingDocumentCommand {
|
||||
async fn wait(self) -> CompletedDocumentCommand {
|
||||
CompletedDocumentCommand {
|
||||
document: self.document,
|
||||
completed: self.pending.wait().await.map_err(|error| error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletedDocumentCommand {
|
||||
fn renderer_output_predecessor(&self) -> Option<crate::RendererOutputFence> {
|
||||
self.completed
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(CompletedPageCommand::renderer_output_predecessor)
|
||||
}
|
||||
|
||||
fn into_parts(self) -> (DocumentHandle, Result<CompletedPageCommand, String>) {
|
||||
(self.document, self.completed)
|
||||
}
|
||||
}
|
||||
|
||||
macro_rules! define_document_command {
|
||||
($pending:ident, $completed:ident) => {
|
||||
pub struct $pending(PendingDocumentCommand);
|
||||
|
||||
pub struct $completed(CompletedDocumentCommand);
|
||||
|
||||
impl $pending {
|
||||
pub fn new(document: DocumentHandle, pending: PendingPageCommand) -> Self {
|
||||
Self(PendingDocumentCommand { document, pending })
|
||||
}
|
||||
|
||||
pub async fn wait(self) -> $completed {
|
||||
$completed(self.0.wait().await)
|
||||
}
|
||||
}
|
||||
|
||||
impl $completed {
|
||||
pub fn document(&self) -> DocumentHandle {
|
||||
self.0.document
|
||||
}
|
||||
|
||||
pub fn into_parts(self) -> (DocumentHandle, Result<CompletedPageCommand, String>) {
|
||||
self.0.into_parts()
|
||||
}
|
||||
}
|
||||
};
|
||||
($pending:ident, $completed:ident, renderer_output_predecessor) => {
|
||||
define_document_command!($pending, $completed);
|
||||
|
||||
impl $completed {
|
||||
pub fn renderer_output_predecessor(&self) -> Option<crate::RendererOutputFence> {
|
||||
self.0.renderer_output_predecessor()
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
define_document_command!(
|
||||
PendingSetDocumentContent,
|
||||
CompletedSetDocumentContent,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingCaptureDocumentSnapshot,
|
||||
CompletedCaptureDocumentSnapshot,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingCaptureDocumentImage,
|
||||
CompletedCaptureDocumentImage,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingCaptureDocumentScreencastFrame,
|
||||
CompletedCaptureDocumentScreencastFrame
|
||||
);
|
||||
define_document_command!(
|
||||
PendingDocumentResourceTextSearch,
|
||||
CompletedDocumentResourceTextSearch,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingDocumentCspBypassUpdate,
|
||||
CompletedDocumentCspBypassUpdate,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingTopLevelSameDocumentNavigation,
|
||||
CompletedTopLevelSameDocumentNavigation,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingTopLevelHistoryTraversal,
|
||||
CompletedTopLevelHistoryTraversal,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingNavigationHistoryReset,
|
||||
CompletedNavigationHistoryReset,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingChildFrameNavigation,
|
||||
CompletedChildFrameNavigation,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingDocumentStorageKeySnapshot,
|
||||
CompletedDocumentStorageKeySnapshot
|
||||
);
|
||||
define_document_command!(
|
||||
PendingChildFrameTreeSnapshot,
|
||||
CompletedChildFrameTreeSnapshot
|
||||
);
|
||||
define_document_command!(
|
||||
PendingDocumentCookieOwnerSnapshot,
|
||||
CompletedDocumentCookieOwnerSnapshot
|
||||
);
|
||||
define_document_command!(PendingDocumentBlobRead, CompletedDocumentBlobRead);
|
||||
define_document_command!(
|
||||
PendingNetworkResourceLoadPreparation,
|
||||
CompletedNetworkResourceLoadPreparation
|
||||
);
|
||||
define_document_command!(
|
||||
PendingAppManifestLoadPreparation,
|
||||
CompletedAppManifestLoadPreparation,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingAppManifestPublication,
|
||||
CompletedAppManifestPublication,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(PendingDocumentInputCommand, CompletedDocumentInputCommand);
|
||||
define_document_command!(
|
||||
PendingDocumentAutofillTrigger,
|
||||
CompletedDocumentAutofillTrigger
|
||||
);
|
||||
define_document_command!(
|
||||
PendingDocumentLifecycleStop,
|
||||
CompletedDocumentLifecycleStop,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
define_document_command!(
|
||||
PendingDocumentDiagnosticsSnapshot,
|
||||
CompletedDocumentDiagnosticsSnapshot,
|
||||
renderer_output_predecessor
|
||||
);
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
impl CompletedDocumentDiagnosticsSnapshot {
|
||||
pub fn page_state_for_test(
|
||||
&self,
|
||||
) -> Option<&std::sync::Arc<moli_renderer_v8::RendererPageState>> {
|
||||
self.0
|
||||
.completed
|
||||
.as_ref()
|
||||
.ok()
|
||||
.map(CompletedPageCommand::page_state)
|
||||
}
|
||||
|
||||
pub fn into_page_completion_for_test(self) -> Result<CompletedPageCommand, String> {
|
||||
self.0.completed
|
||||
}
|
||||
}
|
||||
define_document_command!(
|
||||
PendingChildFrameLifecycleWork,
|
||||
CompletedChildFrameLifecycleWork
|
||||
);
|
||||
define_document_command!(
|
||||
PendingDocumentResourceRuntimeUpdate,
|
||||
CompletedDocumentResourceRuntimeUpdate
|
||||
);
|
||||
|
||||
impl CompletedTopLevelHistoryTraversal {
|
||||
pub fn renderer_accepted(&self) -> Option<bool> {
|
||||
self.0
|
||||
.completed
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(CompletedPageCommand::bool_reply_value)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DocumentSnapshot {
|
||||
pub url: String,
|
||||
pub html: String,
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub async fn apply_document_cookie_facade_overrides_for_test(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
overrides: Option<moli_cookie_jar::BrowserCookieFacadeOverrides>,
|
||||
) -> Result<(), String> {
|
||||
let page = &mut self.document_mut(document)?.page;
|
||||
match overrides {
|
||||
Some(overrides) if overrides != Default::default() => page
|
||||
.apply_document_cookie_facade_overrides_async(&overrides)
|
||||
.await
|
||||
.map_err(|error| error.to_string()),
|
||||
Some(_) | None => page
|
||||
.clear_document_cookie_facade_overrides_async()
|
||||
.await
|
||||
.map_err(|error| error.to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub async fn document_runtime_heap_usage_for_test(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<crate::page::RendererRuntimeHeapUsage, String> {
|
||||
let completion = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_runtime_heap_usage()
|
||||
.map_err(|error| error.to_string())?
|
||||
.wait()
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_runtime_heap_usage(completion)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn document_idle_override_for_test(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<Option<crate::page::EmulatedIdleOverride>, String> {
|
||||
Ok(self.document(document)?.page.idle_override())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub async fn evaluate_document_expression_for_test(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
expression: &str,
|
||||
await_promise: bool,
|
||||
) -> Result<serde_json::Value, String> {
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.evaluate_runtime_expression_without_navigation_follow_with_await_async(
|
||||
expression,
|
||||
await_promise,
|
||||
)
|
||||
.await
|
||||
.map_err(|error| format!("runtime evaluation failed: {error}"))
|
||||
}
|
||||
|
||||
pub fn document_handle_for_web_contents(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<DocumentHandle>, String> {
|
||||
let document = self
|
||||
.web_contents(handle)?
|
||||
.main_frame
|
||||
.current_document
|
||||
.as_ref()
|
||||
.map(|document| document.id);
|
||||
Ok(document.map(|document| DocumentHandle::new(handle, document)))
|
||||
}
|
||||
|
||||
pub fn start_set_document_content(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
html: String,
|
||||
) -> Result<PendingSetDocumentContent, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_set_document_content(frame_id, html)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingSetDocumentContent::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_set_document_content(
|
||||
&mut self,
|
||||
completed: CompletedSetDocumentContent,
|
||||
) -> Result<(RendererSetDocumentContentResult, RendererCommandTurnOutput), String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_set_document_content_command_turn(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_top_level_same_document_navigation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
url: String,
|
||||
) -> Result<PendingTopLevelSameDocumentNavigation, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_top_level_same_document_navigation(url)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingTopLevelSameDocumentNavigation::new(
|
||||
document, pending,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_top_level_same_document_navigation(
|
||||
&mut self,
|
||||
completed: CompletedTopLevelSameDocumentNavigation,
|
||||
) -> Result<(bool, RendererCommandTurnOutput), String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_top_level_same_document_navigation_command_turn(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_capture_document_screencast_frame(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
request: RendererCaptureScreencastFrameRequest,
|
||||
) -> Result<PendingCaptureDocumentScreencastFrame, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_capture_screencast_frame(request)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingCaptureDocumentScreencastFrame::new(
|
||||
document, pending,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_capture_document_screencast_frame(
|
||||
&mut self,
|
||||
completed: CompletedCaptureDocumentScreencastFrame,
|
||||
) -> Result<RendererCaptureScreencastFrameReply, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_capture_screencast_frame(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_capture_document_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingCaptureDocumentSnapshot, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_serialize_html()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingCaptureDocumentSnapshot::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_capture_document_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedCaptureDocumentSnapshot,
|
||||
) -> Result<DocumentSnapshot, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
let document = self.document_mut(document)?;
|
||||
let html = document
|
||||
.page
|
||||
.finish_serialize_html(completion?)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(DocumentSnapshot {
|
||||
url: document.page.final_url().as_str().to_owned(),
|
||||
html,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_capture_document_image(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
request: RendererCaptureScreenshotRequest,
|
||||
) -> Result<PendingCaptureDocumentImage, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_capture_screenshot_with_request(request)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingCaptureDocumentImage::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_capture_document_image(
|
||||
&mut self,
|
||||
completed: CompletedCaptureDocumentImage,
|
||||
) -> Result<RendererCaptureScreenshotReply, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_capture_screenshot(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_top_level_history_traversal(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
delta: i64,
|
||||
) -> Result<PendingTopLevelHistoryTraversal, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_top_level_history_traversal_by_delta(delta)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingTopLevelHistoryTraversal::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_top_level_history_traversal(
|
||||
&mut self,
|
||||
completed: CompletedTopLevelHistoryTraversal,
|
||||
) -> Result<bool, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_top_level_history_traversal_by_delta(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_navigation_history_reset(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingNavigationHistoryReset, String> {
|
||||
self.document(document)?;
|
||||
let pending = self
|
||||
.web_contents
|
||||
.get(&document.web_contents().id())
|
||||
.ok_or("NoDocumentLoaded")?
|
||||
.start_reset_navigation_history()?;
|
||||
Ok(PendingNavigationHistoryReset::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_navigation_history_reset(
|
||||
&mut self,
|
||||
completed: CompletedNavigationHistoryReset,
|
||||
) -> Result<bool, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document(document)?;
|
||||
self.web_contents
|
||||
.get_mut(&document.web_contents().id())
|
||||
.ok_or("NoDocumentLoaded")?
|
||||
.finish_reset_navigation_history(completion?)
|
||||
}
|
||||
|
||||
pub fn start_child_frame_navigation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
url: String,
|
||||
) -> Result<PendingChildFrameNavigation, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_child_frame_navigation_to_url(&frame_id, &url)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingChildFrameNavigation::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_child_frame_navigation(
|
||||
&mut self,
|
||||
completed: CompletedChildFrameNavigation,
|
||||
) -> Result<(bool, RendererCommandTurnOutput), String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_child_frame_navigation_to_url_command_turn(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_child_frame_document_resource_text_search(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
url: String,
|
||||
query: String,
|
||||
case_sensitive: bool,
|
||||
is_regex: bool,
|
||||
) -> Result<PendingDocumentResourceTextSearch, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_child_frame_resource_search_by_lines(
|
||||
frame_id,
|
||||
url,
|
||||
query,
|
||||
case_sensitive,
|
||||
is_regex,
|
||||
)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentResourceTextSearch::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn start_document_text_search(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
text: String,
|
||||
query: String,
|
||||
case_sensitive: bool,
|
||||
is_regex: bool,
|
||||
) -> Result<PendingDocumentResourceTextSearch, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_text_search_by_lines(text, query, case_sensitive, is_regex)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentResourceTextSearch::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_resource_text_search(
|
||||
&mut self,
|
||||
completed: CompletedDocumentResourceTextSearch,
|
||||
) -> Result<RendererResourceTextSearchOutcome, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_resource_search_by_lines(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn set_document_javascript_dialog_handler_enabled(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
enabled: bool,
|
||||
) -> Result<(), String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_set_javascript_dialog_handler_enabled(enabled)
|
||||
.map_err(|error| error.to_string())?;
|
||||
drop(pending);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_document_csp_bypass_update(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
bypass: bool,
|
||||
) -> Result<PendingDocumentCspBypassUpdate, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_set_bypass_content_security_policy(bypass)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentCspBypassUpdate::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_csp_bypass_update(
|
||||
&mut self,
|
||||
completed: CompletedDocumentCspBypassUpdate,
|
||||
) -> Result<(), String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_set_bypass_content_security_policy(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn document_subresource_network_records(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<Vec<SubresourceNetworkRecord>, String> {
|
||||
Ok(self
|
||||
.document(document)?
|
||||
.page
|
||||
.subresource_network_records()
|
||||
.to_vec())
|
||||
}
|
||||
|
||||
pub fn ensure_document_current(&self, document: DocumentHandle) -> Result<(), String> {
|
||||
self.document(document).map(|_| ())
|
||||
}
|
||||
|
||||
pub fn document_observable_output_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<Vec<crate::page::ScriptObservableOutputItem>, String> {
|
||||
Ok(self
|
||||
.document(document)?
|
||||
.page
|
||||
.script_execution()
|
||||
.observable_output_items()
|
||||
.to_vec())
|
||||
}
|
||||
|
||||
pub fn document_response_headers(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<&[(String, String)], String> {
|
||||
Ok(self.document(document)?.page.headers())
|
||||
}
|
||||
|
||||
pub fn start_document_lifecycle_stop(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentLifecycleStop, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_stop_document_lifecycle()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentLifecycleStop::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_lifecycle_stop(
|
||||
&mut self,
|
||||
completed: CompletedDocumentLifecycleStop,
|
||||
) -> Result<RendererCommandTurnOutput, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_stop_document_lifecycle(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_document_diagnostics_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentDiagnosticsSnapshot, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_page_diagnostics_snapshot()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentDiagnosticsSnapshot::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_diagnostics_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedDocumentDiagnosticsSnapshot,
|
||||
) -> Result<RendererPageDiagnosticsSnapshot, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
let snapshot = self
|
||||
.document_mut(document)?
|
||||
.page
|
||||
.finish_page_diagnostics_snapshot(completion?)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(snapshot)
|
||||
}
|
||||
|
||||
pub fn start_document_child_frame_lifecycle_work(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
timeout: std::time::Duration,
|
||||
) -> Result<PendingChildFrameLifecycleWork, String> {
|
||||
self.document(document)?;
|
||||
let storage = self.storage_partition.handles.clone();
|
||||
let pending = self
|
||||
.web_contents_mut(document.web_contents())?
|
||||
.start_child_frame_lifecycle_work(&storage, timeout)?;
|
||||
Ok(PendingChildFrameLifecycleWork::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_child_frame_lifecycle_work(
|
||||
&mut self,
|
||||
completed: CompletedChildFrameLifecycleWork,
|
||||
) -> Result<(bool, RendererCommandTurnOutput), String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document(document)?;
|
||||
let completed = self
|
||||
.web_contents_mut(document.web_contents())?
|
||||
.complete_child_frame_lifecycle_work(completion?)?;
|
||||
Ok(completed)
|
||||
}
|
||||
|
||||
pub fn crash_web_contents_renderer_from_io(
|
||||
&self,
|
||||
web_contents: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
if let Some(document) = self
|
||||
.web_contents(web_contents)?
|
||||
.main_frame
|
||||
.current_document
|
||||
.as_ref()
|
||||
{
|
||||
document.page.crash_devtools_target_from_io();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,364 @@
|
||||
use super::BrowserContext;
|
||||
use crate::browser::DocumentHandle;
|
||||
use crate::page::{
|
||||
CompletedPageCommand, EmulatedIdleOverride, EmulatedMediaOverrides, PendingPageCommand,
|
||||
ViewportSurface,
|
||||
};
|
||||
|
||||
pub enum DocumentPolicyUpdate {
|
||||
NetworkRequestPolicy {
|
||||
extra_headers: moli_fetch::RequestHeaders,
|
||||
bypass_service_worker: bool,
|
||||
cache_disabled: bool,
|
||||
blocked_url_patterns: Vec<String>,
|
||||
},
|
||||
ExtraHttpHeaders(moli_fetch::RequestHeaders),
|
||||
BlockedUrls(Vec<String>),
|
||||
BypassServiceWorker(bool),
|
||||
NetworkOffline(bool),
|
||||
IdleOverride(Option<EmulatedIdleOverride>),
|
||||
NavigatorOverrides(moli_page_types::NavigatorOverrides),
|
||||
EmulatedMedia(EmulatedMediaOverrides),
|
||||
ViewportSurface(Option<ViewportSurface>),
|
||||
ScriptExecutionDisabled(bool),
|
||||
DocumentActivity(moli_page_types::DocumentActivity),
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum DocumentPolicyUpdateKind {
|
||||
SetNetworkRequestPolicy,
|
||||
SetExtraHttpHeaders,
|
||||
SetBlockedUrls,
|
||||
SetBypassServiceWorker,
|
||||
SetNetworkConditions,
|
||||
SetIdleOverride,
|
||||
SetNavigatorOverrides,
|
||||
SetEmulatedMedia,
|
||||
SetViewportSurface,
|
||||
SetScriptExecutionDisabled,
|
||||
SetDocumentActivity,
|
||||
}
|
||||
|
||||
pub struct PendingDocumentPolicyUpdate {
|
||||
document: DocumentHandle,
|
||||
kind: DocumentPolicyUpdateKind,
|
||||
pending: PendingPageCommand,
|
||||
}
|
||||
|
||||
pub struct CompletedDocumentPolicyUpdate {
|
||||
document: DocumentHandle,
|
||||
kind: DocumentPolicyUpdateKind,
|
||||
completed: Result<CompletedPageCommand, String>,
|
||||
}
|
||||
|
||||
pub struct PendingDocumentPolicyBatch {
|
||||
context: crate::browser::BrowserContextId,
|
||||
document: DocumentHandle,
|
||||
admission_error: Option<String>,
|
||||
updates: Vec<PendingDocumentPolicyUpdate>,
|
||||
}
|
||||
|
||||
pub struct CompletedDocumentPolicyBatch {
|
||||
context: crate::browser::BrowserContextId,
|
||||
document: DocumentHandle,
|
||||
admission_error: Option<String>,
|
||||
updates: Vec<CompletedDocumentPolicyUpdate>,
|
||||
}
|
||||
|
||||
pub struct DocumentRuntimePolicyReconciliation {
|
||||
pub script_execution_disabled: bool,
|
||||
pub emulated_media: EmulatedMediaOverrides,
|
||||
pub network_offline: bool,
|
||||
pub viewport_surface: Option<ViewportSurface>,
|
||||
}
|
||||
|
||||
impl PendingDocumentPolicyUpdate {
|
||||
pub async fn wait(self) -> CompletedDocumentPolicyUpdate {
|
||||
CompletedDocumentPolicyUpdate {
|
||||
document: self.document,
|
||||
kind: self.kind,
|
||||
completed: self.pending.wait().await.map_err(|error| error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletedDocumentPolicyUpdate {
|
||||
pub fn document(&self) -> DocumentHandle {
|
||||
self.document
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingDocumentPolicyBatch {
|
||||
pub async fn wait(self) -> CompletedDocumentPolicyBatch {
|
||||
let mut updates = Vec::with_capacity(self.updates.len());
|
||||
for update in self.updates {
|
||||
updates.push(update.wait().await);
|
||||
}
|
||||
CompletedDocumentPolicyBatch {
|
||||
context: self.context,
|
||||
document: self.document,
|
||||
admission_error: self.admission_error,
|
||||
updates,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletedDocumentPolicyBatch {
|
||||
pub fn context(&self) -> crate::browser::BrowserContextId {
|
||||
self.context
|
||||
}
|
||||
|
||||
pub fn document(&self) -> DocumentHandle {
|
||||
self.document
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn start_document_runtime_policy_reconciliation(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
policy: DocumentRuntimePolicyReconciliation,
|
||||
) -> PendingDocumentPolicyBatch {
|
||||
let updates = vec![
|
||||
DocumentPolicyUpdate::ScriptExecutionDisabled(policy.script_execution_disabled),
|
||||
DocumentPolicyUpdate::EmulatedMedia(policy.emulated_media),
|
||||
DocumentPolicyUpdate::NetworkOffline(policy.network_offline),
|
||||
DocumentPolicyUpdate::ViewportSurface(policy.viewport_surface),
|
||||
];
|
||||
self.start_document_policy_batch(document, updates)
|
||||
}
|
||||
|
||||
pub fn start_document_policy_batch(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
updates: Vec<DocumentPolicyUpdate>,
|
||||
) -> PendingDocumentPolicyBatch {
|
||||
let mut pending = Vec::with_capacity(updates.len());
|
||||
let mut admission_error = None;
|
||||
for update in updates {
|
||||
match self.start_document_policy_update(document, update) {
|
||||
Ok(update) => pending.push(update),
|
||||
Err(error) => {
|
||||
admission_error.get_or_insert(error);
|
||||
}
|
||||
}
|
||||
}
|
||||
PendingDocumentPolicyBatch {
|
||||
context: document.web_contents().context(),
|
||||
document,
|
||||
admission_error,
|
||||
updates: pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn start_document_policy_batch_with_surface(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
updates: Vec<DocumentPolicyUpdate>,
|
||||
foreground: bool,
|
||||
navigator_queries: moli_page_types::NavigatorQueryOverrides,
|
||||
global_network_conditions: Option<crate::browser::EmulatedNetworkConditions>,
|
||||
global_geolocation: Option<&crate::browser::EmulatedGeolocationOverrideState>,
|
||||
) -> PendingDocumentPolicyBatch {
|
||||
let mut batch = self.start_document_policy_batch(document, updates);
|
||||
match self.start_document_page_surface_update(
|
||||
document,
|
||||
foreground,
|
||||
navigator_queries,
|
||||
global_network_conditions,
|
||||
global_geolocation,
|
||||
) {
|
||||
Ok(surface) => {
|
||||
batch.updates.extend(surface.updates);
|
||||
if let Some(error) = surface.admission_error {
|
||||
batch.admission_error.get_or_insert(error);
|
||||
}
|
||||
}
|
||||
Err(error) => {
|
||||
batch.admission_error.get_or_insert(error);
|
||||
}
|
||||
}
|
||||
batch
|
||||
}
|
||||
|
||||
pub fn start_document_policy_update(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
update: DocumentPolicyUpdate,
|
||||
) -> Result<PendingDocumentPolicyUpdate, String> {
|
||||
let page = &mut self.document_mut(document)?.page;
|
||||
let (kind, pending) = match update {
|
||||
DocumentPolicyUpdate::NetworkRequestPolicy {
|
||||
extra_headers,
|
||||
bypass_service_worker,
|
||||
cache_disabled,
|
||||
blocked_url_patterns,
|
||||
} => (
|
||||
DocumentPolicyUpdateKind::SetNetworkRequestPolicy,
|
||||
page.start_set_network_request_policy(
|
||||
&extra_headers,
|
||||
bypass_service_worker,
|
||||
cache_disabled,
|
||||
&blocked_url_patterns,
|
||||
),
|
||||
),
|
||||
DocumentPolicyUpdate::ExtraHttpHeaders(headers) => (
|
||||
DocumentPolicyUpdateKind::SetExtraHttpHeaders,
|
||||
page.start_set_extra_http_headers(&headers),
|
||||
),
|
||||
DocumentPolicyUpdate::BlockedUrls(patterns) => (
|
||||
DocumentPolicyUpdateKind::SetBlockedUrls,
|
||||
page.start_set_blocked_url_patterns(&patterns),
|
||||
),
|
||||
DocumentPolicyUpdate::BypassServiceWorker(bypass) => (
|
||||
DocumentPolicyUpdateKind::SetBypassServiceWorker,
|
||||
page.start_set_bypass_service_worker(bypass),
|
||||
),
|
||||
DocumentPolicyUpdate::NetworkOffline(offline) => (
|
||||
DocumentPolicyUpdateKind::SetNetworkConditions,
|
||||
page.start_set_network_offline(offline),
|
||||
),
|
||||
DocumentPolicyUpdate::IdleOverride(override_) => (
|
||||
DocumentPolicyUpdateKind::SetIdleOverride,
|
||||
page.start_set_idle_override(override_),
|
||||
),
|
||||
DocumentPolicyUpdate::NavigatorOverrides(overrides) => (
|
||||
DocumentPolicyUpdateKind::SetNavigatorOverrides,
|
||||
page.start_set_navigator_overrides(&overrides),
|
||||
),
|
||||
DocumentPolicyUpdate::EmulatedMedia(overrides) => (
|
||||
DocumentPolicyUpdateKind::SetEmulatedMedia,
|
||||
page.start_set_emulated_media(&overrides),
|
||||
),
|
||||
DocumentPolicyUpdate::ViewportSurface(surface) => (
|
||||
DocumentPolicyUpdateKind::SetViewportSurface,
|
||||
page.start_set_viewport_surface(surface),
|
||||
),
|
||||
DocumentPolicyUpdate::ScriptExecutionDisabled(disabled) => (
|
||||
DocumentPolicyUpdateKind::SetScriptExecutionDisabled,
|
||||
page.start_set_script_execution_disabled(disabled),
|
||||
),
|
||||
DocumentPolicyUpdate::DocumentActivity(activity) => (
|
||||
DocumentPolicyUpdateKind::SetDocumentActivity,
|
||||
page.start_set_document_activity(activity),
|
||||
),
|
||||
};
|
||||
Ok(PendingDocumentPolicyUpdate {
|
||||
document,
|
||||
kind,
|
||||
pending: pending.map_err(|error| error.to_string())?,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish_document_policy_update(
|
||||
&mut self,
|
||||
completed: CompletedDocumentPolicyUpdate,
|
||||
) -> Result<(), String> {
|
||||
let completion = completed.completed?;
|
||||
let page = match self.document_mut(completed.document) {
|
||||
Ok(document) => &mut document.page,
|
||||
Err(error) => {
|
||||
completion
|
||||
.into_unit_page_command_turn()
|
||||
.map(drop)
|
||||
.map_err(|unexpected| {
|
||||
format!(
|
||||
"retired Document policy command returned an unexpected reply: {unexpected}"
|
||||
)
|
||||
})?;
|
||||
return Err(error);
|
||||
}
|
||||
};
|
||||
match completed.kind {
|
||||
DocumentPolicyUpdateKind::SetNetworkRequestPolicy => {
|
||||
page.finish_set_network_request_policy(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetExtraHttpHeaders => {
|
||||
page.finish_set_extra_http_headers(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetBlockedUrls => {
|
||||
page.finish_set_blocked_url_patterns(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetBypassServiceWorker => {
|
||||
page.finish_set_bypass_service_worker(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetNetworkConditions => {
|
||||
page.finish_set_network_offline(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetIdleOverride => page.finish_set_idle_override(completion),
|
||||
DocumentPolicyUpdateKind::SetNavigatorOverrides => {
|
||||
page.finish_set_navigator_overrides(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetEmulatedMedia => {
|
||||
page.finish_set_emulated_media(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetViewportSurface => {
|
||||
page.finish_set_viewport_surface(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetScriptExecutionDisabled => {
|
||||
page.finish_set_script_execution_disabled(completion)
|
||||
}
|
||||
DocumentPolicyUpdateKind::SetDocumentActivity => {
|
||||
page.finish_set_document_activity(completion)
|
||||
}
|
||||
}
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn finish_document_policy_batch(
|
||||
&mut self,
|
||||
completed: CompletedDocumentPolicyBatch,
|
||||
) -> Result<(), String> {
|
||||
let mut first_error = completed.admission_error;
|
||||
for update in completed.updates {
|
||||
if let Err(error) = self.finish_document_policy_update(update) {
|
||||
first_error.get_or_insert(error);
|
||||
}
|
||||
}
|
||||
first_error.map_or(Ok(()), Err)
|
||||
}
|
||||
|
||||
pub fn start_document_page_surface_update(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
foreground: bool,
|
||||
navigator_queries: moli_page_types::NavigatorQueryOverrides,
|
||||
global_network_conditions: Option<crate::browser::EmulatedNetworkConditions>,
|
||||
global_geolocation: Option<&crate::browser::EmulatedGeolocationOverrideState>,
|
||||
) -> Result<PendingDocumentPolicyBatch, String> {
|
||||
let mut surface = self.page_surface_for_web_contents(
|
||||
document.web_contents(),
|
||||
foreground,
|
||||
global_network_conditions,
|
||||
global_geolocation,
|
||||
)?;
|
||||
surface.navigator_queries = navigator_queries;
|
||||
Ok(self.start_document_policy_batch(
|
||||
document,
|
||||
vec![
|
||||
DocumentPolicyUpdate::NavigatorOverrides(surface.navigator_overrides()),
|
||||
DocumentPolicyUpdate::DocumentActivity(surface.document_activity()),
|
||||
],
|
||||
))
|
||||
}
|
||||
|
||||
pub fn page_surface_for_web_contents(
|
||||
&self,
|
||||
handle: crate::browser::WebContentsHandle,
|
||||
foreground: bool,
|
||||
global_network_conditions: Option<crate::browser::EmulatedNetworkConditions>,
|
||||
global_geolocation: Option<&crate::browser::EmulatedGeolocationOverrideState>,
|
||||
) -> Result<crate::browser::web_contents::PageSurface, String> {
|
||||
Ok(self.web_contents(handle)?.page_surface(
|
||||
foreground,
|
||||
self.emulation_defaults
|
||||
.network_conditions
|
||||
.or(global_network_conditions),
|
||||
self.emulation_defaults
|
||||
.geolocation
|
||||
.as_ref()
|
||||
.or(global_geolocation),
|
||||
self.emulation_defaults.device_metrics.as_ref(),
|
||||
))
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
use super::{
|
||||
BrowserContext,
|
||||
document_commands::{
|
||||
CompletedChildFrameTreeSnapshot, CompletedDocumentCookieOwnerSnapshot,
|
||||
CompletedDocumentStorageKeySnapshot, PendingChildFrameTreeSnapshot,
|
||||
PendingDocumentCookieOwnerSnapshot, PendingDocumentStorageKeySnapshot,
|
||||
},
|
||||
};
|
||||
use crate::browser::DocumentHandle;
|
||||
use crate::page::{ChildFrameTreeSnapshot, DocumentCookieOwnerSnapshot};
|
||||
|
||||
impl BrowserContext {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn document_response_status_for_test(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<u16, String> {
|
||||
Ok(self.document(document)?.page.status())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn document_script_execution_for_test(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<crate::page::ScriptExecutionReport, String> {
|
||||
Ok(self.document(document)?.page.script_execution().clone())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn document_renderer_inspection_endpoint_for_test(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<moli_renderer_v8::RendererInspectionEndpoint, String> {
|
||||
Ok(self.document(document)?.page.renderer_inspection_endpoint())
|
||||
}
|
||||
|
||||
pub fn start_document_storage_key_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentStorageKeySnapshot, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_document_storage_key_snapshot()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentStorageKeySnapshot::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_storage_key_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedDocumentStorageKeySnapshot,
|
||||
) -> Result<String, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_document_storage_key_snapshot(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_child_frame_tree_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingChildFrameTreeSnapshot, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_child_frame_tree_snapshot()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingChildFrameTreeSnapshot::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_child_frame_tree_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedChildFrameTreeSnapshot,
|
||||
) -> Result<Vec<ChildFrameTreeSnapshot>, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_child_frame_tree_snapshot(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_document_cookie_owner_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentCookieOwnerSnapshot, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_document_cookie_owner_snapshot()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentCookieOwnerSnapshot::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_cookie_owner_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedDocumentCookieOwnerSnapshot,
|
||||
) -> Result<DocumentCookieOwnerSnapshot, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_document_cookie_owner_snapshot(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,433 @@
|
||||
use super::BrowserContext;
|
||||
use crate::{
|
||||
RendererOutputFence,
|
||||
browser::{DocumentHandle, WebContentsHandle},
|
||||
page::{
|
||||
CompletedPageCommand, PendingPageCommand, PendingSubresourceContinueOutcome,
|
||||
RendererSyntheticResponseBody, SubresourceAuthCredentials, SubresourceResourceType,
|
||||
},
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
/// Closed Browser-native command family for a concrete renderer Document.
|
||||
/// Frontend routing identities never enter this API.
|
||||
pub enum DocumentFetchCommand {
|
||||
ContinueRequest {
|
||||
internal_id: u64,
|
||||
url: Option<Url>,
|
||||
method: Option<String>,
|
||||
body: Option<Option<String>>,
|
||||
headers: Option<moli_fetch::RequestHeaderOverride>,
|
||||
intercept_response: bool,
|
||||
handle_auth_requests: bool,
|
||||
},
|
||||
ContinueAuth {
|
||||
internal_id: u64,
|
||||
auth: SubresourceAuthCredentials,
|
||||
},
|
||||
CancelAuth {
|
||||
internal_id: u64,
|
||||
},
|
||||
FailAuth {
|
||||
internal_id: u64,
|
||||
error_text: String,
|
||||
},
|
||||
FailRequest {
|
||||
internal_id: u64,
|
||||
error_text: String,
|
||||
},
|
||||
FailResponse {
|
||||
internal_id: u64,
|
||||
error_text: String,
|
||||
},
|
||||
FulfillRequest {
|
||||
internal_id: u64,
|
||||
response_code: u16,
|
||||
response_headers: Vec<(String, String)>,
|
||||
response_body: RendererSyntheticResponseBody,
|
||||
},
|
||||
FulfillResponse {
|
||||
internal_id: u64,
|
||||
response_code: u16,
|
||||
response_headers: Vec<(String, String)>,
|
||||
response_body: RendererSyntheticResponseBody,
|
||||
},
|
||||
ContinueResponse {
|
||||
internal_id: u64,
|
||||
response_code: Option<u16>,
|
||||
response_headers: Option<Vec<(String, String)>>,
|
||||
},
|
||||
DispatchWebSocketText {
|
||||
socket_id: u64,
|
||||
data: String,
|
||||
},
|
||||
DispatchWebSocketBinary {
|
||||
socket_id: u64,
|
||||
data: Vec<u8>,
|
||||
},
|
||||
CloseWebSocket {
|
||||
socket_id: u64,
|
||||
code: Option<u16>,
|
||||
reason: String,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
enum DocumentFetchCommandKind {
|
||||
InterceptionUpdate { accept_stale: bool },
|
||||
ContinueRequest,
|
||||
ContinueAuth,
|
||||
CancelAuth,
|
||||
FailAuth,
|
||||
FailRequest,
|
||||
FailResponse,
|
||||
FulfillRequest,
|
||||
FulfillResponse,
|
||||
ContinueResponse,
|
||||
DispatchWebSocketText,
|
||||
DispatchWebSocketBinary,
|
||||
CloseWebSocket,
|
||||
}
|
||||
|
||||
pub struct PendingDocumentFetchCommand {
|
||||
document: DocumentHandle,
|
||||
kind: DocumentFetchCommandKind,
|
||||
pending: PendingPageCommand,
|
||||
}
|
||||
|
||||
pub struct CompletedDocumentFetchCommand {
|
||||
document: DocumentHandle,
|
||||
kind: DocumentFetchCommandKind,
|
||||
completed: Result<CompletedPageCommand, String>,
|
||||
}
|
||||
|
||||
pub enum DocumentFetchCommandOutcome {
|
||||
Continued(PendingSubresourceContinueOutcome),
|
||||
Complete,
|
||||
}
|
||||
|
||||
impl DocumentFetchCommandOutcome {
|
||||
pub fn into_continue_outcome(self) -> Result<PendingSubresourceContinueOutcome, String> {
|
||||
match self {
|
||||
Self::Continued(outcome) => Ok(outcome),
|
||||
Self::Complete => Err("Fetch command did not produce a continue outcome".to_owned()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingDocumentFetchCommand {
|
||||
fn new(
|
||||
document: DocumentHandle,
|
||||
kind: DocumentFetchCommandKind,
|
||||
pending: PendingPageCommand,
|
||||
) -> Self {
|
||||
Self {
|
||||
document,
|
||||
kind,
|
||||
pending,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn wait(self) -> CompletedDocumentFetchCommand {
|
||||
CompletedDocumentFetchCommand {
|
||||
document: self.document,
|
||||
kind: self.kind,
|
||||
completed: self.pending.wait().await.map_err(|error| error.to_string()),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletedDocumentFetchCommand {
|
||||
pub fn document(&self) -> DocumentHandle {
|
||||
self.document
|
||||
}
|
||||
|
||||
pub fn renderer_output_predecessor(&self) -> Option<RendererOutputFence> {
|
||||
self.completed
|
||||
.as_ref()
|
||||
.ok()
|
||||
.and_then(CompletedPageCommand::renderer_output_predecessor)
|
||||
}
|
||||
|
||||
pub fn is_interception_update(&self) -> bool {
|
||||
matches!(
|
||||
self.kind,
|
||||
DocumentFetchCommandKind::InterceptionUpdate { .. }
|
||||
)
|
||||
}
|
||||
|
||||
fn into_parts(
|
||||
self,
|
||||
) -> (
|
||||
DocumentHandle,
|
||||
DocumentFetchCommandKind,
|
||||
Result<CompletedPageCommand, String>,
|
||||
) {
|
||||
(self.document, self.kind, self.completed)
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn web_contents_fetch_interception_for_test(
|
||||
&self,
|
||||
web_contents: WebContentsHandle,
|
||||
) -> Result<(bool, Option<SubresourceResourceType>), String> {
|
||||
Ok(self
|
||||
.web_contents(web_contents)?
|
||||
.fetch_subresource_interception())
|
||||
}
|
||||
|
||||
pub fn start_document_fetch_command(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
command: DocumentFetchCommand,
|
||||
) -> Result<PendingDocumentFetchCommand, String> {
|
||||
let page = &self.document(document)?.page;
|
||||
let (kind, pending) = match command {
|
||||
DocumentFetchCommand::ContinueRequest {
|
||||
internal_id,
|
||||
url,
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
intercept_response,
|
||||
handle_auth_requests,
|
||||
} => (
|
||||
DocumentFetchCommandKind::ContinueRequest,
|
||||
page.start_continue_pending_subresource_fetch(
|
||||
internal_id,
|
||||
url,
|
||||
method,
|
||||
body,
|
||||
headers,
|
||||
intercept_response,
|
||||
handle_auth_requests,
|
||||
),
|
||||
),
|
||||
DocumentFetchCommand::ContinueAuth { internal_id, auth } => (
|
||||
DocumentFetchCommandKind::ContinueAuth,
|
||||
page.start_continue_pending_subresource_auth(internal_id, auth),
|
||||
),
|
||||
DocumentFetchCommand::CancelAuth { internal_id } => (
|
||||
DocumentFetchCommandKind::CancelAuth,
|
||||
page.start_cancel_pending_subresource_auth(internal_id),
|
||||
),
|
||||
DocumentFetchCommand::FailAuth {
|
||||
internal_id,
|
||||
error_text,
|
||||
} => (
|
||||
DocumentFetchCommandKind::FailAuth,
|
||||
page.start_fail_pending_subresource_auth(internal_id, error_text),
|
||||
),
|
||||
DocumentFetchCommand::FailRequest {
|
||||
internal_id,
|
||||
error_text,
|
||||
} => (
|
||||
DocumentFetchCommandKind::FailRequest,
|
||||
page.start_fail_pending_subresource_fetch(internal_id, error_text),
|
||||
),
|
||||
DocumentFetchCommand::FailResponse {
|
||||
internal_id,
|
||||
error_text,
|
||||
} => (
|
||||
DocumentFetchCommandKind::FailResponse,
|
||||
page.start_fail_pending_subresource_response(internal_id, error_text),
|
||||
),
|
||||
DocumentFetchCommand::FulfillRequest {
|
||||
internal_id,
|
||||
response_code,
|
||||
response_headers,
|
||||
response_body,
|
||||
} => (
|
||||
DocumentFetchCommandKind::FulfillRequest,
|
||||
page.start_fulfill_pending_subresource_fetch(
|
||||
internal_id,
|
||||
response_code,
|
||||
response_headers,
|
||||
response_body,
|
||||
),
|
||||
),
|
||||
DocumentFetchCommand::FulfillResponse {
|
||||
internal_id,
|
||||
response_code,
|
||||
response_headers,
|
||||
response_body,
|
||||
} => (
|
||||
DocumentFetchCommandKind::FulfillResponse,
|
||||
page.start_fulfill_pending_subresource_response(
|
||||
internal_id,
|
||||
response_code,
|
||||
response_headers,
|
||||
response_body,
|
||||
),
|
||||
),
|
||||
DocumentFetchCommand::ContinueResponse {
|
||||
internal_id,
|
||||
response_code,
|
||||
response_headers,
|
||||
} => (
|
||||
DocumentFetchCommandKind::ContinueResponse,
|
||||
page.start_continue_pending_subresource_response(
|
||||
internal_id,
|
||||
response_code,
|
||||
response_headers,
|
||||
),
|
||||
),
|
||||
DocumentFetchCommand::DispatchWebSocketText { socket_id, data } => (
|
||||
DocumentFetchCommandKind::DispatchWebSocketText,
|
||||
page.start_receive_synthetic_websocket_text(socket_id, data),
|
||||
),
|
||||
DocumentFetchCommand::DispatchWebSocketBinary { socket_id, data } => (
|
||||
DocumentFetchCommandKind::DispatchWebSocketBinary,
|
||||
page.start_receive_synthetic_websocket_binary(socket_id, data),
|
||||
),
|
||||
DocumentFetchCommand::CloseWebSocket {
|
||||
socket_id,
|
||||
code,
|
||||
reason,
|
||||
} => (
|
||||
DocumentFetchCommandKind::CloseWebSocket,
|
||||
page.start_close_synthetic_websocket_from_server(socket_id, code, reason),
|
||||
),
|
||||
};
|
||||
Ok(PendingDocumentFetchCommand::new(
|
||||
document,
|
||||
kind,
|
||||
pending.map_err(|error| error.to_string())?,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn start_web_contents_fetch_interception_update(
|
||||
&mut self,
|
||||
web_contents: WebContentsHandle,
|
||||
enabled: bool,
|
||||
resource_type: Option<SubresourceResourceType>,
|
||||
accept_stale_completion: bool,
|
||||
) -> Result<Option<PendingDocumentFetchCommand>, String> {
|
||||
let contents = self.web_contents_mut(web_contents)?;
|
||||
let document = contents
|
||||
.main_frame
|
||||
.current_document
|
||||
.as_ref()
|
||||
.map(|document| document.id);
|
||||
let pending = contents.start_fetch_interception_update(enabled, resource_type)?;
|
||||
Ok(match (document, pending) {
|
||||
(Some(document), Some(pending)) => Some(PendingDocumentFetchCommand::new(
|
||||
DocumentHandle::new(web_contents, document),
|
||||
DocumentFetchCommandKind::InterceptionUpdate {
|
||||
accept_stale: accept_stale_completion,
|
||||
},
|
||||
pending,
|
||||
)),
|
||||
(None, None) => None,
|
||||
_ => unreachable!("fetch interception update must follow current Document presence"),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn install_web_contents_fetch_interception_policy(
|
||||
&mut self,
|
||||
web_contents: WebContentsHandle,
|
||||
enabled: bool,
|
||||
resource_type: Option<SubresourceResourceType>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(web_contents)?
|
||||
.install_fetch_interception_policy(enabled, resource_type);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn finish_document_fetch_command(
|
||||
&mut self,
|
||||
completed: CompletedDocumentFetchCommand,
|
||||
) -> Result<DocumentFetchCommandOutcome, String> {
|
||||
let (document, kind, completed) = completed.into_parts();
|
||||
let completion = completed?;
|
||||
if let DocumentFetchCommandKind::InterceptionUpdate { accept_stale } = kind {
|
||||
return self.finish_document_fetch_interception_update(
|
||||
document,
|
||||
completion,
|
||||
accept_stale,
|
||||
);
|
||||
}
|
||||
let page = &mut self.document_mut(document)?.page;
|
||||
match kind {
|
||||
DocumentFetchCommandKind::ContinueRequest => page
|
||||
.finish_continue_pending_subresource_fetch(completion)
|
||||
.map(DocumentFetchCommandOutcome::Continued),
|
||||
DocumentFetchCommandKind::ContinueAuth => page
|
||||
.finish_continue_pending_subresource_auth(completion)
|
||||
.map(DocumentFetchCommandOutcome::Continued),
|
||||
DocumentFetchCommandKind::CancelAuth => page
|
||||
.finish_cancel_pending_subresource_auth(completion)
|
||||
.map(|_| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::FailAuth => page
|
||||
.finish_fail_pending_subresource_auth(completion)
|
||||
.map(|_| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::FailRequest => page
|
||||
.finish_fail_pending_subresource_fetch(completion)
|
||||
.map(|_| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::FailResponse => page
|
||||
.finish_fail_pending_subresource_response(completion)
|
||||
.map(|_| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::FulfillRequest => page
|
||||
.finish_fulfill_pending_subresource_fetch(completion)
|
||||
.map(|()| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::FulfillResponse => page
|
||||
.finish_fulfill_pending_subresource_response(completion)
|
||||
.map(|()| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::ContinueResponse => page
|
||||
.finish_continue_pending_subresource_response(completion)
|
||||
.map(|()| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::DispatchWebSocketText => page
|
||||
.finish_receive_synthetic_websocket_text(completion)
|
||||
.map(|()| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::DispatchWebSocketBinary => page
|
||||
.finish_receive_synthetic_websocket_binary(completion)
|
||||
.map(|()| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::CloseWebSocket => page
|
||||
.finish_close_synthetic_websocket_from_server(completion)
|
||||
.map(|()| DocumentFetchCommandOutcome::Complete),
|
||||
DocumentFetchCommandKind::InterceptionUpdate { .. } => unreachable!(),
|
||||
}
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
fn finish_document_fetch_interception_update(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
completion: CompletedPageCommand,
|
||||
accept_stale: bool,
|
||||
) -> Result<DocumentFetchCommandOutcome, String> {
|
||||
match self.document_mut(document) {
|
||||
Ok(document) => document
|
||||
.page
|
||||
.finish_set_fetch_subresource_interception(completion)
|
||||
.map(|()| DocumentFetchCommandOutcome::Complete)
|
||||
.map_err(|error| error.to_string()),
|
||||
Err(error) if error == "NoDocumentLoaded" || accept_stale => {
|
||||
completion
|
||||
.into_unit_page_command_turn()
|
||||
.map(drop)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(DocumentFetchCommandOutcome::Complete)
|
||||
}
|
||||
Err(error) if error == "Document changed" => Err("Renderer Page changed".to_owned()),
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_unobserved_document_fetch_interception_update(
|
||||
completed: CompletedDocumentFetchCommand,
|
||||
) -> Result<DocumentFetchCommandOutcome, String> {
|
||||
let (_, kind, completed) = completed.into_parts();
|
||||
if !matches!(kind, DocumentFetchCommandKind::InterceptionUpdate { .. }) {
|
||||
return Err("NoDocumentLoaded".to_owned());
|
||||
}
|
||||
completed?
|
||||
.into_unit_page_command_turn()
|
||||
.map(drop)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(DocumentFetchCommandOutcome::Complete)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
use super::BrowserContext;
|
||||
use super::document_commands::{
|
||||
CompletedDocumentAutofillTrigger, CompletedDocumentInputCommand,
|
||||
PendingDocumentAutofillTrigger, PendingDocumentInputCommand,
|
||||
};
|
||||
use crate::browser::{DocumentHandle, DocumentLifetimeObserver};
|
||||
use crate::page::{
|
||||
PageInputExt, RendererAutofillTriggerOutcome, RendererAutofillTriggerRequest,
|
||||
RendererCommandTurnOutput, RendererDragData, RendererPointerEventProperties,
|
||||
RendererTouchPoint,
|
||||
};
|
||||
|
||||
/// Native input admitted to the current document, independent of inspector sessions.
|
||||
pub enum PageInputCommand {
|
||||
Key {
|
||||
event_name: String,
|
||||
key: String,
|
||||
code: String,
|
||||
text: String,
|
||||
modifiers: u8,
|
||||
auto_repeat: bool,
|
||||
should_insert_text: bool,
|
||||
},
|
||||
Mouse {
|
||||
x: f64,
|
||||
y: f64,
|
||||
event_name: String,
|
||||
button: i32,
|
||||
buttons: Option<i32>,
|
||||
click_count: i32,
|
||||
delta_x: f64,
|
||||
delta_y: f64,
|
||||
pointer: RendererPointerEventProperties,
|
||||
modifiers: u8,
|
||||
},
|
||||
Touch {
|
||||
points: Vec<RendererTouchPoint>,
|
||||
event_name: String,
|
||||
activate: bool,
|
||||
},
|
||||
Drag {
|
||||
x: f64,
|
||||
y: f64,
|
||||
event_name: String,
|
||||
data: RendererDragData,
|
||||
modifiers: u8,
|
||||
},
|
||||
InsertText(String),
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn observe_document_lifetime(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<DocumentLifetimeObserver, String> {
|
||||
Ok(self.document_mut(document)?.lifetime.observe())
|
||||
}
|
||||
|
||||
pub fn start_document_autofill_trigger(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
request: RendererAutofillTriggerRequest,
|
||||
) -> Result<PendingDocumentAutofillTrigger, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_autofill_trigger(request)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentAutofillTrigger::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_autofill_trigger(
|
||||
&mut self,
|
||||
completed: CompletedDocumentAutofillTrigger,
|
||||
) -> Result<RendererAutofillTriggerOutcome, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_autofill_trigger(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_document_input_command(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
command: PageInputCommand,
|
||||
) -> Result<PendingDocumentInputCommand, String> {
|
||||
let page = &self.document(document)?.page;
|
||||
let pending = match command {
|
||||
PageInputCommand::Key {
|
||||
event_name,
|
||||
key,
|
||||
code,
|
||||
text,
|
||||
modifiers,
|
||||
auto_repeat,
|
||||
should_insert_text,
|
||||
} => page.start_dispatch_key_event_with_outcome(
|
||||
&event_name,
|
||||
&key,
|
||||
&code,
|
||||
&text,
|
||||
modifiers,
|
||||
auto_repeat,
|
||||
should_insert_text,
|
||||
),
|
||||
PageInputCommand::Mouse {
|
||||
x,
|
||||
y,
|
||||
event_name,
|
||||
button,
|
||||
buttons,
|
||||
click_count,
|
||||
delta_x,
|
||||
delta_y,
|
||||
pointer,
|
||||
modifiers,
|
||||
} => page.start_dispatch_mouse_event_at_point_with_pointer_outcome(
|
||||
x,
|
||||
y,
|
||||
&event_name,
|
||||
button,
|
||||
buttons,
|
||||
click_count,
|
||||
delta_x,
|
||||
delta_y,
|
||||
pointer,
|
||||
modifiers,
|
||||
),
|
||||
PageInputCommand::Touch {
|
||||
points,
|
||||
event_name,
|
||||
activate,
|
||||
} => page.start_dispatch_touch_event_at_points_with_outcome(
|
||||
points,
|
||||
&event_name,
|
||||
activate,
|
||||
),
|
||||
PageInputCommand::Drag {
|
||||
x,
|
||||
y,
|
||||
event_name,
|
||||
data,
|
||||
modifiers,
|
||||
} => page.start_dispatch_drag_event_at_point_with_outcome(
|
||||
x,
|
||||
y,
|
||||
&event_name,
|
||||
data,
|
||||
modifiers,
|
||||
),
|
||||
PageInputCommand::InsertText(text) => page.start_insert_text_into_active_control(&text),
|
||||
}
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentInputCommand::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_input_command(
|
||||
&mut self,
|
||||
completed: CompletedDocumentInputCommand,
|
||||
) -> Result<RendererCommandTurnOutput, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
let completion = completion?;
|
||||
match self.document_mut(document) {
|
||||
Ok(document) => Ok(document.page.finish_page_command_turn(completion)),
|
||||
// InputInjector acknowledges an event consumed by the retired
|
||||
// renderer, but its frozen Page state cannot enter a replacement.
|
||||
Err(error) if matches!(error.as_str(), "Document changed" | "NoDocumentLoaded") => {
|
||||
Ok(completion.into_output())
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,853 @@
|
||||
use url::Url;
|
||||
|
||||
use crate::{
|
||||
browser::{
|
||||
DocumentHandle, DocumentId, NavigationId, NavigationRequestLoadPolicy,
|
||||
RendererPageResidenceIdentity, WebContentsHandle,
|
||||
web_contents::{
|
||||
AdmittedDocumentMaterialization, AdmittedNavigationLoad, BuiltInitialDocument,
|
||||
ClaimedNavigationRequest, CommittedDocumentNavigation, CommittedInitialDocument,
|
||||
DocumentNavigationDestination, HistoryTraversalDestination, InitialDocumentAdmission,
|
||||
InterceptedNavigationLoad, InterceptedNavigationResponse, NavigationInterceptionPermit,
|
||||
NavigationRequestInterception, PageNavigationHistoryEntry, PausedDocumentTransfer,
|
||||
PreparedDocumentNavigation, PreparedNavigationResponse, ResolvedHistoryTraversal,
|
||||
RetiringDocument, SameDocumentNavigationCommitted,
|
||||
},
|
||||
},
|
||||
page::{RendererDocumentLifecycleEvent, SameDocumentHistoryUpdate},
|
||||
};
|
||||
|
||||
use super::BrowserContext;
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub struct DocumentNavigationMetadata {
|
||||
pub current_url: Url,
|
||||
pub initiator_url: Option<Url>,
|
||||
pub requested_url: Url,
|
||||
pub redirected: bool,
|
||||
pub redirect_count: usize,
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn selected_document_handle(&self) -> Option<DocumentHandle> {
|
||||
let contents = WebContentsHandle::new(self.id, self.selected_web_contents?);
|
||||
let document = self
|
||||
.web_contents(contents)
|
||||
.ok()?
|
||||
.main_frame
|
||||
.current_document
|
||||
.as_ref()?;
|
||||
Some(DocumentHandle::new(contents, document.id))
|
||||
}
|
||||
|
||||
pub fn selected_document_navigation_metadata(&self) -> Option<DocumentNavigationMetadata> {
|
||||
self.document_navigation_metadata(self.selected_document_handle()?)
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub fn document_navigation_metadata(
|
||||
&self,
|
||||
handle: DocumentHandle,
|
||||
) -> Result<DocumentNavigationMetadata, String> {
|
||||
let page = &self.document(handle)?.page;
|
||||
Ok(DocumentNavigationMetadata {
|
||||
current_url: page.final_url().clone(),
|
||||
initiator_url: page.navigation_initiator_url().cloned(),
|
||||
requested_url: page.requested_url().clone(),
|
||||
redirected: page.navigation_redirected(),
|
||||
redirect_count: page.navigation_redirect_count(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn has_loaded_document(&self, handle: WebContentsHandle) -> bool {
|
||||
self.web_contents(handle)
|
||||
.is_ok_and(|contents| contents.main_frame.current_document.is_some())
|
||||
}
|
||||
|
||||
pub fn initial_document_build_pending(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.initial_document_build()
|
||||
.is_some_and(|build| build.completion.pending()))
|
||||
}
|
||||
|
||||
pub fn pending_document(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<(NavigationId, DocumentId)>, String> {
|
||||
Ok(self.web_contents(handle)?.navigation().pending_document())
|
||||
}
|
||||
|
||||
pub fn accepts_document_preparation(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: NavigationId,
|
||||
renderer: RendererPageResidenceIdentity,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.accepts_document_preparation(navigation, renderer))
|
||||
}
|
||||
|
||||
pub fn document_renderer_matches(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
renderer: RendererPageResidenceIdentity,
|
||||
) -> bool {
|
||||
self.document_handle(handle)
|
||||
.ok()
|
||||
.flatten()
|
||||
.and_then(|document| self.document_renderer_residence(document).ok())
|
||||
== Some(renderer)
|
||||
}
|
||||
|
||||
pub fn retire_document(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<RetiringDocument, String> {
|
||||
Ok(RetiringDocument::from_page(
|
||||
self.web_contents_mut(handle)?.replace_document(None),
|
||||
))
|
||||
}
|
||||
|
||||
pub fn document_handle(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<DocumentHandle>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.main_frame
|
||||
.current_document
|
||||
.as_ref()
|
||||
.map(|document| DocumentHandle::new(handle, document.id)))
|
||||
}
|
||||
|
||||
pub fn start_initial_document(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
inherited: crate::browser::web_contents::InheritedDocumentPolicy,
|
||||
) -> Result<InitialDocumentAdmission, String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.start_initial_document_build(inherited)
|
||||
}
|
||||
|
||||
pub fn commit_initial_document(
|
||||
&mut self,
|
||||
built: BuiltInitialDocument,
|
||||
) -> Result<CommittedInitialDocument, Box<BuiltInitialDocument>> {
|
||||
let Some(contents) = self.web_contents.get_mut(&built.key().web_contents()) else {
|
||||
return Err(Box::new(built));
|
||||
};
|
||||
contents.commit_initial_document(built)
|
||||
}
|
||||
|
||||
pub fn start_document_materialization(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: NavigationId,
|
||||
page: PreparedNavigationResponse,
|
||||
destination: DocumentNavigationDestination,
|
||||
inherited: crate::browser::web_contents::InheritedDocumentPolicy,
|
||||
) -> Result<AdmittedDocumentMaterialization, String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.start_document_materialization(navigation, page, destination, inherited)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn start_loaded_document_navigation_for_test(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: NavigationId,
|
||||
page: crate::page::Page,
|
||||
destination: DocumentNavigationDestination,
|
||||
artifacts: &crate::page::RendererPageCreationArtifacts,
|
||||
permissions: Vec<crate::page::PermissionOverrideRegistration>,
|
||||
) -> Result<
|
||||
impl std::future::Future<Output = anyhow::Result<PreparedDocumentNavigation>> + use<>,
|
||||
&'static str,
|
||||
> {
|
||||
self.web_contents(handle)
|
||||
.map_err(|_| "navigation WebContents unavailable")?
|
||||
.start_loaded_document_navigation(navigation, page, destination, artifacts, permissions)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn capture_document_policy_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
inherited: crate::browser::web_contents::InheritedDocumentPolicy,
|
||||
final_url: &Url,
|
||||
) -> Result<crate::runtime::PreparedDocumentPagePolicy, String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.capture_document_policy(inherited, final_url)
|
||||
}
|
||||
|
||||
pub fn start_navigation_load(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: NavigationId,
|
||||
policy: NavigationRequestLoadPolicy,
|
||||
inherited: crate::browser::web_contents::InheritedDocumentPolicy,
|
||||
) -> Result<AdmittedNavigationLoad, String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.start_navigation_load(navigation, policy, inherited)
|
||||
}
|
||||
|
||||
pub fn commit_document_navigation(
|
||||
&mut self,
|
||||
prepared: PreparedDocumentNavigation,
|
||||
) -> Result<CommittedDocumentNavigation, String> {
|
||||
self.web_contents
|
||||
.get_mut(&prepared.web_contents_id())
|
||||
.ok_or("navigation WebContents unavailable")?
|
||||
.commit_document_navigation(prepared)
|
||||
.map_err(str::to_owned)
|
||||
}
|
||||
|
||||
pub fn web_contents_identity(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<
|
||||
(
|
||||
crate::browser::WebContentsId,
|
||||
crate::browser::MainFrameSlotId,
|
||||
),
|
||||
String,
|
||||
> {
|
||||
let contents = self.web_contents(handle)?;
|
||||
Ok((contents.id(), contents.main_frame.id()))
|
||||
}
|
||||
|
||||
pub fn pause_navigation_request(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: NavigationId,
|
||||
request: NavigationRequestInterception,
|
||||
) -> Result<NavigationInterceptionPermit, String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.pause_navigation_request(navigation, request)
|
||||
}
|
||||
|
||||
pub fn take_navigation_request(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
) -> Option<ClaimedNavigationRequest> {
|
||||
self.web_contents
|
||||
.get_mut(&permit.web_contents())?
|
||||
.take_navigation_request(permit)
|
||||
}
|
||||
|
||||
pub fn start_claimed_navigation_request(
|
||||
&mut self,
|
||||
request: ClaimedNavigationRequest,
|
||||
inherited: crate::browser::web_contents::InheritedDocumentPolicy,
|
||||
) -> Result<InterceptedNavigationLoad, String> {
|
||||
self.web_contents
|
||||
.get_mut(&request.permit().web_contents())
|
||||
.ok_or("navigation WebContents unavailable")?
|
||||
.start_claimed_navigation_request(request, inherited)
|
||||
}
|
||||
|
||||
pub fn start_navigation_load_for_interception(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
policy: NavigationRequestLoadPolicy,
|
||||
inherited: crate::browser::web_contents::InheritedDocumentPolicy,
|
||||
) -> Result<AdmittedNavigationLoad, String> {
|
||||
self.web_contents
|
||||
.get_mut(&permit.web_contents())
|
||||
.ok_or("navigation WebContents unavailable")?
|
||||
.start_navigation_load_for_interception(permit, policy, inherited)
|
||||
}
|
||||
|
||||
pub fn pause_navigation_auth(
|
||||
&mut self,
|
||||
response: InterceptedNavigationResponse<moli_fetch::RawResponse>,
|
||||
) -> Result<NavigationInterceptionPermit, String> {
|
||||
self.web_contents
|
||||
.get_mut(&response.web_contents())
|
||||
.ok_or("navigation WebContents unavailable")?
|
||||
.pause_navigation_auth(response)
|
||||
}
|
||||
|
||||
pub fn take_navigation_auth(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
) -> Option<InterceptedNavigationResponse<moli_fetch::RawResponse>> {
|
||||
self.web_contents
|
||||
.get_mut(&permit.web_contents())?
|
||||
.take_navigation_auth(permit)
|
||||
}
|
||||
|
||||
pub fn pause_navigation_response(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: NavigationId,
|
||||
transfer: PausedDocumentTransfer,
|
||||
) -> Result<NavigationInterceptionPermit, String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.pause_navigation_response(navigation, transfer)
|
||||
}
|
||||
|
||||
pub fn take_navigation_response(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
) -> Option<PausedDocumentTransfer> {
|
||||
self.web_contents
|
||||
.get_mut(&permit.web_contents())?
|
||||
.take_navigation_response(permit)
|
||||
}
|
||||
|
||||
pub fn restore_navigation_response(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
transfer: PausedDocumentTransfer,
|
||||
) -> Result<(), Box<PausedDocumentTransfer>> {
|
||||
let Some(contents) = self.web_contents.get_mut(&permit.web_contents()) else {
|
||||
return Err(Box::new(transfer));
|
||||
};
|
||||
contents.restore_navigation_response(permit, transfer)
|
||||
}
|
||||
|
||||
pub fn resolve_history_traversal(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
destination: HistoryTraversalDestination,
|
||||
) -> Result<ResolvedHistoryTraversal, String> {
|
||||
self.web_contents(handle)?
|
||||
.resolve_history_traversal(destination)
|
||||
.map_err(str::to_owned)
|
||||
}
|
||||
|
||||
pub fn document_service_worker_client_id(&self, handle: DocumentHandle) -> Result<u64, String> {
|
||||
Ok(self.document(handle)?.page.service_worker_client_id())
|
||||
}
|
||||
|
||||
pub fn document_renderer_residence(
|
||||
&self,
|
||||
handle: DocumentHandle,
|
||||
) -> Result<RendererPageResidenceIdentity, String> {
|
||||
Ok(RendererPageResidenceIdentity::from_page(
|
||||
&self.document(handle)?.page,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn document_url(&self, handle: DocumentHandle) -> Result<Url, String> {
|
||||
Ok(self.document(handle)?.page.final_url().clone())
|
||||
}
|
||||
|
||||
pub fn document_title(&self, handle: DocumentHandle) -> Result<String, String> {
|
||||
Ok(self.document(handle)?.page.document_title())
|
||||
}
|
||||
|
||||
pub fn commit_document_title(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
change: &crate::RendererDocumentTitleChanged,
|
||||
) -> Result<Option<bool>, String> {
|
||||
Ok(self.web_contents_mut(handle)?.commit_document_title(change))
|
||||
}
|
||||
|
||||
pub fn document_lifecycle_snapshot(
|
||||
&self,
|
||||
handle: DocumentHandle,
|
||||
) -> Result<Option<crate::page::RendererDocumentLifecycleSnapshot>, String> {
|
||||
Ok(self.document(handle)?.lifecycle.snapshot())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn page_for_test(&self, handle: DocumentHandle) -> Option<&crate::page::Page> {
|
||||
Some(&self.document(handle).ok()?.page)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn page_for_test_mut(&mut self, handle: DocumentHandle) -> Option<&mut crate::page::Page> {
|
||||
Some(&mut self.document_mut(handle).ok()?.page)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn document_lifecycle_for_test(
|
||||
&self,
|
||||
handle: DocumentHandle,
|
||||
) -> Option<&crate::browser::DocumentLifecycle> {
|
||||
Some(&self.document(handle).ok()?.lifecycle)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn install_document_lifecycle_for_test(
|
||||
&mut self,
|
||||
handle: DocumentHandle,
|
||||
lifecycle: crate::browser::DocumentLifecycle,
|
||||
) -> Result<(), String> {
|
||||
let snapshot = lifecycle.snapshot().expect("fixture lifecycle");
|
||||
let previous = self
|
||||
.document(handle)?
|
||||
.lifecycle
|
||||
.snapshot()
|
||||
.map(|snapshot| (snapshot.frame, snapshot.document, snapshot.epoch));
|
||||
self.document_mut(handle)?.lifecycle = lifecycle;
|
||||
if previous != Some((snapshot.frame, snapshot.document, snapshot.epoch))
|
||||
|| snapshot.terminated.is_some()
|
||||
{
|
||||
self.web_contents_mut(handle.web_contents())?
|
||||
.javascript_dialogs
|
||||
.clear();
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn apply_document_lifecycle_for_test(
|
||||
&mut self,
|
||||
handle: DocumentHandle,
|
||||
event: RendererDocumentLifecycleEvent,
|
||||
) -> Result<bool, String> {
|
||||
self.document(handle)?;
|
||||
Ok(self
|
||||
.web_contents_mut(handle.web_contents())?
|
||||
.apply_document_lifecycle(event)
|
||||
.is_some())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn replace_document_identity_for_test(
|
||||
&mut self,
|
||||
handle: DocumentHandle,
|
||||
document: crate::browser::DocumentId,
|
||||
) -> Result<(), String> {
|
||||
let host = self.document_mut(handle)?;
|
||||
std::mem::take(&mut host.lifetime).supersede();
|
||||
host.id = document;
|
||||
host.lifecycle = crate::browser::DocumentLifecycle::default();
|
||||
self.web_contents_mut(handle.web_contents())?
|
||||
.javascript_dialogs
|
||||
.clear();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initial_document_url(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<String>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.initial_empty_document_url_if_current()
|
||||
.map(str::to_owned))
|
||||
}
|
||||
|
||||
pub fn begin_initial_empty_document(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
initial_url: String,
|
||||
creator: Option<crate::browser::web_contents::InitialDocumentCreator>,
|
||||
storage_key: Option<moli_storage_key::MoliStorageKey>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?.begin_initial_empty_document(
|
||||
initial_url,
|
||||
creator,
|
||||
storage_key,
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_initial_url_replaces_empty_document(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.mark_next_navigation_history_replace_initial_empty_document();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn mark_initial_empty_document_materialized_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.mark_initial_empty_document_materialized();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn mark_initial_empty_document_exited_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.mark_initial_empty_document_exited();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn initial_document_storage_key(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<moli_storage_key::MoliStorageKey>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.initial_empty_document_storage_key_if_current()
|
||||
.cloned())
|
||||
}
|
||||
|
||||
pub fn is_on_initial_document(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<bool>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.is_on_initial_empty_document())
|
||||
}
|
||||
|
||||
pub fn initial_document_has_pending_navigation(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.initial_empty_document_pending_cross_document_navigation())
|
||||
}
|
||||
|
||||
pub fn navigation_history_snapshot(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(usize, Vec<PageNavigationHistoryEntry>), String> {
|
||||
Ok(self.web_contents(handle)?.navigation_history_snapshot())
|
||||
}
|
||||
|
||||
pub fn navigation_history_entry_url(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
entry_id: i32,
|
||||
) -> Result<Option<String>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation_history_entry_url(entry_id))
|
||||
}
|
||||
|
||||
pub fn mark_next_navigation_history_replace_current(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.mark_next_navigation_history_replace_current();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn mark_next_navigation_history_traverse_to_entry(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
entry_id: i32,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.mark_next_navigation_history_traverse_to_entry(entry_id);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn commit_same_document_navigation(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
document: DocumentId,
|
||||
url: Url,
|
||||
history_update: SameDocumentHistoryUpdate,
|
||||
) -> Result<Option<SameDocumentNavigationCommitted>, String> {
|
||||
Ok(self
|
||||
.web_contents_mut(handle)?
|
||||
.commit_same_document_navigation(document, url, history_update))
|
||||
}
|
||||
|
||||
pub fn mark_renderer_crashed(&mut self, handle: WebContentsHandle) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?.mark_renderer_crashed();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn start_document_navigation(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<NavigationId, String> {
|
||||
Ok(self.web_contents_mut(handle)?.start_document_navigation())
|
||||
}
|
||||
|
||||
pub fn accepts_pending_navigation(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: &NavigationId,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.accepts_pending_document_navigation_event(navigation))
|
||||
}
|
||||
|
||||
pub fn accepts_document_body_completion(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: &NavigationId,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.accepts_document_body_completion_event(navigation))
|
||||
}
|
||||
|
||||
pub fn has_pending_document_navigation(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.has_pending_document_navigation())
|
||||
}
|
||||
|
||||
pub fn navigation_is_default(&self, handle: WebContentsHandle) -> Result<bool, String> {
|
||||
Ok(self.web_contents(handle)?.navigation().is_default())
|
||||
}
|
||||
|
||||
pub fn navigation_retains(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: NavigationId,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.retains_navigation(navigation))
|
||||
}
|
||||
|
||||
pub fn current_document_navigation(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<NavigationId>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.current_document_navigation())
|
||||
}
|
||||
|
||||
pub fn committed_document_navigation(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<NavigationId>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.committed_document_navigation())
|
||||
}
|
||||
|
||||
pub fn clear_pending_navigation_if_matches(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
navigation: &NavigationId,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents_mut(handle)?
|
||||
.clear_pending_document_navigation_if_matches(navigation))
|
||||
}
|
||||
|
||||
pub fn clear_document_navigation_state(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.clear_document_navigation_state();
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn replace_document_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
document: Option<crate::browser::web_contents::DocumentHost>,
|
||||
) -> Result<Option<crate::page::Page>, String> {
|
||||
Ok(self.web_contents_mut(handle)?.replace_document(document))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn navigation_cancellation_handle_for_test(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
token: &NavigationId,
|
||||
) -> Option<moli_fetch::FetchCancelHandle> {
|
||||
self.web_contents(handle)
|
||||
.ok()?
|
||||
.navigation()
|
||||
.document_navigation_cancellation_handle(token)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn arm_background_navigation_completion_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
token: &NavigationId,
|
||||
additional_cancellation: Option<moli_fetch::FetchCancelHandle>,
|
||||
) -> bool {
|
||||
self.web_contents_mut(handle).is_ok_and(|contents| {
|
||||
contents.arm_background_navigation_completion(token, additional_cancellation)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn settle_background_navigation_completion_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
token: &NavigationId,
|
||||
) -> bool {
|
||||
self.web_contents_mut(handle)
|
||||
.is_ok_and(|contents| contents.settle_background_navigation_completion(token))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn commit_pending_document_navigation_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
token: &NavigationId,
|
||||
) -> bool {
|
||||
self.web_contents_mut(handle)
|
||||
.is_ok_and(|contents| contents.commit_pending_document_navigation_if_matches(token))
|
||||
}
|
||||
|
||||
pub fn has_inflight_background_navigation_for_web_contents(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.has_inflight_background_navigation())
|
||||
}
|
||||
|
||||
pub fn accepts_any_pending_navigation_event(&self, token: &NavigationId) -> bool {
|
||||
self.web_contents.values().any(|contents| {
|
||||
contents
|
||||
.navigation()
|
||||
.accepts_pending_document_navigation_event(token)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn document_navigation_cancellation_handle_for_test(
|
||||
&self,
|
||||
token: &NavigationId,
|
||||
) -> Option<moli_fetch::FetchCancelHandle> {
|
||||
self.web_contents.values().find_map(|contents| {
|
||||
contents
|
||||
.navigation()
|
||||
.document_navigation_cancellation_handle(token)
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn accepts_any_document_body_completion_for_test(&self, token: &NavigationId) -> bool {
|
||||
self.web_contents.values().any(|contents| {
|
||||
contents
|
||||
.navigation()
|
||||
.accepts_document_body_completion_event(token)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn arm_background_navigation_completion(
|
||||
&mut self,
|
||||
token: &NavigationId,
|
||||
additional_cancellation: Option<moli_fetch::FetchCancelHandle>,
|
||||
) -> bool {
|
||||
let Some(contents) = self.web_contents.values_mut().find(|contents| {
|
||||
contents
|
||||
.navigation()
|
||||
.accepts_pending_document_navigation_event(token)
|
||||
}) else {
|
||||
if let Some(cancellation) = additional_cancellation {
|
||||
cancellation.cancel();
|
||||
}
|
||||
return false;
|
||||
};
|
||||
contents.arm_background_navigation_completion(token, additional_cancellation)
|
||||
}
|
||||
|
||||
pub fn settle_background_navigation_completion(&mut self, token: &NavigationId) -> bool {
|
||||
self.web_contents
|
||||
.values_mut()
|
||||
.any(|contents| contents.settle_background_navigation_completion(token))
|
||||
}
|
||||
|
||||
pub fn has_inflight_background_navigation(&self) -> bool {
|
||||
self.web_contents
|
||||
.values()
|
||||
.any(|contents| contents.navigation().has_inflight_background_navigation())
|
||||
}
|
||||
|
||||
pub fn apply_renderer_document_lifecycle(
|
||||
&mut self,
|
||||
renderer_page: RendererPageResidenceIdentity,
|
||||
event: RendererDocumentLifecycleEvent,
|
||||
) -> Option<crate::browser::web_contents::DocumentLifecycleEvent> {
|
||||
self.web_contents
|
||||
.values_mut()
|
||||
.find(|contents| {
|
||||
contents
|
||||
.main_frame
|
||||
.current_document
|
||||
.as_ref()
|
||||
.is_some_and(|document| {
|
||||
RendererPageResidenceIdentity::from_page(&document.page) == renderer_page
|
||||
})
|
||||
})?
|
||||
.apply_document_lifecycle(event)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn has_paused_navigation_auth_for_test(&self, handle: WebContentsHandle) -> bool {
|
||||
self.web_contents(handle)
|
||||
.is_ok_and(|contents| contents.navigation().has_paused_auth_for_test())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn paused_navigation_response_for_test(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Option<&PausedDocumentTransfer> {
|
||||
self.web_contents(handle)
|
||||
.ok()?
|
||||
.navigation()
|
||||
.paused_response_for_test()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn record_navigation_history_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
snapshot: (String, String),
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.record_navigation_history_for_test(snapshot);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,117 @@
|
||||
use crate::{
|
||||
browser::{BrowserContextId, DocumentId, PermissionDefaults, WebContentsId},
|
||||
page::{CompletedPageCommand, PendingPageCommand},
|
||||
};
|
||||
|
||||
use super::BrowserContext;
|
||||
|
||||
pub struct PendingContextPermissionUpdate {
|
||||
context_id: BrowserContextId,
|
||||
pages: Vec<PendingPermissionPageUpdate>,
|
||||
}
|
||||
|
||||
struct PendingPermissionPageUpdate {
|
||||
contents_id: WebContentsId,
|
||||
document_id: DocumentId,
|
||||
pending: PendingPageCommand,
|
||||
}
|
||||
|
||||
pub struct CompletedContextPermissionUpdate {
|
||||
context_id: BrowserContextId,
|
||||
pages: Vec<CompletedPermissionPageUpdate>,
|
||||
}
|
||||
|
||||
struct CompletedPermissionPageUpdate {
|
||||
contents_id: WebContentsId,
|
||||
document_id: DocumentId,
|
||||
completed: Result<CompletedPageCommand, String>,
|
||||
}
|
||||
|
||||
impl PendingContextPermissionUpdate {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn page_count_for_test(&self) -> usize {
|
||||
self.pages.len()
|
||||
}
|
||||
|
||||
pub async fn wait(self) -> CompletedContextPermissionUpdate {
|
||||
let mut pages = Vec::with_capacity(self.pages.len());
|
||||
for page in self.pages {
|
||||
pages.push(CompletedPermissionPageUpdate {
|
||||
contents_id: page.contents_id,
|
||||
document_id: page.document_id,
|
||||
completed: page.pending.wait().await.map_err(|error| error.to_string()),
|
||||
});
|
||||
}
|
||||
CompletedContextPermissionUpdate {
|
||||
context_id: self.context_id,
|
||||
pages,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl CompletedContextPermissionUpdate {
|
||||
pub fn context_id(&self) -> BrowserContextId {
|
||||
self.context_id
|
||||
}
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn start_permission_update(
|
||||
&self,
|
||||
defaults: &PermissionDefaults,
|
||||
) -> Result<Option<PendingContextPermissionUpdate>, String> {
|
||||
let mut pages = Vec::new();
|
||||
let overrides = self.permission_overrides.snapshot(defaults);
|
||||
for (contents_id, contents) in &self.web_contents {
|
||||
let Some(document) = &contents.main_frame.current_document else {
|
||||
continue;
|
||||
};
|
||||
pages.push(PendingPermissionPageUpdate {
|
||||
contents_id: *contents_id,
|
||||
document_id: document.id,
|
||||
pending: document
|
||||
.page
|
||||
.start_set_permission_overrides(&overrides)
|
||||
.map_err(|error| {
|
||||
format!("failed to update page permission overrides: {error}")
|
||||
})?,
|
||||
});
|
||||
}
|
||||
Ok(
|
||||
(!pages.is_empty()).then_some(PendingContextPermissionUpdate {
|
||||
context_id: self.id,
|
||||
pages,
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn finish_permission_update(
|
||||
&mut self,
|
||||
completed: CompletedContextPermissionUpdate,
|
||||
) -> Result<(), String> {
|
||||
if completed.context_id != self.id {
|
||||
return Err("Permission update belongs to a different BrowserContext".into());
|
||||
}
|
||||
for page in completed.pages {
|
||||
let completion = page.completed?;
|
||||
let document = self
|
||||
.web_contents
|
||||
.get_mut(&page.contents_id)
|
||||
.and_then(|contents| contents.main_frame.current_document.as_mut())
|
||||
.ok_or("NoDocumentLoaded")?;
|
||||
if document.id == page.document_id {
|
||||
document
|
||||
.page
|
||||
.finish_set_permission_overrides(completion)
|
||||
.map_err(|error| error.to_string())?;
|
||||
} else {
|
||||
completion
|
||||
.into_unit_page_command_turn()
|
||||
.map(drop)
|
||||
.map_err(|error| error.to_string())?;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,488 @@
|
||||
use crate::{
|
||||
browser::{
|
||||
MainFrameSlotId, WebContentsHandle, WebContentsId,
|
||||
web_contents::{
|
||||
EmulationPolicy, EmulationPolicyChange, InitialDocumentSnapshot, NetworkRequestPolicy,
|
||||
WebContents, WindowOpener, WindowSurface, WindowSurfaceState,
|
||||
},
|
||||
},
|
||||
runtime::{NavigationEngine, NavigationRuntimeConfig},
|
||||
};
|
||||
|
||||
use super::BrowserContext;
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn bind_page_navigation_engines(
|
||||
&mut self,
|
||||
config: NavigationRuntimeConfig,
|
||||
renderer_output_transport_sender: Option<crate::RendererOutputTransportSender>,
|
||||
) {
|
||||
self.page_navigation_runtime_config = Some(config.clone());
|
||||
self.renderer_output_transport_sender = renderer_output_transport_sender;
|
||||
|
||||
let sender = self.renderer_output_transport_sender.clone();
|
||||
let runtime = self.renderer_runtime_owner_access();
|
||||
for contents in self.web_contents.values_mut() {
|
||||
if contents.has_navigation_engine() {
|
||||
continue;
|
||||
}
|
||||
let engine = NavigationEngine::new_with_runtime_config_and_browser_context_access(
|
||||
config.clone(),
|
||||
runtime.clone(),
|
||||
)
|
||||
.expect("live BrowserContext owner must accept a page engine");
|
||||
if let Some(sender) = sender.clone() {
|
||||
engine.set_renderer_output_transport_sender(sender);
|
||||
}
|
||||
contents.install_navigation_engine(engine);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_renderer_output_transport_sender(
|
||||
&mut self,
|
||||
sender: crate::RendererOutputTransportSender,
|
||||
) {
|
||||
self.renderer_output_transport_sender = Some(sender.clone());
|
||||
for contents in self.web_contents.values() {
|
||||
contents.set_renderer_output_transport_sender(sender.clone());
|
||||
}
|
||||
}
|
||||
|
||||
pub fn register_web_contents(
|
||||
&mut self,
|
||||
mut contents: WebContents,
|
||||
) -> Result<(WebContentsHandle, MainFrameSlotId), String> {
|
||||
let id = contents.id();
|
||||
let main_frame = contents.main_frame.id();
|
||||
if self.web_contents.contains_key(&id) {
|
||||
return Err("WebContents identity must be unique".into());
|
||||
}
|
||||
if let Some(config) = self.page_navigation_runtime_config.clone() {
|
||||
contents.install_navigation_engine(self.new_page_navigation_engine(config));
|
||||
}
|
||||
self.web_contents.insert(id, contents);
|
||||
Ok((WebContentsHandle::new(self.id, id), main_frame))
|
||||
}
|
||||
|
||||
pub fn contains_web_contents(&self, handle: WebContentsHandle) -> bool {
|
||||
self.web_contents(handle).is_ok()
|
||||
}
|
||||
|
||||
pub fn web_contents_count(&self) -> usize {
|
||||
self.web_contents.len()
|
||||
}
|
||||
|
||||
pub fn selected_web_contents_handle(&self) -> Option<WebContentsHandle> {
|
||||
Some(WebContentsHandle::new(self.id, self.selected_web_contents?))
|
||||
}
|
||||
|
||||
pub fn web_contents_handle_for_window_id(&self, window_id: u64) -> Option<WebContentsHandle> {
|
||||
let id = self
|
||||
.web_contents
|
||||
.keys()
|
||||
.copied()
|
||||
.find(|id| id.get() == window_id)?;
|
||||
Some(WebContentsHandle::new(self.id, id))
|
||||
}
|
||||
|
||||
pub fn web_contents_handle_for_window_name(&self, name: &str) -> Option<WebContentsHandle> {
|
||||
let id = self
|
||||
.web_contents
|
||||
.values()
|
||||
.find(|contents| contents.window.name.as_deref() == Some(name))?
|
||||
.id();
|
||||
Some(WebContentsHandle::new(self.id, id))
|
||||
}
|
||||
|
||||
pub fn web_contents_opener(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<(WebContentsId, bool)>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.window
|
||||
.opener
|
||||
.map(|opener| (opener.web_contents_id, opener.can_access)))
|
||||
}
|
||||
|
||||
pub fn web_contents_window_name(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<&str>, String> {
|
||||
Ok(self.web_contents(handle)?.window.name.as_deref())
|
||||
}
|
||||
|
||||
pub fn clone_session_storage_namespace(
|
||||
&self,
|
||||
web_contents: WebContentsId,
|
||||
) -> Option<crate::browser::web_contents::SessionStorageNamespace> {
|
||||
self.web_contents
|
||||
.get(&web_contents)
|
||||
.map(|contents| contents.session_storage.deep_clone())
|
||||
}
|
||||
|
||||
pub fn web_contents_is_crashed(&self, handle: WebContentsHandle) -> Result<bool, String> {
|
||||
Ok(self.web_contents(handle)?.crashed)
|
||||
}
|
||||
|
||||
pub fn performance_metric_snapshot(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<crate::page::RendererPerformanceMetricSnapshot>, String> {
|
||||
Ok(self.web_contents(handle)?.performance_metric_snapshot())
|
||||
}
|
||||
|
||||
pub fn observe_renderer_page_state(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
snapshot: &std::sync::Arc<moli_renderer_v8::RendererPageState>,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.web_contents_mut(handle)?
|
||||
.observe_renderer_page_state(snapshot))
|
||||
}
|
||||
|
||||
pub fn set_web_contents_crashed(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
crashed: bool,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?.crashed = crashed;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn web_contents_initial_document_state(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<InitialDocumentSnapshot>, String> {
|
||||
Ok(self
|
||||
.web_contents(handle)?
|
||||
.navigation()
|
||||
.initial_empty_document_state()
|
||||
.map(Into::into))
|
||||
}
|
||||
|
||||
pub fn web_contents_window_surface(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<WindowSurface, String> {
|
||||
Ok(self.web_contents(handle)?.window.surface)
|
||||
}
|
||||
|
||||
pub fn update_web_contents_window_surface(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
state: Option<WindowSurfaceState>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
x: Option<i32>,
|
||||
y: Option<i32>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.window
|
||||
.surface
|
||||
.update(state, width, height, x, y);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_web_contents_window_name(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
name: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents(handle)?;
|
||||
for contents in self.web_contents.values_mut() {
|
||||
if contents.id() == handle.id() {
|
||||
contents.window.name = name.clone();
|
||||
} else if name.is_some() && contents.window.name == name {
|
||||
contents.window.name = None;
|
||||
}
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_web_contents_opener(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
opener: Option<WebContentsHandle>,
|
||||
can_access: bool,
|
||||
) -> Result<(), String> {
|
||||
if let Some(opener) = opener {
|
||||
self.web_contents(opener)?;
|
||||
self.web_contents_mut(handle)?.window.opener = Some(WindowOpener {
|
||||
web_contents_id: opener.id(),
|
||||
can_access,
|
||||
});
|
||||
} else {
|
||||
self.web_contents_mut(handle)?.window.opener = None;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn set_web_contents_opener_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
opener: WebContentsId,
|
||||
can_access: bool,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?.window.opener = Some(WindowOpener {
|
||||
web_contents_id: opener,
|
||||
can_access,
|
||||
});
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn set_web_contents_network_offline(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
offline: bool,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?.set_network_offline(offline);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn web_contents_network_offline(&self, handle: WebContentsHandle) -> Result<bool, String> {
|
||||
Ok(self.web_contents(handle)?.network_offline)
|
||||
}
|
||||
|
||||
pub fn web_contents_tls_verify_host_override(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<bool>, String> {
|
||||
Ok(self.web_contents(handle)?.tls_verify_host_override)
|
||||
}
|
||||
|
||||
pub fn set_web_contents_tls_verify_host_override(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
enabled: Option<bool>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.set_tls_verify_host_override(enabled);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn web_contents_bypass_content_security_policy(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self.web_contents(handle)?.bypass_content_security_policy)
|
||||
}
|
||||
|
||||
pub fn set_web_contents_bypass_content_security_policy(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
bypass: bool,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.set_bypass_content_security_policy(bypass);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn web_contents_browser_identity_override(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<moli_browser_profile::BrowserIdentityProfile>, String> {
|
||||
Ok(self.web_contents(handle)?.browser_identity_override.clone())
|
||||
}
|
||||
|
||||
pub fn set_web_contents_browser_identity_override(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
identity: Option<moli_browser_profile::BrowserIdentityProfile>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.set_browser_identity_override(identity);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn web_contents_network_request_policy(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<NetworkRequestPolicy, String> {
|
||||
Ok(self.web_contents(handle)?.network_request_policy.clone())
|
||||
}
|
||||
|
||||
pub fn set_web_contents_network_request_policy(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
policy: NetworkRequestPolicy,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.set_network_request_policy(policy);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn web_contents_locale_override(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<String>, String> {
|
||||
Ok(self.web_contents(handle)?.environment_owner.locale())
|
||||
}
|
||||
|
||||
pub fn set_web_contents_locale_override(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
locale: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents(handle)?
|
||||
.environment_owner
|
||||
.set_locale(locale.as_deref())
|
||||
.map_err(str::to_owned)
|
||||
}
|
||||
|
||||
pub fn web_contents_timezone_override(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<Option<String>, String> {
|
||||
Ok(self.web_contents(handle)?.environment_owner.timezone())
|
||||
}
|
||||
|
||||
pub fn set_web_contents_timezone_override(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
timezone: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents(handle)?
|
||||
.environment_owner
|
||||
.set_timezone(timezone.as_deref())
|
||||
.map_err(str::to_owned)
|
||||
}
|
||||
|
||||
pub fn web_contents_has_non_default_policy(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<bool, String> {
|
||||
let contents = self.web_contents(handle)?;
|
||||
Ok(contents.network_offline
|
||||
|| contents.browser_identity_override.is_some()
|
||||
|| contents.tls_verify_host_override.is_some()
|
||||
|| contents.bypass_content_security_policy
|
||||
|| contents.environment_owner.locale().is_some()
|
||||
|| contents.environment_owner.timezone().is_some()
|
||||
|| contents.emulation_policy != EmulationPolicy::default()
|
||||
|| contents.network_request_policy != NetworkRequestPolicy::default())
|
||||
}
|
||||
|
||||
pub fn web_contents_window_counts(&self) -> (usize, usize, usize) {
|
||||
let opener = self
|
||||
.web_contents
|
||||
.values()
|
||||
.filter(|contents| contents.window.opener.is_some())
|
||||
.count();
|
||||
let accessible_opener = self
|
||||
.web_contents
|
||||
.values()
|
||||
.filter(|contents| {
|
||||
contents
|
||||
.window
|
||||
.opener
|
||||
.is_some_and(|opener| opener.can_access)
|
||||
})
|
||||
.count();
|
||||
let named = self
|
||||
.web_contents
|
||||
.values()
|
||||
.filter(|contents| contents.window.name.is_some())
|
||||
.count();
|
||||
(opener, accessible_opener, named)
|
||||
}
|
||||
|
||||
pub fn web_contents_emulation_policy(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<EmulationPolicy, String> {
|
||||
Ok(self.web_contents(handle)?.emulation_policy.clone())
|
||||
}
|
||||
|
||||
pub fn apply_web_contents_emulation_policy_change(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
change: EmulationPolicyChange,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.emulation_policy
|
||||
.apply(change);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn apply_web_contents_emulation_policy_changes(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
changes: Vec<EmulationPolicyChange>,
|
||||
) -> Result<(), String> {
|
||||
self.web_contents_mut(handle)?
|
||||
.emulation_policy
|
||||
.apply_changes(changes);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn loaded_document_count(&self) -> usize {
|
||||
self.web_contents
|
||||
.values()
|
||||
.filter(|contents| contents.main_frame.current_document.is_some())
|
||||
.count()
|
||||
}
|
||||
|
||||
pub fn has_pending_javascript_dialog(&self) -> bool {
|
||||
self.web_contents
|
||||
.values()
|
||||
.any(|contents| !contents.javascript_dialogs.is_empty())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn navigation_engine_for_test(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Option<&NavigationEngine> {
|
||||
self.web_contents(handle).ok()?.navigation_engine_for_test()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn navigation_engine_for_test_mut(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Option<&mut NavigationEngine> {
|
||||
self.web_contents_mut(handle)
|
||||
.ok()?
|
||||
.navigation_engine_for_test_mut()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn take_document_host_for_test(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Option<crate::browser::web_contents::DocumentHost> {
|
||||
self.web_contents_mut(handle)
|
||||
.ok()?
|
||||
.main_frame
|
||||
.current_document
|
||||
.take()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn web_contents_id_for_test(&self, handle: WebContentsHandle) -> Option<WebContentsId> {
|
||||
Some(self.web_contents(handle).ok()?.id())
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn web_contents_for_test(&self, handle: WebContentsHandle) -> Option<&WebContents> {
|
||||
self.web_contents(handle).ok()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn web_contents_for_test_mut(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Option<&mut WebContents> {
|
||||
self.web_contents_mut(handle).ok()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
use super::{
|
||||
BrowserContext,
|
||||
document_commands::{
|
||||
CompletedAppManifestLoadPreparation, CompletedAppManifestPublication,
|
||||
CompletedDocumentBlobRead, CompletedNetworkResourceLoadPreparation,
|
||||
PendingAppManifestLoadPreparation, PendingAppManifestPublication, PendingDocumentBlobRead,
|
||||
PendingNetworkResourceLoadPreparation,
|
||||
},
|
||||
};
|
||||
use crate::browser::DocumentHandle;
|
||||
use crate::page::{
|
||||
RendererAppManifestLoadPreparation, RendererAppManifestLoadPublication,
|
||||
RendererCommandTurnOutput, RendererNetworkResourceLoadPreparation,
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
pub struct BrowserAppManifestLoadPreparation {
|
||||
pub document: DocumentHandle,
|
||||
pub preparation: RendererAppManifestLoadPreparation,
|
||||
}
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn start_document_blob_read(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
uuid: String,
|
||||
) -> Result<PendingDocumentBlobRead, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_blob_bytes_for_uuid(uuid)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingDocumentBlobRead::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_document_blob_read(
|
||||
&mut self,
|
||||
completed: CompletedDocumentBlobRead,
|
||||
) -> Result<Option<Arc<[u8]>>, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_blob_bytes_for_uuid(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_network_resource_load_preparation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
url: Url,
|
||||
disable_cache: bool,
|
||||
include_credentials: bool,
|
||||
) -> Result<PendingNetworkResourceLoadPreparation, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_prepare_network_resource_load(frame_id, url, disable_cache, include_credentials)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingNetworkResourceLoadPreparation::new(
|
||||
document, pending,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn finish_network_resource_load_preparation(
|
||||
&mut self,
|
||||
completed: CompletedNetworkResourceLoadPreparation,
|
||||
) -> Result<RendererNetworkResourceLoadPreparation, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
let page = self.document_mut(document).map_err(|error| {
|
||||
if error == "Document changed" {
|
||||
"Document changed while preparing the network resource load".to_owned()
|
||||
} else {
|
||||
error
|
||||
}
|
||||
})?;
|
||||
page.page
|
||||
.finish_prepare_network_resource_load(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub fn start_app_manifest_load_preparation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingAppManifestLoadPreparation, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_prepare_app_manifest_load()
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingAppManifestLoadPreparation::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_app_manifest_load_preparation(
|
||||
&mut self,
|
||||
completed: CompletedAppManifestLoadPreparation,
|
||||
) -> Result<BrowserAppManifestLoadPreparation, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
let preparation = self
|
||||
.document_mut(document)?
|
||||
.page
|
||||
.finish_prepare_app_manifest_load(completion?)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(BrowserAppManifestLoadPreparation {
|
||||
document,
|
||||
preparation,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn start_app_manifest_publication(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
publication: RendererAppManifestLoadPublication,
|
||||
) -> Result<PendingAppManifestPublication, String> {
|
||||
let pending = self
|
||||
.document(document)?
|
||||
.page
|
||||
.start_publish_app_manifest_load(publication)
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(PendingAppManifestPublication::new(document, pending))
|
||||
}
|
||||
|
||||
pub fn finish_app_manifest_publication(
|
||||
&mut self,
|
||||
completed: CompletedAppManifestPublication,
|
||||
) -> Result<RendererCommandTurnOutput, String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
self.document_mut(document)?
|
||||
.page
|
||||
.finish_publish_app_manifest_load(completion?)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,167 @@
|
||||
use super::{
|
||||
BrowserContext, CompletedDocumentResourceRuntimeUpdate, PendingDocumentResourceRuntimeUpdate,
|
||||
};
|
||||
use crate::browser::web_contents::WebContents;
|
||||
use crate::network::ResourceRequestClient;
|
||||
use moli_fetch::FetchConfig;
|
||||
|
||||
impl BrowserContext {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub async fn reset_selected_resource_runtime_for_test(&mut self) -> bool {
|
||||
let Some(contents) = self
|
||||
.selected_web_contents_id()
|
||||
.and_then(|id| self.web_contents.get_mut(&id))
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if !contents.has_navigation_engine() {
|
||||
return false;
|
||||
}
|
||||
contents.reset_resource_runtime_for_test().await;
|
||||
true
|
||||
}
|
||||
|
||||
pub fn web_contents_has_navigation_engine(
|
||||
&self,
|
||||
handle: crate::browser::WebContentsHandle,
|
||||
) -> bool {
|
||||
self.web_contents(handle)
|
||||
.is_ok_and(WebContents::has_navigation_engine)
|
||||
}
|
||||
|
||||
pub fn web_contents_navigation_fetch_config(
|
||||
&self,
|
||||
handle: crate::browser::WebContentsHandle,
|
||||
) -> Option<&FetchConfig> {
|
||||
self.web_contents(handle).ok()?.navigation_fetch_config()
|
||||
}
|
||||
|
||||
pub fn web_contents_navigation_layout_policy(
|
||||
&self,
|
||||
handle: crate::browser::WebContentsHandle,
|
||||
) -> Option<crate::LayoutPolicy> {
|
||||
self.web_contents(handle).ok()?.navigation_layout_policy()
|
||||
}
|
||||
|
||||
pub fn web_contents_navigation_renderer_owner_id(
|
||||
&self,
|
||||
handle: crate::browser::WebContentsHandle,
|
||||
) -> Option<u64> {
|
||||
self.web_contents(handle)
|
||||
.ok()?
|
||||
.navigation_renderer_owner_id()
|
||||
}
|
||||
|
||||
pub fn web_contents_navigation_diagnostics(
|
||||
&self,
|
||||
handle: crate::browser::WebContentsHandle,
|
||||
) -> Option<crate::runtime::NavigationEngineDiagnostics> {
|
||||
self.web_contents(handle).ok()?.navigation_diagnostics()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn web_contents_navigation_browser_context_runtime_id_for_test(
|
||||
&self,
|
||||
handle: crate::browser::WebContentsHandle,
|
||||
) -> Option<crate::RendererBrowserContextRuntimeId> {
|
||||
Some(
|
||||
self.web_contents(handle)
|
||||
.ok()?
|
||||
.navigation_engine_for_test()?
|
||||
.browser_context_runtime()
|
||||
.id(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn invalidate_selected_resource_runtime(&mut self) {
|
||||
if let Some(contents) = self
|
||||
.selected_web_contents_id()
|
||||
.and_then(|id| self.web_contents.get_mut(&id))
|
||||
{
|
||||
contents.invalidate_resource_runtime();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn configure_selected_navigation_policy(
|
||||
&mut self,
|
||||
defaults: FetchConfig,
|
||||
global_headers: &moli_fetch::RequestHeaders,
|
||||
global_network_conditions: Option<crate::browser::EmulatedNetworkConditions>,
|
||||
) -> Result<(), String> {
|
||||
let inherited =
|
||||
self.inherited_resource_policy(defaults, global_headers, global_network_conditions);
|
||||
let Some(contents) = self
|
||||
.selected_web_contents_id()
|
||||
.and_then(|id| self.web_contents.get_mut(&id))
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
contents
|
||||
.configure_navigation_engine_policy(&inherited)
|
||||
.map(drop)
|
||||
}
|
||||
|
||||
pub fn ensure_web_contents_resource_request_client(
|
||||
&mut self,
|
||||
web_contents: crate::browser::WebContentsHandle,
|
||||
defaults: FetchConfig,
|
||||
global_headers: &moli_fetch::RequestHeaders,
|
||||
global_network_conditions: Option<crate::browser::EmulatedNetworkConditions>,
|
||||
) -> Result<ResourceRequestClient, String> {
|
||||
let inherited =
|
||||
self.inherited_resource_policy(defaults, global_headers, global_network_conditions);
|
||||
self.web_contents_mut(web_contents)?
|
||||
.ensure_resource_request_client(&inherited)
|
||||
}
|
||||
|
||||
pub fn start_web_contents_resource_runtime_rebuild(
|
||||
&mut self,
|
||||
web_contents: crate::browser::WebContentsHandle,
|
||||
defaults: FetchConfig,
|
||||
global_headers: &moli_fetch::RequestHeaders,
|
||||
global_network_conditions: Option<crate::browser::EmulatedNetworkConditions>,
|
||||
) -> Result<Option<PendingDocumentResourceRuntimeUpdate>, String> {
|
||||
let document = self.document_handle_for_web_contents(web_contents)?;
|
||||
let inherited =
|
||||
self.inherited_resource_policy(defaults, global_headers, global_network_conditions);
|
||||
let pending = self
|
||||
.web_contents_mut(web_contents)?
|
||||
.start_resource_runtime_rebuild(&inherited)?;
|
||||
match (document, pending) {
|
||||
(Some(document), Some(pending)) => Ok(Some(PendingDocumentResourceRuntimeUpdate::new(
|
||||
document, pending,
|
||||
))),
|
||||
(None, None) => Ok(None),
|
||||
_ => Err("resource runtime Document changed during admission".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_document_resource_runtime_update(
|
||||
&mut self,
|
||||
completed: CompletedDocumentResourceRuntimeUpdate,
|
||||
) -> Result<(), String> {
|
||||
let (document, completion) = completed.into_parts();
|
||||
let completion = completion?;
|
||||
match self.document(document) {
|
||||
Ok(_) => self
|
||||
.web_contents_mut(document.web_contents())?
|
||||
.finish_resource_runtime_update(completion),
|
||||
Err(error) if matches!(error.as_str(), "Document changed" | "NoDocumentLoaded") => {
|
||||
match self.web_contents_mut(document.web_contents()) {
|
||||
Ok(contents) => contents.finish_resource_runtime_update(completion),
|
||||
Err(_) => WebContents::finish_unobserved_resource_runtime_update(completion),
|
||||
}
|
||||
}
|
||||
Err(error) => Err(error),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn finish_unobserved_document_resource_runtime_update(
|
||||
completed: CompletedDocumentResourceRuntimeUpdate,
|
||||
) -> Result<(), String> {
|
||||
let (_, completion) = completed.into_parts();
|
||||
WebContents::finish_unobserved_resource_runtime_update(completion?)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,449 @@
|
||||
use std::collections::HashSet;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use std::path::Path;
|
||||
|
||||
use moli_browser_profile::BrowserIdentityProfile;
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use moli_cookie_jar::{BrowserCookieStore, SharedBrowserCookieStore};
|
||||
use moli_cookie_jar::{
|
||||
CookieSource, NetworkCookieRequestContext, StoredCookie, StoredCookiePartitionKey,
|
||||
StoredCookieQueryReport, StoredCookieSetReport,
|
||||
};
|
||||
|
||||
use crate::{
|
||||
RendererBrowserContextRuntimeId, RendererOwnerLocalHostId,
|
||||
browser::{
|
||||
BrowserContextPageStorageHandles, ContextEmulationDefaults, ContextNetworkPolicy,
|
||||
DownloadAccessError, DownloadBody, DownloadObservation, DownloadPolicy,
|
||||
EmulatedDeviceMetrics, EmulatedGeolocationOverrideState, EmulatedNetworkConditions,
|
||||
OriginStorageUsage, PermissionDefaults, SiteDataClearOptions, StoragePartitionKind,
|
||||
WebContentsHandle,
|
||||
},
|
||||
network::{ResourceRequestClient, new_shared_web_storage_store},
|
||||
page::PermissionOverrideRegistration,
|
||||
runtime::RendererSharedWorkerRuntimeDiagnostics,
|
||||
};
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
use crate::{
|
||||
browser::BrowserContextResourceStorageHandles,
|
||||
network::SharedWebStorageStore,
|
||||
storage::{SharedIndexedDbManager, SharedStorageBucketStore},
|
||||
};
|
||||
use moli_fetch::Request;
|
||||
use url::Url;
|
||||
|
||||
use super::BrowserContext;
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn download_policy(&self) -> Option<&DownloadPolicy> {
|
||||
self.download_policy.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_download_policy(&mut self, policy: Option<DownloadPolicy>) {
|
||||
self.download_policy = policy;
|
||||
}
|
||||
|
||||
pub fn start_download_request(
|
||||
&mut self,
|
||||
policy: &DownloadPolicy,
|
||||
client: ResourceRequestClient,
|
||||
request: Request,
|
||||
suggested_filename: Option<String>,
|
||||
) -> Result<Option<DownloadObservation>, String> {
|
||||
self.downloads
|
||||
.start_request(policy, client, request, suggested_filename)
|
||||
}
|
||||
|
||||
pub fn start_download_response(
|
||||
&mut self,
|
||||
web_contents: WebContentsHandle,
|
||||
policy: &DownloadPolicy,
|
||||
url: Url,
|
||||
headers: Vec<(String, String)>,
|
||||
body: DownloadBody,
|
||||
) -> Result<Option<DownloadObservation>, String> {
|
||||
self.web_contents(web_contents)?;
|
||||
self.downloads.start_response(policy, url, headers, body)
|
||||
}
|
||||
|
||||
pub fn cancel_download(&self, guid: &str) -> Option<Result<(), DownloadAccessError>> {
|
||||
self.downloads.cancel(guid)
|
||||
}
|
||||
|
||||
pub fn read_download_artifact(
|
||||
&self,
|
||||
guid: &str,
|
||||
) -> Option<Result<tokio::task::JoinHandle<Result<Vec<u8>, String>>, DownloadAccessError>> {
|
||||
self.downloads.read_artifact(guid)
|
||||
}
|
||||
|
||||
pub fn set_permission_override(
|
||||
&mut self,
|
||||
defaults: &mut PermissionDefaults,
|
||||
registration: PermissionOverrideRegistration,
|
||||
) {
|
||||
self.permission_overrides.set(defaults, registration);
|
||||
}
|
||||
|
||||
pub fn clear_permission_overrides(&mut self) {
|
||||
self.permission_overrides.clear();
|
||||
}
|
||||
|
||||
pub fn permission_override_count(&self) -> usize {
|
||||
self.permission_overrides.override_count()
|
||||
}
|
||||
|
||||
pub fn permission_snapshot(
|
||||
&self,
|
||||
defaults: &PermissionDefaults,
|
||||
) -> Vec<PermissionOverrideRegistration> {
|
||||
self.permission_overrides.snapshot(defaults)
|
||||
}
|
||||
|
||||
pub fn storage_partition_kind(&self) -> StoragePartitionKind {
|
||||
self.storage_partition.kind
|
||||
}
|
||||
|
||||
pub fn storage_partition_kind_label(&self) -> &'static str {
|
||||
self.storage_partition.kind_label()
|
||||
}
|
||||
|
||||
pub fn page_storage_handles(
|
||||
&self,
|
||||
web_contents: Option<WebContentsHandle>,
|
||||
) -> Result<BrowserContextPageStorageHandles, String> {
|
||||
let session_storage_store = match web_contents {
|
||||
Some(handle) => self.web_contents(handle)?.session_storage.store().clone(),
|
||||
None => self
|
||||
.selected_web_contents
|
||||
.and_then(|id| self.web_contents.get(&id))
|
||||
.map(|contents| contents.session_storage.store().clone())
|
||||
.unwrap_or_else(new_shared_web_storage_store),
|
||||
};
|
||||
Ok(self
|
||||
.storage_partition
|
||||
.handles
|
||||
.page_storage_handles(session_storage_store))
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn resource_storage_handles_for_test(&self) -> BrowserContextResourceStorageHandles {
|
||||
let session_storage_store = self
|
||||
.selected_web_contents
|
||||
.and_then(|id| self.web_contents.get(&id))
|
||||
.map(|contents| contents.session_storage.store().clone())
|
||||
.unwrap_or_else(new_shared_web_storage_store);
|
||||
self.storage_partition
|
||||
.handles
|
||||
.resource_storage_handles(session_storage_store)
|
||||
}
|
||||
|
||||
pub fn observe_request_cookie_access_report(
|
||||
&self,
|
||||
request_url: &Url,
|
||||
request_context: NetworkCookieRequestContext,
|
||||
) -> Option<StoredCookieQueryReport> {
|
||||
let mut cookie_store = self.storage_partition.cookie_store().lock();
|
||||
let report =
|
||||
cookie_store.observe_cookie_access_report_for_request(request_url, request_context);
|
||||
(!report.included_cookies.is_empty() || !report.excluded_cookies.is_empty())
|
||||
.then_some(report)
|
||||
}
|
||||
|
||||
pub fn storage_quota_for_origin(&self, origin: &str) -> (f64, bool) {
|
||||
self.storage_partition.storage_quota_for_origin(origin)
|
||||
}
|
||||
|
||||
pub fn set_storage_quota_override(&mut self, origin: String, quota: f64) {
|
||||
self.storage_partition
|
||||
.set_storage_quota_override(origin, quota);
|
||||
}
|
||||
|
||||
pub fn clear_storage_quota_override(&mut self, origin: &str) {
|
||||
self.storage_partition.clear_storage_quota_override(origin);
|
||||
}
|
||||
|
||||
pub fn storage_usage_for_origin(
|
||||
&self,
|
||||
serialized_origin: &str,
|
||||
) -> Result<OriginStorageUsage, String> {
|
||||
self.storage_partition.usage_for_origin(serialized_origin)
|
||||
}
|
||||
|
||||
pub fn clear_site_data_for_origin(
|
||||
&mut self,
|
||||
origin: &Url,
|
||||
options: SiteDataClearOptions,
|
||||
) -> Result<(), String> {
|
||||
self.storage_partition
|
||||
.clear_site_data_for_origin(origin, options)
|
||||
}
|
||||
|
||||
pub fn clear_site_data_for_storage_key(
|
||||
&mut self,
|
||||
storage_key: &moli_storage_key::MoliStorageKey,
|
||||
options: SiteDataClearOptions,
|
||||
) -> Result<(), String> {
|
||||
self.storage_partition
|
||||
.clear_site_data_for_storage_key(storage_key, options)
|
||||
}
|
||||
|
||||
pub fn clear_http_cache(&self) -> Result<(), String> {
|
||||
self.storage_partition.clear_http_cache()
|
||||
}
|
||||
|
||||
pub fn snapshot_cookies(&self) -> Vec<StoredCookie> {
|
||||
self.storage_partition.cookie_store().lock().cookies()
|
||||
}
|
||||
|
||||
pub fn store_cookie(
|
||||
&self,
|
||||
cookie: StoredCookie,
|
||||
request_url: Option<&Url>,
|
||||
source: CookieSource,
|
||||
) -> StoredCookieSetReport {
|
||||
self.storage_partition
|
||||
.cookie_store()
|
||||
.lock()
|
||||
.upsert_with_request_url_report(cookie, request_url, source)
|
||||
}
|
||||
|
||||
pub fn delete_cookies_with_partition_key(
|
||||
&mut self,
|
||||
name: Option<&str>,
|
||||
domain: Option<&str>,
|
||||
path: Option<&str>,
|
||||
url_host: Option<&str>,
|
||||
partition_key: Option<&StoredCookiePartitionKey>,
|
||||
) {
|
||||
self.storage_partition
|
||||
.cookie_store()
|
||||
.lock()
|
||||
.delete_cookies_with_partition_key(name, domain, path, url_host, partition_key);
|
||||
}
|
||||
|
||||
pub fn browser_identity_override(&self) -> Option<&BrowserIdentityProfile> {
|
||||
self.browser_identity_override.as_ref()
|
||||
}
|
||||
|
||||
pub fn set_browser_identity_override(&mut self, identity: Option<BrowserIdentityProfile>) {
|
||||
self.browser_identity_override = identity;
|
||||
}
|
||||
|
||||
pub fn emulation_defaults(&self) -> &ContextEmulationDefaults {
|
||||
&self.emulation_defaults
|
||||
}
|
||||
|
||||
pub fn set_default_locale_override(&mut self, locale: Option<String>) -> Result<(), String> {
|
||||
self.environment_owner
|
||||
.set_locale(locale.as_deref())
|
||||
.map_err(str::to_owned)
|
||||
}
|
||||
|
||||
pub fn set_default_timezone_override(
|
||||
&mut self,
|
||||
timezone: Option<String>,
|
||||
) -> Result<(), String> {
|
||||
self.environment_owner
|
||||
.set_timezone(timezone.as_deref())
|
||||
.map_err(str::to_owned)
|
||||
}
|
||||
|
||||
pub fn default_locale_override(&self) -> Option<String> {
|
||||
self.environment_owner.locale()
|
||||
}
|
||||
|
||||
pub fn default_timezone_override(&self) -> Option<String> {
|
||||
self.environment_owner.timezone()
|
||||
}
|
||||
|
||||
pub fn set_default_network_conditions(
|
||||
&mut self,
|
||||
conditions: Option<EmulatedNetworkConditions>,
|
||||
) {
|
||||
self.emulation_defaults.network_conditions = conditions;
|
||||
}
|
||||
|
||||
pub fn set_default_geolocation_override(
|
||||
&mut self,
|
||||
geolocation: Option<EmulatedGeolocationOverrideState>,
|
||||
) {
|
||||
self.emulation_defaults.geolocation = geolocation;
|
||||
}
|
||||
|
||||
pub fn set_default_device_metrics(&mut self, metrics: EmulatedDeviceMetrics) -> bool {
|
||||
self.emulation_defaults
|
||||
.device_metrics
|
||||
.replace(metrics)
|
||||
.is_some()
|
||||
}
|
||||
|
||||
pub fn network_policy(&self) -> &ContextNetworkPolicy {
|
||||
&self.network_policy
|
||||
}
|
||||
|
||||
pub fn set_default_extra_headers(&mut self, headers: moli_fetch::RequestHeaders) {
|
||||
self.network_policy.extra_headers = headers;
|
||||
}
|
||||
|
||||
pub fn set_network_policy(&mut self, policy: ContextNetworkPolicy) {
|
||||
self.network_policy = policy;
|
||||
}
|
||||
|
||||
pub fn set_context_tls_verify_host_override(&mut self, enabled: bool) {
|
||||
self.network_policy.tls_verify_host = Some(enabled);
|
||||
}
|
||||
|
||||
pub fn set_javascript_dialog_handler_enabled(&self, enabled: bool) {
|
||||
self.renderer_runtime_owner
|
||||
.as_ref()
|
||||
.expect("BrowserContext renderer owner was already taken for teardown")
|
||||
.handle()
|
||||
.set_javascript_dialog_handler_enabled(enabled);
|
||||
}
|
||||
|
||||
pub fn routes_renderer_browser_context_runtime(
|
||||
&self,
|
||||
runtime_id: RendererBrowserContextRuntimeId,
|
||||
) -> bool {
|
||||
self.renderer_runtime_owner
|
||||
.as_ref()
|
||||
.is_some_and(|owner| owner.handle().id() == runtime_id)
|
||||
}
|
||||
|
||||
pub fn renderer_memory_diagnostics(&self) -> serde_json::Value {
|
||||
self.renderer_runtime_owner
|
||||
.as_ref()
|
||||
.expect("BrowserContext renderer owner was already taken for teardown")
|
||||
.handle()
|
||||
.moli_memory_diagnostics()
|
||||
}
|
||||
|
||||
pub fn shared_worker_runtime_diagnostics(&self) -> RendererSharedWorkerRuntimeDiagnostics {
|
||||
self.renderer_runtime_owner
|
||||
.as_ref()
|
||||
.expect("BrowserContext renderer owner was already taken for teardown")
|
||||
.handle()
|
||||
.shared_worker_runtime_diagnostics_for_diagnostics()
|
||||
}
|
||||
|
||||
pub fn web_contents_for_renderer_owner(
|
||||
&self,
|
||||
owner_local_host_id: RendererOwnerLocalHostId,
|
||||
) -> Option<WebContentsHandle> {
|
||||
let id = self.web_contents.iter().find_map(|(id, contents)| {
|
||||
contents
|
||||
.main_frame
|
||||
.current_document
|
||||
.as_ref()
|
||||
.is_some_and(|document| {
|
||||
document.page.renderer_owner_local_host_id() == owner_local_host_id
|
||||
})
|
||||
.then_some(*id)
|
||||
})?;
|
||||
Some(WebContentsHandle::new(self.id, id))
|
||||
}
|
||||
|
||||
pub fn loaded_document_renderer_owner_ids(&self) -> HashSet<u64> {
|
||||
self.web_contents
|
||||
.values()
|
||||
.filter_map(|contents| contents.main_frame.current_document.as_ref())
|
||||
.map(|document| document.page.renderer_owner_local_host_id().as_u64())
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub fn dedicated_worker_running_isolate_count(&self) -> usize {
|
||||
self.web_contents
|
||||
.values()
|
||||
.filter_map(|contents| contents.main_frame.current_document.as_ref())
|
||||
.map(|document| {
|
||||
document
|
||||
.page
|
||||
.dedicated_worker_running_worker_isolate_count_for_diagnostics()
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
pub fn javascript_dialog_handler_enabled(&self) -> bool {
|
||||
self.renderer_runtime_owner
|
||||
.as_ref()
|
||||
.expect("BrowserContext renderer owner was already taken for teardown")
|
||||
.handle()
|
||||
.javascript_dialog_handler_enabled()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn http_cache_configuration_for_test(&self) -> (Option<&Path>, Option<u64>) {
|
||||
self.storage_partition.http_cache_configuration()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn cookie_store_for_test(&self) -> &SharedBrowserCookieStore {
|
||||
self.storage_partition.cookie_store()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn web_storage_store_for_test(&self) -> &SharedWebStorageStore {
|
||||
self.storage_partition.web_storage_store()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn selected_session_storage_store_for_test(&self) -> Option<&SharedWebStorageStore> {
|
||||
Some(
|
||||
self.web_contents
|
||||
.get(&self.selected_web_contents?)?
|
||||
.session_storage
|
||||
.store(),
|
||||
)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn indexed_db_manager_for_test(&self) -> &SharedIndexedDbManager {
|
||||
self.storage_partition.indexed_db_manager()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn storage_bucket_store_for_test(&self) -> &SharedStorageBucketStore {
|
||||
self.storage_partition.storage_bucket_store()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn replace_storage_bucket_store_for_test(
|
||||
&mut self,
|
||||
storage_bucket_store: SharedStorageBucketStore,
|
||||
) {
|
||||
self.storage_partition
|
||||
.replace_storage_bucket_store(storage_bucket_store);
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn with_cookie_store_for_test<R>(&self, f: impl FnOnce(&BrowserCookieStore) -> R) -> R {
|
||||
let cookie_store = self.storage_partition.cookie_store().lock();
|
||||
f(&cookie_store)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn with_cookie_store_mut_for_test<R>(
|
||||
&self,
|
||||
f: impl FnOnce(&mut BrowserCookieStore) -> R,
|
||||
) -> R {
|
||||
let mut cookie_store = self.storage_partition.cookie_store().lock();
|
||||
f(&mut cookie_store)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn set_http_proxy_override_for_test(&mut self, proxy: Option<String>) {
|
||||
self.network_policy.http_proxy = proxy;
|
||||
}
|
||||
}
|
||||
+35
-41
@@ -1,23 +1,23 @@
|
||||
use std::{collections::HashMap, path::PathBuf};
|
||||
|
||||
use moli_cookie_jar::SharedBrowserCookieStore;
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
network::SharedWebStorageStore,
|
||||
storage::{SharedIndexedDbManager, SharedStorageBucketStore, StorageBucketIdentity},
|
||||
};
|
||||
use moli_cookie_jar::SharedBrowserCookieStore;
|
||||
|
||||
use super::BrowserContextStoragePartitionHandles;
|
||||
|
||||
#[derive(Debug, Clone, Copy, Eq, PartialEq)]
|
||||
pub(crate) struct OriginStorageUsage {
|
||||
pub(crate) local_storage_usage: u64,
|
||||
pub(crate) indexed_db_usage: u64,
|
||||
pub(crate) storage_buckets_usage: u64,
|
||||
pub(crate) total_usage: u64,
|
||||
pub struct OriginStorageUsage {
|
||||
pub local_storage_usage: u64,
|
||||
pub indexed_db_usage: u64,
|
||||
pub storage_buckets_usage: u64,
|
||||
pub total_usage: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub(crate) struct SiteDataClearOptions {
|
||||
pub struct SiteDataClearOptions {
|
||||
pub cookies: bool,
|
||||
pub local_storage: bool,
|
||||
pub indexed_db: bool,
|
||||
@@ -26,22 +26,22 @@ pub(crate) struct SiteDataClearOptions {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(super) enum StoragePartitionKind {
|
||||
pub enum StoragePartitionKind {
|
||||
ProfileBacked,
|
||||
Ephemeral,
|
||||
}
|
||||
|
||||
/// The stores and their policy share one owner; only capability handles clone.
|
||||
pub(super) struct StoragePartition {
|
||||
pub(super) kind: StoragePartitionKind,
|
||||
pub(super) handles: BrowserContextStoragePartitionHandles,
|
||||
pub struct StoragePartition {
|
||||
pub kind: StoragePartitionKind,
|
||||
pub handles: BrowserContextStoragePartitionHandles,
|
||||
storage_quota_overrides: HashMap<String, f64>,
|
||||
http_cache_root: Option<PathBuf>,
|
||||
http_cache_max_bytes: Option<u64>,
|
||||
}
|
||||
|
||||
impl StoragePartition {
|
||||
pub(super) fn new(
|
||||
pub fn new(
|
||||
handles: BrowserContextStoragePartitionHandles,
|
||||
kind: StoragePartitionKind,
|
||||
http_cache_root: Option<PathBuf>,
|
||||
@@ -56,15 +56,12 @@ impl StoragePartition {
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn usage_for_origin(
|
||||
&self,
|
||||
serialized_origin: &str,
|
||||
) -> Result<OriginStorageUsage, String> {
|
||||
pub fn usage_for_origin(&self, serialized_origin: &str) -> Result<OriginStorageUsage, String> {
|
||||
let local_storage_usage = {
|
||||
let store = self.web_storage_store().lock();
|
||||
usize_to_u64_saturating(store.usage_bytes_for_origin_areas(serialized_origin))
|
||||
};
|
||||
let indexed_db_usage = moli_core::storage::indexed_db_origins_with_prefix_usage_bytes(
|
||||
let indexed_db_usage = crate::storage::indexed_db_origins_with_prefix_usage_bytes(
|
||||
self.indexed_db_manager(),
|
||||
&moli_storage_key::storage_key_prefix_for_origin(serialized_origin),
|
||||
)?;
|
||||
@@ -90,7 +87,7 @@ impl StoragePartition {
|
||||
};
|
||||
let mut usage = cache_usage;
|
||||
for identity in bucket_identities {
|
||||
let indexed_db_usage = moli_core::storage::indexed_db_origin_usage_bytes(
|
||||
let indexed_db_usage = crate::storage::indexed_db_origin_usage_bytes(
|
||||
self.indexed_db_manager(),
|
||||
&identity.indexed_db_storage_key(),
|
||||
)?;
|
||||
@@ -110,13 +107,13 @@ impl StoragePartition {
|
||||
) -> Result<(), String> {
|
||||
let bucket_store = self.storage_bucket_store();
|
||||
for cleanup in cleanups {
|
||||
moli_core::storage::complete_storage_bucket_deletion(bucket_store, &cleanup)
|
||||
crate::storage::complete_storage_bucket_deletion(bucket_store, &cleanup)
|
||||
.map_err(|error| format!("FailedToCompleteStorageBucketDeletion: {error}"))?;
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn clear_site_data_for_origin(
|
||||
pub fn clear_site_data_for_origin(
|
||||
&mut self,
|
||||
origin: &url::Url,
|
||||
options: SiteDataClearOptions,
|
||||
@@ -137,7 +134,7 @@ impl StoragePartition {
|
||||
}
|
||||
|
||||
if options.indexed_db {
|
||||
moli_core::storage::clear_indexed_db_origins_with_prefix(
|
||||
crate::storage::clear_indexed_db_origins_with_prefix(
|
||||
self.indexed_db_manager(),
|
||||
&moli_storage_key::storage_key_prefix_for_origin(&serialized_origin),
|
||||
)?;
|
||||
@@ -159,7 +156,7 @@ impl StoragePartition {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn clear_site_data_for_storage_key(
|
||||
pub fn clear_site_data_for_storage_key(
|
||||
&mut self,
|
||||
storage_key: &moli_storage_key::MoliStorageKey,
|
||||
options: SiteDataClearOptions,
|
||||
@@ -186,7 +183,7 @@ impl StoragePartition {
|
||||
}
|
||||
|
||||
if options.indexed_db {
|
||||
moli_core::storage::clear_indexed_db_origin(
|
||||
crate::storage::clear_indexed_db_origin(
|
||||
self.indexed_db_manager(),
|
||||
&serialized_storage_key,
|
||||
)?;
|
||||
@@ -208,57 +205,54 @@ impl StoragePartition {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn kind_label(&self) -> &'static str {
|
||||
pub fn kind_label(&self) -> &'static str {
|
||||
match self.kind {
|
||||
StoragePartitionKind::ProfileBacked => "profile-backed",
|
||||
StoragePartitionKind::Ephemeral => "ephemeral",
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn cookie_store(&self) -> &SharedBrowserCookieStore {
|
||||
pub fn cookie_store(&self) -> &SharedBrowserCookieStore {
|
||||
&self.handles.cookie_store
|
||||
}
|
||||
|
||||
pub(super) fn web_storage_store(&self) -> &SharedWebStorageStore {
|
||||
pub fn web_storage_store(&self) -> &SharedWebStorageStore {
|
||||
&self.handles.web_storage_store
|
||||
}
|
||||
|
||||
pub(super) fn indexed_db_manager(&self) -> &SharedIndexedDbManager {
|
||||
pub fn indexed_db_manager(&self) -> &SharedIndexedDbManager {
|
||||
&self.handles.indexed_db_manager
|
||||
}
|
||||
|
||||
pub(super) fn storage_bucket_store(&self) -> &SharedStorageBucketStore {
|
||||
pub fn storage_bucket_store(&self) -> &SharedStorageBucketStore {
|
||||
&self.handles.storage_bucket_store
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn replace_storage_bucket_store(
|
||||
&mut self,
|
||||
storage_bucket_store: SharedStorageBucketStore,
|
||||
) {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn replace_storage_bucket_store(&mut self, storage_bucket_store: SharedStorageBucketStore) {
|
||||
self.handles.storage_bucket_store = storage_bucket_store;
|
||||
}
|
||||
|
||||
pub(super) fn storage_quota_for_origin(&self, origin: &str) -> (f64, bool) {
|
||||
pub fn storage_quota_for_origin(&self, origin: &str) -> (f64, bool) {
|
||||
self.storage_quota_overrides
|
||||
.get(origin)
|
||||
.copied()
|
||||
.map(|quota| (quota, true))
|
||||
.unwrap_or((
|
||||
moli_core::storage::DEFAULT_ORIGIN_STORAGE_QUOTA_BYTES as f64,
|
||||
crate::storage::DEFAULT_ORIGIN_STORAGE_QUOTA_BYTES as f64,
|
||||
false,
|
||||
))
|
||||
}
|
||||
|
||||
pub(super) fn set_storage_quota_override(&mut self, origin: String, quota: f64) {
|
||||
pub fn set_storage_quota_override(&mut self, origin: String, quota: f64) {
|
||||
self.storage_quota_overrides.insert(origin, quota);
|
||||
}
|
||||
|
||||
pub(super) fn clear_storage_quota_override(&mut self, origin: &str) {
|
||||
pub fn clear_storage_quota_override(&mut self, origin: &str) {
|
||||
self.storage_quota_overrides.remove(origin);
|
||||
}
|
||||
|
||||
pub(super) fn clear_http_cache(&self) -> Result<(), String> {
|
||||
pub fn clear_http_cache(&self) -> Result<(), String> {
|
||||
let Some(cache_root) = self.http_cache_root.as_ref() else {
|
||||
return Ok(());
|
||||
};
|
||||
@@ -274,8 +268,8 @@ impl StoragePartition {
|
||||
.map_err(|error| format!("FailedToClearHttpCache: {error}"))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(super) fn http_cache_configuration(&self) -> (Option<&std::path::Path>, Option<u64>) {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn http_cache_configuration(&self) -> (Option<&std::path::Path>, Option<u64>) {
|
||||
(self.http_cache_root.as_deref(), self.http_cache_max_bytes)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,174 @@
|
||||
use crate::{browser::ServiceWorkerCommand, runtime::RendererBrowserContextRuntime};
|
||||
use moli_shared_worker::SharedWorkerInstanceId;
|
||||
|
||||
use super::BrowserContext;
|
||||
|
||||
impl BrowserContext {
|
||||
pub fn execute_service_worker_command(
|
||||
&self,
|
||||
command: ServiceWorkerCommand,
|
||||
) -> Result<(), String> {
|
||||
let runtime = self.renderer_runtime();
|
||||
match command {
|
||||
ServiceWorkerCommand::SetForceUpdateOnPageLoad(force_update) => {
|
||||
runtime.set_service_worker_force_update_on_page_load_for_devtools(force_update);
|
||||
Ok(())
|
||||
}
|
||||
ServiceWorkerCommand::Unregister { scope } => runtime
|
||||
.unregister_service_worker_scope_for_devtools(&scope)
|
||||
.map(drop),
|
||||
ServiceWorkerCommand::Start { scope } => {
|
||||
runtime.start_service_worker_for_devtools(&scope).map(drop)
|
||||
}
|
||||
ServiceWorkerCommand::StopVersion { version_id } => runtime
|
||||
.stop_service_worker_for_devtools(version_id)
|
||||
.map(drop),
|
||||
ServiceWorkerCommand::StopAll => {
|
||||
runtime.stop_all_service_workers_for_devtools().map(drop)
|
||||
}
|
||||
ServiceWorkerCommand::SkipWaiting { scope } => runtime
|
||||
.skip_waiting_service_worker_for_devtools(&scope)
|
||||
.map(drop),
|
||||
ServiceWorkerCommand::UpdateRegistration { scope } => runtime
|
||||
.update_service_worker_registration_for_devtools(&scope)
|
||||
.map(drop),
|
||||
ServiceWorkerCommand::DeliverPushMessage {
|
||||
origin,
|
||||
registration_id,
|
||||
data,
|
||||
} => runtime
|
||||
.deliver_push_message_for_devtools(&origin, registration_id, data)
|
||||
.map(drop),
|
||||
ServiceWorkerCommand::DispatchSyncEvent {
|
||||
origin,
|
||||
registration_id,
|
||||
tag,
|
||||
last_chance,
|
||||
} => runtime
|
||||
.dispatch_sync_event_for_devtools(&origin, registration_id, tag, last_chance)
|
||||
.map(drop),
|
||||
ServiceWorkerCommand::DispatchPeriodicSyncEvent {
|
||||
origin,
|
||||
registration_id,
|
||||
tag,
|
||||
} => runtime
|
||||
.dispatch_periodic_sync_event_for_devtools(&origin, registration_id, tag)
|
||||
.map(drop),
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn service_worker_force_update_on_page_load_for_test(&self) -> bool {
|
||||
self.renderer_runtime()
|
||||
.service_worker_force_update_on_page_load_for_devtools()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn service_worker_pause_on_start_for_test(&self) -> bool {
|
||||
self.renderer_runtime()
|
||||
.service_worker_pause_on_start_for_devtools()
|
||||
}
|
||||
|
||||
pub fn controlled_service_worker_window_client_ids(
|
||||
&self,
|
||||
registration_id: u64,
|
||||
version_id: u64,
|
||||
) -> Vec<u64> {
|
||||
self.renderer_runtime()
|
||||
.controlled_service_worker_window_client_ids_for_devtools(registration_id, version_id)
|
||||
}
|
||||
|
||||
pub fn set_service_worker_pause_on_start_for_version(
|
||||
&self,
|
||||
version_id: u64,
|
||||
pause: bool,
|
||||
) -> bool {
|
||||
self.renderer_runtime()
|
||||
.set_service_worker_pause_on_start_for_version_for_devtools(version_id, pause)
|
||||
}
|
||||
|
||||
pub fn set_service_worker_pause_on_start(&self, pause: bool) {
|
||||
self.renderer_runtime()
|
||||
.set_service_worker_pause_on_start_for_devtools(pause);
|
||||
}
|
||||
|
||||
pub fn set_service_worker_related_pause_on_start_policies(
|
||||
&self,
|
||||
policies: Vec<(u64, u64, String, String)>,
|
||||
) {
|
||||
self.renderer_runtime()
|
||||
.set_service_worker_related_pause_on_start_policies_for_devtools(policies);
|
||||
}
|
||||
|
||||
pub fn set_dedicated_worker_pause_on_start(&self, pause: bool) {
|
||||
self.renderer_runtime()
|
||||
.set_dedicated_worker_pause_on_start_for_devtools(pause);
|
||||
}
|
||||
|
||||
pub fn set_service_worker_inspection_attached(&self, version_id: u64, attached: bool) {
|
||||
self.renderer_runtime()
|
||||
.set_service_worker_devtools_attached(version_id, attached);
|
||||
}
|
||||
|
||||
pub fn close_shared_worker(&self, instance_id: SharedWorkerInstanceId) -> bool {
|
||||
self.renderer_runtime()
|
||||
.close_shared_worker_for_target_close(instance_id)
|
||||
}
|
||||
|
||||
pub fn close_dedicated_worker(&self, instance_id: u64) -> bool {
|
||||
self.renderer_runtime()
|
||||
.close_dedicated_worker_for_devtools(instance_id)
|
||||
}
|
||||
|
||||
pub fn attach_dedicated_worker_inspector_session(
|
||||
&self,
|
||||
instance_id: u64,
|
||||
session_id: Option<String>,
|
||||
) -> bool {
|
||||
self.renderer_runtime()
|
||||
.attach_dedicated_worker_runtime_inspector_session(instance_id, session_id)
|
||||
}
|
||||
|
||||
pub fn detach_shared_worker_inspector_session(
|
||||
&self,
|
||||
instance_id: SharedWorkerInstanceId,
|
||||
session_id: Option<String>,
|
||||
) -> bool {
|
||||
self.renderer_runtime()
|
||||
.detach_shared_worker_runtime_inspector_session(instance_id, session_id)
|
||||
}
|
||||
|
||||
pub fn detach_dedicated_worker_inspector_session(
|
||||
&self,
|
||||
instance_id: u64,
|
||||
session_id: Option<String>,
|
||||
) -> bool {
|
||||
self.renderer_runtime()
|
||||
.detach_dedicated_worker_runtime_inspector_session(instance_id, session_id)
|
||||
}
|
||||
|
||||
pub fn detach_service_worker_inspector_session(
|
||||
&self,
|
||||
version_id: u64,
|
||||
session_id: Option<String>,
|
||||
) -> bool {
|
||||
self.renderer_runtime()
|
||||
.detach_service_worker_runtime_inspector_session(version_id, session_id)
|
||||
}
|
||||
|
||||
pub fn run_dedicated_worker_if_waiting_for_debugger(&self, instance_id: u64) -> bool {
|
||||
self.renderer_runtime()
|
||||
.run_dedicated_worker_if_waiting_for_debugger_for_devtools(instance_id)
|
||||
}
|
||||
|
||||
pub fn run_service_worker_if_waiting_for_debugger(&self, version_id: u64) -> bool {
|
||||
self.renderer_runtime()
|
||||
.run_service_worker_if_waiting_for_debugger_for_devtools(version_id)
|
||||
}
|
||||
|
||||
pub fn worker_runtime_inspection_endpoint(&self) -> RendererBrowserContextRuntime {
|
||||
self.renderer_runtime()
|
||||
}
|
||||
}
|
||||
@@ -15,7 +15,7 @@ use std::os::unix::fs::{DirBuilderExt, OpenOptionsExt, PermissionsExt};
|
||||
use anyhow::{Context, Result, anyhow};
|
||||
|
||||
const DEFAULT_MEMORY_LIMIT: usize = 1024 * 1024;
|
||||
pub(crate) const DEFAULT_BODY_MATERIALIZE_LIMIT: usize = 64 * 1024 * 1024;
|
||||
pub const DEFAULT_BODY_MATERIALIZE_LIMIT: usize = 64 * 1024 * 1024;
|
||||
|
||||
static NEXT_SPOOL_ID: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
@@ -29,7 +29,7 @@ enum CapturedBodyInner {
|
||||
Memory(Vec<u8>),
|
||||
SharedMemory(Arc<[u8]>),
|
||||
File { path: PathBuf, len: usize },
|
||||
Subresource(moli_core::page::SubresourceResponseBody),
|
||||
Subresource(crate::page::SubresourceResponseBody),
|
||||
}
|
||||
|
||||
impl Drop for CapturedBodyInner {
|
||||
@@ -41,23 +41,23 @@ impl Drop for CapturedBodyInner {
|
||||
}
|
||||
|
||||
impl CapturedBody {
|
||||
pub(crate) fn from_string(body: String) -> Self {
|
||||
pub fn from_string(body: String) -> Self {
|
||||
Self::from_bytes(body.into_bytes())
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes(body: Vec<u8>) -> Self {
|
||||
pub fn from_bytes(body: Vec<u8>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(CapturedBodyInner::Memory(body)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_shared_bytes(body: Arc<[u8]>) -> Self {
|
||||
pub fn from_shared_bytes(body: Arc<[u8]>) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(CapturedBodyInner::SharedMemory(body)),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn from_bytes_spooled(body: Vec<u8>) -> Self {
|
||||
pub fn from_bytes_spooled(body: Vec<u8>) -> Self {
|
||||
if body.len() <= DEFAULT_MEMORY_LIMIT {
|
||||
return Self::from_bytes(body);
|
||||
}
|
||||
@@ -70,21 +70,19 @@ impl CapturedBody {
|
||||
Self::from_bytes(body)
|
||||
}
|
||||
|
||||
pub(crate) fn from_optional_renderer_synthetic_response_body(
|
||||
body: Option<moli_core::page::RendererSyntheticResponseBody>,
|
||||
pub fn from_optional_renderer_synthetic_response_body(
|
||||
body: Option<crate::page::RendererSyntheticResponseBody>,
|
||||
) -> Self {
|
||||
Self::from_bytes(body.map(|body| body.into_body_bytes()).unwrap_or_default())
|
||||
}
|
||||
|
||||
pub(crate) fn from_subresource_response_body(
|
||||
body: &moli_core::page::SubresourceResponseBody,
|
||||
) -> Self {
|
||||
pub fn from_subresource_response_body(body: &crate::page::SubresourceResponseBody) -> Self {
|
||||
Self {
|
||||
inner: Arc::new(CapturedBodyInner::Subresource(body.clone())),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
pub fn len(&self) -> usize {
|
||||
match self.inner.as_ref() {
|
||||
CapturedBodyInner::Memory(bytes) => bytes.len(),
|
||||
CapturedBodyInner::SharedMemory(bytes) => bytes.len(),
|
||||
@@ -93,12 +91,16 @@ impl CapturedBody {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn materialize_bytes_limited(&self, limit: usize) -> Result<Vec<u8>> {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.len() == 0
|
||||
}
|
||||
|
||||
pub fn materialize_bytes_limited(&self, limit: usize) -> Result<Vec<u8>> {
|
||||
ensure_materialize_limit(self.len(), limit)?;
|
||||
self.materialize_bytes()
|
||||
}
|
||||
|
||||
pub(crate) fn materialize_bytes(&self) -> Result<Vec<u8>> {
|
||||
pub fn materialize_bytes(&self) -> Result<Vec<u8>> {
|
||||
match self.inner.as_ref() {
|
||||
CapturedBodyInner::Memory(bytes) => Ok(bytes.clone()),
|
||||
CapturedBodyInner::SharedMemory(bytes) => Ok(bytes.to_vec()),
|
||||
@@ -118,7 +120,7 @@ impl CapturedBody {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn read_range(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
|
||||
pub fn read_range(&self, offset: usize, len: usize) -> Result<Vec<u8>> {
|
||||
if len == 0 || offset >= self.len() {
|
||||
return Ok(Vec::new());
|
||||
}
|
||||
@@ -150,13 +152,13 @@ impl CapturedBody {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn materialize_lossy_string(&self) -> Result<String> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn materialize_lossy_string(&self) -> Result<String> {
|
||||
self.materialize_bytes()
|
||||
.map(|bytes| String::from_utf8_lossy(&bytes).into_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn chunk_reader(&self, chunk_size: usize) -> Result<CapturedBodyChunkReader> {
|
||||
pub fn chunk_reader(&self, chunk_size: usize) -> Result<CapturedBodyChunkReader> {
|
||||
if chunk_size == 0 {
|
||||
return Err(anyhow!(
|
||||
"captured response body chunk size must be non-zero"
|
||||
@@ -195,7 +197,7 @@ impl CapturedBody {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct CapturedBodyChunkReader {
|
||||
pub struct CapturedBodyChunkReader {
|
||||
source: CapturedBodyChunkReaderSource,
|
||||
chunk_size: usize,
|
||||
}
|
||||
@@ -207,7 +209,7 @@ enum CapturedBodyChunkReaderSource {
|
||||
}
|
||||
|
||||
impl CapturedBodyChunkReader {
|
||||
pub(crate) fn next_chunk(&mut self) -> Result<Option<Vec<u8>>> {
|
||||
pub fn next_chunk(&mut self) -> Result<Option<Vec<u8>>> {
|
||||
match &mut self.source {
|
||||
CapturedBodyChunkReaderSource::Memory { body, offset } => {
|
||||
let bytes: &[u8] = match body.inner.as_ref() {
|
||||
@@ -251,7 +253,7 @@ impl CapturedBodyChunkReader {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_materialize_limit(len: usize, limit: usize) -> Result<()> {
|
||||
pub fn ensure_materialize_limit(len: usize, limit: usize) -> Result<()> {
|
||||
if len > limit {
|
||||
Err(anyhow!(
|
||||
"response body is {len} bytes, exceeds CDP materialization limit of {limit} bytes"
|
||||
@@ -270,7 +272,7 @@ impl PartialEq for CapturedBody {
|
||||
impl Eq for CapturedBody {}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct CapturedBodyWriter {
|
||||
pub struct CapturedBodyWriter {
|
||||
memory_limit: usize,
|
||||
len: usize,
|
||||
memory: Vec<u8>,
|
||||
@@ -285,7 +287,7 @@ impl Default for CapturedBodyWriter {
|
||||
}
|
||||
|
||||
impl CapturedBodyWriter {
|
||||
pub(crate) fn new(memory_limit: usize) -> Self {
|
||||
pub fn new(memory_limit: usize) -> Self {
|
||||
Self {
|
||||
memory_limit,
|
||||
len: 0,
|
||||
@@ -295,7 +297,7 @@ impl CapturedBodyWriter {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn append(&mut self, bytes: &[u8]) -> Result<()> {
|
||||
pub fn append(&mut self, bytes: &[u8]) -> Result<()> {
|
||||
if bytes.is_empty() {
|
||||
return Ok(());
|
||||
}
|
||||
@@ -313,11 +315,11 @@ impl CapturedBodyWriter {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn finish(mut self) -> Result<CapturedBody> {
|
||||
pub fn finish(mut self) -> Result<CapturedBody> {
|
||||
self.finish_in_place()
|
||||
}
|
||||
|
||||
pub(crate) fn finish_in_place(&mut self) -> Result<CapturedBody> {
|
||||
pub fn finish_in_place(&mut self) -> Result<CapturedBody> {
|
||||
if let Some(file) = self.file.as_mut() {
|
||||
file.flush()
|
||||
.context("failed to flush captured response body spool")?;
|
||||
@@ -559,7 +561,7 @@ mod tests {
|
||||
#[test]
|
||||
fn captured_body_from_subresource_response_body_reads_shared_source() -> Result<()> {
|
||||
let subresource_body =
|
||||
moli_core::page::SubresourceResponseBody::from_bytes(b"hello world".to_vec());
|
||||
crate::page::SubresourceResponseBody::from_bytes(b"hello world".to_vec());
|
||||
let body = CapturedBody::from_subresource_response_body(&subresource_body);
|
||||
|
||||
assert!(matches!(
|
||||
@@ -15,28 +15,28 @@ pub struct EmulatedDeviceMetrics {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct EmulatedNetworkConditions {
|
||||
pub struct EmulatedNetworkConditions {
|
||||
offline: bool,
|
||||
}
|
||||
|
||||
impl EmulatedNetworkConditions {
|
||||
pub(crate) fn offline() -> Self {
|
||||
pub fn offline() -> Self {
|
||||
Self { offline: true }
|
||||
}
|
||||
|
||||
pub(crate) fn navigator_online(&self) -> bool {
|
||||
pub fn navigator_online(&self) -> bool {
|
||||
!self.offline
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) type EmulatedViewportSurface = moli_page_types::ViewportSurface;
|
||||
pub type EmulatedViewportSurface = moli_page_types::ViewportSurface;
|
||||
|
||||
impl EmulatedDeviceMetrics {
|
||||
pub(crate) fn screen_avail_height(&self) -> u32 {
|
||||
pub fn screen_avail_height(&self) -> u32 {
|
||||
self.screen_avail_height
|
||||
}
|
||||
|
||||
pub(crate) fn device_pixel_ratio(&self) -> f64 {
|
||||
pub fn device_pixel_ratio(&self) -> f64 {
|
||||
if self.device_scale_factor.is_finite() && self.device_scale_factor > 0.0 {
|
||||
self.device_scale_factor
|
||||
} else {
|
||||
@@ -44,7 +44,7 @@ impl EmulatedDeviceMetrics {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn viewport_surface(&self) -> EmulatedViewportSurface {
|
||||
pub fn viewport_surface(&self) -> EmulatedViewportSurface {
|
||||
EmulatedViewportSurface {
|
||||
inner_width: self.width,
|
||||
inner_height: self.height,
|
||||
@@ -81,7 +81,7 @@ pub enum EmulatedGeolocationOverrideState {
|
||||
}
|
||||
|
||||
impl EmulatedGeolocationOverrideState {
|
||||
pub(crate) fn position(&self) -> Option<&EmulatedGeolocationOverride> {
|
||||
pub fn position(&self) -> Option<&EmulatedGeolocationOverride> {
|
||||
match self {
|
||||
Self::Position(position) => Some(position),
|
||||
Self::PositionUnavailable => None,
|
||||
@@ -99,7 +99,7 @@ pub struct EmulatedMediaOverrides {
|
||||
pub contrast: Option<String>,
|
||||
}
|
||||
|
||||
impl From<EmulatedMediaOverrides> for moli_core::page::EmulatedMediaOverrides {
|
||||
impl From<EmulatedMediaOverrides> for crate::page::EmulatedMediaOverrides {
|
||||
fn from(value: EmulatedMediaOverrides) -> Self {
|
||||
Self {
|
||||
media: value.media,
|
||||
@@ -112,7 +112,7 @@ impl From<EmulatedMediaOverrides> for moli_core::page::EmulatedMediaOverrides {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&EmulatedMediaOverrides> for moli_core::page::EmulatedMediaOverrides {
|
||||
impl From<&EmulatedMediaOverrides> for crate::page::EmulatedMediaOverrides {
|
||||
fn from(value: &EmulatedMediaOverrides) -> Self {
|
||||
Self {
|
||||
media: value.media.clone(),
|
||||
@@ -128,7 +128,6 @@ impl From<&EmulatedMediaOverrides> for moli_core::page::EmulatedMediaOverrides {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::EmulatedDeviceMetrics;
|
||||
use crate::conn::EmulationPolicy;
|
||||
|
||||
#[test]
|
||||
fn device_pixel_ratio_normalizes_non_positive_and_non_finite_values() {
|
||||
@@ -158,30 +157,4 @@ mod tests {
|
||||
metrics.device_scale_factor = f64::NAN;
|
||||
assert_eq!(metrics.device_pixel_ratio(), 1.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn handler_disable_uses_raw_state_for_conditional_target_resets() {
|
||||
let mut effective = EmulationPolicy {
|
||||
focus_emulation_enabled: true,
|
||||
script_execution_disabled: true,
|
||||
emulated_media: super::EmulatedMediaOverrides {
|
||||
color_scheme: Some("dark".to_owned()),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
let mut raw = crate::conn::DevToolsEmulationSessionState::default();
|
||||
|
||||
effective.apply_changes(raw.disable_policy_changes());
|
||||
|
||||
assert!(
|
||||
effective.focus_emulation_enabled,
|
||||
"an untouched handler must not clear another session's focus setting"
|
||||
);
|
||||
assert!(
|
||||
effective.emulated_media.color_scheme.is_none(),
|
||||
"Blink clears the shared media override on every handler disable"
|
||||
);
|
||||
assert!(!effective.script_execution_disabled);
|
||||
}
|
||||
}
|
||||
@@ -4,20 +4,48 @@
|
||||
//! CDP wire identifiers and their numeric representation does not define an
|
||||
//! ordering relationship.
|
||||
|
||||
mod browser_context;
|
||||
mod captured_body;
|
||||
mod document_lifecycle;
|
||||
mod document_lifetime;
|
||||
mod downloads;
|
||||
mod emulation;
|
||||
mod navigation_error;
|
||||
mod owner;
|
||||
pub use navigation_error::{
|
||||
NavigationNetworkError, NavigationNetworkErrorKind, NavigationRequestBlocked,
|
||||
};
|
||||
mod permissions;
|
||||
mod renderer_residence;
|
||||
mod service_workers;
|
||||
pub mod web_contents;
|
||||
|
||||
pub use browser_context::*;
|
||||
pub use captured_body::{
|
||||
CapturedBody, CapturedBodyChunkReader, CapturedBodyWriter, DEFAULT_BODY_MATERIALIZE_LIMIT,
|
||||
ensure_materialize_limit,
|
||||
};
|
||||
pub use document_lifecycle::DocumentLifecycle;
|
||||
pub use document_lifetime::{DocumentLifetime, DocumentLifetimeObserver, DocumentRetirement};
|
||||
pub use downloads::{
|
||||
DownloadAccessError, DownloadBehavior, DownloadBody, DownloadManager, DownloadMetadata,
|
||||
DownloadObservation, DownloadPolicy, DownloadSnapshot, DownloadState,
|
||||
};
|
||||
pub use emulation::{
|
||||
EmulatedDeviceMetrics, EmulatedGeolocationOverride, EmulatedGeolocationOverrideState,
|
||||
EmulatedMediaOverrides, EmulatedNetworkConditions, EmulatedViewportSurface,
|
||||
};
|
||||
pub use owner::{
|
||||
BrowserBuiltInitialDocument, BrowserCommittedInitialDocument, BrowserContextHandle,
|
||||
BrowserDocumentMaterialization, BrowserDocumentNavigationCommit, BrowserHandle,
|
||||
BrowserInitialDocumentAdmission, BrowserInitialDocumentBuild, BrowserInterceptedNavigationLoad,
|
||||
BrowserInterceptedNavigationResponse, BrowserNavigationLoad, BrowserPreparedDocumentNavigation,
|
||||
BrowserPreparedNavigationResponse, BrowserService, PendingDocumentRetirement,
|
||||
PendingWebContentsClose, WebContentsCreation,
|
||||
};
|
||||
pub use permissions::{PermissionDefaults, PermissionOverrides};
|
||||
pub use renderer_residence::RendererPageResidenceIdentity;
|
||||
pub use service_workers::ServiceWorkerCommand;
|
||||
|
||||
/// Navigation semantics, independent of the protocol that requested the load.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
@@ -92,6 +120,60 @@ define_browser_identity!(
|
||||
"Identity of one browser-owned request decision, independent of protocol request IDs."
|
||||
);
|
||||
|
||||
/// Stable routing capability for one browser-owned WebContents.
|
||||
///
|
||||
/// This carries only physical Browser identities. Frontend target and session
|
||||
/// identifiers are deliberately resolved before this capability is created.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct WebContentsHandle {
|
||||
context: BrowserContextId,
|
||||
web_contents: WebContentsId,
|
||||
}
|
||||
|
||||
impl WebContentsHandle {
|
||||
pub const fn new(context: BrowserContextId, web_contents: WebContentsId) -> Self {
|
||||
Self {
|
||||
context,
|
||||
web_contents,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn context(self) -> BrowserContextId {
|
||||
self.context
|
||||
}
|
||||
|
||||
pub const fn id(self) -> WebContentsId {
|
||||
self.web_contents
|
||||
}
|
||||
}
|
||||
|
||||
/// Exact routing capability for one replaceable browser-owned Document.
|
||||
///
|
||||
/// A delayed operation must present this complete capability again at commit;
|
||||
/// matching only the stable WebContents is insufficient after navigation.
|
||||
#[derive(Clone, Copy, Debug, Eq, Hash, PartialEq)]
|
||||
pub struct DocumentHandle {
|
||||
web_contents: WebContentsHandle,
|
||||
document: DocumentId,
|
||||
}
|
||||
|
||||
impl DocumentHandle {
|
||||
pub const fn new(web_contents: WebContentsHandle, document: DocumentId) -> Self {
|
||||
Self {
|
||||
web_contents,
|
||||
document,
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn web_contents(self) -> WebContentsHandle {
|
||||
self.web_contents
|
||||
}
|
||||
|
||||
pub const fn id(self) -> DocumentId {
|
||||
self.document
|
||||
}
|
||||
}
|
||||
|
||||
static NEXT_BROWSER_SEQUENCE: AtomicU64 = AtomicU64::new(1);
|
||||
|
||||
/// Process-monotonic sequence of completed Browser semantic occurrences.
|
||||
@@ -176,4 +258,16 @@ mod tests {
|
||||
size_of::<BrowserSequence>()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_handle_preserves_the_complete_physical_route() {
|
||||
let context = BrowserContextId::allocate();
|
||||
let contents = WebContentsId::allocate();
|
||||
let document = DocumentId::allocate();
|
||||
let handle = DocumentHandle::new(WebContentsHandle::new(context, contents), document);
|
||||
|
||||
assert_eq!(handle.web_contents().context(), context);
|
||||
assert_eq!(handle.web_contents().id(), contents);
|
||||
assert_eq!(handle.id(), document);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,57 @@
|
||||
use std::{error::Error, fmt};
|
||||
|
||||
use url::Url;
|
||||
|
||||
/// Network policy failures produced by the navigation request loaders.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub enum NavigationNetworkErrorKind {
|
||||
InternetDisconnected,
|
||||
}
|
||||
|
||||
impl NavigationNetworkErrorKind {
|
||||
pub fn error_text(self) -> &'static str {
|
||||
match self {
|
||||
Self::InternetDisconnected => "net::ERR_INTERNET_DISCONNECTED",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A navigation request rejected by emulated offline network policy.
|
||||
///
|
||||
/// Transport failures retain `moli_fetch::NetworkFetchFailureContext` and use
|
||||
/// their existing handling paths; this cause currently represents offline
|
||||
/// enforcement only.
|
||||
///
|
||||
/// This is an error cause rather than display context, so callers can recover
|
||||
/// its request identity through `anyhow::Error::downcast_ref` after adding
|
||||
/// diagnostic context along the navigation pipeline.
|
||||
#[derive(Debug)]
|
||||
pub struct NavigationNetworkError {
|
||||
pub kind: NavigationNetworkErrorKind,
|
||||
pub unreachable_url: Url,
|
||||
pub request_method: String,
|
||||
pub request_headers: moli_fetch::RequestHeaders,
|
||||
}
|
||||
|
||||
impl fmt::Display for NavigationNetworkError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(self.kind.error_text())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for NavigationNetworkError {}
|
||||
|
||||
/// A navigation request rejected by `Network.setBlockedURLs`.
|
||||
///
|
||||
/// The protocol boundary preserves its canonical error text independently of
|
||||
/// diagnostic context on other navigation failures.
|
||||
#[derive(Debug)]
|
||||
pub struct NavigationRequestBlocked;
|
||||
|
||||
impl fmt::Display for NavigationRequestBlocked {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("net::ERR_BLOCKED_BY_CLIENT")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for NavigationRequestBlocked {}
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,39 @@
|
||||
use url::Url;
|
||||
|
||||
/// BrowserContext-scoped ServiceWorker control with no frontend attribution.
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub enum ServiceWorkerCommand {
|
||||
SetForceUpdateOnPageLoad(bool),
|
||||
Unregister {
|
||||
scope: Url,
|
||||
},
|
||||
Start {
|
||||
scope: Url,
|
||||
},
|
||||
StopVersion {
|
||||
version_id: u64,
|
||||
},
|
||||
StopAll,
|
||||
SkipWaiting {
|
||||
scope: Url,
|
||||
},
|
||||
UpdateRegistration {
|
||||
scope: Url,
|
||||
},
|
||||
DeliverPushMessage {
|
||||
origin: Url,
|
||||
registration_id: u64,
|
||||
data: Option<Vec<u8>>,
|
||||
},
|
||||
DispatchSyncEvent {
|
||||
origin: Url,
|
||||
registration_id: u64,
|
||||
tag: String,
|
||||
last_chance: bool,
|
||||
},
|
||||
DispatchPeriodicSyncEvent {
|
||||
origin: Url,
|
||||
registration_id: u64,
|
||||
tag: String,
|
||||
},
|
||||
}
|
||||
+79
-68
@@ -1,4 +1,4 @@
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
browser::{MainFrameSlotId, WebContentsId},
|
||||
page::{Page, RendererDocumentLifecycleEvent, RendererDocumentLifecycleEventKind},
|
||||
runtime::NavigationEngine,
|
||||
@@ -7,56 +7,64 @@ use moli_core::{
|
||||
mod navigation_controller;
|
||||
use navigation_controller::NavigationController;
|
||||
pub use navigation_controller::PageNavigationHistoryEntry;
|
||||
pub(crate) use navigation_controller::{
|
||||
HistoryTraversalDestination, InitialDocument, InitialDocumentCreator, ResolvedHistoryTraversal,
|
||||
pub use navigation_controller::{
|
||||
HistoryTraversalDestination, InitialDocument, InitialDocumentCreator, InitialDocumentSnapshot,
|
||||
ResolvedHistoryTraversal,
|
||||
};
|
||||
|
||||
mod document_host;
|
||||
mod document_policy;
|
||||
pub(in crate::conn::state) use document_policy::InheritedDocumentPolicy;
|
||||
pub use document_policy::InheritedDocumentPolicy;
|
||||
mod emulation_policy;
|
||||
mod initial_document;
|
||||
mod javascript_dialog;
|
||||
pub(in crate::conn) use initial_document::InitialDocumentAdmission;
|
||||
pub(in crate::conn::state) use initial_document::InitialDocumentBuildState;
|
||||
pub(crate) use initial_document::{
|
||||
BuiltInitialDocument, InitialDocumentBuildKey, InitialDocumentPageBuildWaiter,
|
||||
pub use initial_document::InitialDocumentAdmission;
|
||||
pub use initial_document::InitialDocumentBuildState;
|
||||
pub use initial_document::{
|
||||
AdmittedInitialDocumentBuild, BuiltInitialDocument, CommittedInitialDocument,
|
||||
InitialDocumentBuildKey, InitialDocumentPageBuildWaiter,
|
||||
};
|
||||
mod navigation_commit;
|
||||
pub(in crate::conn) use navigation_commit::AdmittedDocumentMaterialization;
|
||||
pub use navigation_commit::AdmittedDocumentMaterialization;
|
||||
mod navigation_history;
|
||||
mod navigation_interception;
|
||||
pub(crate) use navigation_interception::{
|
||||
pub use navigation_interception::{
|
||||
ClaimedNavigationRequest, InterceptedNavigationLoad, InterceptedNavigationResponse,
|
||||
NavigationInterceptionPermit, NavigationRequestInterception,
|
||||
};
|
||||
mod navigation_load;
|
||||
pub(crate) use navigation_history::SameDocumentNavigationCommitted;
|
||||
pub(in crate::conn) use navigation_load::{AdmittedNavigationLoad, PreparedNavigationResponse};
|
||||
pub use navigation_history::SameDocumentNavigationCommitted;
|
||||
pub use navigation_load::{AdmittedNavigationLoad, PreparedNavigationResponse};
|
||||
mod network_request_policy;
|
||||
pub(crate) use navigation_commit::{
|
||||
CommittedDocumentLifecycle, DocumentNavigationDestination, PreparedDocumentNavigation,
|
||||
RetiringDocument,
|
||||
pub use navigation_commit::{
|
||||
CommittedDocumentInfo, CommittedDocumentLifecycle, CommittedDocumentNavigation,
|
||||
DocumentNavigationDestination, PreparedDocumentNavigation, RetiringDocument,
|
||||
};
|
||||
mod page_surface;
|
||||
mod paused_document_transfer;
|
||||
mod resource_runtime;
|
||||
mod session_storage;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
mod window;
|
||||
pub(in crate::conn) use document_host::DocumentHost;
|
||||
pub(crate) use document_host::DocumentLifecycleEvent;
|
||||
pub(crate) use emulation_policy::{EmulationPolicy, EmulationPolicyChange};
|
||||
pub use document_host::DocumentHost;
|
||||
pub use document_host::DocumentLifecycleEvent;
|
||||
pub use emulation_policy::{EmulationPolicy, EmulationPolicyChange};
|
||||
use javascript_dialog::JavaScriptDialogs;
|
||||
pub(crate) use javascript_dialog::{
|
||||
pub use javascript_dialog::{
|
||||
JavaScriptDialogClosed, JavaScriptDialogError, JavaScriptDialogKey, JavaScriptDialogSnapshot,
|
||||
};
|
||||
pub(in crate::conn) use network_request_policy::NetworkRequestPolicy;
|
||||
pub(in crate::conn) use network_request_policy::merge_extra_header_layers;
|
||||
pub(in crate::conn) use page_surface::PageSurface;
|
||||
pub(crate) use session_storage::SessionStorageNamespace;
|
||||
pub(in crate::conn) use window::{Window, WindowOpener};
|
||||
pub(crate) use window::{WindowSurface, WindowSurfaceState};
|
||||
pub use network_request_policy::NetworkRequestPolicy;
|
||||
pub use network_request_policy::merge_extra_header_layers;
|
||||
pub use page_surface::PageSurface;
|
||||
pub use paused_document_transfer::{
|
||||
DocumentBodySource, OpenBodyStreamError, PausedDocumentTransfer,
|
||||
PausedResponsePreparedDocument, PausedStreamingDocumentResponse,
|
||||
PendingFetchResponseOpenedBodyStream, SyntheticDocumentResponseContext,
|
||||
};
|
||||
pub use session_storage::SessionStorageNamespace;
|
||||
pub use window::{Window, WindowOpener};
|
||||
pub use window::{WindowSurface, WindowSurfaceState};
|
||||
|
||||
/// Stable Browser page ownership, independent of DevTools bindings.
|
||||
///
|
||||
@@ -65,25 +73,24 @@ pub(crate) use window::{WindowSurface, WindowSurfaceState};
|
||||
/// Declaration order cancels pending work and retires the Document before
|
||||
/// releasing the engine and storage. This owner is deliberately not Clone.
|
||||
#[derive(Debug)]
|
||||
pub(in crate::conn) struct WebContents {
|
||||
pub struct WebContents {
|
||||
id: WebContentsId,
|
||||
navigation: NavigationController,
|
||||
// Dismiss modal renderer work before Document/Page teardown.
|
||||
pub(in crate::conn) javascript_dialogs: JavaScriptDialogs,
|
||||
pub(in crate::conn) main_frame: MainFrameSlot,
|
||||
pub(crate) javascript_dialogs: JavaScriptDialogs,
|
||||
pub(crate) main_frame: MainFrameSlot,
|
||||
navigation_engine: Option<NavigationEngine>,
|
||||
pub(in crate::conn) session_storage: SessionStorageNamespace,
|
||||
pub(in crate::conn) window: Window,
|
||||
pub(in crate::conn) crashed: bool,
|
||||
pub(in crate::conn) emulation_policy: EmulationPolicy,
|
||||
pub(in crate::conn) network_request_policy: NetworkRequestPolicy,
|
||||
fetch_subresource_interception: (bool, Option<moli_core::page::SubresourceResourceType>),
|
||||
pub(in crate::conn) network_offline: bool,
|
||||
pub(in crate::conn) tls_verify_host_override: Option<bool>,
|
||||
pub(in crate::conn) bypass_content_security_policy: bool,
|
||||
pub(in crate::conn) browser_identity_override:
|
||||
Option<moli_browser_profile::BrowserIdentityProfile>,
|
||||
pub(in crate::conn) environment_owner: moli_core::ProcessEnvironmentOwner,
|
||||
pub(crate) session_storage: SessionStorageNamespace,
|
||||
pub(crate) window: Window,
|
||||
pub(crate) crashed: bool,
|
||||
pub(crate) emulation_policy: EmulationPolicy,
|
||||
pub(crate) network_request_policy: NetworkRequestPolicy,
|
||||
fetch_subresource_interception: (bool, Option<crate::page::SubresourceResourceType>),
|
||||
pub(crate) network_offline: bool,
|
||||
pub(crate) tls_verify_host_override: Option<bool>,
|
||||
pub(crate) bypass_content_security_policy: bool,
|
||||
pub(crate) browser_identity_override: Option<moli_browser_profile::BrowserIdentityProfile>,
|
||||
pub(crate) environment_owner: crate::ProcessEnvironmentOwner,
|
||||
}
|
||||
|
||||
impl Default for WebContents {
|
||||
@@ -110,18 +117,22 @@ impl Default for WebContents {
|
||||
}
|
||||
|
||||
impl WebContents {
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn fetch_subresource_interception(
|
||||
pub fn install_session_storage_namespace(&mut self, namespace: SessionStorageNamespace) {
|
||||
self.session_storage = namespace;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn fetch_subresource_interception(
|
||||
&self,
|
||||
) -> (bool, Option<moli_core::page::SubresourceResourceType>) {
|
||||
) -> (bool, Option<crate::page::SubresourceResourceType>) {
|
||||
self.fetch_subresource_interception
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_fetch_interception_update(
|
||||
pub fn start_fetch_interception_update(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
resource_type: Option<moli_core::page::SubresourceResourceType>,
|
||||
) -> Result<Option<moli_core::page::PendingPageCommand>, String> {
|
||||
resource_type: Option<crate::page::SubresourceResourceType>,
|
||||
) -> Result<Option<crate::page::PendingPageCommand>, String> {
|
||||
// Install effective Browser policy even before the first Document, and
|
||||
// retain it if the outgoing renderer has already stopped accepting work.
|
||||
self.install_fetch_interception_policy(enabled, resource_type);
|
||||
@@ -137,17 +148,17 @@ impl WebContents {
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn install_fetch_interception_policy(
|
||||
pub fn install_fetch_interception_policy(
|
||||
&mut self,
|
||||
enabled: bool,
|
||||
resource_type: Option<moli_core::page::SubresourceResourceType>,
|
||||
resource_type: Option<crate::page::SubresourceResourceType>,
|
||||
) {
|
||||
self.fetch_subresource_interception = (enabled, resource_type);
|
||||
}
|
||||
|
||||
/// Retire Browser authority synchronously, then close the renderer without
|
||||
/// retaining any Context/registry borrow across await.
|
||||
pub(in crate::conn) fn begin_close(mut self) -> ClosingWebContents {
|
||||
pub fn begin_close(mut self) -> ClosingWebContents {
|
||||
self.navigation.clear_document_navigation_state();
|
||||
let page = self.replace_document(None);
|
||||
ClosingWebContents {
|
||||
@@ -156,9 +167,9 @@ impl WebContents {
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn performance_metric_snapshot(
|
||||
pub fn performance_metric_snapshot(
|
||||
&self,
|
||||
) -> Option<moli_core::page::RendererPerformanceMetricSnapshot> {
|
||||
) -> Option<crate::page::RendererPerformanceMetricSnapshot> {
|
||||
Some(
|
||||
self.main_frame
|
||||
.current_document
|
||||
@@ -171,7 +182,7 @@ impl WebContents {
|
||||
/// Browser observation is independent of the DevTools command that
|
||||
/// produced this snapshot. The Page cache validates physical residence
|
||||
/// and revision; a rejected observation never invalidates a frozen reply.
|
||||
pub(in crate::conn) fn observe_renderer_page_state(
|
||||
pub fn observe_renderer_page_state(
|
||||
&mut self,
|
||||
snapshot: &std::sync::Arc<moli_renderer_v8::RendererPageState>,
|
||||
) -> bool {
|
||||
@@ -181,7 +192,7 @@ impl WebContents {
|
||||
.is_some_and(|document| document.page.observe_renderer_page_state(snapshot))
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn apply_document_lifecycle(
|
||||
pub fn apply_document_lifecycle(
|
||||
&mut self,
|
||||
event: RendererDocumentLifecycleEvent,
|
||||
) -> Option<DocumentLifecycleEvent> {
|
||||
@@ -204,8 +215,8 @@ impl WebContents {
|
||||
if matches!(
|
||||
event.kind,
|
||||
RendererDocumentLifecycleEventKind::Started {
|
||||
reason: moli_core::page::RendererLifecycleStartReason::ExplicitDocumentOpen
|
||||
| moli_core::page::RendererLifecycleStartReason::JavascriptDocumentReplacement
|
||||
reason: crate::page::RendererLifecycleStartReason::ExplicitDocumentOpen
|
||||
| crate::page::RendererLifecycleStartReason::JavascriptDocumentReplacement
|
||||
}
|
||||
) {
|
||||
self.navigation.mark_initial_empty_document_exited();
|
||||
@@ -213,7 +224,7 @@ impl WebContents {
|
||||
Some(DocumentLifecycleEvent::new(document.id, event))
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn replace_document(&mut self, next: Option<DocumentHost>) -> Option<Page> {
|
||||
pub fn replace_document(&mut self, next: Option<DocumentHost>) -> Option<Page> {
|
||||
self.navigation.cancel_initial_document_build();
|
||||
self.javascript_dialogs.clear();
|
||||
if let Some(document) = &next {
|
||||
@@ -225,37 +236,37 @@ impl WebContents {
|
||||
self.main_frame.replace_document(next)
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn id(&self) -> WebContentsId {
|
||||
pub fn id(&self) -> WebContentsId {
|
||||
self.id
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn set_network_request_policy(&mut self, policy: NetworkRequestPolicy) {
|
||||
pub fn set_network_request_policy(&mut self, policy: NetworkRequestPolicy) {
|
||||
if let Some(engine) = self.navigation_engine.as_mut() {
|
||||
engine.set_cache_disabled(policy.cache_disabled);
|
||||
}
|
||||
self.network_request_policy = policy;
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn set_network_offline(&mut self, offline: bool) {
|
||||
pub fn set_network_offline(&mut self, offline: bool) {
|
||||
self.network_offline = offline;
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn set_tls_verify_host_override(&mut self, enabled: Option<bool>) {
|
||||
pub fn set_tls_verify_host_override(&mut self, enabled: Option<bool>) {
|
||||
self.tls_verify_host_override = enabled;
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn set_bypass_content_security_policy(&mut self, bypass: bool) {
|
||||
pub fn set_bypass_content_security_policy(&mut self, bypass: bool) {
|
||||
self.bypass_content_security_policy = bypass;
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn set_browser_identity_override(
|
||||
pub fn set_browser_identity_override(
|
||||
&mut self,
|
||||
identity: Option<moli_browser_profile::BrowserIdentityProfile>,
|
||||
) {
|
||||
self.browser_identity_override = identity;
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn install_navigation_engine(&mut self, mut engine: NavigationEngine) {
|
||||
pub fn install_navigation_engine(&mut self, mut engine: NavigationEngine) {
|
||||
assert!(
|
||||
self.navigation_engine.is_none(),
|
||||
"WebContents must retain its first installed NavigationEngine"
|
||||
@@ -268,13 +279,13 @@ impl WebContents {
|
||||
|
||||
/// A move-owned Browser teardown participant, not a mutable WebContents handle.
|
||||
/// Its Page retires before the engine and storage even if cleanup is cancelled.
|
||||
pub(in crate::conn) struct ClosingWebContents {
|
||||
pub struct ClosingWebContents {
|
||||
page: Option<Page>,
|
||||
_contents: WebContents,
|
||||
}
|
||||
|
||||
impl ClosingWebContents {
|
||||
pub(in crate::conn) async fn close_async(mut self) {
|
||||
pub async fn close_async(mut self) {
|
||||
if let Some(page) = self.page.take() {
|
||||
let _ = page.close_async().await;
|
||||
}
|
||||
@@ -283,9 +294,9 @@ impl ClosingWebContents {
|
||||
|
||||
/// Stable main-frame slot; only the current Document is replaced on navigation.
|
||||
#[derive(Debug)]
|
||||
pub(in crate::conn) struct MainFrameSlot {
|
||||
pub struct MainFrameSlot {
|
||||
id: MainFrameSlotId,
|
||||
pub(in crate::conn) current_document: Option<DocumentHost>,
|
||||
pub current_document: Option<DocumentHost>,
|
||||
}
|
||||
|
||||
impl Default for MainFrameSlot {
|
||||
@@ -298,7 +309,7 @@ impl Default for MainFrameSlot {
|
||||
}
|
||||
|
||||
impl MainFrameSlot {
|
||||
pub(in crate::conn) fn id(&self) -> MainFrameSlotId {
|
||||
pub fn id(&self) -> MainFrameSlotId {
|
||||
self.id
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
use crate::{
|
||||
browser::{DocumentId, DocumentLifecycle, DocumentLifetime},
|
||||
page::{Page, RendererDocumentLifecycleEvent},
|
||||
};
|
||||
|
||||
/// Exact native occurrence carried to a possibly delayed DevTools projection.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub struct DocumentLifecycleEvent {
|
||||
document: DocumentId,
|
||||
event: RendererDocumentLifecycleEvent,
|
||||
}
|
||||
|
||||
impl DocumentLifecycleEvent {
|
||||
pub fn new(document: DocumentId, event: RendererDocumentLifecycleEvent) -> Self {
|
||||
Self { document, event }
|
||||
}
|
||||
|
||||
pub fn document(&self) -> DocumentId {
|
||||
self.document
|
||||
}
|
||||
pub fn event(&self) -> RendererDocumentLifecycleEvent {
|
||||
self.event
|
||||
}
|
||||
}
|
||||
|
||||
/// One browser Document incarnation, including its concrete renderer Page.
|
||||
///
|
||||
/// Private in the current residence until the typed API cutover (Commit 24b).
|
||||
/// No Target/session state or public mutable Page capability belongs here.
|
||||
#[derive(Debug)]
|
||||
pub struct DocumentHost {
|
||||
pub(crate) id: DocumentId,
|
||||
pub(crate) page: Page,
|
||||
pub(crate) lifecycle: DocumentLifecycle,
|
||||
pub(crate) lifetime: DocumentLifetime,
|
||||
}
|
||||
|
||||
impl DocumentHost {
|
||||
pub fn new(id: DocumentId, page: Page) -> Self {
|
||||
Self {
|
||||
id,
|
||||
page,
|
||||
lifecycle: DocumentLifecycle::default(),
|
||||
lifetime: DocumentLifetime::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn id(&self) -> DocumentId {
|
||||
self.id
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub async fn evaluate_runtime_expression_for_test(
|
||||
&mut self,
|
||||
expression: &str,
|
||||
await_promise: bool,
|
||||
) -> anyhow::Result<serde_json::Value> {
|
||||
self.page
|
||||
.evaluate_runtime_expression_without_navigation_follow_with_await_async(
|
||||
expression,
|
||||
await_promise,
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub async fn runtime_heap_usage_for_test(
|
||||
&mut self,
|
||||
) -> anyhow::Result<crate::page::RendererRuntimeHeapUsage> {
|
||||
self.page.runtime_heap_usage_async().await
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn start_blob_bytes_for_uuid_for_test(
|
||||
&self,
|
||||
uuid: String,
|
||||
) -> anyhow::Result<crate::page::PendingPageCommand> {
|
||||
self.page.start_blob_bytes_for_uuid(uuid)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn finish_blob_bytes_for_uuid_for_test(
|
||||
&mut self,
|
||||
completion: crate::page::CompletedPageCommand,
|
||||
) -> anyhow::Result<Option<std::sync::Arc<[u8]>>> {
|
||||
self.page.finish_blob_bytes_for_uuid(completion)
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn document_title_for_test(&self) -> String {
|
||||
self.page.document_title()
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn renderer_page_residence_identity_for_test(
|
||||
&self,
|
||||
) -> crate::browser::RendererPageResidenceIdentity {
|
||||
crate::browser::RendererPageResidenceIdentity::from_page(&self.page)
|
||||
}
|
||||
|
||||
/// Retire the browser incarnation before handing its Page to async cleanup.
|
||||
pub(super) fn retire(self) -> Page {
|
||||
self.lifetime.supersede();
|
||||
self.page
|
||||
}
|
||||
}
|
||||
+12
-14
@@ -1,22 +1,20 @@
|
||||
use moli_core::{page::PermissionOverrideRegistration, runtime::PreparedDocumentPagePolicy};
|
||||
use crate::{page::PermissionOverrideRegistration, runtime::PreparedDocumentPagePolicy};
|
||||
use moli_fetch::FetchConfig;
|
||||
use url::Url;
|
||||
|
||||
use super::{WebContents, network_request_policy::merge_extra_header_layers};
|
||||
use crate::conn::state::browser_context::{
|
||||
BrowserContextStoragePartitionHandles, ContextEmulationDefaults,
|
||||
};
|
||||
use crate::browser::{BrowserContextStoragePartitionHandles, ContextEmulationDefaults};
|
||||
|
||||
/// Source-free context inheritance, captured during Browser admission. It is
|
||||
/// not a second stored policy and contains no inspection/session configuration.
|
||||
pub(in crate::conn::state) struct InheritedDocumentPolicy {
|
||||
pub(in crate::conn::state) selected_web_contents: Option<moli_core::browser::WebContentsId>,
|
||||
pub(in crate::conn::state) navigator_queries: moli_page_types::NavigatorQueryOverrides,
|
||||
pub(in crate::conn::state) fetch_config: FetchConfig,
|
||||
pub(in crate::conn::state) extra_headers: moli_fetch::RequestHeaders,
|
||||
pub(in crate::conn::state) emulation: ContextEmulationDefaults,
|
||||
pub(in crate::conn::state) permissions: Vec<PermissionOverrideRegistration>,
|
||||
pub(in crate::conn::state) storage: BrowserContextStoragePartitionHandles,
|
||||
pub struct InheritedDocumentPolicy {
|
||||
pub selected_web_contents: Option<crate::browser::WebContentsId>,
|
||||
pub navigator_queries: moli_page_types::NavigatorQueryOverrides,
|
||||
pub fetch_config: FetchConfig,
|
||||
pub extra_headers: moli_fetch::RequestHeaders,
|
||||
pub emulation: ContextEmulationDefaults,
|
||||
pub permissions: Vec<PermissionOverrideRegistration>,
|
||||
pub storage: BrowserContextStoragePartitionHandles,
|
||||
}
|
||||
|
||||
impl WebContents {
|
||||
@@ -45,7 +43,7 @@ impl WebContents {
|
||||
Ok(policy)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn configure_navigation_engine_policy(
|
||||
pub fn configure_navigation_engine_policy(
|
||||
&mut self,
|
||||
inherited: &InheritedDocumentPolicy,
|
||||
) -> Result<
|
||||
@@ -90,7 +88,7 @@ impl WebContents {
|
||||
Ok((navigator_identity, extra_http_headers, network_offline))
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn capture_document_policy(
|
||||
pub fn capture_document_policy(
|
||||
&mut self,
|
||||
inherited: InheritedDocumentPolicy,
|
||||
final_url: &Url,
|
||||
+14
-14
@@ -1,4 +1,4 @@
|
||||
use super::super::emulation::{
|
||||
use crate::browser::{
|
||||
EmulatedDeviceMetrics, EmulatedGeolocationOverrideState, EmulatedMediaOverrides,
|
||||
EmulatedNetworkConditions,
|
||||
};
|
||||
@@ -6,20 +6,20 @@ use super::super::emulation::{
|
||||
/// Source-free values applied to one Browser page. Session contributions use
|
||||
/// the same value representation, but do not own the installed policy.
|
||||
#[derive(Debug, Clone, Default, PartialEq)]
|
||||
pub(crate) struct EmulationPolicy {
|
||||
pub(crate) default_background_color: Option<[u8; 4]>,
|
||||
pub(crate) network_conditions: Option<EmulatedNetworkConditions>,
|
||||
pub(crate) geolocation_override: Option<EmulatedGeolocationOverrideState>,
|
||||
pub(crate) emulated_media: EmulatedMediaOverrides,
|
||||
pub(crate) emulated_device_metrics: Option<EmulatedDeviceMetrics>,
|
||||
pub(crate) max_touch_points: u32,
|
||||
pub(crate) emit_touch_events_for_mouse: bool,
|
||||
pub(crate) focus_emulation_enabled: bool,
|
||||
pub(crate) script_execution_disabled: bool,
|
||||
pub struct EmulationPolicy {
|
||||
pub default_background_color: Option<[u8; 4]>,
|
||||
pub network_conditions: Option<EmulatedNetworkConditions>,
|
||||
pub geolocation_override: Option<EmulatedGeolocationOverrideState>,
|
||||
pub emulated_media: EmulatedMediaOverrides,
|
||||
pub emulated_device_metrics: Option<EmulatedDeviceMetrics>,
|
||||
pub max_touch_points: u32,
|
||||
pub emit_touch_events_for_mouse: bool,
|
||||
pub focus_emulation_enabled: bool,
|
||||
pub script_execution_disabled: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) enum EmulationPolicyChange {
|
||||
pub enum EmulationPolicyChange {
|
||||
DefaultBackgroundColor(Option<[u8; 4]>),
|
||||
NetworkConditions(Option<EmulatedNetworkConditions>),
|
||||
Geolocation(Option<EmulatedGeolocationOverrideState>),
|
||||
@@ -32,7 +32,7 @@ pub(crate) enum EmulationPolicyChange {
|
||||
}
|
||||
|
||||
impl EmulationPolicy {
|
||||
pub(in crate::conn) fn apply(&mut self, change: EmulationPolicyChange) {
|
||||
pub fn apply(&mut self, change: EmulationPolicyChange) {
|
||||
match change {
|
||||
EmulationPolicyChange::DefaultBackgroundColor(value) => {
|
||||
self.default_background_color = value
|
||||
@@ -52,7 +52,7 @@ impl EmulationPolicy {
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn apply_changes(&mut self, changes: Vec<EmulationPolicyChange>) {
|
||||
pub fn apply_changes(&mut self, changes: Vec<EmulationPolicyChange>) {
|
||||
for change in changes {
|
||||
self.apply(change);
|
||||
}
|
||||
+50
-35
@@ -1,4 +1,4 @@
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
browser::{
|
||||
BrowserSequence, DocumentId, DocumentLifecycle, MainFrameSlotId,
|
||||
RendererPageResidenceIdentity, WebContentsId,
|
||||
@@ -7,13 +7,10 @@ use moli_core::{
|
||||
};
|
||||
use tokio::sync::watch;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
use super::{CommittedDocumentLifecycle, DocumentHost, InheritedDocumentPolicy, WebContents};
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct InitialDocumentBuildKey {
|
||||
pub struct InitialDocumentBuildKey {
|
||||
web_contents: WebContentsId,
|
||||
frame_slot: MainFrameSlotId,
|
||||
document: DocumentId,
|
||||
@@ -21,13 +18,13 @@ pub(crate) struct InitialDocumentBuildKey {
|
||||
}
|
||||
|
||||
impl InitialDocumentBuildKey {
|
||||
pub(in crate::conn) fn web_contents(&self) -> WebContentsId {
|
||||
pub fn web_contents(&self) -> WebContentsId {
|
||||
self.web_contents
|
||||
}
|
||||
pub(in crate::conn) fn document(&self) -> DocumentId {
|
||||
pub fn document(&self) -> DocumentId {
|
||||
self.document
|
||||
}
|
||||
pub(in crate::conn) fn renderer(&self) -> RendererPageResidenceIdentity {
|
||||
pub fn renderer(&self) -> RendererPageResidenceIdentity {
|
||||
self.renderer
|
||||
}
|
||||
}
|
||||
@@ -36,7 +33,7 @@ impl InitialDocumentBuildKey {
|
||||
/// Dropping either finishes the same one-shot observation without borrowing the
|
||||
/// registry. A stale worker can never complete a later build's waiters.
|
||||
#[derive(Debug)]
|
||||
pub(in crate::conn::state) struct InitialDocumentBuildCompletion {
|
||||
pub struct InitialDocumentBuildCompletion {
|
||||
sender: watch::Sender<Option<Result<(), String>>>,
|
||||
}
|
||||
|
||||
@@ -60,7 +57,7 @@ impl InitialDocumentBuildCompletion {
|
||||
true
|
||||
});
|
||||
}
|
||||
pub(in crate::conn::state) fn pending(&self) -> bool {
|
||||
pub fn pending(&self) -> bool {
|
||||
self.sender.borrow().is_none()
|
||||
}
|
||||
fn waiter(&self) -> InitialDocumentPageBuildWaiter {
|
||||
@@ -77,12 +74,12 @@ impl Drop for InitialDocumentBuildCompletion {
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub(crate) struct InitialDocumentPageBuildWaiter {
|
||||
pub struct InitialDocumentPageBuildWaiter {
|
||||
receiver: watch::Receiver<Option<Result<(), String>>>,
|
||||
}
|
||||
|
||||
impl InitialDocumentPageBuildWaiter {
|
||||
pub(crate) async fn wait(mut self) -> Result<(), String> {
|
||||
pub async fn wait(mut self) -> Result<(), String> {
|
||||
loop {
|
||||
if let Some(result) = self.receiver.borrow().clone() {
|
||||
return result;
|
||||
@@ -96,47 +93,47 @@ impl InitialDocumentPageBuildWaiter {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::conn::state) struct InitialDocumentBuildState {
|
||||
pub(in crate::conn::state) key: InitialDocumentBuildKey,
|
||||
pub(in crate::conn::state) completion: InitialDocumentBuildCompletion,
|
||||
pub struct InitialDocumentBuildState {
|
||||
pub key: InitialDocumentBuildKey,
|
||||
pub completion: InitialDocumentBuildCompletion,
|
||||
}
|
||||
|
||||
impl InitialDocumentBuildState {
|
||||
pub(in crate::conn::state) fn waiter(&self) -> InitialDocumentPageBuildWaiter {
|
||||
pub fn waiter(&self) -> InitialDocumentPageBuildWaiter {
|
||||
self.completion.waiter()
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn) struct AdmittedInitialDocumentBuild {
|
||||
pub struct AdmittedInitialDocumentBuild {
|
||||
key: InitialDocumentBuildKey,
|
||||
completion: InitialDocumentBuildCompletion,
|
||||
page: PendingPreparedDocumentPage,
|
||||
policy: PreparedDocumentPagePolicy,
|
||||
}
|
||||
|
||||
pub(in crate::conn) enum InitialDocumentAdmission {
|
||||
pub enum InitialDocumentAdmission {
|
||||
Present,
|
||||
Join(InitialDocumentPageBuildWaiter),
|
||||
Build(Box<AdmittedInitialDocumentBuild>),
|
||||
}
|
||||
|
||||
impl AdmittedInitialDocumentBuild {
|
||||
pub(in crate::conn) fn start_preparation(&mut self) -> anyhow::Result<()> {
|
||||
pub fn start_preparation(&mut self) -> anyhow::Result<()> {
|
||||
anyhow::ensure!(
|
||||
self.completion.pending(),
|
||||
"InitialDocumentPageBuildCancelled"
|
||||
);
|
||||
self.page.start_preparation()
|
||||
}
|
||||
pub(in crate::conn) fn key(&self) -> InitialDocumentBuildKey {
|
||||
pub fn key(&self) -> InitialDocumentBuildKey {
|
||||
self.key
|
||||
}
|
||||
pub(in crate::conn) fn inspection_endpoint(
|
||||
pub fn inspection_endpoint(
|
||||
&self,
|
||||
) -> moli_renderer_v8::RendererPreparedDocumentInspectionEndpoint {
|
||||
self.page.inspection_configuration_endpoint()
|
||||
}
|
||||
pub(in crate::conn) async fn materialize(self) -> anyhow::Result<BuiltInitialDocument> {
|
||||
pub async fn materialize(self) -> anyhow::Result<BuiltInitialDocument> {
|
||||
let Self {
|
||||
key,
|
||||
completion,
|
||||
@@ -167,7 +164,7 @@ impl AdmittedInitialDocumentBuild {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct BuiltInitialDocument {
|
||||
pub struct BuiltInitialDocument {
|
||||
key: InitialDocumentBuildKey,
|
||||
completion: InitialDocumentBuildCompletion,
|
||||
built: BuiltDocumentPage,
|
||||
@@ -175,23 +172,41 @@ pub(crate) struct BuiltInitialDocument {
|
||||
}
|
||||
|
||||
impl BuiltInitialDocument {
|
||||
pub(in crate::conn) fn key(&self) -> InitialDocumentBuildKey {
|
||||
pub fn key(&self) -> InitialDocumentBuildKey {
|
||||
self.key
|
||||
}
|
||||
pub(in crate::conn) async fn retire(self) {
|
||||
pub async fn retire(self) {
|
||||
let _ = self.built.page.close_async().await;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn built_mut_for_test(&mut self) -> &mut BuiltDocumentPage {
|
||||
&mut self.built
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn set_lifecycle_for_test(&mut self, lifecycle: DocumentLifecycle) {
|
||||
self.lifecycle = lifecycle;
|
||||
}
|
||||
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
#[doc(hidden)]
|
||||
pub fn lifecycle_for_test(&self) -> &DocumentLifecycle {
|
||||
&self.lifecycle
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn) struct CommittedInitialDocument {
|
||||
pub(in crate::conn) key: InitialDocumentBuildKey,
|
||||
pub(in crate::conn) lifecycle: CommittedDocumentLifecycle,
|
||||
pub(in crate::conn) diagnostics: moli_core::page::RendererPageCreationDiagnostics,
|
||||
pub(in crate::conn) inspection_endpoint: moli_renderer_v8::RendererInspectionEndpoint,
|
||||
pub struct CommittedInitialDocument {
|
||||
pub key: InitialDocumentBuildKey,
|
||||
pub lifecycle: CommittedDocumentLifecycle,
|
||||
pub diagnostics: crate::page::RendererPageCreationDiagnostics,
|
||||
pub inspection_endpoint: moli_renderer_v8::RendererInspectionEndpoint,
|
||||
}
|
||||
|
||||
impl WebContents {
|
||||
pub(in crate::conn::state) fn start_initial_document_build(
|
||||
pub fn start_initial_document_build(
|
||||
&mut self,
|
||||
inherited: InheritedDocumentPolicy,
|
||||
) -> Result<InitialDocumentAdmission, String> {
|
||||
@@ -255,7 +270,7 @@ impl WebContents {
|
||||
)))
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn commit_initial_document(
|
||||
pub fn commit_initial_document(
|
||||
&mut self,
|
||||
candidate: BuiltInitialDocument,
|
||||
) -> Result<CommittedInitialDocument, Box<BuiltInitialDocument>> {
|
||||
@@ -301,9 +316,9 @@ impl WebContents {
|
||||
.iter()
|
||||
.any(|event| {
|
||||
matches!(event.kind,
|
||||
moli_core::page::RendererDocumentLifecycleEventKind::Started { reason:
|
||||
moli_core::page::RendererLifecycleStartReason::ExplicitDocumentOpen
|
||||
| moli_core::page::RendererLifecycleStartReason::JavascriptDocumentReplacement })
|
||||
crate::page::RendererDocumentLifecycleEventKind::Started { reason:
|
||||
crate::page::RendererLifecycleStartReason::ExplicitDocumentOpen
|
||||
| crate::page::RendererLifecycleStartReason::JavascriptDocumentReplacement })
|
||||
})
|
||||
{
|
||||
self.navigation.mark_initial_empty_document_exited();
|
||||
+20
-23
@@ -1,4 +1,4 @@
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
browser::DocumentId,
|
||||
page::{
|
||||
RendererDocumentLifecycleIdentity, RendererJavaScriptDialogId,
|
||||
@@ -9,14 +9,14 @@ use moli_core::{
|
||||
/// Reuses the Browser incarnation and exact renderer source; no new allocator.
|
||||
/// The source distinguishes a parked popup from its later materialized Page.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct JavaScriptDialogKey {
|
||||
pub(in crate::conn) document: DocumentId,
|
||||
pub struct JavaScriptDialogKey {
|
||||
pub document: DocumentId,
|
||||
source: RendererDocumentLifecycleIdentity,
|
||||
dialog: RendererJavaScriptDialogId,
|
||||
}
|
||||
|
||||
impl JavaScriptDialogKey {
|
||||
pub(crate) fn new(document: DocumentId, dialog: &RendererPendingJavaScriptDialog) -> Self {
|
||||
pub fn new(document: DocumentId, dialog: &RendererPendingJavaScriptDialog) -> Self {
|
||||
Self {
|
||||
document,
|
||||
source: dialog.source_document(),
|
||||
@@ -26,20 +26,20 @@ impl JavaScriptDialogKey {
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) struct JavaScriptDialogSnapshot {
|
||||
pub(crate) dialog_type: String,
|
||||
pub(crate) message: String,
|
||||
pub(crate) default_prompt: String,
|
||||
pub struct JavaScriptDialogSnapshot {
|
||||
pub dialog_type: String,
|
||||
pub message: String,
|
||||
pub default_prompt: String,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct JavaScriptDialogClosed {
|
||||
pub(crate) dialog_type: String,
|
||||
pub(crate) user_input: String,
|
||||
pub struct JavaScriptDialogClosed {
|
||||
pub dialog_type: String,
|
||||
pub user_input: String,
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum JavaScriptDialogError {
|
||||
pub enum JavaScriptDialogError {
|
||||
NotFound,
|
||||
NotPrompt,
|
||||
}
|
||||
@@ -59,12 +59,12 @@ impl JavaScriptDialog {
|
||||
|
||||
/// Browser-owned modal state. Session snapshots can copy keys, never this owner.
|
||||
#[derive(Debug, Default)]
|
||||
pub(in crate::conn) struct JavaScriptDialogs {
|
||||
pub struct JavaScriptDialogs {
|
||||
pending: Vec<JavaScriptDialog>,
|
||||
}
|
||||
|
||||
impl JavaScriptDialogs {
|
||||
pub(in crate::conn) fn install(
|
||||
pub fn install(
|
||||
&mut self,
|
||||
document: DocumentId,
|
||||
renderer: RendererPendingJavaScriptDialog,
|
||||
@@ -83,10 +83,7 @@ impl JavaScriptDialogs {
|
||||
key
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn snapshot(
|
||||
&self,
|
||||
key: JavaScriptDialogKey,
|
||||
) -> Option<JavaScriptDialogSnapshot> {
|
||||
pub fn snapshot(&self, key: JavaScriptDialogKey) -> Option<JavaScriptDialogSnapshot> {
|
||||
let dialog = &self
|
||||
.pending
|
||||
.iter()
|
||||
@@ -99,7 +96,7 @@ impl JavaScriptDialogs {
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn set_prompt_text(
|
||||
pub fn set_prompt_text(
|
||||
&mut self,
|
||||
key: JavaScriptDialogKey,
|
||||
prompt_text: String,
|
||||
@@ -116,7 +113,7 @@ impl JavaScriptDialogs {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn finish(
|
||||
pub fn finish(
|
||||
&mut self,
|
||||
key: JavaScriptDialogKey,
|
||||
accepted: bool,
|
||||
@@ -134,15 +131,15 @@ impl JavaScriptDialogs {
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn dismiss(&mut self, key: JavaScriptDialogKey) {
|
||||
pub fn dismiss(&mut self, key: JavaScriptDialogKey) {
|
||||
let _ = self.finish(key, false, Some(String::new()));
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn is_empty(&self) -> bool {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
self.pending.is_empty()
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn clear(&mut self) {
|
||||
pub fn clear(&mut self) {
|
||||
for dialog in self.pending.drain(..) {
|
||||
let _ = dialog.renderer.finish(false, String::new());
|
||||
}
|
||||
+52
-45
@@ -1,4 +1,4 @@
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
browser::{
|
||||
BrowserSequence, DocumentId, DocumentLifecycle, MainFrameSlotId, NavigationId,
|
||||
RendererPageResidenceIdentity, WebContentsId,
|
||||
@@ -13,7 +13,7 @@ use super::{DocumentHost, InheritedDocumentPolicy, WebContents};
|
||||
/// Browser-only commit participant in the private migration residence (20/24b).
|
||||
/// No frontend identity, renderer attachment, arbitrary callback or Page lease.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct PreparedDocumentNavigation {
|
||||
pub struct PreparedDocumentNavigation {
|
||||
identity: DocumentNavigationIdentity,
|
||||
page: Page,
|
||||
lifecycle: DocumentLifecycle,
|
||||
@@ -39,15 +39,15 @@ impl DocumentNavigationIdentity {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct DocumentNavigationDestination {
|
||||
pub(crate) url: Url,
|
||||
pub(crate) security_origin: String,
|
||||
pub(crate) secure_context_type: String,
|
||||
pub struct DocumentNavigationDestination {
|
||||
pub url: Url,
|
||||
pub security_origin: String,
|
||||
pub secure_context_type: String,
|
||||
}
|
||||
|
||||
/// An admitted operation owns the candidate, not a reusable permission to
|
||||
/// configure or commit a different renderer reservation.
|
||||
pub(in crate::conn) struct AdmittedDocumentMaterialization {
|
||||
pub struct AdmittedDocumentMaterialization {
|
||||
identity: DocumentNavigationIdentity,
|
||||
page: PreparedDocumentPage,
|
||||
destination: DocumentNavigationDestination,
|
||||
@@ -55,7 +55,7 @@ pub(in crate::conn) struct AdmittedDocumentMaterialization {
|
||||
}
|
||||
|
||||
impl AdmittedDocumentMaterialization {
|
||||
pub(in crate::conn) async fn materialize(
|
||||
pub async fn materialize(
|
||||
self,
|
||||
) -> anyhow::Result<BuiltDocumentPage<PreparedDocumentNavigation>> {
|
||||
anyhow::ensure!(
|
||||
@@ -89,21 +89,25 @@ impl AdmittedDocumentMaterialization {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(in crate::conn) struct CommittedDocumentInfo {
|
||||
pub(in crate::conn) url: Url,
|
||||
pub(in crate::conn) title: String,
|
||||
pub(in crate::conn) security_origin: String,
|
||||
pub(in crate::conn) secure_context_type: String,
|
||||
pub struct CommittedDocumentInfo {
|
||||
pub url: Url,
|
||||
pub title: String,
|
||||
pub security_origin: String,
|
||||
pub secure_context_type: String,
|
||||
}
|
||||
|
||||
impl PreparedDocumentNavigation {
|
||||
pub(crate) fn renderer_residence(&self) -> RendererPageResidenceIdentity {
|
||||
RendererPageResidenceIdentity::from_page(&self.page)
|
||||
}
|
||||
|
||||
/// Configure the move-owned Browser participant without borrowing its owner.
|
||||
/// These are effective values, never frontend registration/session identities.
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
async fn apply_document_policy(
|
||||
mut self,
|
||||
interception: (bool, Option<moli_core::page::SubresourceResourceType>),
|
||||
permissions: &[moli_core::page::PermissionOverrideRegistration],
|
||||
interception: (bool, Option<crate::page::SubresourceResourceType>),
|
||||
permissions: &[crate::page::PermissionOverrideRegistration],
|
||||
) -> anyhow::Result<Self> {
|
||||
use anyhow::Context;
|
||||
// Empty/disabled is an effective value too: policy may have been
|
||||
@@ -119,11 +123,11 @@ impl PreparedDocumentNavigation {
|
||||
Ok(self)
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn navigation(&self) -> NavigationId {
|
||||
pub fn navigation(&self) -> NavigationId {
|
||||
self.identity.navigation
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn web_contents_id(&self) -> WebContentsId {
|
||||
pub fn web_contents_id(&self) -> WebContentsId {
|
||||
self.identity.web_contents
|
||||
}
|
||||
|
||||
@@ -155,34 +159,37 @@ impl PreparedDocumentNavigation {
|
||||
}
|
||||
|
||||
/// Self-contained Browser occurrence plus a move-owned post-commit retirement.
|
||||
pub(in crate::conn) struct CommittedDocumentNavigation {
|
||||
pub(in crate::conn) web_contents: WebContentsId,
|
||||
pub(in crate::conn) frame_slot: MainFrameSlotId,
|
||||
pub(in crate::conn) navigation: NavigationId,
|
||||
pub(in crate::conn) document: DocumentId,
|
||||
pub(in crate::conn) previous_document: Option<DocumentId>,
|
||||
pub(in crate::conn) previous_renderer: Option<RendererPageResidenceIdentity>,
|
||||
pub(in crate::conn) inspection_endpoint: moli_renderer_v8::RendererInspectionEndpoint,
|
||||
pub(in crate::conn) lifecycle: CommittedDocumentLifecycle,
|
||||
pub(in crate::conn) info: CommittedDocumentInfo,
|
||||
pub(in crate::conn) retirement: RetiringDocument,
|
||||
pub(in crate::conn) post_response_continuation:
|
||||
Option<RendererPageCommandPostResponseContinuation>,
|
||||
pub struct CommittedDocumentNavigation {
|
||||
pub web_contents: WebContentsId,
|
||||
pub frame_slot: MainFrameSlotId,
|
||||
pub navigation: NavigationId,
|
||||
pub document: DocumentId,
|
||||
pub previous_document: Option<DocumentId>,
|
||||
pub previous_renderer: Option<RendererPageResidenceIdentity>,
|
||||
pub inspection_endpoint: moli_renderer_v8::RendererInspectionEndpoint,
|
||||
pub lifecycle: CommittedDocumentLifecycle,
|
||||
pub info: CommittedDocumentInfo,
|
||||
pub retirement: RetiringDocument,
|
||||
pub post_response_continuation: Option<RendererPageCommandPostResponseContinuation>,
|
||||
}
|
||||
|
||||
/// Retirement has no commit authority and never retains a Browser registry borrow.
|
||||
pub(crate) struct RetiringDocument(Option<Page>);
|
||||
pub struct RetiringDocument(Option<Page>);
|
||||
|
||||
/// A creation prefix published by the Browser commit, never a request to
|
||||
/// initialize or rewind native state from a frontend projection.
|
||||
pub(crate) struct CommittedDocumentLifecycle {
|
||||
pub(crate) document: DocumentId,
|
||||
pub(crate) browser_sequence: BrowserSequence,
|
||||
pub(crate) artifacts: RendererPageCreationArtifacts,
|
||||
pub struct CommittedDocumentLifecycle {
|
||||
pub document: DocumentId,
|
||||
pub browser_sequence: BrowserSequence,
|
||||
pub artifacts: RendererPageCreationArtifacts,
|
||||
}
|
||||
|
||||
impl RetiringDocument {
|
||||
pub(crate) async fn close(self) {
|
||||
pub(crate) fn from_page(page: Option<Page>) -> Self {
|
||||
Self(page)
|
||||
}
|
||||
|
||||
pub async fn close(self) {
|
||||
if let Some(page) = self.0 {
|
||||
let _ = page.close_async().await;
|
||||
}
|
||||
@@ -218,14 +225,14 @@ impl WebContents {
|
||||
|
||||
/// Construct a commit participant for fixtures with an already built Page.
|
||||
/// Production navigation must materialize an admitted prepared Document.
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn start_loaded_document_navigation(
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn start_loaded_document_navigation(
|
||||
&self,
|
||||
navigation: NavigationId,
|
||||
page: Page,
|
||||
destination: DocumentNavigationDestination,
|
||||
artifacts: &RendererPageCreationArtifacts,
|
||||
permissions: Vec<moli_core::page::PermissionOverrideRegistration>,
|
||||
permissions: Vec<crate::page::PermissionOverrideRegistration>,
|
||||
) -> Result<
|
||||
impl std::future::Future<Output = anyhow::Result<PreparedDocumentNavigation>> + use<>,
|
||||
&'static str,
|
||||
@@ -249,7 +256,7 @@ impl WebContents {
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_document_materialization(
|
||||
pub fn start_document_materialization(
|
||||
&mut self,
|
||||
navigation: NavigationId,
|
||||
response: super::PreparedNavigationResponse,
|
||||
@@ -285,7 +292,7 @@ impl WebContents {
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn commit_document_navigation(
|
||||
pub fn commit_document_navigation(
|
||||
&mut self,
|
||||
mut prepared: PreparedDocumentNavigation,
|
||||
) -> Result<CommittedDocumentNavigation, &'static str> {
|
||||
@@ -353,7 +360,7 @@ impl WebContents {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
browser::DocumentRetirement,
|
||||
runtime::{Browser, BrowserConfig},
|
||||
};
|
||||
@@ -402,7 +409,7 @@ mod tests {
|
||||
.evaluate_runtime_expression_with_await_async(query, true)
|
||||
.await
|
||||
.unwrap();
|
||||
page.set_permission_overrides_async(&[moli_core::page::PermissionOverrideRegistration {
|
||||
page.set_permission_overrides_async(&[crate::page::PermissionOverrideRegistration {
|
||||
permission: serde_json::json!({"name": "geolocation"}),
|
||||
setting: "granted".into(),
|
||||
origin: None,
|
||||
@@ -606,7 +613,7 @@ mod tests {
|
||||
((true, None), Vec::new()),
|
||||
(
|
||||
(false, None),
|
||||
vec![moli_core::page::PermissionOverrideRegistration {
|
||||
vec![crate::page::PermissionOverrideRegistration {
|
||||
permission: serde_json::json!({"name": "geolocation"}),
|
||||
setting: "granted".into(),
|
||||
origin: None,
|
||||
+126
-98
@@ -1,22 +1,22 @@
|
||||
//! Browser navigation state, private in the current residence until Commit 24b.
|
||||
//! Protocol loader correlation and renderer output binding stay in TargetPageSlot.
|
||||
|
||||
use moli_core::browser::{DocumentId, NavigationId, WebContentsId};
|
||||
use crate::browser::{DocumentId, NavigationId, WebContentsId};
|
||||
|
||||
mod history;
|
||||
pub use history::PageNavigationHistoryEntry;
|
||||
pub(crate) use history::{HistoryTraversalDestination, ResolvedHistoryTraversal};
|
||||
pub use history::{HistoryTraversalDestination, ResolvedHistoryTraversal};
|
||||
use history::{NavigationHistoryState, PendingNavigationHistoryUpdate};
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct InitialDocumentCreator {
|
||||
pub struct InitialDocumentCreator {
|
||||
web_contents_id: WebContentsId,
|
||||
security_origin: String,
|
||||
secure_context_type: String,
|
||||
}
|
||||
|
||||
impl InitialDocumentCreator {
|
||||
pub(crate) fn new(
|
||||
pub fn new(
|
||||
web_contents_id: WebContentsId,
|
||||
security_origin: String,
|
||||
secure_context_type: String,
|
||||
@@ -28,13 +28,13 @@ impl InitialDocumentCreator {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn web_contents_id(&self) -> WebContentsId {
|
||||
pub fn web_contents_id(&self) -> WebContentsId {
|
||||
self.web_contents_id
|
||||
}
|
||||
pub(crate) fn security_origin(&self) -> &str {
|
||||
pub fn security_origin(&self) -> &str {
|
||||
&self.security_origin
|
||||
}
|
||||
pub(crate) fn secure_context_type(&self) -> &str {
|
||||
pub fn secure_context_type(&self) -> &str {
|
||||
&self.secure_context_type
|
||||
}
|
||||
}
|
||||
@@ -49,13 +49,61 @@ enum InitialDocumentLifecycle {
|
||||
|
||||
/// Browser seed and lifecycle; no Target, loader or mirrored pending state.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InitialDocument {
|
||||
pub struct InitialDocument {
|
||||
initial_url: String,
|
||||
creator: Option<InitialDocumentCreator>,
|
||||
storage_key: Option<moli_storage_key::MoliStorageKey>,
|
||||
lifecycle: InitialDocumentLifecycle,
|
||||
}
|
||||
|
||||
/// Owned diagnostic projection of an initial empty Document.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct InitialDocumentSnapshot {
|
||||
initial_url: String,
|
||||
creator: Option<InitialDocumentCreator>,
|
||||
storage_key: Option<moli_storage_key::MoliStorageKey>,
|
||||
materialized: bool,
|
||||
exited: bool,
|
||||
}
|
||||
|
||||
impl InitialDocumentSnapshot {
|
||||
pub fn initial_url(&self) -> &str {
|
||||
&self.initial_url
|
||||
}
|
||||
|
||||
pub fn creator(&self) -> Option<&InitialDocumentCreator> {
|
||||
self.creator.as_ref()
|
||||
}
|
||||
|
||||
pub fn storage_key(&self) -> Option<&moli_storage_key::MoliStorageKey> {
|
||||
self.storage_key.as_ref()
|
||||
}
|
||||
|
||||
pub fn materialized(&self) -> bool {
|
||||
self.materialized
|
||||
}
|
||||
|
||||
pub fn exited(&self) -> bool {
|
||||
self.exited
|
||||
}
|
||||
|
||||
pub fn is_on_initial_empty_document(&self) -> bool {
|
||||
!self.exited
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&InitialDocument> for InitialDocumentSnapshot {
|
||||
fn from(document: &InitialDocument) -> Self {
|
||||
Self {
|
||||
initial_url: document.initial_url.clone(),
|
||||
creator: document.creator.clone(),
|
||||
storage_key: document.storage_key.clone(),
|
||||
materialized: document.materialized(),
|
||||
exited: document.exited(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl InitialDocument {
|
||||
fn new(
|
||||
initial_url: String,
|
||||
@@ -69,25 +117,25 @@ impl InitialDocument {
|
||||
lifecycle: InitialDocumentLifecycle::Unmaterialized,
|
||||
}
|
||||
}
|
||||
pub(crate) fn initial_url(&self) -> &str {
|
||||
pub fn initial_url(&self) -> &str {
|
||||
&self.initial_url
|
||||
}
|
||||
pub(crate) fn creator(&self) -> Option<&InitialDocumentCreator> {
|
||||
pub fn creator(&self) -> Option<&InitialDocumentCreator> {
|
||||
self.creator.as_ref()
|
||||
}
|
||||
pub(crate) fn storage_key(&self) -> Option<&moli_storage_key::MoliStorageKey> {
|
||||
pub fn storage_key(&self) -> Option<&moli_storage_key::MoliStorageKey> {
|
||||
self.storage_key.as_ref()
|
||||
}
|
||||
pub(crate) fn materialized(&self) -> bool {
|
||||
pub fn materialized(&self) -> bool {
|
||||
matches!(self.lifecycle, InitialDocumentLifecycle::Materialized)
|
||||
}
|
||||
pub(crate) fn exited(&self) -> bool {
|
||||
pub fn exited(&self) -> bool {
|
||||
matches!(self.lifecycle, InitialDocumentLifecycle::Exited)
|
||||
}
|
||||
pub(crate) fn is_on_initial_empty_document(&self) -> bool {
|
||||
pub fn is_on_initial_empty_document(&self) -> bool {
|
||||
!self.exited()
|
||||
}
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
fn mark_materialized(&mut self) {
|
||||
if !self.exited() {
|
||||
self.lifecycle = InitialDocumentLifecycle::Materialized;
|
||||
@@ -112,7 +160,7 @@ struct PendingNavigationRequest {
|
||||
history_update: Option<PendingNavigationHistoryUpdate>,
|
||||
paused_interception: Option<super::navigation_interception::PausedNavigationInterception>,
|
||||
document_preparation: Option<(
|
||||
moli_core::browser::RendererPageResidenceIdentity,
|
||||
crate::browser::RendererPageResidenceIdentity,
|
||||
moli_fetch::FetchCancelHandle,
|
||||
)>,
|
||||
cancellation_handles: Vec<moli_fetch::FetchCancelHandle>,
|
||||
@@ -180,7 +228,7 @@ impl Drop for PendingNavigationRequest {
|
||||
}
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub(in crate::conn) struct NavigationController {
|
||||
pub struct NavigationController {
|
||||
pending_navigation_request: Option<PendingNavigationRequest>,
|
||||
committed_document_navigation: Option<NavigationId>,
|
||||
history: NavigationHistoryState,
|
||||
@@ -188,8 +236,8 @@ pub(in crate::conn) struct NavigationController {
|
||||
}
|
||||
|
||||
impl NavigationController {
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn has_paused_request_for_test(&self) -> bool {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn has_paused_request_for_test(&self) -> bool {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
.and_then(|request| request.paused_interception.as_ref())
|
||||
@@ -201,8 +249,8 @@ impl NavigationController {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn has_paused_auth_for_test(&self) -> bool {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn has_paused_auth_for_test(&self) -> bool {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
.and_then(|request| request.paused_interception.as_ref())
|
||||
@@ -214,10 +262,8 @@ impl NavigationController {
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn paused_response_for_test(
|
||||
&self,
|
||||
) -> Option<&crate::conn::PausedDocumentTransfer> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn paused_response_for_test(&self) -> Option<&super::PausedDocumentTransfer> {
|
||||
match self
|
||||
.pending_navigation_request
|
||||
.as_ref()?
|
||||
@@ -250,7 +296,7 @@ impl NavigationController {
|
||||
if pending.paused_interception.is_some() {
|
||||
return Err("navigation already has a paused interception".to_owned());
|
||||
}
|
||||
let request = moli_core::browser::BrowserRequestId::allocate();
|
||||
let request = crate::browser::BrowserRequestId::allocate();
|
||||
let permit = super::NavigationInterceptionPermit {
|
||||
web_contents,
|
||||
navigation,
|
||||
@@ -322,7 +368,7 @@ impl NavigationController {
|
||||
if pending.paused_interception.is_some() {
|
||||
return Err("navigation already has a paused interception".to_owned());
|
||||
}
|
||||
let request = moli_core::browser::BrowserRequestId::allocate();
|
||||
let request = crate::browser::BrowserRequestId::allocate();
|
||||
let permit = super::NavigationInterceptionPermit {
|
||||
web_contents: identity.web_contents,
|
||||
navigation: identity.navigation,
|
||||
@@ -376,7 +422,7 @@ impl NavigationController {
|
||||
&mut self,
|
||||
web_contents: WebContentsId,
|
||||
navigation: NavigationId,
|
||||
transfer: crate::conn::PausedDocumentTransfer,
|
||||
transfer: super::PausedDocumentTransfer,
|
||||
) -> Result<super::NavigationInterceptionPermit, String> {
|
||||
let pending = self
|
||||
.pending_navigation_request
|
||||
@@ -390,7 +436,7 @@ impl NavigationController {
|
||||
if pending.paused_interception.is_some() {
|
||||
return Err("navigation already has a paused interception".to_owned());
|
||||
}
|
||||
let request = moli_core::browser::BrowserRequestId::allocate();
|
||||
let request = crate::browser::BrowserRequestId::allocate();
|
||||
let permit = super::NavigationInterceptionPermit {
|
||||
web_contents,
|
||||
navigation,
|
||||
@@ -408,7 +454,7 @@ impl NavigationController {
|
||||
pub(super) fn take_response(
|
||||
&mut self,
|
||||
permit: super::NavigationInterceptionPermit,
|
||||
) -> Option<crate::conn::PausedDocumentTransfer> {
|
||||
) -> Option<super::PausedDocumentTransfer> {
|
||||
let pending = self.pending_navigation_request.as_mut().filter(|pending| {
|
||||
pending.navigation_id == permit.navigation
|
||||
&& pending.document_id == permit.document
|
||||
@@ -443,8 +489,8 @@ impl NavigationController {
|
||||
pub(super) fn restore_response(
|
||||
&mut self,
|
||||
permit: super::NavigationInterceptionPermit,
|
||||
transfer: crate::conn::PausedDocumentTransfer,
|
||||
) -> Result<(), Box<crate::conn::PausedDocumentTransfer>> {
|
||||
transfer: super::PausedDocumentTransfer,
|
||||
) -> Result<(), Box<super::PausedDocumentTransfer>> {
|
||||
let Some(pending) = self.pending_navigation_request.as_mut().filter(|pending| {
|
||||
pending.navigation_id == permit.navigation
|
||||
&& pending.document_id == permit.document
|
||||
@@ -472,16 +518,14 @@ impl NavigationController {
|
||||
self.history.resolve_traversal(destination)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn initial_document_build(
|
||||
&self,
|
||||
) -> Option<&super::InitialDocumentBuildState> {
|
||||
pub fn initial_document_build(&self) -> Option<&super::InitialDocumentBuildState> {
|
||||
match &self.initial_empty_document.as_ref()?.lifecycle {
|
||||
InitialDocumentLifecycle::Building(build) => Some(build),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn admit_initial_document_build(
|
||||
pub fn admit_initial_document_build(
|
||||
&mut self,
|
||||
url: &str,
|
||||
build: super::InitialDocumentBuildState,
|
||||
@@ -492,7 +536,7 @@ impl NavigationController {
|
||||
initial.lifecycle = InitialDocumentLifecycle::Building(build);
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn cancel_initial_document_build(&mut self) {
|
||||
pub fn cancel_initial_document_build(&mut self) {
|
||||
if let Some(initial) = self.initial_empty_document.as_mut()
|
||||
&& matches!(initial.lifecycle, InitialDocumentLifecycle::Building(_))
|
||||
{
|
||||
@@ -500,9 +544,7 @@ impl NavigationController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn take_initial_document_build_for_commit(
|
||||
&mut self,
|
||||
) -> super::InitialDocumentBuildState {
|
||||
pub fn take_initial_document_build_for_commit(&mut self) -> super::InitialDocumentBuildState {
|
||||
let initial = self
|
||||
.initial_empty_document
|
||||
.as_mut()
|
||||
@@ -516,24 +558,24 @@ impl NavigationController {
|
||||
build
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn pending_document(&self) -> Option<(NavigationId, DocumentId)> {
|
||||
pub fn pending_document(&self) -> Option<(NavigationId, DocumentId)> {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
.filter(|request| !request.committed)
|
||||
.map(|request| (request.navigation_id, request.document_id))
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn current_document_navigation(&self) -> Option<NavigationId> {
|
||||
pub fn current_document_navigation(&self) -> Option<NavigationId> {
|
||||
self.pending_document()
|
||||
.map(|(navigation, _)| navigation)
|
||||
.or(self.committed_document_navigation)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn committed_document_navigation(&self) -> Option<NavigationId> {
|
||||
pub fn committed_document_navigation(&self) -> Option<NavigationId> {
|
||||
self.committed_document_navigation
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn retains_navigation(&self, navigation: NavigationId) -> bool {
|
||||
pub fn retains_navigation(&self, navigation: NavigationId) -> bool {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
.is_some_and(|request| request.matches(&navigation))
|
||||
@@ -592,7 +634,7 @@ impl NavigationController {
|
||||
self.history.clear_pending_update();
|
||||
}
|
||||
|
||||
pub(crate) fn initial_empty_document_pending_cross_document_navigation(&self) -> bool {
|
||||
pub fn initial_empty_document_pending_cross_document_navigation(&self) -> bool {
|
||||
self.is_on_initial_empty_document() == Some(true) && self.has_pending_document_navigation()
|
||||
}
|
||||
|
||||
@@ -600,18 +642,18 @@ impl NavigationController {
|
||||
self.history.clear();
|
||||
}
|
||||
|
||||
pub(crate) fn is_default(&self) -> bool {
|
||||
pub fn is_default(&self) -> bool {
|
||||
self.pending_navigation_request.is_none()
|
||||
&& self.committed_document_navigation.is_none()
|
||||
&& self.initial_empty_document.is_none()
|
||||
&& self.history == NavigationHistoryState::default()
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn current_url(&self) -> Option<&str> {
|
||||
pub fn current_url(&self) -> Option<&str> {
|
||||
self.history.current_url()
|
||||
}
|
||||
|
||||
pub(crate) fn document_navigation_cancellation_handle(
|
||||
pub fn document_navigation_cancellation_handle(
|
||||
&self,
|
||||
token: &NavigationId,
|
||||
) -> Option<moli_fetch::FetchCancelHandle> {
|
||||
@@ -638,7 +680,7 @@ impl NavigationController {
|
||||
pub(super) fn admit_document_load(
|
||||
&mut self,
|
||||
navigation: NavigationId,
|
||||
renderer: moli_core::browser::RendererPageResidenceIdentity,
|
||||
renderer: crate::browser::RendererPageResidenceIdentity,
|
||||
cancellation: moli_fetch::FetchCancelHandle,
|
||||
request_cancellation: moli_fetch::FetchCancelHandle,
|
||||
) -> bool {
|
||||
@@ -662,10 +704,10 @@ impl NavigationController {
|
||||
true
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn accepts_document_preparation(
|
||||
pub fn accepts_document_preparation(
|
||||
&self,
|
||||
navigation: NavigationId,
|
||||
renderer: moli_core::browser::RendererPageResidenceIdentity,
|
||||
renderer: crate::browser::RendererPageResidenceIdentity,
|
||||
) -> bool {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
@@ -714,26 +756,26 @@ impl NavigationController {
|
||||
true
|
||||
}
|
||||
|
||||
pub(crate) fn has_inflight_background_navigation(&self) -> bool {
|
||||
pub fn has_inflight_background_navigation(&self) -> bool {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
.is_some_and(|request| request.background_completion_pending)
|
||||
}
|
||||
|
||||
pub(crate) fn accepts_pending_document_navigation_event(&self, token: &NavigationId) -> bool {
|
||||
pub fn accepts_pending_document_navigation_event(&self, token: &NavigationId) -> bool {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
.is_some_and(|request| request.matches(token) && !request.committed)
|
||||
}
|
||||
|
||||
pub(crate) fn accepts_document_body_completion_event(&self, token: &NavigationId) -> bool {
|
||||
pub fn accepts_document_body_completion_event(&self, token: &NavigationId) -> bool {
|
||||
match self.pending_navigation_request.as_ref() {
|
||||
Some(pending) => pending.matches(token),
|
||||
None => self.committed_document_navigation.as_ref() == Some(token),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn has_pending_document_navigation(&self) -> bool {
|
||||
pub fn has_pending_document_navigation(&self) -> bool {
|
||||
self.pending_navigation_request
|
||||
.as_ref()
|
||||
.is_some_and(|request| !request.committed)
|
||||
@@ -763,7 +805,7 @@ impl NavigationController {
|
||||
self.initial_empty_document = Some(InitialDocument::new(initial_url, creator, storage_key));
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(super) fn mark_initial_empty_document_materialized(&mut self) {
|
||||
if let Some(state) = self.initial_empty_document.as_mut() {
|
||||
state.mark_materialized();
|
||||
@@ -776,17 +818,17 @@ impl NavigationController {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn initial_empty_document_state(&self) -> Option<&InitialDocument> {
|
||||
pub fn initial_empty_document_state(&self) -> Option<&InitialDocument> {
|
||||
self.initial_empty_document.as_ref()
|
||||
}
|
||||
|
||||
pub(crate) fn initial_empty_document_url_if_current(&self) -> Option<&str> {
|
||||
pub fn initial_empty_document_url_if_current(&self) -> Option<&str> {
|
||||
self.initial_empty_document_state()
|
||||
.filter(|state| state.is_on_initial_empty_document())
|
||||
.map(InitialDocument::initial_url)
|
||||
}
|
||||
|
||||
pub(crate) fn initial_empty_document_storage_key_if_current(
|
||||
pub fn initial_empty_document_storage_key_if_current(
|
||||
&self,
|
||||
) -> Option<&moli_storage_key::MoliStorageKey> {
|
||||
self.initial_empty_document_state()
|
||||
@@ -794,20 +836,20 @@ impl NavigationController {
|
||||
.and_then(InitialDocument::storage_key)
|
||||
}
|
||||
|
||||
pub(crate) fn is_on_initial_empty_document(&self) -> Option<bool> {
|
||||
pub fn is_on_initial_empty_document(&self) -> Option<bool> {
|
||||
self.initial_empty_document_state()
|
||||
.map(InitialDocument::is_on_initial_empty_document)
|
||||
}
|
||||
|
||||
pub(crate) fn can_install_current_initial_empty_document_page(&self) -> bool {
|
||||
pub fn can_install_current_initial_empty_document_page(&self) -> bool {
|
||||
!self.has_pending_document_navigation()
|
||||
&& self
|
||||
.initial_empty_document_state()
|
||||
.is_none_or(InitialDocument::is_on_initial_empty_document)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn has_materialized_current_initial_empty_document(&self) -> bool {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn has_materialized_current_initial_empty_document(&self) -> bool {
|
||||
self.initial_empty_document_state()
|
||||
.is_some_and(|state| state.is_on_initial_empty_document() && state.materialized())
|
||||
}
|
||||
@@ -855,11 +897,11 @@ impl NavigationController {
|
||||
self.history.mark_traverse_to_entry(entry_id);
|
||||
}
|
||||
|
||||
pub(crate) fn navigation_history_entry_url(&self, entry_id: i32) -> Option<String> {
|
||||
pub fn navigation_history_entry_url(&self, entry_id: i32) -> Option<String> {
|
||||
self.history.entry_url(entry_id)
|
||||
}
|
||||
|
||||
pub(crate) fn navigation_history_snapshot(&self) -> (usize, Vec<PageNavigationHistoryEntry>) {
|
||||
pub fn navigation_history_snapshot(&self) -> (usize, Vec<PageNavigationHistoryEntry>) {
|
||||
self.history.snapshot()
|
||||
}
|
||||
|
||||
@@ -890,7 +932,7 @@ impl NavigationController {
|
||||
self.history.record_loaded_entry(entry, update);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub(super) fn record_navigation_history_for_test(&mut self, snapshot: (String, String)) {
|
||||
let entry = self.navigation_history_entry_for_page_snapshot(snapshot);
|
||||
let update = self.history.take_pending_update();
|
||||
@@ -901,7 +943,7 @@ impl NavigationController {
|
||||
&mut self,
|
||||
url: String,
|
||||
title: String,
|
||||
history_update: moli_core::page::SameDocumentHistoryUpdate,
|
||||
history_update: crate::page::SameDocumentHistoryUpdate,
|
||||
) -> bool {
|
||||
self.history
|
||||
.record_same_document_update(url, title, history_update)
|
||||
@@ -909,36 +951,30 @@ impl NavigationController {
|
||||
}
|
||||
|
||||
impl super::WebContents {
|
||||
pub(in crate::conn::state) fn navigation(&self) -> &NavigationController {
|
||||
pub fn navigation(&self) -> &NavigationController {
|
||||
&self.navigation
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_document_navigation(&mut self) -> NavigationId {
|
||||
pub fn start_document_navigation(&mut self) -> NavigationId {
|
||||
self.navigation.start_document_navigation()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn commit_pending_document_navigation_if_matches(
|
||||
&mut self,
|
||||
token: &NavigationId,
|
||||
) -> bool {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn commit_pending_document_navigation_if_matches(&mut self, token: &NavigationId) -> bool {
|
||||
self.navigation
|
||||
.commit_pending_document_navigation_if_matches(token)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn clear_pending_document_navigation_if_matches(
|
||||
&mut self,
|
||||
token: &NavigationId,
|
||||
) -> bool {
|
||||
pub fn clear_pending_document_navigation_if_matches(&mut self, token: &NavigationId) -> bool {
|
||||
self.navigation
|
||||
.clear_pending_document_navigation_if_matches(token)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn clear_document_navigation_state(&mut self) {
|
||||
pub fn clear_document_navigation_state(&mut self) {
|
||||
self.navigation.clear_document_navigation_state()
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn arm_background_navigation_completion(
|
||||
pub fn arm_background_navigation_completion(
|
||||
&mut self,
|
||||
token: &NavigationId,
|
||||
additional_cancellation: Option<moli_fetch::FetchCancelHandle>,
|
||||
@@ -947,15 +983,12 @@ impl super::WebContents {
|
||||
.arm_background_navigation_completion(token, additional_cancellation)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn settle_background_navigation_completion(
|
||||
&mut self,
|
||||
token: &NavigationId,
|
||||
) -> bool {
|
||||
pub fn settle_background_navigation_completion(&mut self, token: &NavigationId) -> bool {
|
||||
self.navigation
|
||||
.settle_background_navigation_completion(token)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn begin_initial_empty_document(
|
||||
pub fn begin_initial_empty_document(
|
||||
&mut self,
|
||||
initial_url: String,
|
||||
creator: Option<InitialDocumentCreator>,
|
||||
@@ -965,32 +998,27 @@ impl super::WebContents {
|
||||
.begin_initial_empty_document(initial_url, creator, storage_key)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn mark_initial_empty_document_materialized(&mut self) {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn mark_initial_empty_document_materialized(&mut self) {
|
||||
self.navigation.mark_initial_empty_document_materialized()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn mark_initial_empty_document_exited(&mut self) {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn mark_initial_empty_document_exited(&mut self) {
|
||||
self.navigation.mark_initial_empty_document_exited()
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn mark_next_navigation_history_replace_current(&mut self) {
|
||||
pub fn mark_next_navigation_history_replace_current(&mut self) {
|
||||
self.navigation
|
||||
.mark_next_navigation_history_replace_current()
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn mark_next_navigation_history_replace_initial_empty_document(
|
||||
&mut self,
|
||||
) {
|
||||
pub fn mark_next_navigation_history_replace_initial_empty_document(&mut self) {
|
||||
self.navigation
|
||||
.mark_next_navigation_history_replace_initial_empty_document()
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn mark_next_navigation_history_traverse_to_entry(
|
||||
&mut self,
|
||||
entry_id: i32,
|
||||
) {
|
||||
pub fn mark_next_navigation_history_traverse_to_entry(&mut self, entry_id: i32) {
|
||||
self.navigation
|
||||
.mark_next_navigation_history_traverse_to_entry(entry_id)
|
||||
}
|
||||
@@ -1006,7 +1034,7 @@ fn is_initial_empty_document_url(raw_url: &str) -> bool {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use moli_core::page::SameDocumentHistoryUpdate;
|
||||
use crate::page::SameDocumentHistoryUpdate;
|
||||
|
||||
#[test]
|
||||
fn navigation_history_seed_entry_preserves_pending_update() {
|
||||
+3
-3
@@ -1,12 +1,12 @@
|
||||
use moli_core::page::SessionHistoryUpdateKind;
|
||||
use crate::page::SessionHistoryUpdateKind;
|
||||
|
||||
pub(crate) enum HistoryTraversalDestination {
|
||||
pub enum HistoryTraversalDestination {
|
||||
Entry(i32),
|
||||
Delta(i64),
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub(crate) enum ResolvedHistoryTraversal {
|
||||
pub enum ResolvedHistoryTraversal {
|
||||
Noop,
|
||||
Entry {
|
||||
entry_id: i32,
|
||||
+17
-28
@@ -1,7 +1,6 @@
|
||||
use super::WebContents;
|
||||
use crate::conn::state::PageNavigationHistoryEntry;
|
||||
use crate::conn::state::{HistoryTraversalDestination, ResolvedHistoryTraversal};
|
||||
use moli_core::{
|
||||
use super::{HistoryTraversalDestination, PageNavigationHistoryEntry, ResolvedHistoryTraversal};
|
||||
use crate::{
|
||||
browser::{DocumentId, WebContentsId},
|
||||
page::{CompletedPageCommand, PendingPageCommand, SameDocumentHistoryUpdate},
|
||||
};
|
||||
@@ -10,49 +9,41 @@ use url::Url;
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
pub(crate) struct SameDocumentNavigationCommitted {
|
||||
pub(crate) web_contents: WebContentsId,
|
||||
pub(crate) document: DocumentId,
|
||||
pub(crate) url: Url,
|
||||
pub struct SameDocumentNavigationCommitted {
|
||||
pub web_contents: WebContentsId,
|
||||
pub document: DocumentId,
|
||||
pub url: Url,
|
||||
}
|
||||
|
||||
impl WebContents {
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn record_navigation_history_for_test(
|
||||
&mut self,
|
||||
snapshot: (String, String),
|
||||
) {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn record_navigation_history_for_test(&mut self, snapshot: (String, String)) {
|
||||
self.navigation.record_navigation_history_for_test(snapshot);
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn mark_renderer_crashed(&mut self) {
|
||||
pub fn mark_renderer_crashed(&mut self) {
|
||||
self.crashed = true;
|
||||
self.navigation.clear_navigation_history();
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn resolve_history_traversal(
|
||||
pub fn resolve_history_traversal(
|
||||
&self,
|
||||
destination: HistoryTraversalDestination,
|
||||
) -> Result<ResolvedHistoryTraversal, &'static str> {
|
||||
self.navigation.resolve_history_traversal(destination)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn navigation_history_snapshot(
|
||||
&self,
|
||||
) -> (usize, Vec<PageNavigationHistoryEntry>) {
|
||||
pub fn navigation_history_snapshot(&self) -> (usize, Vec<PageNavigationHistoryEntry>) {
|
||||
self.navigation.navigation_history_snapshot()
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn navigation_history_entry_url(
|
||||
&self,
|
||||
entry_id: i32,
|
||||
) -> Option<String> {
|
||||
pub fn navigation_history_entry_url(&self, entry_id: i32) -> Option<String> {
|
||||
self.navigation.navigation_history_entry_url(entry_id)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn commit_document_title(
|
||||
pub fn commit_document_title(
|
||||
&mut self,
|
||||
change: &moli_core::RendererDocumentTitleChanged,
|
||||
change: &crate::RendererDocumentTitleChanged,
|
||||
) -> Option<bool> {
|
||||
let document = self.main_frame.current_document.as_ref()?;
|
||||
let snapshot = document.lifecycle.snapshot()?;
|
||||
@@ -71,7 +62,7 @@ impl WebContents {
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn commit_same_document_navigation(
|
||||
pub fn commit_same_document_navigation(
|
||||
&mut self,
|
||||
document_id: DocumentId,
|
||||
url: Url,
|
||||
@@ -102,9 +93,7 @@ impl WebContents {
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_reset_navigation_history(
|
||||
&self,
|
||||
) -> Result<PendingPageCommand, String> {
|
||||
pub fn start_reset_navigation_history(&self) -> Result<PendingPageCommand, String> {
|
||||
if !self.navigation.can_reset_navigation_history() {
|
||||
return Err("History cannot be pruned".to_owned());
|
||||
}
|
||||
@@ -117,7 +106,7 @@ impl WebContents {
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn finish_reset_navigation_history(
|
||||
pub fn finish_reset_navigation_history(
|
||||
&mut self,
|
||||
completion: CompletedPageCommand,
|
||||
) -> Result<bool, String> {
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
use super::*;
|
||||
use crate::conn::state::web_contents::tests::BrowserFixture;
|
||||
use moli_core::{RendererDocumentTitleChanged, page::RendererDocumentLifecycleIdentity};
|
||||
use crate::browser::web_contents::tests::BrowserFixture;
|
||||
use crate::{RendererDocumentTitleChanged, page::RendererDocumentLifecycleIdentity};
|
||||
use serde_json::json;
|
||||
|
||||
fn title_change(browser: &BrowserFixture, title: &str) -> RendererDocumentTitleChanged {
|
||||
+65
-80
@@ -1,10 +1,10 @@
|
||||
use anyhow::Context;
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
browser::{
|
||||
BrowserRequestId, DocumentId, NavigationId, NavigationRequestLoadPolicy, WebContentsId,
|
||||
},
|
||||
page::SubresourceAuthCredentials,
|
||||
};
|
||||
use anyhow::Context;
|
||||
use moli_fetch::{
|
||||
NetworkFetchResult, NetworkObservationJournal, RawResponse, StreamingRawResponse,
|
||||
};
|
||||
@@ -18,7 +18,7 @@ use super::{
|
||||
/// A single Browser decision. Copying a protocol correlation cannot duplicate
|
||||
/// its authority: the owning pending navigation consumes this request once.
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct NavigationInterceptionPermit {
|
||||
pub struct NavigationInterceptionPermit {
|
||||
pub(super) web_contents: WebContentsId,
|
||||
pub(super) navigation: NavigationId,
|
||||
pub(super) document: DocumentId,
|
||||
@@ -26,16 +26,16 @@ pub(crate) struct NavigationInterceptionPermit {
|
||||
}
|
||||
|
||||
impl NavigationInterceptionPermit {
|
||||
pub(crate) fn navigation(self) -> NavigationId {
|
||||
pub fn navigation(self) -> NavigationId {
|
||||
self.navigation
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn web_contents(self) -> WebContentsId {
|
||||
pub fn web_contents(self) -> WebContentsId {
|
||||
self.web_contents
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn for_correlation_test() -> Self {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn for_correlation_test() -> Self {
|
||||
Self {
|
||||
web_contents: WebContentsId::allocate(),
|
||||
navigation: NavigationId::allocate(),
|
||||
@@ -49,7 +49,7 @@ impl NavigationInterceptionPermit {
|
||||
/// and how to resume it. It deliberately contains no Target, session, loader,
|
||||
/// protocol request id, or frontend result state.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NavigationRequestInterception {
|
||||
pub struct NavigationRequestInterception {
|
||||
requested_url: Url,
|
||||
method: String,
|
||||
body: Option<Vec<u8>>,
|
||||
@@ -59,7 +59,7 @@ pub(crate) struct NavigationRequestInterception {
|
||||
}
|
||||
|
||||
impl NavigationRequestInterception {
|
||||
pub(crate) fn new(
|
||||
pub fn new(
|
||||
requested_url: Url,
|
||||
method: String,
|
||||
body: Option<Vec<u8>>,
|
||||
@@ -117,7 +117,7 @@ impl NavigationRequestInterception {
|
||||
/// Navigation identified by `permit`; supersession makes admission fail rather
|
||||
/// than falling back to a current Target/loader selection.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct ClaimedNavigationRequest {
|
||||
pub struct ClaimedNavigationRequest {
|
||||
permit: NavigationInterceptionPermit,
|
||||
request: NavigationRequestInterception,
|
||||
}
|
||||
@@ -130,11 +130,11 @@ impl ClaimedNavigationRequest {
|
||||
Self { permit, request }
|
||||
}
|
||||
|
||||
pub(crate) fn permit(&self) -> NavigationInterceptionPermit {
|
||||
pub fn permit(&self) -> NavigationInterceptionPermit {
|
||||
self.permit
|
||||
}
|
||||
|
||||
pub(crate) fn apply_overrides(
|
||||
pub fn apply_overrides(
|
||||
&mut self,
|
||||
requested_url: Option<Url>,
|
||||
method: Option<String>,
|
||||
@@ -149,12 +149,12 @@ impl ClaimedNavigationRequest {
|
||||
/// The admitted Browser operation retains the actual request across auth
|
||||
/// pauses and retries. No Target, session, loader ID or frontend configuration
|
||||
/// is needed to resume it, and no Browser borrow is held while fetching.
|
||||
pub(crate) struct InterceptedNavigationLoad {
|
||||
pub(in crate::conn) load: AdmittedNavigationLoad,
|
||||
pub(in crate::conn) requested_url: Url,
|
||||
pub(in crate::conn) method: String,
|
||||
pub struct InterceptedNavigationLoad {
|
||||
pub load: AdmittedNavigationLoad,
|
||||
pub requested_url: Url,
|
||||
pub method: String,
|
||||
body: Option<Vec<u8>>,
|
||||
pub(in crate::conn) headers: moli_fetch::RequestHeaders,
|
||||
pub headers: moli_fetch::RequestHeaders,
|
||||
prior_observations: NetworkObservationJournal,
|
||||
}
|
||||
|
||||
@@ -168,7 +168,7 @@ impl std::fmt::Debug for InterceptedNavigationLoad {
|
||||
}
|
||||
|
||||
impl InterceptedNavigationLoad {
|
||||
pub(in crate::conn) fn new(
|
||||
pub fn new(
|
||||
load: AdmittedNavigationLoad,
|
||||
requested_url: Url,
|
||||
method: String,
|
||||
@@ -185,7 +185,7 @@ impl InterceptedNavigationLoad {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_streaming(
|
||||
pub async fn fetch_streaming(
|
||||
self,
|
||||
auth: Option<SubresourceAuthCredentials>,
|
||||
) -> anyhow::Result<InterceptedNavigationResponse<StreamingRawResponse>> {
|
||||
@@ -203,7 +203,7 @@ impl InterceptedNavigationLoad {
|
||||
Ok(self.with_response(response))
|
||||
}
|
||||
|
||||
pub(crate) async fn fetch_auth(
|
||||
pub async fn fetch_auth(
|
||||
self,
|
||||
auth: SubresourceAuthCredentials,
|
||||
) -> anyhow::Result<InterceptedNavigationResponse<RawResponse>> {
|
||||
@@ -221,7 +221,7 @@ impl InterceptedNavigationLoad {
|
||||
Ok(self.with_response(response))
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn with_response<R>(
|
||||
pub fn with_response<R>(
|
||||
mut self,
|
||||
response: NetworkFetchResult<R>,
|
||||
) -> InterceptedNavigationResponse<R> {
|
||||
@@ -234,7 +234,7 @@ impl InterceptedNavigationLoad {
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn into_request_parts(
|
||||
pub fn into_request_parts(
|
||||
self,
|
||||
) -> (
|
||||
AdmittedNavigationLoad,
|
||||
@@ -251,28 +251,40 @@ impl InterceptedNavigationLoad {
|
||||
self.headers,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn into_owner_parts(
|
||||
self,
|
||||
) -> (
|
||||
AdmittedNavigationLoad,
|
||||
Url,
|
||||
String,
|
||||
Option<Vec<u8>>,
|
||||
moli_fetch::RequestHeaders,
|
||||
) {
|
||||
self.into_request_parts()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct InterceptedNavigationResponse<R> {
|
||||
pub struct InterceptedNavigationResponse<R> {
|
||||
work: InterceptedNavigationLoad,
|
||||
response: NetworkFetchResult<R>,
|
||||
}
|
||||
|
||||
impl<R> InterceptedNavigationResponse<R> {
|
||||
pub(in crate::conn) fn web_contents(&self) -> WebContentsId {
|
||||
pub fn web_contents(&self) -> WebContentsId {
|
||||
self.identity().web_contents
|
||||
}
|
||||
|
||||
pub(crate) fn response(&self) -> &R {
|
||||
pub fn response(&self) -> &R {
|
||||
self.response.response()
|
||||
}
|
||||
|
||||
pub(crate) fn observation_journal(&self) -> &NetworkObservationJournal {
|
||||
pub fn observation_journal(&self) -> &NetworkObservationJournal {
|
||||
self.response.observation_journal()
|
||||
}
|
||||
|
||||
pub(crate) fn into_parts(self) -> (InterceptedNavigationLoad, NetworkFetchResult<R>) {
|
||||
pub fn into_parts(self) -> (InterceptedNavigationLoad, NetworkFetchResult<R>) {
|
||||
(self.work, self.response)
|
||||
}
|
||||
|
||||
@@ -281,48 +293,19 @@ impl<R> InterceptedNavigationResponse<R> {
|
||||
}
|
||||
}
|
||||
|
||||
impl InterceptedNavigationResponse<StreamingRawResponse> {
|
||||
pub(crate) async fn materialize(
|
||||
self,
|
||||
) -> anyhow::Result<InterceptedNavigationResponse<RawResponse>> {
|
||||
let (response, observations) = self.response.into_parts_with_observation_journal();
|
||||
let response = response
|
||||
.into_materialized_raw_response()
|
||||
.await
|
||||
.context("failed to read page body from stream")?;
|
||||
Ok(InterceptedNavigationResponse {
|
||||
work: self.work,
|
||||
response: NetworkFetchResult::with_observation_journal(response, observations),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InterceptedNavigationResponse<RawResponse> {
|
||||
pub(crate) fn retry(self) -> InterceptedNavigationLoad {
|
||||
pub fn retry(self) -> InterceptedNavigationLoad {
|
||||
let (mut work, response) = self.into_parts();
|
||||
let (response, observations) = response.into_parts_with_observation_journal();
|
||||
let redirect_headers = if response.redirect_chain.len() > work.load.redirect_chain.len() {
|
||||
work.load.redirect_headers.take()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let mut request = moli_fetch::Request::get_with_url(work.requested_url.clone())
|
||||
.with_redirect_headers(redirect_headers);
|
||||
let mut request = moli_fetch::Request::get_with_url(work.requested_url);
|
||||
request.method = work.method;
|
||||
request.body = work.body;
|
||||
request.request_headers = work.headers;
|
||||
for redirect in response
|
||||
.redirect_chain
|
||||
.iter()
|
||||
.skip(work.load.redirect_chain.len())
|
||||
{
|
||||
request.apply_redirect_status(redirect.status);
|
||||
}
|
||||
work.requested_url = response.final_url;
|
||||
let request = work.load.redirected_request(response, request);
|
||||
work.requested_url = request.url;
|
||||
work.method = request.method;
|
||||
work.body = request.body;
|
||||
work.headers = request.request_headers;
|
||||
work.load.redirect_chain = response.redirect_chain;
|
||||
work.prior_observations = observations;
|
||||
work
|
||||
}
|
||||
@@ -349,7 +332,7 @@ pub(super) struct PausedNavigationRequest {
|
||||
#[derive(Debug)]
|
||||
pub(super) struct PausedNavigationResponse {
|
||||
pub(super) request: BrowserRequestId,
|
||||
pub(super) transfer: crate::conn::PausedDocumentTransfer,
|
||||
pub(super) transfer: super::PausedDocumentTransfer,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
@@ -360,7 +343,7 @@ pub(super) enum PausedNavigationInterception {
|
||||
}
|
||||
|
||||
impl WebContents {
|
||||
pub(in crate::conn::state) fn pause_navigation_request(
|
||||
pub fn pause_navigation_request(
|
||||
&mut self,
|
||||
navigation: NavigationId,
|
||||
request: NavigationRequestInterception,
|
||||
@@ -368,7 +351,7 @@ impl WebContents {
|
||||
self.navigation.pause_request(self.id, navigation, request)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn take_navigation_request(
|
||||
pub fn take_navigation_request(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
) -> Option<ClaimedNavigationRequest> {
|
||||
@@ -378,7 +361,7 @@ impl WebContents {
|
||||
self.navigation.take_request(permit)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_claimed_navigation_request(
|
||||
pub fn start_claimed_navigation_request(
|
||||
&mut self,
|
||||
request: ClaimedNavigationRequest,
|
||||
inherited: InheritedDocumentPolicy,
|
||||
@@ -398,7 +381,7 @@ impl WebContents {
|
||||
))
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_navigation_load_for_interception(
|
||||
pub fn start_navigation_load_for_interception(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
policy: NavigationRequestLoadPolicy,
|
||||
@@ -410,7 +393,7 @@ impl WebContents {
|
||||
self.start_navigation_load(permit.navigation, policy, inherited)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn pause_navigation_auth(
|
||||
pub fn pause_navigation_auth(
|
||||
&mut self,
|
||||
response: InterceptedNavigationResponse<RawResponse>,
|
||||
) -> Result<NavigationInterceptionPermit, String> {
|
||||
@@ -420,7 +403,7 @@ impl WebContents {
|
||||
self.navigation.pause_auth_response(response)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn take_navigation_auth(
|
||||
pub fn take_navigation_auth(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
) -> Option<InterceptedNavigationResponse<RawResponse>> {
|
||||
@@ -430,30 +413,30 @@ impl WebContents {
|
||||
self.navigation.take_auth_response(permit)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn pause_navigation_response(
|
||||
pub fn pause_navigation_response(
|
||||
&mut self,
|
||||
navigation: NavigationId,
|
||||
transfer: crate::conn::PausedDocumentTransfer,
|
||||
transfer: super::PausedDocumentTransfer,
|
||||
) -> Result<NavigationInterceptionPermit, String> {
|
||||
self.navigation
|
||||
.pause_response(self.id, navigation, transfer)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn take_navigation_response(
|
||||
pub fn take_navigation_response(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
) -> Option<crate::conn::PausedDocumentTransfer> {
|
||||
) -> Option<super::PausedDocumentTransfer> {
|
||||
if permit.web_contents != self.id {
|
||||
return None;
|
||||
}
|
||||
self.navigation.take_response(permit)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn restore_navigation_response(
|
||||
pub fn restore_navigation_response(
|
||||
&mut self,
|
||||
permit: NavigationInterceptionPermit,
|
||||
transfer: crate::conn::PausedDocumentTransfer,
|
||||
) -> Result<(), Box<crate::conn::PausedDocumentTransfer>> {
|
||||
transfer: super::PausedDocumentTransfer,
|
||||
) -> Result<(), Box<super::PausedDocumentTransfer>> {
|
||||
if permit.web_contents != self.id {
|
||||
return Err(Box::new(transfer));
|
||||
}
|
||||
@@ -464,8 +447,10 @@ impl WebContents {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::conn::state::web_contents::tests::BrowserFixture;
|
||||
use moli_core::page::SubresourceAuthScheme;
|
||||
use crate::browser::web_contents::{
|
||||
DocumentBodySource, PausedDocumentTransfer, tests::BrowserFixture,
|
||||
};
|
||||
use crate::page::SubresourceAuthScheme;
|
||||
use moli_fetch::ResponseHead;
|
||||
use tokio::{
|
||||
io::{AsyncReadExt, AsyncWriteExt},
|
||||
@@ -523,7 +508,7 @@ mod tests {
|
||||
)
|
||||
}
|
||||
|
||||
fn paused_response(url: Url) -> crate::conn::PausedDocumentTransfer {
|
||||
fn paused_response(url: Url) -> PausedDocumentTransfer {
|
||||
let response = RawResponse::from_head_and_body(
|
||||
ResponseHead {
|
||||
final_url: url.clone(),
|
||||
@@ -538,9 +523,9 @@ mod tests {
|
||||
},
|
||||
b"response body".to_vec(),
|
||||
);
|
||||
crate::conn::PausedDocumentTransfer::pending(
|
||||
PausedDocumentTransfer::pending(
|
||||
NavigationRequestLoadPolicy::DocumentInitiated,
|
||||
crate::conn::DocumentBodySource::BufferedRaw {
|
||||
DocumentBodySource::BufferedRaw {
|
||||
requested_url: url,
|
||||
request_method: "GET".to_owned(),
|
||||
request_headers: Default::default(),
|
||||
@@ -793,7 +778,7 @@ mod tests {
|
||||
let mut browser = BrowserFixture::new();
|
||||
let work = request(&mut browser, url);
|
||||
let auth = SubresourceAuthCredentials {
|
||||
target: moli_core::page::SubresourceAuthTarget::Server,
|
||||
target: crate::page::SubresourceAuthTarget::Server,
|
||||
username: "user".to_owned(),
|
||||
password: "pass".to_owned(),
|
||||
scheme: SubresourceAuthScheme::Digest,
|
||||
+45
-27
@@ -1,4 +1,4 @@
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
browser::{
|
||||
DocumentId, NavigationId, NavigationRequestLoadPolicy, RendererPageResidenceIdentity,
|
||||
WebContentsId,
|
||||
@@ -24,7 +24,7 @@ mod tests;
|
||||
|
||||
/// Move-owned navigation work. No caller can borrow or reconfigure its engine,
|
||||
/// replace its storage, or bind its response to a different Browser navigation.
|
||||
pub(in crate::conn) struct AdmittedNavigationLoad {
|
||||
pub struct AdmittedNavigationLoad {
|
||||
// Response preparation consumes admission even if renderer preparation fails.
|
||||
identity: Option<DocumentNavigationIdentity>,
|
||||
engine: NavigationEngine,
|
||||
@@ -42,35 +42,33 @@ pub(in crate::conn) struct AdmittedNavigationLoad {
|
||||
|
||||
/// The identity is inherited from load admission, never reconstructed from the
|
||||
/// current Target, session, or pending navigation when the response completes.
|
||||
pub(in crate::conn) struct PreparedNavigationResponse {
|
||||
pub struct PreparedNavigationResponse {
|
||||
pub(super) identity: DocumentNavigationIdentity,
|
||||
pub(super) page: PreparedDocumentPage,
|
||||
}
|
||||
|
||||
impl PreparedNavigationResponse {
|
||||
pub(in crate::conn) fn renderer_devtools_agent_token(
|
||||
&self,
|
||||
) -> moli_core::page::RendererDevToolsAgentToken {
|
||||
pub fn renderer_devtools_agent_token(&self) -> crate::page::RendererDevToolsAgentToken {
|
||||
self.page.renderer_devtools_agent_token()
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn inspection_configuration_endpoint(
|
||||
pub fn inspection_configuration_endpoint(
|
||||
&self,
|
||||
) -> moli_renderer_v8::RendererPreparedDocumentInspectionEndpoint {
|
||||
self.page.inspection_configuration_endpoint()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn) async fn materialize(
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn materialize(
|
||||
self,
|
||||
policy: Option<moli_core::runtime::PreparedDocumentPagePolicy>,
|
||||
) -> anyhow::Result<moli_core::runtime::BuiltDocumentPage> {
|
||||
policy: Option<crate::runtime::PreparedDocumentPagePolicy>,
|
||||
) -> anyhow::Result<crate::runtime::BuiltDocumentPage> {
|
||||
self.page.materialize(policy).await
|
||||
}
|
||||
}
|
||||
|
||||
impl WebContents {
|
||||
pub(in crate::conn::state) fn start_navigation_load(
|
||||
pub fn start_navigation_load(
|
||||
&mut self,
|
||||
navigation: NavigationId,
|
||||
policy: NavigationRequestLoadPolicy,
|
||||
@@ -141,23 +139,43 @@ impl AdmittedNavigationLoad {
|
||||
.expect("navigation response admission already consumed")
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn web_contents_id(&self) -> WebContentsId {
|
||||
pub fn web_contents_id(&self) -> WebContentsId {
|
||||
self.identity().web_contents
|
||||
}
|
||||
pub(in crate::conn) fn navigation_id(&self) -> NavigationId {
|
||||
pub fn navigation_id(&self) -> NavigationId {
|
||||
self.identity().navigation
|
||||
}
|
||||
pub(in crate::conn) fn document_id(&self) -> DocumentId {
|
||||
pub fn document_id(&self) -> DocumentId {
|
||||
self.identity().document
|
||||
}
|
||||
pub(in crate::conn) fn renderer_page(&self) -> RendererPageResidenceIdentity {
|
||||
pub fn renderer_page(&self) -> RendererPageResidenceIdentity {
|
||||
RendererPageResidenceIdentity::from_parts(
|
||||
self.reservation.local_host_id(),
|
||||
self.reservation.page_id(),
|
||||
)
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn set_redirect_state(
|
||||
pub(crate) fn redirected_request(
|
||||
&mut self,
|
||||
response: moli_fetch::RawResponse,
|
||||
mut request: moli_fetch::Request,
|
||||
) -> moli_fetch::Request {
|
||||
if response.redirect_chain.len() > self.redirect_chain.len() {
|
||||
request = request.with_redirect_headers(self.redirect_headers.take());
|
||||
}
|
||||
for redirect in response
|
||||
.redirect_chain
|
||||
.iter()
|
||||
.skip(self.redirect_chain.len())
|
||||
{
|
||||
request.apply_redirect_status(redirect.status);
|
||||
}
|
||||
request.url = response.final_url;
|
||||
self.redirect_chain = response.redirect_chain;
|
||||
request
|
||||
}
|
||||
|
||||
pub fn set_redirect_state(
|
||||
&mut self,
|
||||
headers: Option<moli_fetch::RequestHeaders>,
|
||||
chain: Vec<moli_fetch::RedirectInfo>,
|
||||
@@ -181,11 +199,11 @@ impl AdmittedNavigationLoad {
|
||||
.iter()
|
||||
.any(|pattern| moli_fetch::url_pattern_matches(pattern, raw_url))
|
||||
{
|
||||
return Err(crate::conn::NavigationRequestBlocked.into());
|
||||
return Err(crate::browser::NavigationRequestBlocked.into());
|
||||
}
|
||||
if self.network_offline {
|
||||
return Err(crate::conn::NavigationNetworkError {
|
||||
kind: crate::conn::NavigationNetworkErrorKind::InternetDisconnected,
|
||||
return Err(crate::browser::NavigationNetworkError {
|
||||
kind: crate::browser::NavigationNetworkErrorKind::InternetDisconnected,
|
||||
unreachable_url: Url::parse(raw_url)?,
|
||||
request_method: method.to_owned(),
|
||||
request_headers: headers.clone(),
|
||||
@@ -206,7 +224,7 @@ impl AdmittedNavigationLoad {
|
||||
self.policy != NavigationRequestLoadPolicy::BrowserInitiated
|
||||
}
|
||||
|
||||
pub(in crate::conn) async fn fetch_navigation(
|
||||
pub async fn fetch_navigation(
|
||||
&mut self,
|
||||
method: &str,
|
||||
raw_url: &str,
|
||||
@@ -256,7 +274,7 @@ impl AdmittedNavigationLoad {
|
||||
Ok(request)
|
||||
}
|
||||
|
||||
pub(in crate::conn) async fn fetch_intercepted_response(
|
||||
pub async fn fetch_intercepted_response(
|
||||
&self,
|
||||
method: &str,
|
||||
raw_url: &str,
|
||||
@@ -275,7 +293,7 @@ impl AdmittedNavigationLoad {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(in crate::conn) async fn fetch_intercepted_auth_response(
|
||||
pub async fn fetch_intercepted_auth_response(
|
||||
&self,
|
||||
method: &str,
|
||||
raw_url: &str,
|
||||
@@ -298,7 +316,7 @@ impl AdmittedNavigationLoad {
|
||||
Ok(response)
|
||||
}
|
||||
|
||||
pub(in crate::conn) async fn prepare_document_response_async(
|
||||
pub async fn prepare_document_response_async(
|
||||
&mut self,
|
||||
requested_url: Url,
|
||||
final_url: Url,
|
||||
@@ -347,8 +365,8 @@ impl AdmittedNavigationLoad {
|
||||
}
|
||||
|
||||
/// Direct Page-builder fixtures have no Browser transaction to commit.
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn) fn for_fixture(
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn for_fixture(
|
||||
engine: NavigationEngine,
|
||||
resource_storage: NavigationResourceStorageHandles,
|
||||
page_storage: NavigationPageStorageHandles,
|
||||
@@ -361,7 +379,7 @@ impl AdmittedNavigationLoad {
|
||||
Self {
|
||||
identity: Some(DocumentNavigationIdentity {
|
||||
web_contents: WebContentsId::allocate(),
|
||||
frame_slot: moli_core::browser::MainFrameSlotId::allocate(),
|
||||
frame_slot: crate::browser::MainFrameSlotId::allocate(),
|
||||
navigation: NavigationId::allocate(),
|
||||
document: DocumentId::allocate(),
|
||||
cancellation: FetchCancelHandle::new(),
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::conn::state::web_contents::tests::{BrowserFixture, prepare};
|
||||
use crate::browser::web_contents::tests::{BrowserFixture, prepare};
|
||||
|
||||
#[tokio::test]
|
||||
async fn precommit_retirement_preserves_document_and_retires_request_history() {
|
||||
+6
-6
@@ -1,13 +1,13 @@
|
||||
/// Installed page-level request policy. Frontends resolve their contributions
|
||||
/// before supplying this value; context header defaults remain context-owned.
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub(in crate::conn) struct NetworkRequestPolicy {
|
||||
pub(in crate::conn) cache_disabled: bool,
|
||||
pub(in crate::conn) bypass_service_worker: bool,
|
||||
pub(in crate::conn) blocked_url_patterns: Vec<String>,
|
||||
pub(in crate::conn) extra_headers: moli_fetch::RequestHeaders,
|
||||
pub struct NetworkRequestPolicy {
|
||||
pub cache_disabled: bool,
|
||||
pub bypass_service_worker: bool,
|
||||
pub blocked_url_patterns: Vec<String>,
|
||||
pub extra_headers: moli_fetch::RequestHeaders,
|
||||
}
|
||||
pub(in crate::conn) fn merge_extra_header_layers(
|
||||
pub fn merge_extra_header_layers(
|
||||
layers: &[&moli_fetch::RequestHeaders],
|
||||
) -> moli_fetch::RequestHeaders {
|
||||
let mut headers = moli_fetch::RequestHeaders::default();
|
||||
+11
-11
@@ -1,23 +1,23 @@
|
||||
use super::super::emulation::{
|
||||
use super::WebContents;
|
||||
use crate::browser::{
|
||||
EmulatedDeviceMetrics, EmulatedGeolocationOverrideState, EmulatedNetworkConditions,
|
||||
};
|
||||
use super::WebContents;
|
||||
#[cfg(test)]
|
||||
use serde_json::json;
|
||||
|
||||
/// A derived Browser value, never stored alongside the installed policy.
|
||||
pub(in crate::conn) struct PageSurface {
|
||||
pub struct PageSurface {
|
||||
network_conditions: Option<EmulatedNetworkConditions>,
|
||||
geolocation_override: Option<EmulatedGeolocationOverrideState>,
|
||||
max_touch_points: u32,
|
||||
pub(in crate::conn) navigator_queries: moli_page_types::NavigatorQueryOverrides,
|
||||
pub navigator_queries: moli_page_types::NavigatorQueryOverrides,
|
||||
focus_emulation_enabled: bool,
|
||||
foreground: bool,
|
||||
window_document_hidden: bool,
|
||||
}
|
||||
|
||||
impl WebContents {
|
||||
pub(in crate::conn) fn page_surface(
|
||||
pub fn page_surface(
|
||||
&self,
|
||||
foreground: bool,
|
||||
default_network_conditions: Option<EmulatedNetworkConditions>,
|
||||
@@ -49,15 +49,15 @@ impl PageSurface {
|
||||
self.max_touch_points
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn document_has_focus(&self) -> bool {
|
||||
pub fn document_has_focus(&self) -> bool {
|
||||
self.document_is_focused()
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn document_hidden(&self) -> bool {
|
||||
pub fn document_hidden(&self) -> bool {
|
||||
!self.document_is_visible()
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn document_visibility_state(&self) -> &'static str {
|
||||
pub fn document_visibility_state(&self) -> &'static str {
|
||||
if self.document_is_visible() {
|
||||
"visible"
|
||||
} else {
|
||||
@@ -65,7 +65,7 @@ impl PageSurface {
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn navigator_overrides(&self) -> moli_page_types::NavigatorOverrides {
|
||||
pub fn navigator_overrides(&self) -> moli_page_types::NavigatorOverrides {
|
||||
moli_page_types::NavigatorOverrides {
|
||||
online: self
|
||||
.network_conditions
|
||||
@@ -98,7 +98,7 @@ impl PageSurface {
|
||||
(self.foreground && !self.window_document_hidden) || self.focus_emulation_enabled
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn document_activity(&self) -> moli_page_types::DocumentActivity {
|
||||
pub fn document_activity(&self) -> moli_page_types::DocumentActivity {
|
||||
moli_page_types::DocumentActivity::new(
|
||||
self.document_is_focused(),
|
||||
self.document_is_visible(),
|
||||
@@ -138,7 +138,7 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn browser_page_surface_runs_without_devtools_registration() {
|
||||
use moli_core::runtime::{Browser, BrowserConfig};
|
||||
use crate::runtime::{Browser, BrowserConfig};
|
||||
let browser = Browser::new(BrowserConfig::default()).unwrap();
|
||||
let mut page = browser
|
||||
.fetch("data:text/html,<p>page surface</p>")
|
||||
@@ -0,0 +1,709 @@
|
||||
use anyhow::Context as _;
|
||||
use tokio::sync::{mpsc, oneshot};
|
||||
use url::Url;
|
||||
|
||||
use crate::browser::{
|
||||
CapturedBody, CapturedBodyWriter, NavigationRequestLoadPolicy, ensure_materialize_limit,
|
||||
};
|
||||
use moli_cookie_jar::StoredCookieQueryReport;
|
||||
use moli_fetch::{NetworkObservationJournal, RawResponse, ResponseHead, StreamingRawResponse};
|
||||
|
||||
use crate::browser::BrowserPreparedNavigationResponse;
|
||||
|
||||
/// Renderer resources prepared before a response-stage interception decision.
|
||||
pub struct PausedResponsePreparedDocument {
|
||||
prepared_page: BrowserPreparedNavigationResponse,
|
||||
renderer_body_tx: mpsc::Sender<Vec<u8>>,
|
||||
renderer_completion_tx: oneshot::Sender<anyhow::Result<()>>,
|
||||
}
|
||||
|
||||
impl std::fmt::Debug for PausedResponsePreparedDocument {
|
||||
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
formatter
|
||||
.debug_struct("PausedResponsePreparedDocument")
|
||||
.field(
|
||||
"renderer_devtools_agent_token",
|
||||
&self.prepared_page.renderer_devtools_agent_token(),
|
||||
)
|
||||
.finish_non_exhaustive()
|
||||
}
|
||||
}
|
||||
|
||||
impl PausedResponsePreparedDocument {
|
||||
pub fn new(
|
||||
prepared_page: BrowserPreparedNavigationResponse,
|
||||
renderer_body_tx: mpsc::Sender<Vec<u8>>,
|
||||
renderer_completion_tx: oneshot::Sender<anyhow::Result<()>>,
|
||||
) -> Self {
|
||||
Self {
|
||||
prepared_page,
|
||||
renderer_body_tx,
|
||||
renderer_completion_tx,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn renderer_devtools_agent_token(&self) -> crate::page::RendererDevToolsAgentToken {
|
||||
self.prepared_page.renderer_devtools_agent_token()
|
||||
}
|
||||
|
||||
pub fn into_parts(
|
||||
self,
|
||||
) -> (
|
||||
BrowserPreparedNavigationResponse,
|
||||
mpsc::Sender<Vec<u8>>,
|
||||
oneshot::Sender<anyhow::Result<()>>,
|
||||
) {
|
||||
(
|
||||
self.prepared_page,
|
||||
self.renderer_body_tx,
|
||||
self.renderer_completion_tx,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// Browser-owned response body and renderer preparation retained while a
|
||||
/// response-stage interception decision is pending.
|
||||
#[derive(Debug)]
|
||||
pub enum DocumentBodySource {
|
||||
BufferedRaw {
|
||||
requested_url: Url,
|
||||
request_method: String,
|
||||
request_headers: moli_fetch::RequestHeaders,
|
||||
response: RawResponse,
|
||||
network_observation_journal: NetworkObservationJournal,
|
||||
},
|
||||
StreamingRaw {
|
||||
requested_url: Url,
|
||||
request_method: String,
|
||||
request_headers: moli_fetch::RequestHeaders,
|
||||
response: StreamingRawResponse,
|
||||
network_observation_journal: NetworkObservationJournal,
|
||||
prepared_document: Option<Box<PausedResponsePreparedDocument>>,
|
||||
},
|
||||
CapturedRaw {
|
||||
requested_url: Url,
|
||||
request_method: String,
|
||||
request_headers: moli_fetch::RequestHeaders,
|
||||
head: ResponseHead,
|
||||
body: CapturedBody,
|
||||
network_observation_journal: NetworkObservationJournal,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PausedDocumentTransfer {
|
||||
request_load_policy: NavigationRequestLoadPolicy,
|
||||
state: PausedDocumentTransferState,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
enum PausedDocumentTransferState {
|
||||
Pending {
|
||||
body: DocumentBodySource,
|
||||
},
|
||||
ActiveBodyStream {
|
||||
stream: ActiveDocumentBodyStreamState,
|
||||
},
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct ActiveDocumentBodyStreamState {
|
||||
requested_url: Url,
|
||||
request_method: String,
|
||||
request_headers: moli_fetch::RequestHeaders,
|
||||
response: StreamingRawResponse,
|
||||
network_observation_journal: NetworkObservationJournal,
|
||||
captured_body: CapturedBodyWriter,
|
||||
unread_body: Vec<u8>,
|
||||
offset: usize,
|
||||
finished: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PendingFetchResponseOpenedBodyStream {
|
||||
pub handle: String,
|
||||
pub buffered_bytes: Option<Vec<u8>>,
|
||||
pub transfer: PausedDocumentTransfer,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum OpenBodyStreamError {
|
||||
NotOpenable(Box<PausedDocumentTransfer>),
|
||||
Failed {
|
||||
transfer: Box<PausedDocumentTransfer>,
|
||||
error: anyhow::Error,
|
||||
},
|
||||
}
|
||||
|
||||
pub struct PausedStreamingDocumentResponse {
|
||||
pub request_load_policy: NavigationRequestLoadPolicy,
|
||||
pub requested_url: Url,
|
||||
pub request_method: String,
|
||||
pub request_headers: moli_fetch::RequestHeaders,
|
||||
pub response: StreamingRawResponse,
|
||||
pub network_observation_journal: NetworkObservationJournal,
|
||||
pub prepared_document: Option<Box<PausedResponsePreparedDocument>>,
|
||||
}
|
||||
|
||||
pub struct SyntheticDocumentResponseContext {
|
||||
pub request_load_policy: NavigationRequestLoadPolicy,
|
||||
pub requested_url: Url,
|
||||
pub request_method: String,
|
||||
pub request_headers: moli_fetch::RequestHeaders,
|
||||
pub final_url: Url,
|
||||
pub request_cookie_report: Option<StoredCookieQueryReport>,
|
||||
}
|
||||
|
||||
impl PausedDocumentTransfer {
|
||||
pub fn pending(
|
||||
request_load_policy: NavigationRequestLoadPolicy,
|
||||
body: DocumentBodySource,
|
||||
) -> Self {
|
||||
Self {
|
||||
request_load_policy,
|
||||
state: PausedDocumentTransferState::Pending { body },
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prepared_renderer_agent_token(&self) -> Option<crate::page::RendererDevToolsAgentToken> {
|
||||
match &self.state {
|
||||
PausedDocumentTransferState::Pending {
|
||||
body:
|
||||
DocumentBodySource::StreamingRaw {
|
||||
prepared_document: Some(prepared_document),
|
||||
..
|
||||
},
|
||||
} => Some(prepared_document.renderer_devtools_agent_token()),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_pending(
|
||||
self,
|
||||
) -> Result<(NavigationRequestLoadPolicy, DocumentBodySource), Box<Self>> {
|
||||
let Self {
|
||||
request_load_policy,
|
||||
state,
|
||||
} = self;
|
||||
match state {
|
||||
PausedDocumentTransferState::Pending { body } => Ok((request_load_policy, body)),
|
||||
state => Err(Box::new(Self {
|
||||
request_load_policy,
|
||||
state,
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_streaming_response(self) -> Result<PausedStreamingDocumentResponse, Box<Self>> {
|
||||
let (request_load_policy, body) = self.into_pending()?;
|
||||
match body {
|
||||
DocumentBodySource::StreamingRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
prepared_document,
|
||||
} => Ok(PausedStreamingDocumentResponse {
|
||||
request_load_policy,
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
prepared_document,
|
||||
}),
|
||||
body => Err(Box::new(Self::pending(request_load_policy, body))),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn open_body_stream(
|
||||
self,
|
||||
handle: String,
|
||||
) -> Result<PendingFetchResponseOpenedBodyStream, OpenBodyStreamError> {
|
||||
let (request_load_policy, body) = self
|
||||
.into_pending()
|
||||
.map_err(OpenBodyStreamError::NotOpenable)?;
|
||||
match body {
|
||||
DocumentBodySource::StreamingRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
prepared_document: _,
|
||||
} => Ok(PendingFetchResponseOpenedBodyStream {
|
||||
handle,
|
||||
buffered_bytes: None,
|
||||
transfer: Self {
|
||||
request_load_policy,
|
||||
state: PausedDocumentTransferState::ActiveBodyStream {
|
||||
stream: ActiveDocumentBodyStreamState::new(
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
),
|
||||
},
|
||||
},
|
||||
}),
|
||||
DocumentBodySource::BufferedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
} => {
|
||||
let bytes = response.clone_body_bytes();
|
||||
Ok(PendingFetchResponseOpenedBodyStream {
|
||||
handle,
|
||||
buffered_bytes: Some(bytes),
|
||||
transfer: Self::pending(
|
||||
request_load_policy,
|
||||
DocumentBodySource::BufferedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
},
|
||||
),
|
||||
})
|
||||
}
|
||||
DocumentBodySource::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head,
|
||||
body,
|
||||
network_observation_journal,
|
||||
} => {
|
||||
let bytes = match body.materialize_bytes() {
|
||||
Ok(bytes) => bytes,
|
||||
Err(error) => {
|
||||
return Err(OpenBodyStreamError::Failed {
|
||||
transfer: Box::new(Self::pending(
|
||||
request_load_policy,
|
||||
DocumentBodySource::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head,
|
||||
body,
|
||||
network_observation_journal,
|
||||
},
|
||||
)),
|
||||
error: error.context("failed to materialize captured response body"),
|
||||
});
|
||||
}
|
||||
};
|
||||
Ok(PendingFetchResponseOpenedBodyStream {
|
||||
handle,
|
||||
buffered_bytes: Some(bytes),
|
||||
transfer: Self::pending(
|
||||
request_load_policy,
|
||||
DocumentBodySource::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head,
|
||||
body,
|
||||
network_observation_journal,
|
||||
},
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn body_stream_offset(&self) -> Option<usize> {
|
||||
match &self.state {
|
||||
PausedDocumentTransferState::ActiveBodyStream { stream } => Some(stream.offset),
|
||||
PausedDocumentTransferState::Pending { .. } => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn read_body_stream_async(
|
||||
self,
|
||||
size: Option<usize>,
|
||||
) -> Result<(Vec<u8>, bool, Self), (Self, anyhow::Error)> {
|
||||
let Self {
|
||||
request_load_policy,
|
||||
state,
|
||||
} = self;
|
||||
let PausedDocumentTransferState::ActiveBodyStream { mut stream } = state else {
|
||||
return Err((
|
||||
Self {
|
||||
request_load_policy,
|
||||
state,
|
||||
},
|
||||
anyhow::anyhow!("StreamHandleNotFound"),
|
||||
));
|
||||
};
|
||||
match stream.read_async(size).await {
|
||||
Ok((bytes, eof)) => {
|
||||
let state = if eof {
|
||||
match stream.finish_pending_body_source() {
|
||||
Ok(body) => PausedDocumentTransferState::Pending { body },
|
||||
Err(message) => {
|
||||
return Err((
|
||||
Self {
|
||||
request_load_policy,
|
||||
state: PausedDocumentTransferState::ActiveBodyStream { stream },
|
||||
},
|
||||
message,
|
||||
));
|
||||
}
|
||||
}
|
||||
} else {
|
||||
PausedDocumentTransferState::ActiveBodyStream { stream }
|
||||
};
|
||||
Ok((
|
||||
bytes,
|
||||
eof,
|
||||
Self {
|
||||
request_load_policy,
|
||||
state,
|
||||
},
|
||||
))
|
||||
}
|
||||
Err(message) => Err((
|
||||
Self {
|
||||
request_load_policy,
|
||||
state: PausedDocumentTransferState::ActiveBodyStream { stream },
|
||||
},
|
||||
message,
|
||||
)),
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn finish_body_stream_async(
|
||||
self,
|
||||
) -> Result<(NavigationRequestLoadPolicy, DocumentBodySource), (Self, anyhow::Error)> {
|
||||
let Self {
|
||||
request_load_policy,
|
||||
state,
|
||||
} = self;
|
||||
match state {
|
||||
PausedDocumentTransferState::Pending { body } => Ok((request_load_policy, body)),
|
||||
PausedDocumentTransferState::ActiveBodyStream { mut stream } => {
|
||||
if let Err(message) = stream.read_async(None).await {
|
||||
return Err((
|
||||
Self {
|
||||
request_load_policy,
|
||||
state: PausedDocumentTransferState::ActiveBodyStream { stream },
|
||||
},
|
||||
message,
|
||||
));
|
||||
}
|
||||
match stream.finish_pending_body_source() {
|
||||
Ok(body) => Ok((request_load_policy, body)),
|
||||
Err(message) => Err((
|
||||
Self {
|
||||
request_load_policy,
|
||||
state: PausedDocumentTransferState::ActiveBodyStream { stream },
|
||||
},
|
||||
message,
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn into_synthetic_response_context(self) -> SyntheticDocumentResponseContext {
|
||||
let Self {
|
||||
request_load_policy,
|
||||
state,
|
||||
} = self;
|
||||
let (requested_url, request_method, request_headers, final_url, request_cookie_report) =
|
||||
match state {
|
||||
PausedDocumentTransferState::Pending { body } => match body {
|
||||
DocumentBodySource::BufferedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
..
|
||||
} => (
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response.final_url.clone(),
|
||||
response.request_cookie_report.clone(),
|
||||
),
|
||||
DocumentBodySource::StreamingRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
..
|
||||
} => (
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response.final_url.clone(),
|
||||
response.request_cookie_report.clone(),
|
||||
),
|
||||
DocumentBodySource::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head,
|
||||
..
|
||||
} => (
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head.final_url.clone(),
|
||||
head.request_cookie_report.clone(),
|
||||
),
|
||||
},
|
||||
PausedDocumentTransferState::ActiveBodyStream { stream } => (
|
||||
stream.requested_url,
|
||||
stream.request_method,
|
||||
stream.request_headers,
|
||||
stream.response.final_url.clone(),
|
||||
stream.response.request_cookie_report.clone(),
|
||||
),
|
||||
};
|
||||
SyntheticDocumentResponseContext {
|
||||
request_load_policy,
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
final_url,
|
||||
request_cookie_report,
|
||||
}
|
||||
}
|
||||
|
||||
pub async fn materialize_body_limited_async(
|
||||
self,
|
||||
limit: usize,
|
||||
) -> Result<(Option<Vec<u8>>, Self), (anyhow::Error, Self)> {
|
||||
let (request_load_policy, body) = match self.into_pending() {
|
||||
Ok(parts) => parts,
|
||||
Err(transfer) => return Ok((None, *transfer)),
|
||||
};
|
||||
match body.materialize_body_limited_async(limit).await {
|
||||
Ok((bytes, body)) => Ok((Some(bytes), Self::pending(request_load_policy, body))),
|
||||
Err((message, body)) => Err((message, Self::pending(request_load_policy, body))),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl DocumentBodySource {
|
||||
pub async fn materialize_body_limited_async(
|
||||
self,
|
||||
limit: usize,
|
||||
) -> Result<(Vec<u8>, Self), (anyhow::Error, Self)> {
|
||||
match self {
|
||||
Self::BufferedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
} => {
|
||||
if let Err(error) = ensure_materialize_limit(response.body_bytes().len(), limit) {
|
||||
return Err((
|
||||
error,
|
||||
Self::BufferedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
},
|
||||
));
|
||||
}
|
||||
let bytes = response.clone_body_bytes();
|
||||
Ok((
|
||||
bytes,
|
||||
Self::BufferedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
},
|
||||
))
|
||||
}
|
||||
Self::StreamingRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
prepared_document: _,
|
||||
} => {
|
||||
let preserved_head = response.head();
|
||||
let (head, body) = match capture_streaming_raw_response(response).await {
|
||||
Ok(captured) => captured,
|
||||
Err(message) => {
|
||||
return Err((
|
||||
message,
|
||||
Self::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head: preserved_head,
|
||||
body: CapturedBody::from_bytes(Vec::new()),
|
||||
network_observation_journal,
|
||||
},
|
||||
));
|
||||
}
|
||||
};
|
||||
let result = body
|
||||
.materialize_bytes_limited(limit)
|
||||
.context("failed to materialize captured response body");
|
||||
let source = Self::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head,
|
||||
body,
|
||||
network_observation_journal,
|
||||
};
|
||||
match result {
|
||||
Ok(bytes) => Ok((bytes, source)),
|
||||
Err(message) => Err((message, source)),
|
||||
}
|
||||
}
|
||||
Self::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head,
|
||||
body,
|
||||
network_observation_journal,
|
||||
} => {
|
||||
let result = body
|
||||
.materialize_bytes_limited(limit)
|
||||
.context("failed to materialize captured response body");
|
||||
let source = Self::CapturedRaw {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
head,
|
||||
body,
|
||||
network_observation_journal,
|
||||
};
|
||||
match result {
|
||||
Ok(bytes) => Ok((bytes, source)),
|
||||
Err(message) => Err((message, source)),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ActiveDocumentBodyStreamState {
|
||||
fn new(
|
||||
requested_url: Url,
|
||||
request_method: String,
|
||||
request_headers: moli_fetch::RequestHeaders,
|
||||
response: StreamingRawResponse,
|
||||
network_observation_journal: NetworkObservationJournal,
|
||||
) -> Self {
|
||||
Self {
|
||||
requested_url,
|
||||
request_method,
|
||||
request_headers,
|
||||
response,
|
||||
network_observation_journal,
|
||||
captured_body: CapturedBodyWriter::default(),
|
||||
unread_body: Vec::new(),
|
||||
offset: 0,
|
||||
finished: false,
|
||||
}
|
||||
}
|
||||
|
||||
async fn read_async(&mut self, size: Option<usize>) -> Result<(Vec<u8>, bool), anyhow::Error> {
|
||||
let mut bytes = Vec::new();
|
||||
match size {
|
||||
Some(limit) => {
|
||||
while bytes.len() < limit {
|
||||
if self.unread_body.is_empty() && !self.finished {
|
||||
self.read_next_chunk_async().await?;
|
||||
}
|
||||
if self.unread_body.is_empty() {
|
||||
break;
|
||||
}
|
||||
let remaining = limit.saturating_sub(bytes.len());
|
||||
self.drain_unread(&mut bytes, remaining);
|
||||
}
|
||||
}
|
||||
None => {
|
||||
while !self.unread_body.is_empty() || !self.finished {
|
||||
if self.unread_body.is_empty() {
|
||||
self.read_next_chunk_async().await?;
|
||||
}
|
||||
let remaining = self.unread_body.len();
|
||||
self.drain_unread(&mut bytes, remaining);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.offset = self.offset.saturating_add(bytes.len());
|
||||
Ok((bytes, self.finished && self.unread_body.is_empty()))
|
||||
}
|
||||
|
||||
fn drain_unread(&mut self, bytes: &mut Vec<u8>, limit: usize) {
|
||||
let take = limit.min(self.unread_body.len());
|
||||
bytes.extend(self.unread_body.drain(..take));
|
||||
}
|
||||
|
||||
async fn read_next_chunk_async(&mut self) -> anyhow::Result<()> {
|
||||
if self.finished {
|
||||
return Ok(());
|
||||
}
|
||||
if let Some(chunk) = self.response.next_chunk().await {
|
||||
self.captured_body
|
||||
.append(&chunk)
|
||||
.context("failed to capture response body stream")?;
|
||||
self.unread_body.extend(chunk);
|
||||
return Ok(());
|
||||
}
|
||||
self.response
|
||||
.finish()
|
||||
.await
|
||||
.context("failed to read page body from stream")?;
|
||||
self.finished = true;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn finish_pending_body_source(&mut self) -> anyhow::Result<DocumentBodySource> {
|
||||
let head = self.response.head();
|
||||
let body = self
|
||||
.captured_body
|
||||
.finish_in_place()
|
||||
.context("failed to finish captured response body")?;
|
||||
Ok(DocumentBodySource::CapturedRaw {
|
||||
requested_url: self.requested_url.clone(),
|
||||
request_method: self.request_method.clone(),
|
||||
request_headers: self.request_headers.clone(),
|
||||
head,
|
||||
body,
|
||||
network_observation_journal: self.network_observation_journal.clone(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
async fn capture_streaming_raw_response(
|
||||
mut response: StreamingRawResponse,
|
||||
) -> anyhow::Result<(ResponseHead, CapturedBody)> {
|
||||
let head = response.head();
|
||||
let mut body = CapturedBodyWriter::default();
|
||||
while let Some(chunk) = response.next_chunk().await {
|
||||
body.append(&chunk)
|
||||
.context("failed to capture response body stream")?;
|
||||
}
|
||||
response
|
||||
.finish()
|
||||
.await
|
||||
.context("failed to read page body from stream")?;
|
||||
let body = body
|
||||
.finish()
|
||||
.context("failed to finish captured response body")?;
|
||||
Ok((head, body))
|
||||
}
|
||||
+22
-30
@@ -1,66 +1,60 @@
|
||||
use moli_core::{
|
||||
use crate::{
|
||||
network::ResourceRequestClient,
|
||||
page::{CompletedPageCommand, PendingPageCommand, RendererCommandTurnOutput},
|
||||
runtime::{NavigationEngine, NavigationEngineDiagnostics},
|
||||
};
|
||||
|
||||
use super::{InheritedDocumentPolicy, WebContents};
|
||||
use crate::conn::state::browser_context::BrowserContextStoragePartitionHandles;
|
||||
use crate::browser::BrowserContextStoragePartitionHandles;
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests;
|
||||
|
||||
impl WebContents {
|
||||
pub(in crate::conn::state) fn has_navigation_engine(&self) -> bool {
|
||||
pub fn has_navigation_engine(&self) -> bool {
|
||||
self.navigation_engine.is_some()
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn navigation_fetch_config(
|
||||
&self,
|
||||
) -> Option<&moli_fetch::FetchConfig> {
|
||||
pub fn navigation_fetch_config(&self) -> Option<&moli_fetch::FetchConfig> {
|
||||
self.navigation_engine
|
||||
.as_ref()
|
||||
.map(NavigationEngine::fetch_config)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn navigation_layout_policy(
|
||||
&self,
|
||||
) -> Option<moli_core::LayoutPolicy> {
|
||||
pub fn navigation_layout_policy(&self) -> Option<crate::LayoutPolicy> {
|
||||
self.navigation_engine
|
||||
.as_ref()
|
||||
.map(NavigationEngine::layout_policy)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn navigation_renderer_owner_id(&self) -> Option<u64> {
|
||||
pub fn navigation_renderer_owner_id(&self) -> Option<u64> {
|
||||
self.navigation_engine
|
||||
.as_ref()
|
||||
.map(NavigationEngine::renderer_owner_id_for_diagnostics)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn navigation_diagnostics(
|
||||
&self,
|
||||
) -> Option<NavigationEngineDiagnostics> {
|
||||
pub fn navigation_diagnostics(&self) -> Option<NavigationEngineDiagnostics> {
|
||||
self.navigation_engine
|
||||
.as_ref()
|
||||
.map(NavigationEngine::diagnostics)
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn set_renderer_output_transport_sender(
|
||||
pub fn set_renderer_output_transport_sender(
|
||||
&self,
|
||||
sender: moli_core::RendererOutputTransportSender,
|
||||
sender: crate::RendererOutputTransportSender,
|
||||
) {
|
||||
if let Some(engine) = &self.navigation_engine {
|
||||
engine.set_renderer_output_transport_sender(sender);
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn invalidate_resource_runtime(&mut self) {
|
||||
pub fn invalidate_resource_runtime(&mut self) {
|
||||
if let Some(engine) = self.navigation_engine.as_mut() {
|
||||
engine.reset_resource_runtime_without_loaded_page();
|
||||
}
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn ensure_resource_request_client(
|
||||
pub fn ensure_resource_request_client(
|
||||
&mut self,
|
||||
inherited: &InheritedDocumentPolicy,
|
||||
) -> Result<ResourceRequestClient, String> {
|
||||
@@ -72,7 +66,7 @@ impl WebContents {
|
||||
.ok_or_else(|| "resource request client unavailable".to_owned())
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_resource_runtime_rebuild(
|
||||
pub fn start_resource_runtime_rebuild(
|
||||
&mut self,
|
||||
inherited: &InheritedDocumentPolicy,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
@@ -103,7 +97,7 @@ impl WebContents {
|
||||
.map_err(|error| format!("failed to update page resource runtime: {error}"))
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn finish_resource_runtime_update(
|
||||
pub fn finish_resource_runtime_update(
|
||||
&mut self,
|
||||
completion: CompletedPageCommand,
|
||||
) -> Result<(), String> {
|
||||
@@ -127,7 +121,7 @@ impl WebContents {
|
||||
result
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn finish_unobserved_resource_runtime_update(
|
||||
pub fn finish_unobserved_resource_runtime_update(
|
||||
completion: CompletedPageCommand,
|
||||
) -> Result<(), String> {
|
||||
completion
|
||||
@@ -138,7 +132,7 @@ impl WebContents {
|
||||
})
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn start_child_frame_lifecycle_work(
|
||||
pub fn start_child_frame_lifecycle_work(
|
||||
&mut self,
|
||||
storage: &BrowserContextStoragePartitionHandles,
|
||||
timeout: std::time::Duration,
|
||||
@@ -161,7 +155,7 @@ impl WebContents {
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub(in crate::conn::state) fn complete_child_frame_lifecycle_work(
|
||||
pub fn complete_child_frame_lifecycle_work(
|
||||
&mut self,
|
||||
completion: CompletedPageCommand,
|
||||
) -> Result<(bool, RendererCommandTurnOutput), String> {
|
||||
@@ -177,20 +171,18 @@ impl WebContents {
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn navigation_engine_for_test(&self) -> Option<&NavigationEngine> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn navigation_engine_for_test(&self) -> Option<&NavigationEngine> {
|
||||
self.navigation_engine.as_ref()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) fn navigation_engine_for_test_mut(
|
||||
&mut self,
|
||||
) -> Option<&mut NavigationEngine> {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub fn navigation_engine_for_test_mut(&mut self) -> Option<&mut NavigationEngine> {
|
||||
self.navigation_engine.as_mut()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(in crate::conn::state) async fn reset_resource_runtime_for_test(&mut self) {
|
||||
#[cfg(any(test, feature = "test-support"))]
|
||||
pub async fn reset_resource_runtime_for_test(&mut self) {
|
||||
if let Some(engine) = self.navigation_engine.as_mut() {
|
||||
engine
|
||||
.reset_resource_runtime_async(
|
||||
+1
-1
@@ -1,4 +1,4 @@
|
||||
use crate::conn::state::web_contents::tests::BrowserFixture;
|
||||
use crate::browser::web_contents::tests::BrowserFixture;
|
||||
use moli_browser_profile::BrowserIdentityProfile;
|
||||
use serde_json::json;
|
||||
|
||||
+5
-5
@@ -1,11 +1,11 @@
|
||||
use std::sync::OnceLock;
|
||||
|
||||
use moli_core::network::{
|
||||
use crate::network::{
|
||||
SharedWebStorageStore, deep_clone_shared_web_storage_store, new_shared_web_storage_store,
|
||||
};
|
||||
|
||||
#[derive(Default)]
|
||||
pub(crate) struct SessionStorageNamespace {
|
||||
pub struct SessionStorageNamespace {
|
||||
store: OnceLock<SharedWebStorageStore>,
|
||||
}
|
||||
|
||||
@@ -18,17 +18,17 @@ impl std::fmt::Debug for SessionStorageNamespace {
|
||||
}
|
||||
|
||||
impl SessionStorageNamespace {
|
||||
pub(crate) fn from_store(store: SharedWebStorageStore) -> Self {
|
||||
pub fn from_store(store: SharedWebStorageStore) -> Self {
|
||||
Self {
|
||||
store: OnceLock::from(store),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn store(&self) -> &SharedWebStorageStore {
|
||||
pub fn store(&self) -> &SharedWebStorageStore {
|
||||
self.store.get_or_init(new_shared_web_storage_store)
|
||||
}
|
||||
|
||||
pub(crate) fn deep_clone(&self) -> Self {
|
||||
pub fn deep_clone(&self) -> Self {
|
||||
Self {
|
||||
store: OnceLock::from(deep_clone_shared_web_storage_store(self.store())),
|
||||
}
|
||||
+4
-7
@@ -1,9 +1,6 @@
|
||||
use super::*;
|
||||
use crate::conn::state::{
|
||||
NavigationRequestLoadPolicy, browser_context::BrowserContextStoragePartitionHandles,
|
||||
};
|
||||
use moli_core::{
|
||||
browser::NavigationId,
|
||||
use crate::{
|
||||
browser::{BrowserContextStoragePartitionHandles, NavigationId, NavigationRequestLoadPolicy},
|
||||
runtime::{
|
||||
CommittedDocumentResourceSource, ExternalRawDocumentBodyStream, PageVmInitStage,
|
||||
RendererReplyBoundary,
|
||||
@@ -17,7 +14,7 @@ pub(super) struct BrowserFixture {
|
||||
}
|
||||
|
||||
impl BrowserFixture {
|
||||
pub(super) async fn navigate(&mut self, title: &str) -> moli_core::browser::DocumentId {
|
||||
pub(super) async fn navigate(&mut self, title: &str) -> crate::browser::DocumentId {
|
||||
let navigation = self.contents.navigation.start_document_navigation();
|
||||
self.complete_navigation(navigation, title).await
|
||||
}
|
||||
@@ -26,7 +23,7 @@ impl BrowserFixture {
|
||||
&mut self,
|
||||
navigation: NavigationId,
|
||||
title: &str,
|
||||
) -> moli_core::browser::DocumentId {
|
||||
) -> crate::browser::DocumentId {
|
||||
let response = prepare(&mut self.start(navigation).unwrap(), title)
|
||||
.await
|
||||
.unwrap();
|
||||
+42
-18
@@ -1,22 +1,22 @@
|
||||
use moli_core::browser::WebContentsId;
|
||||
use crate::browser::WebContentsId;
|
||||
|
||||
/// Browser window state has the lifetime of its owning WebContents.
|
||||
#[derive(Debug, Default)]
|
||||
pub(in crate::conn) struct Window {
|
||||
pub(in crate::conn) name: Option<String>,
|
||||
pub(in crate::conn) opener: Option<WindowOpener>,
|
||||
pub(in crate::conn) surface: WindowSurface,
|
||||
pub struct Window {
|
||||
pub name: Option<String>,
|
||||
pub opener: Option<WindowOpener>,
|
||||
pub surface: WindowSurface,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
pub(in crate::conn) struct WindowOpener {
|
||||
pub(in crate::conn) web_contents_id: WebContentsId,
|
||||
pub struct WindowOpener {
|
||||
pub web_contents_id: WebContentsId,
|
||||
// A noopener window still has creator attribution, but no script access.
|
||||
pub(in crate::conn) can_access: bool,
|
||||
pub can_access: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub(crate) enum WindowSurfaceState {
|
||||
pub enum WindowSurfaceState {
|
||||
#[default]
|
||||
Normal,
|
||||
Maximized,
|
||||
@@ -25,11 +25,21 @@ pub(crate) enum WindowSurfaceState {
|
||||
}
|
||||
|
||||
impl WindowSurfaceState {
|
||||
pub(crate) fn document_hidden(self) -> bool {
|
||||
pub fn from_label(label: &str) -> Option<Self> {
|
||||
match label {
|
||||
"normal" => Some(Self::Normal),
|
||||
"maximized" => Some(Self::Maximized),
|
||||
"minimized" => Some(Self::Minimized),
|
||||
"fullscreen" => Some(Self::Fullscreen),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn document_hidden(self) -> bool {
|
||||
matches!(self, Self::Minimized)
|
||||
}
|
||||
|
||||
pub(crate) fn label(self) -> &'static str {
|
||||
pub fn label(self) -> &'static str {
|
||||
match self {
|
||||
Self::Normal => "normal",
|
||||
Self::Maximized => "maximized",
|
||||
@@ -41,16 +51,16 @@ impl WindowSurfaceState {
|
||||
|
||||
/// A value snapshot; it confers no mutable access to Browser state.
|
||||
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
|
||||
pub(crate) struct WindowSurface {
|
||||
pub(crate) state: WindowSurfaceState,
|
||||
pub(crate) width: u32,
|
||||
pub(crate) height: u32,
|
||||
pub(crate) x: i32,
|
||||
pub(crate) y: i32,
|
||||
pub struct WindowSurface {
|
||||
pub state: WindowSurfaceState,
|
||||
pub width: u32,
|
||||
pub height: u32,
|
||||
pub x: i32,
|
||||
pub y: i32,
|
||||
}
|
||||
|
||||
impl WindowSurface {
|
||||
pub(in crate::conn) fn set_geometry(
|
||||
pub fn set_geometry(
|
||||
&mut self,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
@@ -70,4 +80,18 @@ impl WindowSurface {
|
||||
self.y = y;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(
|
||||
&mut self,
|
||||
state: Option<WindowSurfaceState>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
x: Option<i32>,
|
||||
y: Option<i32>,
|
||||
) {
|
||||
if let Some(state) = state {
|
||||
self.state = state;
|
||||
}
|
||||
self.set_geometry(width, height, x, y);
|
||||
}
|
||||
}
|
||||
@@ -227,6 +227,25 @@ impl Page {
|
||||
)
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn start_page_surface_override_script(&self, source: &str) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::RunPageSurfaceOverrideScript {
|
||||
source: source.to_owned(),
|
||||
})
|
||||
}
|
||||
|
||||
pub fn finish_page_surface_override_script(
|
||||
&mut self,
|
||||
completion: CompletedPageCommand,
|
||||
) -> Result<()> {
|
||||
let reply = self.finish_page_command(completion);
|
||||
expect_page_reply!(
|
||||
reply,
|
||||
"run page surface override script",
|
||||
"a unit reply",
|
||||
RendererPageReply::Unit => Ok(()),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Decoding a frozen inspection reply requires no Browser Page residence.
|
||||
|
||||
@@ -239,6 +239,26 @@ impl Page {
|
||||
.await
|
||||
}
|
||||
|
||||
pub fn start_set_script_execution_disabled(
|
||||
&self,
|
||||
disabled: bool,
|
||||
) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::SetScriptExecutionDisabled(disabled))
|
||||
}
|
||||
|
||||
pub fn finish_set_script_execution_disabled(
|
||||
&mut self,
|
||||
completion: CompletedPageCommand,
|
||||
) -> Result<()> {
|
||||
let reply = self.finish_page_command(completion);
|
||||
expect_page_reply!(
|
||||
reply,
|
||||
"set script execution disabled",
|
||||
"a unit reply",
|
||||
RendererPageReply::Unit => Ok(()),
|
||||
)
|
||||
}
|
||||
|
||||
pub async fn set_bypass_content_security_policy_async(&mut self, bypass: bool) -> Result<()> {
|
||||
self.dispatch_unit_page_command_async(
|
||||
RendererPageCommand::SetBypassContentSecurityPolicy(bypass),
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use anyhow::Result;
|
||||
use anyhow::{Result, bail};
|
||||
use url::Url;
|
||||
|
||||
use super::protocol_support::{PendingSubresourceContinueOutcome, SubresourceAuthCredentials};
|
||||
use super::{CompletedPageCommand, Page, PendingPageCommand};
|
||||
use super::{CompletedPageCommand, Page, PendingPageCommand, RendererCommandTurnOutput};
|
||||
use super::{RendererPendingJavaScriptDialog, RendererSyntheticResponseBody};
|
||||
use crate::RendererOutputFence;
|
||||
use crate::renderer::{RendererPageCommand, RendererPageReply};
|
||||
@@ -12,16 +12,19 @@ impl Page {
|
||||
self.handle.take_pending_modal_javascript_dialogs()
|
||||
}
|
||||
|
||||
pub async fn stop_document_lifecycle_async(&mut self) -> Result<()> {
|
||||
let reply = self
|
||||
.dispatch_page_command_async(RendererPageCommand::StopDocumentLifecycle)
|
||||
.await?;
|
||||
expect_page_reply!(
|
||||
reply,
|
||||
"stop document lifecycle page command",
|
||||
"a unit reply",
|
||||
RendererPageReply::Unit => Ok(()),
|
||||
)
|
||||
pub fn start_stop_document_lifecycle(&self) -> Result<PendingPageCommand> {
|
||||
self.start_page_command(RendererPageCommand::StopDocumentLifecycle)
|
||||
}
|
||||
|
||||
pub fn finish_stop_document_lifecycle(
|
||||
&mut self,
|
||||
completion: CompletedPageCommand,
|
||||
) -> Result<RendererCommandTurnOutput> {
|
||||
let output = self.finish_page_command_turn(completion);
|
||||
if !matches!(output.completion().reply(), RendererPageReply::Unit) {
|
||||
bail!("stop document lifecycle page command expected a unit reply");
|
||||
}
|
||||
Ok(output)
|
||||
}
|
||||
|
||||
pub async fn continue_pending_subresource_fetch_async(
|
||||
|
||||
@@ -334,7 +334,8 @@ pub(crate) fn import_cookies_into_store(
|
||||
let mut cookie_store = cookie_store.lock();
|
||||
let mut accepted = 0usize;
|
||||
for cookie in cookies {
|
||||
let report = cookie_store.upsert_with_request_url_report(cookie, None, CookieSource::Cdp);
|
||||
let report =
|
||||
cookie_store.upsert_with_request_url_report(cookie, None, CookieSource::Management);
|
||||
if report.is_accepted() {
|
||||
accepted += 1;
|
||||
}
|
||||
@@ -374,7 +375,8 @@ fn commit_cookie_delta_to_store(
|
||||
}
|
||||
|
||||
for cookie in final_cookies {
|
||||
let report = cookie_store.upsert_with_request_url_report(cookie, None, CookieSource::Cdp);
|
||||
let report =
|
||||
cookie_store.upsert_with_request_url_report(cookie, None, CookieSource::Management);
|
||||
if !report.is_accepted() {
|
||||
return Err(anyhow!("failed to commit cookie into storage partition"));
|
||||
}
|
||||
|
||||
@@ -7,8 +7,8 @@ use std::{
|
||||
use moli_cookie_jar::StoredCookie;
|
||||
use moli_core::page::RendererVisualStateToken;
|
||||
use moli_core::{
|
||||
RendererOutputTransportMessage, page::RendererDocumentLifecycleMilestone,
|
||||
runtime::NavigationRuntimeConfig,
|
||||
RendererOutputTransportMessage, browser::BrowserHandle,
|
||||
page::RendererDocumentLifecycleMilestone, runtime::NavigationRuntimeConfig,
|
||||
};
|
||||
use moli_protocol::{
|
||||
AgentHostDispatchResult, BackgroundNavigationCompletion, BackgroundProtocolEvent,
|
||||
@@ -717,10 +717,12 @@ impl CdpScheduler {
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_initial_state_runtime_config(
|
||||
browser: BrowserHandle,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
) -> (Self, CdpSchedulerEventReceivers) {
|
||||
Self::new_with_initial_state_runtime_config_and_target_host_integration(
|
||||
browser,
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
None,
|
||||
@@ -728,11 +730,13 @@ impl CdpScheduler {
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_initial_state_runtime_config_and_target_host_integration(
|
||||
browser: BrowserHandle,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
target_host_integration: Option<CdpTargetHostIntegration>,
|
||||
) -> (Self, CdpSchedulerEventReceivers) {
|
||||
Self::new_with_default_target_runtime_initialization(
|
||||
browser,
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
target_host_integration,
|
||||
@@ -742,12 +746,14 @@ impl CdpScheduler {
|
||||
}
|
||||
|
||||
pub(crate) fn new_with_deferred_default_target_runtime_and_screencast_interval(
|
||||
browser: BrowserHandle,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
target_host_integration: Option<CdpTargetHostIntegration>,
|
||||
page_screencast_interval_ms: u32,
|
||||
) -> (Self, CdpSchedulerEventReceivers) {
|
||||
Self::new_with_default_target_runtime_initialization(
|
||||
browser,
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
target_host_integration,
|
||||
@@ -757,26 +763,18 @@ impl CdpScheduler {
|
||||
}
|
||||
|
||||
fn new_with_default_target_runtime_initialization(
|
||||
browser: BrowserHandle,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
target_host_integration: Option<CdpTargetHostIntegration>,
|
||||
initialization: DefaultTargetRuntimeInitialization,
|
||||
page_screencast_interval_ms: u32,
|
||||
) -> (Self, CdpSchedulerEventReceivers) {
|
||||
let conn = match initialization {
|
||||
DefaultTargetRuntimeInitialization::Materialized => {
|
||||
CdpConnection::new_with_initial_storage_partition_and_runtime_config(
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
)
|
||||
}
|
||||
DefaultTargetRuntimeInitialization::Deferred => {
|
||||
CdpConnection::new_with_deferred_navigation_runtime(
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
)
|
||||
}
|
||||
};
|
||||
let conn = CdpConnection::new(
|
||||
browser,
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
);
|
||||
let mut scheduler = Self::new_with_screencast_interval(conn, page_screencast_interval_ms);
|
||||
if let Some(target_host_integration) = target_host_integration {
|
||||
target_host_integration.install(&mut scheduler.conn);
|
||||
|
||||
@@ -2046,7 +2046,7 @@ fn trace_command(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use moli_core::RendererRuntimeInspectorAsyncCompletion;
|
||||
use moli_protocol::CdpConnection;
|
||||
|
||||
use serde_json::json;
|
||||
|
||||
use super::*;
|
||||
@@ -2132,7 +2132,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn unmatched_runtime_inspector_response_is_not_downgraded_to_protocol_output() {
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
|
||||
let output = route_unmatched_runtime_inspector_response(
|
||||
&mut scheduler,
|
||||
|
||||
@@ -282,7 +282,7 @@ impl ProtocolAdapterScheduler {
|
||||
mod tests {
|
||||
use moli_core::{PageId, RendererOutputResidenceIdentity, RendererOwnerLocalHostId};
|
||||
use moli_protocol::{
|
||||
CdpConnection, CdpSchedulerEvent, ProtocolSchedulerWork,
|
||||
CdpSchedulerEvent, ProtocolSchedulerWork,
|
||||
test_support::{
|
||||
deferred_main_document_load_observation_id,
|
||||
deferred_main_document_load_output_interest, root_frame_stopped_loading_work,
|
||||
@@ -315,7 +315,7 @@ mod tests {
|
||||
#[test]
|
||||
fn pending_exact_load_observation_allows_independent_protocol_residence() {
|
||||
let observation_id = deferred_main_document_load_observation_id(1);
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.apply_scheduler_events(vec![CdpSchedulerEvent::ProtocolWorkPublished {
|
||||
work: protocol_observation(1),
|
||||
}]);
|
||||
@@ -414,7 +414,7 @@ mod tests {
|
||||
async fn self_turn_is_coalesced_and_preserves_the_client_turn_boundary() {
|
||||
LocalSet::new()
|
||||
.run_until(async {
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.apply_scheduler_events(vec![CdpSchedulerEvent::ProtocolWorkPublished {
|
||||
work: protocol_observation(1),
|
||||
}]);
|
||||
|
||||
@@ -11,8 +11,7 @@ use moli_core::{
|
||||
},
|
||||
};
|
||||
use moli_protocol::{
|
||||
BackgroundProtocolEvent, CdpConnection, CdpSchedulerEvent,
|
||||
DeferredMainDocumentLoadCompletionOutputAction,
|
||||
BackgroundProtocolEvent, CdpSchedulerEvent, DeferredMainDocumentLoadCompletionOutputAction,
|
||||
DeferredMainDocumentLoadCompletionOutputInterest, DeferredMainDocumentLoadPredecessorCandidate,
|
||||
ProtocolSchedulerWork,
|
||||
conn::RuntimeInspectorResponseReady,
|
||||
@@ -397,7 +396,7 @@ fn network_finished_event_for_target(
|
||||
|
||||
#[test]
|
||||
fn mixed_owner_protocol_output_keeps_the_conservative_global_gate() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = moli_protocol::test_support::connection();
|
||||
let navigation = arm_background_navigation_request(&mut conn, "LOADER-known");
|
||||
let target_id = navigation.target_id().to_owned();
|
||||
let known = network_response_event_for_target(
|
||||
@@ -622,7 +621,7 @@ fn concrete_renderer_output_precedes_work_published_by_the_same_ingress() {
|
||||
#[test]
|
||||
fn protocol_work_published_by_held_ingress_inherits_exact_load_predecessor() {
|
||||
let load = deferred_main_document_load_observation_id(1);
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.apply_scheduler_events_with_load_predecessors(
|
||||
vec![CdpSchedulerEvent::ProtocolWorkPublished {
|
||||
work: root_frame_stopped_loading_work(1, "FRAME-1"),
|
||||
@@ -851,7 +850,7 @@ async fn runtime_response_releases_its_frozen_renderer_publication_predecessor()
|
||||
let publication = post_load_renderer_publication(PageId::new_for_testing(1), 1);
|
||||
let candidate = future_load_candidate(&publication);
|
||||
let cursor = publication_cursor(&publication);
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.queues.enqueue_renderer_output_publication(
|
||||
cursor,
|
||||
ProtocolOutputSequence::from_messages(vec![json!({
|
||||
@@ -880,7 +879,7 @@ async fn runtime_response_cannot_release_renderer_output_with_an_exact_load_pred
|
||||
let publication = post_load_renderer_publication(PageId::new_for_testing(1), 1);
|
||||
let load = deferred_main_document_load_observation_id(1);
|
||||
let cursor = publication_cursor(&publication);
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.queues.enqueue_renderer_output_publication(
|
||||
cursor,
|
||||
ProtocolOutputSequence::from_messages(vec![json!({
|
||||
@@ -936,7 +935,7 @@ async fn specialized_wait_yield_closes_candidate_retained_in_the_main_fifo() {
|
||||
let candidate = future_load_candidate(&publication);
|
||||
let interest = load_interest_for_publication(&publication);
|
||||
let unrelated_later_load = deferred_main_document_load_observation_id(1);
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.queues.enqueue_renderer_output_publication(
|
||||
cursor,
|
||||
ProtocolOutputSequence::from_messages(vec![json!({"method": "Runtime.consoleAPICalled"})]),
|
||||
@@ -965,7 +964,7 @@ fn scheduler_holds_concrete_renderer_output_until_its_exact_load_predecessor_fin
|
||||
let publication = post_load_renderer_publication(PageId::new_for_testing(7), 1);
|
||||
let cursor = publication_cursor(&publication);
|
||||
let load = deferred_main_document_load_observation_id(1);
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.queues.enqueue_renderer_output_publication(
|
||||
cursor,
|
||||
ProtocolOutputSequence::from_messages(vec![json!({"method": "Runtime.consoleAPICalled"})]),
|
||||
@@ -994,7 +993,7 @@ async fn renderer_stream_control_is_consumed_without_protocol_residence() {
|
||||
)),
|
||||
}
|
||||
.into();
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
let output = scheduler.ingest_renderer_publication_now(control).await;
|
||||
|
||||
assert!(output.is_empty());
|
||||
@@ -1021,7 +1020,7 @@ async fn closed_renderer_transport_fails_an_unprojected_command_fence() {
|
||||
RendererOutputStreamIdentity::new_page_for_protocol_test(PageId::new_for_testing(90));
|
||||
let predecessor =
|
||||
RendererOutputFence::new_for_test(RendererOutputCursor::new_for_test(stream, 1));
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
|
||||
let failure = scheduler
|
||||
.project_renderer_output_predecessor_before_devtools_result(&mut receivers, &predecessor)
|
||||
@@ -1038,7 +1037,7 @@ async fn closed_renderer_transport_fails_an_unprojected_command_fence() {
|
||||
|
||||
#[test]
|
||||
fn concrete_protocol_output_waits_for_its_client_turn_predecessor() {
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.apply_scheduler_events(vec![CdpSchedulerEvent::ProtocolWorkPublished {
|
||||
work: root_frame_stopped_loading_work(1, "FRAME-1"),
|
||||
}]);
|
||||
@@ -1067,7 +1066,7 @@ fn concrete_protocol_output_waits_for_its_client_turn_predecessor() {
|
||||
|
||||
#[test]
|
||||
fn concrete_protocol_output_preserves_protocol_publish_order() {
|
||||
let mut scheduler = CdpScheduler::new(CdpConnection::new());
|
||||
let mut scheduler = CdpScheduler::new(moli_protocol::test_support::connection());
|
||||
scheduler.apply_scheduler_events(vec![
|
||||
CdpSchedulerEvent::ProtocolWorkPublished {
|
||||
work: root_frame_stopped_loading_work(1, "FRAME-1"),
|
||||
@@ -1122,7 +1121,7 @@ fn concrete_protocol_output_rejects_missing_earlier_publication() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn background_navigation_blocks_only_its_target_protocol_residences() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = moli_protocol::test_support::connection();
|
||||
let navigation = arm_background_navigation_request(&mut conn, "LOADER-nav");
|
||||
let navigation_target_id = navigation.target_id().to_owned();
|
||||
let browser_context_id = conn.default_browser_context_id().to_owned();
|
||||
@@ -1185,7 +1184,7 @@ async fn background_navigation_blocks_only_its_target_protocol_residences() {
|
||||
|
||||
#[tokio::test]
|
||||
async fn protocol_residence_snapshot_skips_another_targets_navigation_gate() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = moli_protocol::test_support::connection();
|
||||
let navigation = arm_background_navigation_request(&mut conn, "LOADER-A");
|
||||
let target_a = navigation.target_id().to_owned();
|
||||
let browser_context_id = conn.default_browser_context_id().to_owned();
|
||||
@@ -1234,7 +1233,7 @@ async fn protocol_residence_snapshot_skips_another_targets_navigation_gate() {
|
||||
|
||||
#[test]
|
||||
fn replacement_navigation_cancels_and_exactly_settles_the_target_owned_request() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = moli_protocol::test_support::connection();
|
||||
let source = arm_background_navigation_request(&mut conn, "LOADER-source");
|
||||
let replacement = arm_background_navigation_request(&mut conn, "LOADER-replacement");
|
||||
assert!(source.is_cancelled());
|
||||
@@ -1254,7 +1253,7 @@ fn replacement_navigation_cancels_and_exactly_settles_the_target_owned_request()
|
||||
|
||||
#[test]
|
||||
fn scheduler_defers_subresource_network_events_until_background_navigation_gate_clears() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = moli_protocol::test_support::connection();
|
||||
let navigation = arm_background_navigation_request(&mut conn, "LOADER-nav");
|
||||
let target_id = navigation.target_id().to_owned();
|
||||
let mut scheduler = CdpScheduler::new(conn);
|
||||
@@ -1319,7 +1318,7 @@ fn scheduler_defers_subresource_network_events_until_background_navigation_gate_
|
||||
|
||||
#[tokio::test]
|
||||
async fn target_a_navigation_does_not_defer_target_b_network_events() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = moli_protocol::test_support::connection();
|
||||
conn.install_default_browser_target();
|
||||
let context = DevToolsCommandContext {
|
||||
protocol: DevToolsProtocol::WebDriverBidi,
|
||||
@@ -1408,7 +1407,7 @@ async fn target_a_navigation_does_not_defer_target_b_network_events() {
|
||||
|
||||
#[test]
|
||||
fn navigation_gate_release_precedes_later_renderer_boundary_network_output() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = moli_protocol::test_support::connection();
|
||||
let navigation = arm_background_navigation_request(&mut conn, "LOADER-nav");
|
||||
let target_id = navigation.target_id().to_owned();
|
||||
let mut scheduler = CdpScheduler::new(conn);
|
||||
|
||||
@@ -4,7 +4,10 @@ use std::{
|
||||
};
|
||||
|
||||
use anyhow::{Context, Result, bail};
|
||||
use moli_core::runtime::{NavigationRuntimeConfig, storage_partition::StoragePartitionState};
|
||||
use moli_core::{
|
||||
browser::BrowserHandle,
|
||||
runtime::{NavigationRuntimeConfig, storage_partition::StoragePartitionState},
|
||||
};
|
||||
use moli_protocol::CdpInitialStoragePartition;
|
||||
use parking_lot::Mutex;
|
||||
use tokio::sync::{Notify, mpsc, oneshot};
|
||||
@@ -51,6 +54,7 @@ struct CdpOwnerRecord {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct CdpOwnerRuntimeConfig {
|
||||
browser: BrowserHandle,
|
||||
directory: SharedCdpAgentHostDirectory,
|
||||
target_id_allocator: Arc<AtomicU64>,
|
||||
tab_target_id_allocator: Arc<AtomicU64>,
|
||||
@@ -62,6 +66,7 @@ struct CdpOwnerRuntimeConfig {
|
||||
|
||||
impl SharedCdpOwnerRegistry {
|
||||
pub(super) fn new(
|
||||
browser: BrowserHandle,
|
||||
directory: SharedCdpAgentHostDirectory,
|
||||
target_id_allocator: Arc<AtomicU64>,
|
||||
tab_target_id_allocator: Arc<AtomicU64>,
|
||||
@@ -73,6 +78,7 @@ impl SharedCdpOwnerRegistry {
|
||||
Self {
|
||||
inner: Arc::new(CdpOwnerRegistryInner {
|
||||
config: CdpOwnerRuntimeConfig {
|
||||
browser,
|
||||
directory,
|
||||
target_id_allocator,
|
||||
tab_target_id_allocator,
|
||||
@@ -133,6 +139,7 @@ impl SharedCdpOwnerRegistry {
|
||||
checkpoint_rx,
|
||||
);
|
||||
let owner_finished_rx = spawn_owner_task(
|
||||
self.inner.config.browser.clone(),
|
||||
receivers,
|
||||
initial_storage_partition,
|
||||
self.inner.config.navigation_runtime_config.clone(),
|
||||
@@ -201,6 +208,7 @@ impl Drop for CdpOwnerRegistryInner {
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
fn spawn_owner_task(
|
||||
browser: BrowserHandle,
|
||||
receivers: CdpFrontendReceivers,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
@@ -212,6 +220,7 @@ fn spawn_owner_task(
|
||||
spawn_protocol_local_task("cdp-owner", move || async move {
|
||||
let (scheduler, scheduler_receivers) =
|
||||
CdpScheduler::new_with_deferred_default_target_runtime_and_screencast_interval(
|
||||
browser,
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
target_host_integration,
|
||||
|
||||
@@ -19,6 +19,7 @@ use axum::{
|
||||
use moli_cookie_jar::StoredCookie;
|
||||
use moli_core::{
|
||||
LayoutPolicy, OptionalResourceFetchMask,
|
||||
browser::BrowserService,
|
||||
runtime::{NavigationRuntimeConfig, storage_partition::StoragePartitionState},
|
||||
};
|
||||
use moli_fetch::FetchConfig;
|
||||
@@ -159,6 +160,7 @@ impl ProtocolServer {
|
||||
)?;
|
||||
|
||||
let cdp_owner_registry = app_state.cdp_owner_registry.clone();
|
||||
let browser_service = app_state.browser_service.clone();
|
||||
let app = build_router(app_state);
|
||||
|
||||
let listener = listener.tap_io(|tcp_stream| {
|
||||
@@ -166,6 +168,7 @@ impl ProtocolServer {
|
||||
});
|
||||
let result = axum::serve(listener, app).await;
|
||||
cdp_owner_registry.shutdown().await;
|
||||
browser_service.shutdown();
|
||||
result.context("protocol server failed")
|
||||
}
|
||||
}
|
||||
@@ -737,6 +740,7 @@ fn is_websocket_upgrade_request(request: &Request<Body>) -> bool {
|
||||
|
||||
#[derive(Clone)]
|
||||
struct AppState {
|
||||
browser_service: BrowserService,
|
||||
browser_ws_url: String,
|
||||
page_ws_url: String,
|
||||
bidi_ws_url: String,
|
||||
@@ -781,13 +785,13 @@ impl AppState {
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
screencast_interval_ms: u32,
|
||||
) -> anyhow::Result<Self> {
|
||||
Ok(Self::from_parts(
|
||||
Self::from_parts(
|
||||
addr,
|
||||
SharedCookieProfile::from_storage_partition(storage_partition.clone()),
|
||||
storage_partition,
|
||||
navigation_runtime_config,
|
||||
screencast_interval_ms,
|
||||
))
|
||||
)
|
||||
}
|
||||
|
||||
fn from_parts(
|
||||
@@ -796,11 +800,13 @@ impl AppState {
|
||||
storage_partition: Arc<StoragePartitionState>,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
screencast_interval_ms: u32,
|
||||
) -> Self {
|
||||
) -> anyhow::Result<Self> {
|
||||
let browser_service = BrowserService::start().map_err(anyhow::Error::msg)?;
|
||||
let cdp_agent_host_directory = SharedCdpAgentHostDirectory::default();
|
||||
let cdp_target_id_allocator = Arc::new(AtomicU64::new(0));
|
||||
let cdp_tab_target_id_allocator = Arc::new(AtomicU64::new(0));
|
||||
let cdp_owner_registry = SharedCdpOwnerRegistry::new(
|
||||
browser_service.handle(),
|
||||
cdp_agent_host_directory.clone(),
|
||||
cdp_target_id_allocator,
|
||||
cdp_tab_target_id_allocator,
|
||||
@@ -809,7 +815,8 @@ impl AppState {
|
||||
navigation_runtime_config.clone(),
|
||||
screencast_interval_ms,
|
||||
);
|
||||
Self {
|
||||
Ok(Self {
|
||||
browser_service,
|
||||
browser_ws_url: format!("ws://{addr}/devtools/browser/{DEFAULT_BROWSER_ID}"),
|
||||
page_ws_url: format!("ws://{addr}/devtools/page/{DEFAULT_TARGET_ID}"),
|
||||
bidi_ws_url: format!("ws://{addr}/session"),
|
||||
@@ -826,7 +833,7 @@ impl AppState {
|
||||
optional_resource_fetch_mask: navigation_runtime_config.optional_resource_fetch_mask(),
|
||||
subframe_loading_enabled: navigation_runtime_config.subframe_loading_enabled(),
|
||||
layout_policy: navigation_runtime_config.layout_policy(),
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
fn initial_storage_partition(
|
||||
|
||||
@@ -198,8 +198,10 @@ fn app_state_storage_partition_backing_derives_initial_partition_from_owner() ->
|
||||
)?;
|
||||
let initial_storage_partition =
|
||||
state.initial_storage_partition(vec![stored_cookie("owner-cookie", "owner-value")]);
|
||||
let mut conn =
|
||||
moli_protocol::CdpConnection::new_with_initial_storage_partition(initial_storage_partition);
|
||||
let mut conn = moli_protocol::test_support::connection_with_config(
|
||||
initial_storage_partition,
|
||||
Default::default(),
|
||||
);
|
||||
|
||||
assert!(conn.snapshot_profile_backed_cookies().is_none());
|
||||
conn.install_default_browser_target();
|
||||
|
||||
@@ -14,7 +14,9 @@ use axum::{
|
||||
};
|
||||
use futures_util::SinkExt;
|
||||
use moli_cookie_jar::StoredCookie;
|
||||
use moli_core::{RendererOutputTransportMessage, runtime::NavigationRuntimeConfig};
|
||||
use moli_core::{
|
||||
RendererOutputTransportMessage, browser::BrowserHandle, runtime::NavigationRuntimeConfig,
|
||||
};
|
||||
use moli_protocol::{
|
||||
BackgroundCommandResponsePayload, BackgroundProtocolEvent, CdpInitialStoragePartition,
|
||||
conn::RuntimeInspectorResponseReady,
|
||||
@@ -71,6 +73,7 @@ pub(super) async fn ws_bidi_session_upgrade_handler(
|
||||
let web_socket_url = state.bidi_ws_url;
|
||||
let session_registry = state.bidi_session_registry;
|
||||
let cookie_profile = state.cookie_profile;
|
||||
let browser = state.browser_service.handle();
|
||||
let navigation_runtime_config = NavigationRuntimeConfig::new(
|
||||
state.fetch_config,
|
||||
state.optional_resource_fetch_mask,
|
||||
@@ -79,6 +82,7 @@ pub(super) async fn ws_bidi_session_upgrade_handler(
|
||||
);
|
||||
ws.on_upgrade(move |socket| {
|
||||
handle_bidi_session_socket(
|
||||
browser,
|
||||
socket,
|
||||
web_socket_url,
|
||||
session_registry,
|
||||
@@ -130,6 +134,7 @@ pub(super) async fn ws_bidi_existing_session_upgrade_handler(
|
||||
}
|
||||
|
||||
async fn handle_bidi_session_socket(
|
||||
browser: BrowserHandle,
|
||||
socket: WebSocket,
|
||||
web_socket_url: String,
|
||||
session_registry: SharedBidiSessionRegistry,
|
||||
@@ -140,6 +145,7 @@ async fn handle_bidi_session_socket(
|
||||
) {
|
||||
let cookie_commit = spawn_protocol_local_task("bidi-socket", move || {
|
||||
handle_bidi_session_socket_local(
|
||||
browser,
|
||||
socket,
|
||||
web_socket_url,
|
||||
session_registry,
|
||||
@@ -163,6 +169,7 @@ async fn handle_bidi_session_socket(
|
||||
}
|
||||
|
||||
async fn handle_bidi_session_socket_local(
|
||||
browser: BrowserHandle,
|
||||
socket: WebSocket,
|
||||
web_socket_url: String,
|
||||
session_registry: SharedBidiSessionRegistry,
|
||||
@@ -172,6 +179,7 @@ async fn handle_bidi_session_socket_local(
|
||||
) -> CookieProfileCommit {
|
||||
let mut actor = BidiSocketActor::new(socket, web_socket_url);
|
||||
let (mut scheduler, mut receivers) = CdpScheduler::new_with_initial_state_runtime_config(
|
||||
browser,
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
);
|
||||
|
||||
@@ -1661,6 +1661,7 @@ pub(super) async fn webdriver_classic_new_session(
|
||||
let initial_cookie_snapshot = initial_cookies.clone();
|
||||
let initial_storage_partition = state.initial_storage_partition(initial_cookies);
|
||||
let runtime = ClassicSessionRuntimeHandle::spawn(
|
||||
state.browser_service.handle(),
|
||||
initial_cookie_snapshot,
|
||||
initial_storage_partition,
|
||||
moli_core::runtime::NavigationRuntimeConfig::new(
|
||||
|
||||
@@ -8,7 +8,10 @@ use std::{
|
||||
|
||||
use axum::extract::ws::WebSocket;
|
||||
use moli_cookie_jar::StoredCookie;
|
||||
use moli_core::{page::RendererDocumentLifecycleMilestone, runtime::NavigationRuntimeConfig};
|
||||
use moli_core::{
|
||||
browser::BrowserHandle, page::RendererDocumentLifecycleMilestone,
|
||||
runtime::NavigationRuntimeConfig,
|
||||
};
|
||||
use moli_protocol::{
|
||||
CdpInitialStoragePartition, DevToolsPageResidenceIdentity,
|
||||
devtools_runtime::{
|
||||
@@ -477,6 +480,7 @@ pub(in crate::protocol_server) struct ClassicSessionRuntimeHandle {
|
||||
|
||||
impl ClassicSessionRuntimeHandle {
|
||||
pub(super) fn spawn(
|
||||
browser: BrowserHandle,
|
||||
initial_cookie_snapshot: Vec<StoredCookie>,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
@@ -484,6 +488,7 @@ impl ClassicSessionRuntimeHandle {
|
||||
let (tx, rx) = mpsc::unbounded_channel();
|
||||
let _runtime_finished_rx = spawn_protocol_local_task("classic-session", move || {
|
||||
classic_session_runtime_loop(
|
||||
browser,
|
||||
rx,
|
||||
initial_cookie_snapshot,
|
||||
initial_storage_partition,
|
||||
@@ -1320,12 +1325,14 @@ fn classic_pending_navigation_timeout_error() -> DevToolsError {
|
||||
}
|
||||
|
||||
async fn classic_session_runtime_loop(
|
||||
browser: BrowserHandle,
|
||||
mut rx: mpsc::UnboundedReceiver<ClassicSessionRuntimeRequest>,
|
||||
initial_cookie_snapshot: Vec<StoredCookie>,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
) -> CookieProfileCommit {
|
||||
let (mut scheduler, mut receivers) = CdpScheduler::new_with_initial_state_runtime_config(
|
||||
browser,
|
||||
initial_storage_partition,
|
||||
navigation_runtime_config,
|
||||
);
|
||||
|
||||
@@ -6,7 +6,7 @@ edition = "2024"
|
||||
build = "build.rs"
|
||||
|
||||
[features]
|
||||
test-support = []
|
||||
test-support = ["moli-core/test-support"]
|
||||
|
||||
[dependencies]
|
||||
anyhow = "1.0.100"
|
||||
|
||||
+113
-496
@@ -1,5 +1,4 @@
|
||||
use std::{
|
||||
cell::OnceCell,
|
||||
collections::{HashMap, HashSet},
|
||||
path::PathBuf,
|
||||
sync::{
|
||||
@@ -10,7 +9,6 @@ use std::{
|
||||
|
||||
use indexmap::IndexMap;
|
||||
use moli_cookie_jar::{StoredCookie, StoredCookieQueryReport};
|
||||
use moli_fetch::FetchConfig;
|
||||
use parking_lot::Mutex;
|
||||
use serde_json::json;
|
||||
|
||||
@@ -20,12 +18,10 @@ use crate::devtools_runtime::{
|
||||
use crate::domains::command_output::{BackgroundProtocolEventBuffer, CommandOutputBuffer};
|
||||
|
||||
use moli_core::{
|
||||
LayoutPolicy, OptionalResourceFetchMask, RendererOutputPublicationOrdering,
|
||||
RendererOutputTransportMessage,
|
||||
LayoutPolicy, RendererOutputPublicationOrdering, RendererOutputTransportMessage,
|
||||
browser::BrowserHandle,
|
||||
network::{SharedWebStorageStore, new_shared_web_storage_store},
|
||||
runtime::{
|
||||
NavigationEngine, NavigationRuntimeConfig, storage_partition::StoragePartitionState,
|
||||
},
|
||||
runtime::{NavigationRuntimeConfig, storage_partition::StoragePartitionState},
|
||||
};
|
||||
|
||||
pub const DEFAULT_CDP_PAGE_TARGET_ID: &str = "moli-default";
|
||||
@@ -33,8 +29,8 @@ pub const DEFAULT_CDP_TAB_TARGET_ID: &str = "moli-default-tab";
|
||||
|
||||
mod activity_source;
|
||||
mod bidi_channel_work;
|
||||
mod body_spool;
|
||||
mod browser_context;
|
||||
mod browser_worker_commands;
|
||||
mod command_owner_scope;
|
||||
mod command_view;
|
||||
mod cookie_manager_surface;
|
||||
@@ -48,6 +44,7 @@ mod dispatch;
|
||||
mod download_policy;
|
||||
mod downloads;
|
||||
pub(crate) use download_policy::parse_download_behavior;
|
||||
pub(crate) use downloads::PreparedDownloadActivation;
|
||||
#[cfg(test)]
|
||||
mod download_policy_tests;
|
||||
mod fetch_support;
|
||||
@@ -55,6 +52,8 @@ mod fetch_support;
|
||||
mod inspection_binding_tests;
|
||||
mod inspector_route;
|
||||
mod navigation_error;
|
||||
#[cfg(test)]
|
||||
pub(crate) use navigation_error::NavigationNetworkErrorKind;
|
||||
mod output;
|
||||
#[cfg(test)]
|
||||
mod permission_tests;
|
||||
@@ -72,12 +71,39 @@ mod settings;
|
||||
#[cfg(test)]
|
||||
mod site_data_manager_surface;
|
||||
mod state;
|
||||
pub(crate) use state::PageInputCommand;
|
||||
pub(crate) use state::{
|
||||
BrowserAppManifestLoadPreparation, CompletedAppManifestLoadPreparation,
|
||||
CompletedAppManifestPublication, CompletedCaptureDocumentImage,
|
||||
CompletedCaptureDocumentScreencastFrame, CompletedCaptureDocumentSnapshot,
|
||||
CompletedChildFrameNavigation, CompletedChildFrameTreeSnapshot,
|
||||
CompletedDocumentAutofillTrigger, CompletedDocumentBlobRead,
|
||||
CompletedDocumentCookieOwnerSnapshot, CompletedDocumentCspBypassUpdate,
|
||||
CompletedDocumentDiagnosticsSnapshot, CompletedDocumentFetchCommand,
|
||||
CompletedDocumentInputCommand, CompletedDocumentLifecycleStop, CompletedDocumentPolicyUpdate,
|
||||
CompletedDocumentResourceRuntimeUpdate, CompletedDocumentResourceTextSearch,
|
||||
CompletedDocumentStorageKeySnapshot, CompletedNavigationHistoryReset,
|
||||
CompletedNetworkResourceLoadPreparation, CompletedSetDocumentContent,
|
||||
CompletedTopLevelHistoryTraversal, CompletedTopLevelSameDocumentNavigation,
|
||||
DocumentFetchCommand, DocumentFetchCommandOutcome, DocumentPolicyUpdate,
|
||||
DocumentRuntimePolicyReconciliation, PendingAppManifestLoadPreparation,
|
||||
PendingAppManifestPublication, PendingCaptureDocumentImage,
|
||||
PendingCaptureDocumentScreencastFrame, PendingCaptureDocumentSnapshot,
|
||||
PendingChildFrameLifecycleWork, PendingChildFrameNavigation, PendingChildFrameTreeSnapshot,
|
||||
PendingDocumentAutofillTrigger, PendingDocumentBlobRead, PendingDocumentCookieOwnerSnapshot,
|
||||
PendingDocumentCspBypassUpdate, PendingDocumentDiagnosticsSnapshot,
|
||||
PendingDocumentFetchCommand, PendingDocumentInputCommand, PendingDocumentLifecycleStop,
|
||||
PendingDocumentPolicyBatch, PendingDocumentPolicyUpdate, PendingDocumentResourceRuntimeUpdate,
|
||||
PendingDocumentResourceTextSearch, PendingDocumentStorageKeySnapshot,
|
||||
PendingNavigationHistoryReset, PendingNetworkResourceLoadPreparation,
|
||||
PendingSetDocumentContent, PendingTopLevelHistoryTraversal,
|
||||
PendingTopLevelSameDocumentNavigation,
|
||||
};
|
||||
pub(crate) use state::{
|
||||
ClaimedNavigationRequest, InterceptedNavigationLoad, InterceptedNavigationResponse,
|
||||
NavigationRequestInterception,
|
||||
};
|
||||
pub(crate) use state::{CompletedContextPermissionUpdate, PendingContextPermissionUpdate};
|
||||
pub(crate) use state::{NetworkPolicyUpdateKind, PageInputCommand, PagePolicyUpdateKind};
|
||||
mod target;
|
||||
mod top_level_navigation_work;
|
||||
|
||||
@@ -88,7 +114,6 @@ pub(crate) use bidi_channel_work::{
|
||||
BidiChannelListenerResidence, BidiChannelOwnerAction, BidiChannelOwnerActionBody,
|
||||
BidiChannelPageOwner,
|
||||
};
|
||||
pub(crate) use body_spool::{CapturedBody, CapturedBodyWriter};
|
||||
pub(crate) use browser_context::{
|
||||
PageLifecycleEventsEnableResult, SessionOwnerInspectorEnableResult,
|
||||
SessionOwnerRuntimeFrontendEnableResult, TargetNavigationLoadInputs,
|
||||
@@ -122,9 +147,8 @@ pub(crate) use fetch_support::{
|
||||
PendingFetchResponseNavigation, PendingSubresourceFetchResidence,
|
||||
};
|
||||
pub use fetch_support::{
|
||||
DocumentBodySource, FetchAuthChallenge, FetchInterceptionPattern, FetchRequestStage,
|
||||
FetchResourceTypeFilter, InFlightSubresourceFetchRequest, PausedDocumentTransfer,
|
||||
PendingFetchAuthNavigation, PendingFetchNavigation, PendingFetchResponseOpenedBodyStream,
|
||||
FetchAuthChallenge, FetchInterceptionPattern, FetchRequestStage, FetchResourceTypeFilter,
|
||||
InFlightSubresourceFetchRequest, PendingFetchAuthNavigation, PendingFetchNavigation,
|
||||
PendingSubresourceFetchAuthRequest, PendingSubresourceFetchAuthStage,
|
||||
PendingSubresourceFetchAuthStageChain, PendingSubresourceFetchOwnerKind,
|
||||
PendingSubresourceFetchRequest, PendingSubresourceFetchRequestStage,
|
||||
@@ -133,6 +157,11 @@ pub use fetch_support::{
|
||||
ResponseStageUrlMatchPolicy, fetch_subresource_interception_config,
|
||||
fetch_subresource_interception_config_for_patterns,
|
||||
};
|
||||
pub(crate) use moli_core::browser::web_contents::OpenBodyStreamError;
|
||||
pub use moli_core::browser::web_contents::{
|
||||
DocumentBodySource, PausedDocumentTransfer, PendingFetchResponseOpenedBodyStream,
|
||||
};
|
||||
pub(crate) use moli_core::browser::{CapturedBody, CapturedBodyWriter};
|
||||
pub use moli_protocol_cdp::{
|
||||
CdpRendererCommandPolicy, CdpRendererCommandReplacement, CdpRendererCommandReplayDispatch,
|
||||
CdpRequest, ParsedCdpCommand,
|
||||
@@ -511,9 +540,7 @@ impl CommandDispatchContext {
|
||||
}
|
||||
|
||||
pub(crate) use moli_protocol_cdp::{DEFAULT_LOADER_ID, monotonic_timestamp_seconds};
|
||||
pub(crate) use navigation_error::{
|
||||
NavigationNetworkError, NavigationNetworkErrorKind, NavigationRequestBlocked,
|
||||
};
|
||||
pub(crate) use navigation_error::{NavigationNetworkError, NavigationRequestBlocked};
|
||||
pub(crate) use output::NavigationBackgroundEvent;
|
||||
pub use output::{
|
||||
BackgroundCommandResponsePayload, BackgroundEventSender, BackgroundProtocolEvent,
|
||||
@@ -560,11 +587,11 @@ pub(crate) use site_data_manager_surface::{
|
||||
pub(crate) use state::BrowserContextResourceStorageHandles;
|
||||
pub(crate) use state::JavaScriptDialogError;
|
||||
pub use state::{
|
||||
BrowserContext, BrowserWindowBounds, DevToolsPageResidenceIdentity, DocumentStartScript,
|
||||
DownloadNavigation, EmulatedDeviceMetrics, EmulatedGeolocationOverride,
|
||||
EmulatedGeolocationOverrideState, EmulatedMediaOverrides, IsolatedWorldDefinition,
|
||||
LoadedNavigation, NavigationDispatchState, NavigationLoadOutcome, NavigationRequestLoadPolicy,
|
||||
PageAgentHost, PageNavigationHistoryEntry, RuntimeBindingDefinition, TargetInfo, URL_BASE,
|
||||
BrowserContext, DevToolsPageResidenceIdentity, DocumentStartScript, DownloadNavigation,
|
||||
EmulatedDeviceMetrics, EmulatedGeolocationOverride, EmulatedGeolocationOverrideState,
|
||||
EmulatedMediaOverrides, IsolatedWorldDefinition, LoadedNavigation, NavigationDispatchState,
|
||||
NavigationLoadOutcome, NavigationRequestLoadPolicy, PageAgentHost, PageNavigationHistoryEntry,
|
||||
RuntimeBindingDefinition, TargetInfo, URL_BASE,
|
||||
};
|
||||
pub(crate) use state::{
|
||||
BrowserContextPageStorageHandles, BrowserContextStoragePartitionHandles,
|
||||
@@ -687,25 +714,6 @@ impl ConnectionNetworkRequestIdAllocator {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
pub(crate) struct IdleNavigationEngineReleaseResult {
|
||||
pub(crate) reset: bool,
|
||||
pub(crate) reason: &'static str,
|
||||
pub(crate) loaded_browser_context_count: usize,
|
||||
pub(crate) live_target_browser_context_count: usize,
|
||||
}
|
||||
|
||||
impl IdleNavigationEngineReleaseResult {
|
||||
pub(crate) fn to_protocol_json(self) -> serde_json::Value {
|
||||
json!({
|
||||
"reset": self.reset,
|
||||
"reason": self.reason,
|
||||
"loadedBrowserContextCount": self.loaded_browser_context_count,
|
||||
"liveTargetBrowserContextCount": self.live_target_browser_context_count,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl PendingDeferredMainDocumentLoadCompletion {
|
||||
pub(crate) fn new(
|
||||
inner: crate::domains::activity::PendingDeferredMainDocumentLoadCompletionActivity,
|
||||
@@ -899,11 +907,13 @@ impl CdpInitialStoragePartitionOwner {
|
||||
|
||||
fn new_default_browser_context(
|
||||
&self,
|
||||
browser: &BrowserHandle,
|
||||
id: String,
|
||||
http_cache_root: Option<PathBuf>,
|
||||
http_cache_max_bytes: Option<u64>,
|
||||
) -> BrowserContext {
|
||||
BrowserContext::new_with_storage_partition_handles_and_http_cache(
|
||||
browser,
|
||||
id,
|
||||
self.handles.clone(),
|
||||
http_cache_root,
|
||||
@@ -1022,126 +1032,19 @@ pub(crate) struct ServiceWorkerAutoAttachRelatedOwnerSession {
|
||||
pub(crate) wait_for_debugger_on_start: bool,
|
||||
}
|
||||
|
||||
/// Drop-addressable fallback engine for commands without a concrete Page host.
|
||||
///
|
||||
/// `CdpConnection::drop` must release this engine before joining its extracted
|
||||
/// BrowserContext network roots. A plain field would only be dropped after the
|
||||
/// Drop implementation returned, which reverses that order.
|
||||
struct StandaloneNavigationEngineSlot {
|
||||
engine: OnceCell<NavigationEngine>,
|
||||
runtime_config: NavigationRuntimeConfig,
|
||||
renderer_publication_sender: Option<moli_core::RendererOutputTransportSender>,
|
||||
}
|
||||
|
||||
impl StandaloneNavigationEngineSlot {
|
||||
fn materialized(engine: NavigationEngine) -> Self {
|
||||
let runtime_config = engine.runtime_config();
|
||||
let slot = Self {
|
||||
engine: OnceCell::new(),
|
||||
runtime_config,
|
||||
renderer_publication_sender: None,
|
||||
};
|
||||
slot.engine
|
||||
.set(engine)
|
||||
.expect("fresh standalone navigation engine slot must be empty");
|
||||
slot
|
||||
}
|
||||
|
||||
fn deferred(runtime_config: NavigationRuntimeConfig) -> Self {
|
||||
Self {
|
||||
engine: OnceCell::new(),
|
||||
runtime_config,
|
||||
renderer_publication_sender: None,
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn is_materialized(&self) -> bool {
|
||||
self.engine.get().is_some()
|
||||
}
|
||||
|
||||
fn runtime_config(&self) -> NavigationRuntimeConfig {
|
||||
self.engine
|
||||
.get()
|
||||
.map(NavigationEngine::runtime_config)
|
||||
.unwrap_or_else(|| self.runtime_config.clone())
|
||||
}
|
||||
|
||||
fn fetch_config(&self) -> &moli_fetch::FetchConfig {
|
||||
self.engine
|
||||
.get()
|
||||
.map(NavigationEngine::fetch_config)
|
||||
.unwrap_or_else(|| self.runtime_config.fetch_config())
|
||||
}
|
||||
|
||||
fn layout_policy(&self) -> LayoutPolicy {
|
||||
self.engine
|
||||
.get()
|
||||
.map(NavigationEngine::layout_policy)
|
||||
.unwrap_or_else(|| self.runtime_config.layout_policy())
|
||||
}
|
||||
|
||||
fn apply_fetch_defaults(&mut self, defaults: moli_fetch::FetchConfig) {
|
||||
let config = self.runtime_config.fetch_config_mut();
|
||||
config.set_browser_identity(defaults.browser_identity().clone());
|
||||
config.set_http_proxy(defaults.http_proxy().map(str::to_owned));
|
||||
config.set_http_no_proxy(defaults.http_no_proxy().map(str::to_owned));
|
||||
config.set_tls_verify_host(defaults.tls_verify_host());
|
||||
if let Some(engine) = self.engine.get_mut() {
|
||||
engine.set_browser_identity_override(defaults.browser_identity().clone());
|
||||
engine.set_http_proxy_override(defaults.http_proxy().map(str::to_owned));
|
||||
engine.set_http_no_proxy_override(defaults.http_no_proxy().map(str::to_owned));
|
||||
engine.set_tls_verify_host(defaults.tls_verify_host());
|
||||
}
|
||||
}
|
||||
|
||||
fn set_renderer_output_transport_sender(
|
||||
&mut self,
|
||||
sender: moli_core::RendererOutputTransportSender,
|
||||
) {
|
||||
if let Some(engine) = self.engine.get() {
|
||||
engine.set_renderer_output_transport_sender(sender.clone());
|
||||
}
|
||||
self.renderer_publication_sender = Some(sender);
|
||||
}
|
||||
|
||||
fn replace(&mut self, engine: NavigationEngine) -> Option<NavigationEngine> {
|
||||
self.runtime_config = engine.runtime_config();
|
||||
if let Some(sender) = self.renderer_publication_sender.as_ref() {
|
||||
engine.set_renderer_output_transport_sender(sender.clone());
|
||||
}
|
||||
let previous = self.engine.take();
|
||||
self.engine
|
||||
.set(engine)
|
||||
.expect("standalone navigation engine slot must be empty after take");
|
||||
previous
|
||||
}
|
||||
|
||||
fn take(&mut self) -> Option<NavigationEngine> {
|
||||
self.engine.take()
|
||||
}
|
||||
|
||||
fn ensure(&self) -> &NavigationEngine {
|
||||
self.engine.get_or_init(|| {
|
||||
let engine = NavigationEngine::new_with_runtime_config(self.runtime_config.clone());
|
||||
if let Some(sender) = self.renderer_publication_sender.as_ref() {
|
||||
engine.set_renderer_output_transport_sender(sender.clone());
|
||||
}
|
||||
engine
|
||||
})
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
fn ensure_mut(&mut self) -> &mut NavigationEngine {
|
||||
self.ensure();
|
||||
self.engine
|
||||
.get_mut()
|
||||
.expect("standalone navigation engine was just materialized")
|
||||
}
|
||||
/// The single Browser-wide source for defaults inherited by every Context.
|
||||
/// Clones are short-lived operation snapshots and never Context residents.
|
||||
#[derive(Clone, Default)]
|
||||
pub(crate) struct BrowserGlobalOverrides {
|
||||
pub(crate) extra_headers: moli_fetch::RequestHeaders,
|
||||
pub(crate) network_conditions: Option<EmulatedNetworkConditions>,
|
||||
pub(crate) geolocation: Option<EmulatedGeolocationOverrideState>,
|
||||
pub(crate) cache_disabled: bool,
|
||||
}
|
||||
|
||||
/// Persistent per-connection state.
|
||||
pub struct CdpConnection {
|
||||
browser: BrowserHandle,
|
||||
// Browser/session routing state.
|
||||
pub browser_context: Option<BrowserContext>,
|
||||
pub inactive_browser_contexts: Vec<BrowserContext>,
|
||||
@@ -1175,20 +1078,13 @@ pub struct CdpConnection {
|
||||
next_page_domain_subscription_generation: u64,
|
||||
next_internal_runtime_command_id: u64,
|
||||
network_request_id_allocator: ConnectionNetworkRequestIdAllocator,
|
||||
// Browser profile, permissions, download and global IO state.
|
||||
pub window_bounds: BrowserWindowBounds,
|
||||
// Browser profile, download and global IO state.
|
||||
download_policy: moli_core::browser::DownloadPolicy,
|
||||
download_subscriptions: download_policy::DownloadSubscriptions,
|
||||
// Browser defaults remain embedded until the Browser aggregate cutover.
|
||||
// Each physical Context owns its scoped rules; no wire-id registry lives here.
|
||||
permission_defaults: moli_core::browser::PermissionDefaults,
|
||||
next_global_io_stream_id: u64,
|
||||
base_browser_identity: moli_browser_profile::BrowserIdentityProfile,
|
||||
global_extra_headers: moli_fetch::RequestHeaders,
|
||||
pub(crate) browser_global_overrides: BrowserGlobalOverrides,
|
||||
global_browser_identity_override: Option<moli_browser_profile::BrowserIdentityProfile>,
|
||||
global_network_conditions: Option<EmulatedNetworkConditions>,
|
||||
global_geolocation_override: Option<EmulatedGeolocationOverrideState>,
|
||||
global_cache_disabled: bool,
|
||||
pub(crate) network_data_collectors: crate::domains::network::NetworkDataCollectorStore,
|
||||
base_http_proxy: Option<String>,
|
||||
base_http_no_proxy: Option<String>,
|
||||
@@ -1207,110 +1103,16 @@ pub struct CdpConnection {
|
||||
// source-specific queue ownership is being migrated outward.
|
||||
scheduler_state: CdpConnectionSchedulerState,
|
||||
|
||||
// Standalone navigation state for commands that have no concrete Page
|
||||
// owner. Every page-owned engine lives in its stable PageAgentHost.
|
||||
standalone_navigation_engine: StandaloneNavigationEngineSlot,
|
||||
}
|
||||
|
||||
impl Default for CdpConnection {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for CdpConnection {
|
||||
fn drop(&mut self) {
|
||||
// Separate per-context roots from target/page state so teardown can
|
||||
// follow the observable producer order even though NavigationEngines
|
||||
// and detached local tasks retain only weak owner access.
|
||||
let mut contexts = Vec::new();
|
||||
if let Some(context) = self.browser_context.take() {
|
||||
contexts.push(context);
|
||||
}
|
||||
contexts.append(&mut self.inactive_browser_contexts);
|
||||
let roots = contexts
|
||||
.iter_mut()
|
||||
.filter_map(BrowserContext::take_renderer_runtime_owner_for_teardown)
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
let mut roots = roots;
|
||||
for root in &mut roots {
|
||||
root.terminate_renderer_producers_for_owner_shutdown();
|
||||
}
|
||||
|
||||
// Dropping contexts releases every PageAgentHost and its Page state.
|
||||
drop(contexts);
|
||||
|
||||
// RenderRuntimeOwner joins happen when the last JsRuntime-backed
|
||||
// NavigationEngine handle is released. Do that explicitly here rather
|
||||
// than relying on field drop after this method returns.
|
||||
drop(self.standalone_navigation_engine.take());
|
||||
|
||||
// Only after every Page engine and the standalone fallback are gone
|
||||
// may the context roots close fetch admission and join network owners.
|
||||
for root in &mut roots {
|
||||
root.shutdown_network_and_join();
|
||||
}
|
||||
drop(roots);
|
||||
}
|
||||
// Defaults only. Live engines belong to Core WebContents, never DevTools.
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
}
|
||||
|
||||
impl CdpConnection {
|
||||
pub fn new() -> Self {
|
||||
Self::new_with_initial_storage_partition(CdpInitialStoragePartition::memory())
|
||||
}
|
||||
|
||||
pub(crate) fn layout_policy(&self) -> LayoutPolicy {
|
||||
self.browser_context
|
||||
.as_ref()
|
||||
.and_then(|context| context.page_navigation_layout_policy(context.active_target_id()?))
|
||||
.unwrap_or_else(|| self.standalone_navigation_engine.layout_policy())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn active_navigation_engine(&self) -> &NavigationEngine {
|
||||
if let Some(engine) = self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.and_then(|context| context.page_navigation_engine(context.active_target_id()?))
|
||||
{
|
||||
return engine;
|
||||
}
|
||||
self.standalone_navigation_engine.ensure()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn active_navigation_engine_mut(&mut self) -> &mut NavigationEngine {
|
||||
let active_owner = self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.and_then(|context| Some((context.id.clone(), context.active_target_id()?.to_owned())));
|
||||
if let Some((browser_context_id, target_id)) = active_owner {
|
||||
return self
|
||||
.ensure_page_navigation_engine_for_target(&browser_context_id, &target_id)
|
||||
.expect("active PageAgentHost navigation engine disappeared");
|
||||
}
|
||||
self.standalone_navigation_engine.ensure_mut()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn ensure_page_navigation_engine_for_target(
|
||||
&mut self,
|
||||
browser_context_id: &str,
|
||||
target_id: &str,
|
||||
) -> Option<&mut NavigationEngine> {
|
||||
let needs_engine = self
|
||||
.browser_context_by_id(browser_context_id)?
|
||||
.page_navigation_engine(target_id)
|
||||
.is_none();
|
||||
if needs_engine {
|
||||
let config = self.standalone_navigation_engine.runtime_config();
|
||||
let sender = self.scheduler_hooks.renderer_publication_sender();
|
||||
self.browser_context_by_id_mut(browser_context_id)?
|
||||
.bind_page_navigation_engines(config, sender);
|
||||
}
|
||||
self.browser_context_by_id_mut(browser_context_id)?
|
||||
.page_navigation_engine_mut(target_id)
|
||||
.unwrap_or_else(|| self.navigation_runtime_config.layout_policy())
|
||||
}
|
||||
|
||||
pub fn has_pending_javascript_dialog(&self) -> bool {
|
||||
@@ -1324,25 +1126,10 @@ impl CdpConnection {
|
||||
let Some(browser_context) = self.browser_context.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
browser_context
|
||||
.renderer_runtime()
|
||||
.set_javascript_dialog_handler_enabled(enabled);
|
||||
browser_context.set_javascript_dialog_handler_enabled(enabled);
|
||||
true
|
||||
}
|
||||
|
||||
pub fn new_with_initial_cookies(initial_cookies: Vec<StoredCookie>) -> Self {
|
||||
Self::new_with_initial_storage_partition(CdpInitialStoragePartition::with_cookies(
|
||||
initial_cookies,
|
||||
))
|
||||
}
|
||||
|
||||
pub fn new_with_fetch_config(fetch_config: FetchConfig) -> Self {
|
||||
Self::new_with_initial_storage_partition_and_fetch_config(
|
||||
CdpInitialStoragePartition::memory(),
|
||||
fetch_config,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn enable_webdriver_bidi_download_events(&mut self) -> bool {
|
||||
self.download_subscriptions.enable_webdriver_bidi_events()
|
||||
}
|
||||
@@ -1351,80 +1138,10 @@ impl CdpConnection {
|
||||
self.download_subscriptions.disable_webdriver_bidi_events()
|
||||
}
|
||||
|
||||
pub fn new_with_initial_storage_partition(
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
) -> Self {
|
||||
Self::new_with_initial_storage_partition_and_fetch_config(
|
||||
initial_storage_partition,
|
||||
FetchConfig::default(),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_initial_storage_partition_and_fetch_config(
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
fetch_config: FetchConfig,
|
||||
) -> Self {
|
||||
Self::new_with_initial_storage_partition_fetch_config_and_resource_loading(
|
||||
initial_storage_partition,
|
||||
fetch_config,
|
||||
OptionalResourceFetchMask::NONE,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_initial_storage_partition_fetch_config_and_image_fetch_enabled(
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
fetch_config: FetchConfig,
|
||||
image_fetch_enabled: bool,
|
||||
) -> Self {
|
||||
let optional_resource_fetch_mask = if image_fetch_enabled {
|
||||
OptionalResourceFetchMask::IMAGE
|
||||
} else {
|
||||
OptionalResourceFetchMask::NONE
|
||||
};
|
||||
Self::new_with_initial_storage_partition_fetch_config_and_resource_loading(
|
||||
initial_storage_partition,
|
||||
fetch_config,
|
||||
optional_resource_fetch_mask,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_initial_storage_partition_fetch_config_and_resource_loading(
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
fetch_config: FetchConfig,
|
||||
optional_resource_fetch_mask: OptionalResourceFetchMask,
|
||||
subframe_loading_enabled: bool,
|
||||
) -> Self {
|
||||
Self::new_with_initial_storage_partition_and_runtime_config(
|
||||
initial_storage_partition,
|
||||
NavigationRuntimeConfig::new(
|
||||
fetch_config,
|
||||
optional_resource_fetch_mask,
|
||||
subframe_loading_enabled,
|
||||
LayoutPolicy::default(),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
pub fn new_with_initial_storage_partition_and_runtime_config(
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
) -> Self {
|
||||
Self::new_with_initial_storage_partition_owner_and_runtime_config(
|
||||
CdpInitialStoragePartitionOwner::from_initial_storage_partition(
|
||||
initial_storage_partition,
|
||||
),
|
||||
navigation_runtime_config,
|
||||
)
|
||||
}
|
||||
|
||||
/// Creates protocol state without starting the renderer runtime.
|
||||
///
|
||||
/// The runtime is materialized on first engine-backed operation. This is
|
||||
/// intended for browser-level CDP owners that publish a target before any
|
||||
/// frontend attaches to its page.
|
||||
pub fn new_with_deferred_navigation_runtime(
|
||||
/// Creates DevTools state attached to an externally owned Browser service.
|
||||
/// Renderer resources are allocated when a concrete target is installed.
|
||||
pub fn new(
|
||||
browser: BrowserHandle,
|
||||
initial_storage_partition: CdpInitialStoragePartition,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
) -> Self {
|
||||
@@ -1432,34 +1149,13 @@ impl CdpConnection {
|
||||
CdpInitialStoragePartitionOwner::from_initial_storage_partition(
|
||||
initial_storage_partition,
|
||||
);
|
||||
Self::new_with_initial_storage_partition_owner_and_engine(
|
||||
initial_storage_partition,
|
||||
StandaloneNavigationEngineSlot::deferred(navigation_runtime_config),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_with_initial_storage_partition_owner_and_runtime_config(
|
||||
initial_storage_partition: CdpInitialStoragePartitionOwner,
|
||||
navigation_runtime_config: NavigationRuntimeConfig,
|
||||
) -> Self {
|
||||
Self::new_with_initial_storage_partition_owner_and_engine(
|
||||
initial_storage_partition,
|
||||
StandaloneNavigationEngineSlot::materialized(
|
||||
NavigationEngine::new_with_runtime_config(navigation_runtime_config),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn new_with_initial_storage_partition_owner_and_engine(
|
||||
initial_storage_partition: CdpInitialStoragePartitionOwner,
|
||||
standalone_navigation_engine: StandaloneNavigationEngineSlot,
|
||||
) -> Self {
|
||||
let fetch_config = standalone_navigation_engine.fetch_config();
|
||||
let fetch_config = navigation_runtime_config.fetch_config();
|
||||
let base_browser_identity = fetch_config.browser_identity().clone();
|
||||
let base_http_proxy = fetch_config.http_proxy().map(str::to_owned);
|
||||
let base_http_no_proxy = fetch_config.http_no_proxy().map(str::to_owned);
|
||||
let base_tls_verify_host = fetch_config.tls_verify_host();
|
||||
Self {
|
||||
browser,
|
||||
browser_context: None,
|
||||
inactive_browser_contexts: Vec::new(),
|
||||
target_discovery_enabled: false,
|
||||
@@ -1472,10 +1168,8 @@ impl CdpConnection {
|
||||
service_worker_pause_on_start_owner_sessions: HashSet::new(),
|
||||
dedicated_worker_pause_on_start_owner_sessions: HashSet::new(),
|
||||
install_default_target_on_auto_attach: false,
|
||||
window_bounds: BrowserWindowBounds::default(),
|
||||
download_policy: moli_core::browser::DownloadPolicy::default(),
|
||||
download_subscriptions: download_policy::DownloadSubscriptions::default(),
|
||||
permission_defaults: moli_core::browser::PermissionDefaults::default(),
|
||||
next_bc_id: 0,
|
||||
next_global_io_stream_id: 0,
|
||||
next_target_id: 0,
|
||||
@@ -1487,11 +1181,8 @@ impl CdpConnection {
|
||||
next_internal_runtime_command_id: 902_000_000,
|
||||
network_request_id_allocator: ConnectionNetworkRequestIdAllocator::default(),
|
||||
base_browser_identity,
|
||||
global_extra_headers: Default::default(),
|
||||
browser_global_overrides: BrowserGlobalOverrides::default(),
|
||||
global_browser_identity_override: None,
|
||||
global_network_conditions: None,
|
||||
global_geolocation_override: None,
|
||||
global_cache_disabled: false,
|
||||
network_data_collectors: crate::domains::network::NetworkDataCollectorStore::default(),
|
||||
base_http_proxy,
|
||||
base_http_no_proxy,
|
||||
@@ -1502,7 +1193,7 @@ impl CdpConnection {
|
||||
scheduler_hooks: CdpSchedulerHooks::default(),
|
||||
target_host_lifecycle_observer: None,
|
||||
scheduler_state: CdpConnectionSchedulerState::default(),
|
||||
standalone_navigation_engine,
|
||||
navigation_runtime_config,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1546,8 +1237,6 @@ impl CdpConnection {
|
||||
) {
|
||||
self.scheduler_hooks
|
||||
.set_renderer_publication_sender(sender.clone());
|
||||
self.standalone_navigation_engine
|
||||
.set_renderer_output_transport_sender(sender.clone());
|
||||
for context in self
|
||||
.browser_context
|
||||
.iter_mut()
|
||||
@@ -1781,17 +1470,6 @@ impl CdpConnection {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
/// Validates ordinary document-command admission without lending Browser state.
|
||||
/// Configuration and interruptible work intentionally do not use this gate.
|
||||
pub(crate) fn resolve_document_command_owner(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Result<(String, String), String> {
|
||||
self.ensure_document_accessible_for_owner(owner)?;
|
||||
self.loaded_document_owner_identity_for_owner(owner)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn current_document_loader_id_for_session_owner(
|
||||
&self,
|
||||
session_id: Option<&str>,
|
||||
@@ -2608,18 +2286,6 @@ impl CdpConnection {
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) fn replace_standalone_navigation_engine(&mut self, engine: NavigationEngine) {
|
||||
let engine = engine;
|
||||
self.apply_scheduler_senders_to_navigation_engine(&engine);
|
||||
drop(self.standalone_navigation_engine.replace(engine));
|
||||
}
|
||||
|
||||
pub(crate) fn apply_scheduler_senders_to_navigation_engine(&self, engine: &NavigationEngine) {
|
||||
if let Some(sender) = self.scheduler_hooks.renderer_publication_sender() {
|
||||
engine.set_renderer_output_transport_sender(sender);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn enqueue_deferred_main_document_load_completion(
|
||||
&mut self,
|
||||
admission: crate::domains::activity::DeferredMainDocumentLoadCompletionAdmission,
|
||||
@@ -2894,7 +2560,7 @@ impl CdpConnection {
|
||||
pub(crate) fn response_body_materialize_limit(&self) -> usize {
|
||||
self.fetch_config()
|
||||
.http_max_response_size()
|
||||
.unwrap_or(body_spool::DEFAULT_BODY_MATERIALIZE_LIMIT)
|
||||
.unwrap_or(moli_core::browser::DEFAULT_BODY_MATERIALIZE_LIMIT)
|
||||
}
|
||||
|
||||
pub(crate) fn moli_memory_diagnostics(&self) -> serde_json::Value {
|
||||
@@ -3012,19 +2678,20 @@ impl CdpConnection {
|
||||
let active_engine = self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.and_then(|context| context.page_navigation_diagnostics(context.active_target_id()?))
|
||||
.unwrap_or_else(|| self.standalone_navigation_engine.ensure().diagnostics());
|
||||
let active_renderer_owner_id = active_engine.renderer_owner_id;
|
||||
.and_then(|context| context.page_navigation_diagnostics(context.active_target_id()?));
|
||||
let active_renderer_owner_id = active_engine
|
||||
.as_ref()
|
||||
.map(|engine| engine.renderer_owner_id);
|
||||
let mut page_navigation_engine_renderer_owner_ids = HashSet::new();
|
||||
let mut estimated_renderer_owner_ids = HashSet::new();
|
||||
estimated_renderer_owner_ids.insert(active_renderer_owner_id);
|
||||
estimated_renderer_owner_ids.extend(active_renderer_owner_id);
|
||||
estimated_renderer_owner_ids.extend(document_renderer_owner_ids.iter().copied());
|
||||
for renderer_owner_id in self.browser_contexts().flat_map(|browser_context| {
|
||||
browser_context.page_targets.iter().filter_map(|target| {
|
||||
browser_context.page_navigation_renderer_owner_id(target.target_id())
|
||||
})
|
||||
}) {
|
||||
if renderer_owner_id != active_renderer_owner_id {
|
||||
if Some(renderer_owner_id) != active_renderer_owner_id {
|
||||
page_navigation_engine_renderer_owner_ids.insert(renderer_owner_id);
|
||||
}
|
||||
estimated_renderer_owner_ids.insert(renderer_owner_id);
|
||||
@@ -3032,10 +2699,12 @@ impl CdpConnection {
|
||||
let page_navigation_engine_renderer_owner_count =
|
||||
page_navigation_engine_renderer_owner_ids.len();
|
||||
let estimated_renderer_owner_count = estimated_renderer_owner_ids.len();
|
||||
let document_isolate_model = active_engine.document_isolate_model;
|
||||
let document_isolate_model =
|
||||
moli_core::page::RendererDocumentIsolateAccountingDiagnostics::MODEL;
|
||||
let estimated_document_isolate_count =
|
||||
loaded_document_page_count + pending_document_page_build_count;
|
||||
let document_isolate_accounting = active_engine.document_isolate_accounting;
|
||||
let document_isolate_accounting =
|
||||
moli_core::page::RendererDocumentIsolateAccountingDiagnostics::snapshot();
|
||||
let document_isolate_accounting = json!({
|
||||
"scope": "renderer-process",
|
||||
"created": document_isolate_accounting.created,
|
||||
@@ -3047,13 +2716,15 @@ impl CdpConnection {
|
||||
+ shared_worker_running_worker_isolate_count;
|
||||
let estimated_live_v8_isolate_count =
|
||||
estimated_document_isolate_count + estimated_worker_isolate_count;
|
||||
let active_navigation_engine_resource_runtime = active_engine.resource_runtime;
|
||||
let active_navigation_engine_resource_runtime_id =
|
||||
active_navigation_engine_resource_runtime
|
||||
.as_ref()
|
||||
.map(|diagnostics| diagnostics.runtime_id);
|
||||
let active_navigation_engine_memory_cache =
|
||||
active_navigation_engine_resource_runtime.map(|diagnostics| diagnostics.memory_cache);
|
||||
let active_navigation_engine = active_engine.map(|engine| json!({
|
||||
"imageFetchEnabled": engine.image_fetch_enabled,
|
||||
"optionalResourceFetchMask": engine.optional_resource_fetch_mask.bits(),
|
||||
"subframeLoadingEnabled": engine.subframe_loading_enabled,
|
||||
"resourceRuntime": engine.resource_runtime,
|
||||
"resourceRuntimeId": engine.resource_runtime.as_ref().map(|runtime| runtime.runtime_id),
|
||||
"networkMemoryCache": engine.resource_runtime.map(|runtime| runtime.memory_cache),
|
||||
"browserContextRuntime": engine.browser_context_runtime,
|
||||
}));
|
||||
json!({
|
||||
"connection": {
|
||||
"hasActiveBrowserContext": self.browser_context.is_some(),
|
||||
@@ -3067,20 +2738,12 @@ impl CdpConnection {
|
||||
"autoAttach": self.auto_attach_enabled(),
|
||||
"targetDiscoveryEnabled": self.target_discovery_enabled,
|
||||
"targetInfoChangeEventsEnabled": self.target_info_change_events_enabled,
|
||||
"activeNavigationEngine": {
|
||||
"imageFetchEnabled": active_engine.image_fetch_enabled,
|
||||
"optionalResourceFetchMask": active_engine.optional_resource_fetch_mask.bits(),
|
||||
"subframeLoadingEnabled": active_engine.subframe_loading_enabled,
|
||||
"resourceRuntime": active_navigation_engine_resource_runtime,
|
||||
"resourceRuntimeId": active_navigation_engine_resource_runtime_id,
|
||||
"networkMemoryCache": active_navigation_engine_memory_cache,
|
||||
"browserContextRuntime": active_engine.browser_context_runtime,
|
||||
},
|
||||
"activeNavigationEngine": active_navigation_engine,
|
||||
},
|
||||
"isolateScope": {
|
||||
"documentIsolateModel": document_isolate_model,
|
||||
"workerIsolateModel": "per-worker-thread",
|
||||
"activeNavigationEngineRendererOwnerCount": 1,
|
||||
"activeNavigationEngineRendererOwnerCount": usize::from(active_renderer_owner_id.is_some()),
|
||||
"pageNavigationEngineRendererOwnerCount": page_navigation_engine_renderer_owner_count,
|
||||
"estimatedRendererOwnerCount": estimated_renderer_owner_count,
|
||||
"browserContextCount": browser_context_count,
|
||||
@@ -3123,7 +2786,7 @@ impl CdpConnection {
|
||||
})
|
||||
}
|
||||
|
||||
fn idle_navigation_engine_release_counts(&self) -> (usize, usize) {
|
||||
pub(crate) fn moli_reset_idle_navigation_engine_for_diagnostics(&self) -> serde_json::Value {
|
||||
let loaded_browser_context_count = self
|
||||
.browser_contexts()
|
||||
.filter(|browser_context| browser_context.loaded_document_page_count() != 0)
|
||||
@@ -3136,65 +2799,24 @@ impl CdpConnection {
|
||||
})
|
||||
.count();
|
||||
|
||||
(
|
||||
loaded_browser_context_count,
|
||||
live_target_browser_context_count,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn release_idle_navigation_engine_memory_if_idle(
|
||||
&mut self,
|
||||
) -> IdleNavigationEngineReleaseResult {
|
||||
let (loaded_browser_context_count, live_target_browser_context_count) =
|
||||
self.idle_navigation_engine_release_counts();
|
||||
let eligible = loaded_browser_context_count == 0 && live_target_browser_context_count == 0;
|
||||
if !eligible {
|
||||
return IdleNavigationEngineReleaseResult {
|
||||
reset: false,
|
||||
reason: "not-idle",
|
||||
loaded_browser_context_count,
|
||||
live_target_browser_context_count,
|
||||
};
|
||||
}
|
||||
|
||||
let replacement = NavigationEngine::new_with_runtime_config(
|
||||
self.standalone_navigation_engine.runtime_config(),
|
||||
);
|
||||
self.replace_standalone_navigation_engine(replacement);
|
||||
IdleNavigationEngineReleaseResult {
|
||||
reset: true,
|
||||
reason: "idle-engine-replaced",
|
||||
loaded_browser_context_count,
|
||||
live_target_browser_context_count,
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn release_idle_navigation_engine_memory_after_target_close(&mut self) {
|
||||
let result = self.release_idle_navigation_engine_memory_if_idle();
|
||||
if result.reset {
|
||||
tracing::debug!(
|
||||
target: "moli_cdp_memory",
|
||||
reason = result.reason,
|
||||
"released idle navigation engine after final target close"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn moli_reset_idle_navigation_engine_for_diagnostics(
|
||||
&mut self,
|
||||
) -> serde_json::Value {
|
||||
self.release_idle_navigation_engine_memory_if_idle()
|
||||
.to_protocol_json()
|
||||
// Retain the diagnostic command without allocating a replacement for
|
||||
// an engine DevTools no longer owns. Core releases target resources.
|
||||
json!({
|
||||
"reset": false,
|
||||
"reason": if eligible { "no-standalone-engine" } else { "not-idle" },
|
||||
"loadedBrowserContextCount": loaded_browser_context_count,
|
||||
"liveTargetBrowserContextCount": live_target_browser_context_count,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn new_browser_context(&self, id: String) -> BrowserContext {
|
||||
let mut browser_context = self.initial_storage_partition.new_default_browser_context(
|
||||
self.initial_storage_partition.new_default_browser_context(
|
||||
&self.browser,
|
||||
id,
|
||||
self.fetch_config().http_cache_dir().map(PathBuf::from),
|
||||
self.fetch_config().http_cache_max_bytes(),
|
||||
);
|
||||
self.apply_global_browser_context_state(&mut browser_context);
|
||||
browser_context
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_browser_context_for_implicit_target_creation(&mut self) {
|
||||
@@ -3209,21 +2831,13 @@ impl CdpConnection {
|
||||
self.insert_browser_context(self.new_browser_context(browser_context_id));
|
||||
}
|
||||
|
||||
fn apply_global_browser_context_state(&self, browser_context: &mut BrowserContext) {
|
||||
browser_context.global_cache_disabled = self.global_cache_disabled;
|
||||
browser_context.global_extra_headers = self.global_extra_headers.clone();
|
||||
browser_context.global_network_conditions = self.global_network_conditions;
|
||||
browser_context.global_geolocation_override = self.global_geolocation_override.clone();
|
||||
}
|
||||
|
||||
pub(crate) fn new_ephemeral_browser_context(&self, id: String) -> BrowserContext {
|
||||
let mut browser_context = BrowserContext::new_ephemeral_with_http_cache(
|
||||
BrowserContext::new_ephemeral_with_http_cache(
|
||||
&self.browser,
|
||||
id,
|
||||
self.fetch_config().http_cache_dir().map(PathBuf::from),
|
||||
self.fetch_config().http_cache_max_bytes(),
|
||||
);
|
||||
self.apply_global_browser_context_state(&mut browser_context);
|
||||
browser_context
|
||||
)
|
||||
}
|
||||
|
||||
pub fn snapshot_cookies(&mut self) -> Vec<StoredCookie> {
|
||||
@@ -3875,6 +3489,7 @@ impl CdpConnection {
|
||||
let was_placeholder = self.default_target_lifecycle.is_placeholder();
|
||||
let default_browser_context_id = self.default_browser_context_id().to_owned();
|
||||
let default_target_id = self.default_target_id().to_owned();
|
||||
let browser_cache_disabled = self.browser_global_overrides.cache_disabled;
|
||||
if !self.has_browser_context_id(&default_browser_context_id) {
|
||||
let mut browser_context = self.new_browser_context(default_browser_context_id.clone());
|
||||
browser_context.set_active_target_id(default_target_id.clone());
|
||||
@@ -3904,6 +3519,8 @@ impl CdpConnection {
|
||||
browser_context
|
||||
.begin_active_target_initial_empty_document("about:blank".to_owned());
|
||||
}
|
||||
browser_context
|
||||
.set_base_cache_disabled_for_target(&default_target_id, browser_cache_disabled);
|
||||
}
|
||||
}
|
||||
if !self
|
||||
|
||||
@@ -1,101 +1,16 @@
|
||||
use moli_core::page::{CompletedPageCommand, PendingPageCommand, RendererPageDiagnosticsSnapshot};
|
||||
use moli_core::page::RendererPageDiagnosticsSnapshot;
|
||||
use std::time::Instant;
|
||||
|
||||
use super::{CdpConnection, CdpSessionRoute, CommandOwnerScope};
|
||||
|
||||
pub(crate) struct PendingChildFrameLifecycleWork {
|
||||
owner: CommandOwnerScope,
|
||||
pending: PendingPageCommand,
|
||||
}
|
||||
|
||||
pub(crate) struct CompletedChildFrameLifecycleWork {
|
||||
owner: CommandOwnerScope,
|
||||
completion: CompletedPageCommand,
|
||||
}
|
||||
|
||||
impl PendingChildFrameLifecycleWork {
|
||||
pub(crate) async fn wait(self) -> Result<CompletedChildFrameLifecycleWork, String> {
|
||||
let completion = self
|
||||
.pending
|
||||
.wait()
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
Ok(CompletedChildFrameLifecycleWork {
|
||||
owner: self.owner,
|
||||
completion,
|
||||
})
|
||||
}
|
||||
}
|
||||
use super::{CdpConnection, CommandOwnerScope, PendingChildFrameLifecycleWork};
|
||||
|
||||
impl CdpConnection {
|
||||
fn activity_source_page_route(&mut self, session_id: Option<&str>) -> Option<(String, String)> {
|
||||
let owner = CommandOwnerScope::capture(self, session_id);
|
||||
self.activity_source_page_route_for_owner(&owner)
|
||||
}
|
||||
|
||||
fn activity_source_page_route_for_owner(
|
||||
&mut self,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Option<(String, String)> {
|
||||
let route = owner.resolve_route(self)?;
|
||||
let (browser_context_id, target_id) = match &route {
|
||||
CdpSessionRoute::PageTarget {
|
||||
browser_context_id,
|
||||
target_id,
|
||||
..
|
||||
} => (browser_context_id.clone(), target_id.clone()),
|
||||
CdpSessionRoute::Browser => {
|
||||
let context = self.browser_context.as_ref()?;
|
||||
(context.id.clone(), context.active_target_id()?.to_owned())
|
||||
}
|
||||
CdpSessionRoute::BrowserContext { browser_context_id } => (
|
||||
browser_context_id.clone(),
|
||||
self.browser_context_by_id(browser_context_id)?
|
||||
.active_target_id()?
|
||||
.to_owned(),
|
||||
),
|
||||
CdpSessionRoute::TabTarget { .. }
|
||||
| CdpSessionRoute::SharedWorkerTarget { .. }
|
||||
| CdpSessionRoute::DedicatedWorkerTarget { .. }
|
||||
| CdpSessionRoute::ServiceWorkerTarget { .. } => return None,
|
||||
};
|
||||
Some((browser_context_id, target_id))
|
||||
}
|
||||
|
||||
pub(crate) fn start_child_frame_lifecycle_work_for_owner(
|
||||
&mut self,
|
||||
owner: CommandOwnerScope,
|
||||
timeout: std::time::Duration,
|
||||
) -> Result<PendingChildFrameLifecycleWork, String> {
|
||||
let (context_id, target_id) = self
|
||||
.activity_source_page_route_for_owner(&owner)
|
||||
.ok_or("NoDocumentLoaded")?;
|
||||
let pending = self
|
||||
.browser_context_by_id_mut(&context_id)
|
||||
.ok_or("NoDocumentLoaded")?
|
||||
.start_target_child_frame_lifecycle_work(&target_id, timeout)?;
|
||||
Ok(PendingChildFrameLifecycleWork { owner, pending })
|
||||
}
|
||||
|
||||
pub(crate) fn complete_child_frame_lifecycle_work_command_turn_for_session_owner(
|
||||
&mut self,
|
||||
pending: CompletedChildFrameLifecycleWork,
|
||||
) -> Result<(bool, moli_core::page::RendererCommandTurnOutput), String> {
|
||||
let (context_id, target_id) = self
|
||||
.activity_source_page_route_for_owner(&pending.owner)
|
||||
.ok_or("NoDocumentLoaded")?;
|
||||
self.browser_context_by_id_mut(&context_id)
|
||||
.ok_or("NoDocumentLoaded")?
|
||||
.complete_target_child_frame_lifecycle_work(&target_id, pending.completion)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn complete_child_frame_lifecycle_work_for_session_owner(
|
||||
&mut self,
|
||||
pending: CompletedChildFrameLifecycleWork,
|
||||
) -> Result<bool, String> {
|
||||
self.complete_child_frame_lifecycle_work_command_turn_for_session_owner(pending)
|
||||
.map(|(completed, _output)| completed)
|
||||
let document = self.loaded_browser_document_for_owner(&owner)?;
|
||||
self.start_document_child_frame_lifecycle_work(document, timeout)
|
||||
}
|
||||
|
||||
pub async fn page_diagnostics_snapshot_for_session_owner_async(
|
||||
@@ -108,7 +23,8 @@ impl CdpConnection {
|
||||
session_id,
|
||||
trace_started,
|
||||
);
|
||||
let Some((context_id, target_id)) = self.activity_source_page_route(session_id) else {
|
||||
let owner = CommandOwnerScope::capture(self, session_id);
|
||||
let Ok(document) = self.loaded_browser_document_for_owner(&owner) else {
|
||||
trace_activity_source_stage(
|
||||
"conn_page_diagnostics_snapshot_missing_owner",
|
||||
session_id,
|
||||
@@ -116,34 +32,23 @@ impl CdpConnection {
|
||||
);
|
||||
return Ok(RendererPageDiagnosticsSnapshot::default());
|
||||
};
|
||||
let context = self
|
||||
.browser_context_by_id_mut(&context_id)
|
||||
.expect("resolved BrowserContext");
|
||||
if !context.target_has_loaded_page(&target_id) {
|
||||
trace_activity_source_stage(
|
||||
"conn_page_diagnostics_snapshot_missing_page",
|
||||
session_id,
|
||||
trace_started,
|
||||
);
|
||||
return Ok(RendererPageDiagnosticsSnapshot::default());
|
||||
};
|
||||
let renderer_started = moli_trace::cdp_runtime_trace_enabled().then(Instant::now);
|
||||
let snapshot = context
|
||||
.target_page_diagnostics_snapshot_async(&target_id)
|
||||
.await
|
||||
.map_err(|error| error.to_string())?;
|
||||
let completed = self
|
||||
.start_document_diagnostics_snapshot(document)?
|
||||
.wait()
|
||||
.await;
|
||||
trace_activity_source_stage(
|
||||
"conn_page_diagnostics_snapshot_renderer_done",
|
||||
session_id,
|
||||
renderer_started,
|
||||
);
|
||||
let ingest_started = moli_trace::cdp_runtime_trace_enabled().then(Instant::now);
|
||||
let ingested = context.ingest_owner_page_observable_output_updates_for_target(&target_id);
|
||||
let snapshot = self.finish_document_diagnostics_snapshot(completed)?;
|
||||
trace_activity_source_stage_with_bool(
|
||||
"conn_page_diagnostics_snapshot_ingest_done",
|
||||
session_id,
|
||||
ingest_started,
|
||||
ingested,
|
||||
true,
|
||||
);
|
||||
trace_activity_source_stage(
|
||||
"conn_page_diagnostics_snapshot_done",
|
||||
|
||||
@@ -158,11 +158,10 @@ impl BidiChannelOwnerAction {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::conn::BrowserContext;
|
||||
|
||||
fn connection_with_page_session() -> CdpConnection {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-owner".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context = conn.new_browser_context_fixture_for_test("BID-owner".to_owned());
|
||||
browser_context.set_active_target_id("TID-owner");
|
||||
browser_context.attach_active_session("SID-owner".to_owned());
|
||||
conn.install_browser_context_fixture_for_test(browser_context);
|
||||
|
||||
@@ -5,8 +5,8 @@ use crate::conn::{
|
||||
state::{JavaScriptDialogClosed, JavaScriptDialogError, JavaScriptDialogSnapshot},
|
||||
};
|
||||
|
||||
// Exact routing remains in DevTools; the value-only Browser calls replace this
|
||||
// in-place bridge at Commit 22. No renderer dispatch lane or Page borrow here.
|
||||
// DevTools resolves its projection once; Browser calls below receive only the
|
||||
// exact physical Document capability and neutral dialog key.
|
||||
impl CdpConnection {
|
||||
pub(crate) fn install_javascript_dialog_for_session(
|
||||
&mut self,
|
||||
@@ -19,13 +19,34 @@ impl CdpConnection {
|
||||
let _ = dialog.finish(false, String::new());
|
||||
return false;
|
||||
};
|
||||
owner.browser_context.install_javascript_dialog_for_target(
|
||||
let Some(document) = owner
|
||||
.browser_context
|
||||
.document_handle_for_target(&owner.target_id)
|
||||
.filter(|document| document.id() == page_owner.document_id())
|
||||
else {
|
||||
let _ = dialog.finish(false, String::new());
|
||||
return false;
|
||||
};
|
||||
let Ok(key) = owner
|
||||
.browser_context
|
||||
.install_document_javascript_dialog(document, dialog)
|
||||
else {
|
||||
return false;
|
||||
};
|
||||
if owner.browser_context.project_javascript_dialog_for_session(
|
||||
&owner.target_id,
|
||||
&owner.session_key,
|
||||
page_owner,
|
||||
source_frame_id,
|
||||
dialog,
|
||||
)
|
||||
document,
|
||||
key,
|
||||
) {
|
||||
true
|
||||
} else {
|
||||
owner
|
||||
.browser_context
|
||||
.dismiss_document_javascript_dialog(document, key);
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn javascript_dialog_snapshot_for_owner(
|
||||
@@ -33,9 +54,12 @@ impl CdpConnection {
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Option<JavaScriptDialogSnapshot> {
|
||||
let owner = self.target_session_owner_ref_for_owner(owner)?;
|
||||
let (document, key) = owner
|
||||
.browser_context
|
||||
.projected_javascript_dialog_for_session(&owner.target_id, &owner.session_key)?;
|
||||
owner
|
||||
.browser_context
|
||||
.javascript_dialog_snapshot_for_target(&owner.target_id, &owner.session_key)
|
||||
.document_javascript_dialog_snapshot(document, key)
|
||||
}
|
||||
|
||||
pub(crate) fn set_javascript_dialog_prompt_text_for_owner(
|
||||
@@ -46,13 +70,13 @@ impl CdpConnection {
|
||||
let owner = self
|
||||
.target_session_owner_mut_for_owner(owner)
|
||||
.ok_or(JavaScriptDialogError::NotFound)?;
|
||||
let (document, key) = owner
|
||||
.browser_context
|
||||
.projected_javascript_dialog_for_session(&owner.target_id, &owner.session_key)
|
||||
.ok_or(JavaScriptDialogError::NotFound)?;
|
||||
owner
|
||||
.browser_context
|
||||
.set_javascript_dialog_prompt_text_for_target(
|
||||
&owner.target_id,
|
||||
&owner.session_key,
|
||||
prompt_text,
|
||||
)
|
||||
.set_document_javascript_dialog_prompt_text(document, key, prompt_text)
|
||||
}
|
||||
|
||||
pub(crate) fn handle_javascript_dialog_for_owner(
|
||||
@@ -62,11 +86,33 @@ impl CdpConnection {
|
||||
prompt_text: Option<String>,
|
||||
) -> Option<(String, JavaScriptDialogClosed)> {
|
||||
let owner = self.target_session_owner_mut_for_owner(owner)?;
|
||||
owner.browser_context.handle_javascript_dialog_for_target(
|
||||
&owner.target_id,
|
||||
&owner.session_key,
|
||||
let Some((document, key)) = owner
|
||||
.browser_context
|
||||
.projected_javascript_dialog_for_session(&owner.target_id, &owner.session_key)
|
||||
else {
|
||||
let projections = owner
|
||||
.browser_context
|
||||
.take_projected_javascript_dialogs_for_session(
|
||||
&owner.target_id,
|
||||
&owner.session_key,
|
||||
);
|
||||
owner
|
||||
.browser_context
|
||||
.dismiss_projected_javascript_dialogs(projections);
|
||||
return None;
|
||||
};
|
||||
let projection = owner
|
||||
.browser_context
|
||||
.pop_projected_javascript_dialog_for_session(&owner.target_id, &owner.session_key)?;
|
||||
let (source_frame_id, projected_document, projected_key) = projection.into_parts();
|
||||
debug_assert_eq!(projected_document, document);
|
||||
debug_assert_eq!(projected_key, key);
|
||||
let outcome = owner.browser_context.finish_document_javascript_dialog(
|
||||
document,
|
||||
key,
|
||||
accepted,
|
||||
prompt_text,
|
||||
)
|
||||
)?;
|
||||
Some((source_frame_id, outcome))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -53,7 +53,7 @@ impl TargetSessionOwnerMut<'_> {
|
||||
&mut self,
|
||||
locale_override: Option<String>,
|
||||
fallback_identity: &moli_browser_profile::BrowserIdentityProfile,
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), String> {
|
||||
self.browser_context
|
||||
.set_base_locale_override_for_target(&self.target_id, locale_override.clone())?;
|
||||
self.browser_context
|
||||
@@ -68,7 +68,7 @@ impl TargetSessionOwnerMut<'_> {
|
||||
fn set_base_timezone_override(
|
||||
&mut self,
|
||||
timezone_override: Option<String>,
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), String> {
|
||||
self.browser_context
|
||||
.set_base_timezone_override_for_target(&self.target_id, timezone_override)
|
||||
}
|
||||
@@ -89,7 +89,7 @@ impl TargetSessionOwnerRef<'_> {
|
||||
Some(
|
||||
target
|
||||
.devtools_sessions
|
||||
.emulation_disposal_is_effectively_noop(&self.session_key, effective),
|
||||
.emulation_disposal_is_effectively_noop(&self.session_key, &effective),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -160,7 +160,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
owner: &crate::conn::CommandOwnerScope,
|
||||
locale_override: Option<String>,
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), String> {
|
||||
let fallback_identity = self.base_browser_identity.clone();
|
||||
self.target_session_owner_mut_for_owner(owner)
|
||||
.ok_or("BrowserContextNotLoaded")?
|
||||
@@ -171,7 +171,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
owner: &crate::conn::CommandOwnerScope,
|
||||
timezone_override: Option<String>,
|
||||
) -> Result<(), &'static str> {
|
||||
) -> Result<(), String> {
|
||||
self.target_session_owner_mut_for_owner(owner)
|
||||
.ok_or("BrowserContextNotLoaded")?
|
||||
.set_base_timezone_override(timezone_override)
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use super::target_session_owner::TargetSessionOwnerMut;
|
||||
use super::*;
|
||||
use crate::conn::fetch_support::OpenBodyStreamError;
|
||||
use crate::conn::OpenBodyStreamError;
|
||||
use crate::conn::state::{
|
||||
ClaimedNavigationRequest, InterceptedNavigationResponse, NavigationInterceptionPermit,
|
||||
NavigationRequestInterception,
|
||||
@@ -11,8 +11,8 @@ use crate::conn::state::{
|
||||
use crate::conn::{
|
||||
CapturedBody, ClaimedFetchNavigation, ClaimedFetchResponseNavigation, CommandOwnerScope,
|
||||
CompletedFetchResponseBodyStreamReadDispatch, FetchInterceptionPattern, FetchRequestStage,
|
||||
InFlightSubresourceFetchRequest, PausedDocumentTransfer, PendingFetchAuthNavigation,
|
||||
PendingFetchNavigation, PendingFetchResponseBodyStreamRead,
|
||||
InFlightSubresourceFetchRequest, PausedDocumentTransfer, PendingDocumentFetchCommand,
|
||||
PendingFetchAuthNavigation, PendingFetchNavigation, PendingFetchResponseBodyStreamRead,
|
||||
PendingFetchResponseBodyStreamReadDispatch, PendingFetchResponseBodyStreamReadStart,
|
||||
PendingFetchResponseNavigation, PendingSubresourceFetchAuthRequest,
|
||||
PendingSubresourceFetchRequest, PendingSubresourceFetchResponseRequest, TargetRuntimeSlot,
|
||||
@@ -213,20 +213,28 @@ fn target_scoped_stream_owner_matches_session(
|
||||
fn remove_network_intercept_from_browser_context(
|
||||
browser_context: &mut BrowserContext,
|
||||
intercept_id: &str,
|
||||
) -> Result<Option<Option<moli_core::page::PendingPageCommand>>, String> {
|
||||
) -> Result<Option<Option<PendingDocumentFetchCommand>>, String> {
|
||||
let target_ids = browser_context
|
||||
.page_targets
|
||||
.iter()
|
||||
.map(|target| target.target_id().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
for target_id in target_ids {
|
||||
let web_contents = browser_context
|
||||
.web_contents_handle_for_target(&target_id)
|
||||
.ok_or("WebContents unavailable")?;
|
||||
let target = browser_context
|
||||
.page_target_mut(&target_id)
|
||||
.expect("target must remain registered");
|
||||
if target.fetch_owner.remove_network_intercept(intercept_id) {
|
||||
let (enabled, resource_type) = target.fetch_owner.subresource_interception_config();
|
||||
return browser_context
|
||||
.start_target_fetch_interception_update(&target_id, enabled, resource_type)
|
||||
.start_web_contents_fetch_interception_update(
|
||||
web_contents,
|
||||
enabled,
|
||||
resource_type,
|
||||
true,
|
||||
)
|
||||
.map(Some)
|
||||
.map_err(|error| format!("failed to update page fetch interception: {error}"));
|
||||
}
|
||||
@@ -236,28 +244,6 @@ fn remove_network_intercept_from_browser_context(
|
||||
}
|
||||
|
||||
impl CdpConnection {
|
||||
pub(crate) fn finish_removed_network_interception(
|
||||
&mut self,
|
||||
completion: moli_core::page::CompletedPageCommand,
|
||||
) -> Result<(), String> {
|
||||
// BiDi's global removal may refer to a background or inactive page.
|
||||
// Observe only the physical origin of the completion, not the command
|
||||
// caller's selected page. Retirement does not undo the installed policy.
|
||||
for context in self
|
||||
.browser_context
|
||||
.iter_mut()
|
||||
.chain(self.inactive_browser_contexts.iter_mut())
|
||||
{
|
||||
if context.observe_renderer_page_state(completion.page_state()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
completion
|
||||
.into_unit_page_command_turn()
|
||||
.map(drop)
|
||||
.map_err(|error| error.to_string())
|
||||
}
|
||||
|
||||
pub(crate) fn target_fetch_subresource_interception_snapshot_for_owner(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
@@ -587,6 +573,8 @@ impl CdpConnection {
|
||||
document_navigation_token: Option<crate::conn::NavigationId>,
|
||||
navigation: crate::conn::NavigationDispatchState,
|
||||
body: crate::conn::DocumentBodySource,
|
||||
body_progress_source: crate::domains::network::MainDocumentBodyProgressSource,
|
||||
prepared_document: Option<Box<crate::conn::PausedResponsePreparedDocument>>,
|
||||
) -> bool {
|
||||
let Some((browser_context_id, target_id)) =
|
||||
self.resolved_page_owner_identity_for_owner(owner)
|
||||
@@ -615,7 +603,12 @@ impl CdpConnection {
|
||||
.fetch_owner
|
||||
.register_pending_fetch_response_navigation(
|
||||
request_id,
|
||||
PendingFetchResponseNavigation::new(navigation, permit),
|
||||
PendingFetchResponseNavigation::new_with_response_projection(
|
||||
navigation,
|
||||
permit,
|
||||
body_progress_source,
|
||||
prepared_document,
|
||||
),
|
||||
);
|
||||
true
|
||||
}
|
||||
@@ -1360,7 +1353,7 @@ impl CdpConnection {
|
||||
session_id: Option<&str>,
|
||||
handle_auth_requests: bool,
|
||||
patterns: Vec<FetchInterceptionPattern>,
|
||||
) -> Result<Option<moli_core::page::PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentFetchCommand>, String> {
|
||||
let owner = CommandOwnerScope::capture(self, session_id);
|
||||
self.start_enable_fetch_for_owner(&owner, handle_auth_requests, patterns)
|
||||
}
|
||||
@@ -1370,7 +1363,7 @@ impl CdpConnection {
|
||||
command_owner: &CommandOwnerScope,
|
||||
handle_auth_requests: bool,
|
||||
patterns: Vec<FetchInterceptionPattern>,
|
||||
) -> Result<Option<moli_core::page::PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentFetchCommand>, String> {
|
||||
let Some(mut owner) = self.target_session_owner_mut_for_owner(command_owner) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
@@ -1379,12 +1372,17 @@ impl CdpConnection {
|
||||
handle_auth_requests,
|
||||
patterns,
|
||||
);
|
||||
let web_contents = owner
|
||||
.browser_context
|
||||
.web_contents_handle_for_target(&owner.target_id)
|
||||
.ok_or("WebContents unavailable")?;
|
||||
owner
|
||||
.browser_context
|
||||
.start_target_fetch_interception_update(
|
||||
&owner.target_id,
|
||||
.start_web_contents_fetch_interception_update(
|
||||
web_contents,
|
||||
subresource_enabled,
|
||||
subresource_resource_type,
|
||||
false,
|
||||
)
|
||||
.map_err(|error| format!("failed to update page fetch interception: {error}"))
|
||||
}
|
||||
@@ -1398,7 +1396,7 @@ impl CdpConnection {
|
||||
handle_auth_requests: bool,
|
||||
auth_url_patterns: Vec<String>,
|
||||
patterns: Vec<FetchInterceptionPattern>,
|
||||
) -> Result<Option<moli_core::page::PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentFetchCommand>, String> {
|
||||
let Some(mut owner) = self.target_session_owner_mut_for_owner(command_owner) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
@@ -1409,12 +1407,17 @@ impl CdpConnection {
|
||||
auth_url_patterns,
|
||||
patterns,
|
||||
);
|
||||
let web_contents = owner
|
||||
.browser_context
|
||||
.web_contents_handle_for_target(&owner.target_id)
|
||||
.ok_or("WebContents unavailable")?;
|
||||
owner
|
||||
.browser_context
|
||||
.start_target_fetch_interception_update(
|
||||
&owner.target_id,
|
||||
.start_web_contents_fetch_interception_update(
|
||||
web_contents,
|
||||
subresource_enabled,
|
||||
subresource_resource_type,
|
||||
false,
|
||||
)
|
||||
.map_err(|error| format!("failed to update page fetch interception: {error}"))
|
||||
}
|
||||
@@ -1424,7 +1427,7 @@ impl CdpConnection {
|
||||
command_owner: &CommandOwnerScope,
|
||||
intercept_id: &str,
|
||||
allow_global_lookup: bool,
|
||||
) -> Result<Option<moli_core::page::PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentFetchCommand>, String> {
|
||||
let Some(mut owner) = self.target_session_owner_mut_for_owner(command_owner) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
@@ -1436,12 +1439,17 @@ impl CdpConnection {
|
||||
}
|
||||
return Err("NetworkInterceptNotFound".to_owned());
|
||||
};
|
||||
let web_contents = owner
|
||||
.browser_context
|
||||
.web_contents_handle_for_target(&owner.target_id)
|
||||
.ok_or("WebContents unavailable")?;
|
||||
owner
|
||||
.browser_context
|
||||
.start_target_fetch_interception_update(
|
||||
&owner.target_id,
|
||||
.start_web_contents_fetch_interception_update(
|
||||
web_contents,
|
||||
subresource_enabled,
|
||||
subresource_resource_type,
|
||||
false,
|
||||
)
|
||||
.map_err(|error| format!("failed to update page fetch interception: {error}"))
|
||||
}
|
||||
@@ -1449,7 +1457,7 @@ impl CdpConnection {
|
||||
fn start_remove_network_intercept_from_any_target(
|
||||
&mut self,
|
||||
intercept_id: &str,
|
||||
) -> Result<Option<moli_core::page::PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentFetchCommand>, String> {
|
||||
if let Some(browser_context) = self.browser_context.as_mut()
|
||||
&& let Some(pending) =
|
||||
remove_network_intercept_from_browser_context(browser_context, intercept_id)?
|
||||
@@ -1471,7 +1479,7 @@ impl CdpConnection {
|
||||
session_id: Option<&str>,
|
||||
) -> Option<(
|
||||
SessionOwnerPendingFetchState,
|
||||
Result<Option<moli_core::page::PendingPageCommand>, String>,
|
||||
Result<Option<PendingDocumentFetchCommand>, String>,
|
||||
)> {
|
||||
let owner = CommandOwnerScope::capture(self, session_id);
|
||||
self.start_disable_fetch_for_owner(&owner, true)
|
||||
@@ -1483,7 +1491,7 @@ impl CdpConnection {
|
||||
renderer_policy_reconciled: bool,
|
||||
) -> Option<(
|
||||
SessionOwnerPendingFetchState,
|
||||
Result<Option<moli_core::page::PendingPageCommand>, String>,
|
||||
Result<Option<PendingDocumentFetchCommand>, String>,
|
||||
)> {
|
||||
let owner = CommandOwnerScope::capture(self, session_id);
|
||||
self.start_disable_fetch_for_owner(&owner, !renderer_policy_reconciled)
|
||||
@@ -1495,7 +1503,7 @@ impl CdpConnection {
|
||||
enqueue_renderer_update: bool,
|
||||
) -> Option<(
|
||||
SessionOwnerPendingFetchState,
|
||||
Result<Option<moli_core::page::PendingPageCommand>, String>,
|
||||
Result<Option<PendingDocumentFetchCommand>, String>,
|
||||
)> {
|
||||
let mut owner = self.target_session_owner_mut_for_owner(command_owner)?;
|
||||
let (pending, (subresource_enabled, subresource_resource_type), removed) = owner
|
||||
@@ -1506,21 +1514,34 @@ impl CdpConnection {
|
||||
// renderer lifecycle interrupt, so only publish it to the Browser
|
||||
// owner here and never wait behind active JavaScript.
|
||||
let page_command = if enqueue_renderer_update {
|
||||
owner
|
||||
let web_contents = owner
|
||||
.browser_context
|
||||
.start_target_fetch_interception_update(
|
||||
&owner.target_id,
|
||||
subresource_enabled,
|
||||
subresource_resource_type,
|
||||
)
|
||||
.web_contents_handle_for_target(&owner.target_id)
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned());
|
||||
web_contents.and_then(|web_contents| {
|
||||
owner
|
||||
.browser_context
|
||||
.start_web_contents_fetch_interception_update(
|
||||
web_contents,
|
||||
subresource_enabled,
|
||||
subresource_resource_type,
|
||||
false,
|
||||
)
|
||||
})
|
||||
} else if removed {
|
||||
owner
|
||||
.browser_context
|
||||
.install_target_fetch_interception_policy(
|
||||
&owner.target_id,
|
||||
subresource_enabled,
|
||||
subresource_resource_type,
|
||||
)
|
||||
.web_contents_handle_for_target(&owner.target_id)
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())
|
||||
.and_then(|web_contents| {
|
||||
owner
|
||||
.browser_context
|
||||
.install_web_contents_fetch_interception_policy(
|
||||
web_contents,
|
||||
subresource_enabled,
|
||||
subresource_resource_type,
|
||||
)
|
||||
})
|
||||
.map(|()| None)
|
||||
} else {
|
||||
Ok(None)
|
||||
|
||||
@@ -1,6 +1,19 @@
|
||||
use super::*;
|
||||
|
||||
impl CdpConnection {
|
||||
pub(crate) fn active_browser_context_id(&self) -> Option<moli_core::browser::BrowserContextId> {
|
||||
self.browser_context
|
||||
.as_ref()
|
||||
.map(BrowserContext::browser_context_id)
|
||||
}
|
||||
|
||||
pub(crate) fn activate_browser_context_by_browser_id(
|
||||
&mut self,
|
||||
browser_context_id: moli_core::browser::BrowserContextId,
|
||||
) -> bool {
|
||||
self.activate_matching_browser_context(|bc| bc.browser_context_id() == browser_context_id)
|
||||
}
|
||||
|
||||
pub fn activate_browser_context_by_id(&mut self, browser_context_id: &str) -> bool {
|
||||
self.activate_matching_browser_context(|bc| bc.id == browser_context_id)
|
||||
}
|
||||
@@ -40,18 +53,14 @@ impl CdpConnection {
|
||||
}
|
||||
|
||||
pub fn insert_browser_context(&mut self, mut browser_context: BrowserContext) {
|
||||
browser_context.apply_browser_cache_disabled(self.browser_global_overrides.cache_disabled);
|
||||
browser_context
|
||||
.renderer_runtime()
|
||||
.set_service_worker_pause_on_start_for_devtools(
|
||||
self.service_worker_pause_on_start_for_devtools(),
|
||||
);
|
||||
browser_context
|
||||
.renderer_runtime()
|
||||
.set_dedicated_worker_pause_on_start_for_devtools(
|
||||
self.dedicated_worker_pause_on_start_for_devtools(),
|
||||
);
|
||||
.set_service_worker_pause_on_start(self.service_worker_pause_on_start_for_devtools());
|
||||
browser_context.set_dedicated_worker_pause_on_start(
|
||||
self.dedicated_worker_pause_on_start_for_devtools(),
|
||||
);
|
||||
browser_context.bind_page_navigation_engines(
|
||||
self.standalone_navigation_engine.runtime_config(),
|
||||
self.navigation_runtime_config.clone(),
|
||||
self.scheduler_hooks.renderer_publication_sender(),
|
||||
);
|
||||
if self.browser_context.is_none() {
|
||||
@@ -66,22 +75,33 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
browser_context_id: &str,
|
||||
restore_browser_context_id: Option<&str>,
|
||||
) -> Option<BrowserContext> {
|
||||
let browser_context_id = self
|
||||
.browser_context_by_id(browser_context_id)
|
||||
.map(BrowserContext::browser_context_id)?;
|
||||
let restore_browser_context_id = restore_browser_context_id.and_then(|id| {
|
||||
self.browser_context_by_id(id)
|
||||
.map(BrowserContext::browser_context_id)
|
||||
});
|
||||
self.remove_browser_context_restoring_active(browser_context_id, restore_browser_context_id)
|
||||
}
|
||||
|
||||
pub(crate) fn remove_browser_context_restoring_active(
|
||||
&mut self,
|
||||
browser_context_id: moli_core::browser::BrowserContextId,
|
||||
restore_browser_context_id: Option<moli_core::browser::BrowserContextId>,
|
||||
) -> Option<BrowserContext> {
|
||||
if self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.is_some_and(|bc| bc.id == browser_context_id)
|
||||
.is_some_and(|bc| bc.browser_context_id() == browser_context_id)
|
||||
{
|
||||
let removed = self.browser_context.take();
|
||||
if self.browser_context.is_none() && !self.inactive_browser_contexts.is_empty() {
|
||||
self.select_inactive_browser_context_as_active(0);
|
||||
}
|
||||
self.invalidate_resource_runtime();
|
||||
self.restore_preferred_browser_context_async(
|
||||
restore_browser_context_id,
|
||||
browser_context_id,
|
||||
)
|
||||
.await;
|
||||
self.restore_preferred_browser_context(restore_browser_context_id, browser_context_id);
|
||||
self.apply_active_engine_fetch_overrides();
|
||||
return removed;
|
||||
}
|
||||
@@ -89,14 +109,10 @@ impl CdpConnection {
|
||||
if let Some(index) = self
|
||||
.inactive_browser_contexts
|
||||
.iter()
|
||||
.position(|bc| bc.id == browser_context_id)
|
||||
.position(|bc| bc.browser_context_id() == browser_context_id)
|
||||
{
|
||||
let removed = self.inactive_browser_contexts.swap_remove(index);
|
||||
self.restore_preferred_browser_context_async(
|
||||
restore_browser_context_id,
|
||||
browser_context_id,
|
||||
)
|
||||
.await;
|
||||
self.restore_preferred_browser_context(restore_browser_context_id, browser_context_id);
|
||||
Some(removed)
|
||||
} else {
|
||||
None
|
||||
@@ -137,10 +153,10 @@ impl CdpConnection {
|
||||
true
|
||||
}
|
||||
|
||||
async fn restore_preferred_browser_context_async(
|
||||
fn restore_preferred_browser_context(
|
||||
&mut self,
|
||||
restore_browser_context_id: Option<&str>,
|
||||
removed_browser_context_id: &str,
|
||||
restore_browser_context_id: Option<moli_core::browser::BrowserContextId>,
|
||||
removed_browser_context_id: moli_core::browser::BrowserContextId,
|
||||
) {
|
||||
let Some(restore_browser_context_id) = restore_browser_context_id else {
|
||||
return;
|
||||
@@ -151,12 +167,10 @@ impl CdpConnection {
|
||||
if self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.is_some_and(|bc| bc.id == restore_browser_context_id)
|
||||
.is_some_and(|bc| bc.browser_context_id() == restore_browser_context_id)
|
||||
{
|
||||
return;
|
||||
}
|
||||
let _ = self
|
||||
.activate_browser_context_by_id_async(restore_browser_context_id)
|
||||
.await;
|
||||
let _ = self.activate_browser_context_by_browser_id(restore_browser_context_id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,18 +80,4 @@ impl CdpConnection {
|
||||
.find(|bc| bc.page_target(target_id).is_some())
|
||||
.map(|bc| bc.id.as_str())
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_background_target_surface_overrides_async(
|
||||
&mut self,
|
||||
target_id: &str,
|
||||
) -> anyhow::Result<bool> {
|
||||
let browser_context_id = self
|
||||
.browser_context_id_for_target(target_id)
|
||||
.map(str::to_owned)
|
||||
.ok_or_else(|| anyhow::anyhow!("UnknownTargetId"))?;
|
||||
self.browser_context_by_id_mut(&browser_context_id)
|
||||
.ok_or_else(|| anyhow::anyhow!("UnknownBrowserContextId"))?
|
||||
.apply_background_target_surface_overrides_async(target_id)
|
||||
.await
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,13 +2,15 @@ use super::target_session_owner::{TargetSessionOwnerMut, TargetSessionOwnerRef};
|
||||
use super::*;
|
||||
use crate::conn::CdpSessionRoute;
|
||||
use crate::conn::{CapturedBody, TargetRuntimeSlot};
|
||||
use crate::conn::{
|
||||
DocumentPolicyUpdate, PendingDocumentPolicyUpdate, PendingDocumentResourceRuntimeUpdate,
|
||||
};
|
||||
use crate::devtools_runtime::DevToolsNetworkDataType;
|
||||
use crate::domains::network::{
|
||||
CapturedRequestBody, CapturedResponseBody, CollectedNetworkDataArtifact,
|
||||
NetworkBacklogPreferredRequestId, PendingNetworkBacklogDeliverySnapshot,
|
||||
TargetNetworkBacklogPreparedDelivery,
|
||||
};
|
||||
use moli_core::page::PendingPageCommand;
|
||||
use moli_page_types::DevToolsSessionKey;
|
||||
|
||||
struct TargetNetworkListenerOwnerMut<'a> {
|
||||
@@ -163,61 +165,115 @@ impl TargetSessionOwnerMut<'_> {
|
||||
fn start_set_cache_disabled(
|
||||
mut self,
|
||||
cache_disabled: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
global_extra_headers: &moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
self.mutate_network_policy_session_state(|state| {
|
||||
state.cache_disabled = cache_disabled;
|
||||
});
|
||||
self.start_replay_effective_network_request_policy()
|
||||
self.start_replay_effective_network_request_policy(global_extra_headers)
|
||||
}
|
||||
|
||||
fn start_set_bypass_service_worker(
|
||||
mut self,
|
||||
bypass_service_worker: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
self.mutate_network_policy_session_state(|state| {
|
||||
state.bypass_service_worker = bypass_service_worker;
|
||||
});
|
||||
self.browser_context
|
||||
.start_target_service_worker_bypass_refresh(&self.target_id)
|
||||
let effective = self
|
||||
.browser_context
|
||||
.effective_policy_for_target(&self.target_id);
|
||||
self.start_document_policy_update(DocumentPolicyUpdate::BypassServiceWorker(
|
||||
effective.bypass_service_worker(),
|
||||
))
|
||||
}
|
||||
|
||||
fn start_set_blocked_url_patterns(
|
||||
mut self,
|
||||
blocked_url_patterns: Vec<String>,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
self.mutate_network_policy_session_state(|state| {
|
||||
state.blocked_url_patterns = blocked_url_patterns;
|
||||
});
|
||||
self.browser_context
|
||||
.start_target_blocked_urls_refresh(&self.target_id)
|
||||
let effective = self
|
||||
.browser_context
|
||||
.effective_policy_for_target(&self.target_id);
|
||||
self.start_document_policy_update(DocumentPolicyUpdate::BlockedUrls(
|
||||
effective.blocked_url_patterns().to_vec(),
|
||||
))
|
||||
}
|
||||
|
||||
fn start_set_extra_http_headers(
|
||||
mut self,
|
||||
extra_headers: moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
global_extra_headers: &moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
self.mutate_network_policy_session_state(|state| {
|
||||
state.extra_headers = extra_headers;
|
||||
});
|
||||
self.browser_context
|
||||
.start_target_extra_headers_refresh(&self.target_id)
|
||||
self.start_effective_extra_http_headers_update(global_extra_headers)
|
||||
}
|
||||
|
||||
fn start_set_target_extra_http_headers(
|
||||
self,
|
||||
mut self,
|
||||
extra_headers: moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
global_extra_headers: &moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
self.browser_context
|
||||
.set_base_extra_headers_for_target(&self.target_id, extra_headers);
|
||||
self.start_effective_extra_http_headers_update(global_extra_headers)
|
||||
}
|
||||
|
||||
fn start_effective_extra_http_headers_update(
|
||||
&mut self,
|
||||
global_extra_headers: &moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let headers = self
|
||||
.browser_context
|
||||
.effective_extra_headers_for_target(&self.target_id, global_extra_headers);
|
||||
let Some(document) = self
|
||||
.browser_context
|
||||
.document_handle_for_target(&self.target_id)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.browser_context
|
||||
.start_target_extra_headers_refresh(&self.target_id)
|
||||
.start_document_policy_update(document, DocumentPolicyUpdate::ExtraHttpHeaders(headers))
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn start_replay_effective_network_request_policy(
|
||||
&mut self,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
global_extra_headers: &moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let effective = self
|
||||
.browser_context
|
||||
.effective_policy_for_target(&self.target_id);
|
||||
let headers = self.browser_context.merged_extra_headers_for_target_policy(
|
||||
global_extra_headers,
|
||||
effective.extra_headers(),
|
||||
);
|
||||
self.start_document_policy_update(DocumentPolicyUpdate::NetworkRequestPolicy {
|
||||
extra_headers: headers,
|
||||
bypass_service_worker: effective.bypass_service_worker(),
|
||||
cache_disabled: effective.cache_disabled(),
|
||||
blocked_url_patterns: effective.blocked_url_patterns().to_vec(),
|
||||
})
|
||||
}
|
||||
|
||||
fn start_document_policy_update(
|
||||
&mut self,
|
||||
update: DocumentPolicyUpdate,
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let Some(document) = self
|
||||
.browser_context
|
||||
.document_handle_for_target(&self.target_id)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.browser_context
|
||||
.start_target_network_request_policy_refresh(&self.target_id)
|
||||
.start_document_policy_update(document, update)
|
||||
.map(Some)
|
||||
}
|
||||
|
||||
fn set_devtools_browser_identity_override(
|
||||
@@ -261,11 +317,24 @@ impl TargetSessionOwnerMut<'_> {
|
||||
fn start_set_network_offline(
|
||||
self,
|
||||
offline: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let Some(handle) = self
|
||||
.browser_context
|
||||
.web_contents_handle_for_target(&self.target_id)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.browser_context
|
||||
.set_network_offline_for_target(&self.target_id, offline);
|
||||
.set_web_contents_network_offline(handle, offline)?;
|
||||
let Some(document) = self
|
||||
.browser_context
|
||||
.document_handle_for_target(&self.target_id)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
self.browser_context
|
||||
.start_target_network_offline_refresh(&self.target_id)
|
||||
.start_document_policy_update(document, DocumentPolicyUpdate::NetworkOffline(offline))
|
||||
.map(Some)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -539,32 +608,18 @@ impl CdpConnection {
|
||||
}
|
||||
|
||||
pub(crate) fn set_global_cache_disabled(&mut self, cache_disabled: bool) {
|
||||
self.global_cache_disabled = cache_disabled;
|
||||
self.browser_global_overrides.cache_disabled = cache_disabled;
|
||||
for browser_context in self
|
||||
.browser_context
|
||||
.iter_mut()
|
||||
.chain(self.inactive_browser_contexts.iter_mut())
|
||||
{
|
||||
browser_context.global_cache_disabled = cache_disabled;
|
||||
let targets = browser_context
|
||||
.page_targets
|
||||
.iter()
|
||||
.map(|target| target.target_id().to_owned())
|
||||
.collect::<Vec<_>>();
|
||||
for target_id in targets {
|
||||
browser_context.set_base_cache_disabled_for_target(&target_id, cache_disabled);
|
||||
}
|
||||
browser_context.apply_browser_cache_disabled(cache_disabled);
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_global_extra_headers(&mut self, extra_headers: moli_fetch::RequestHeaders) {
|
||||
self.global_extra_headers = extra_headers.clone();
|
||||
if let Some(browser_context) = self.browser_context.as_mut() {
|
||||
browser_context.global_extra_headers = extra_headers.clone();
|
||||
}
|
||||
for browser_context in &mut self.inactive_browser_contexts {
|
||||
browser_context.global_extra_headers = extra_headers.clone();
|
||||
}
|
||||
self.browser_global_overrides.extra_headers = extra_headers;
|
||||
}
|
||||
|
||||
pub(crate) fn disable_network_listener_for_session_owner(
|
||||
@@ -579,11 +634,12 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
cache_disabled: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let global_extra_headers = self.browser_global_overrides.extra_headers.clone();
|
||||
let Some(owner) = self.target_session_owner_mut(session_id) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
owner.start_set_cache_disabled(cache_disabled)
|
||||
owner.start_set_cache_disabled(cache_disabled, &global_extra_headers)
|
||||
}
|
||||
|
||||
pub(crate) fn set_cache_disabled_for_target(
|
||||
@@ -608,7 +664,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
bypass_service_worker: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let Some(owner) = self.target_session_owner_mut(session_id) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
@@ -619,7 +675,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
blocked_url_patterns: Vec<String>,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let Some(owner) = self.target_session_owner_mut(session_id) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
@@ -630,28 +686,30 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
extra_headers: moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let global_extra_headers = self.browser_global_overrides.extra_headers.clone();
|
||||
let Some(owner) = self.target_session_owner_mut(session_id) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
owner.start_set_extra_http_headers(extra_headers)
|
||||
owner.start_set_extra_http_headers(extra_headers, &global_extra_headers)
|
||||
}
|
||||
|
||||
pub(crate) fn start_set_target_extra_http_headers_for_owner(
|
||||
&mut self,
|
||||
command_owner: &crate::conn::CommandOwnerScope,
|
||||
extra_headers: moli_fetch::RequestHeaders,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let global_extra_headers = self.browser_global_overrides.extra_headers.clone();
|
||||
let Some(owner) = self.target_session_owner_mut_for_owner(command_owner) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
owner.start_set_target_extra_http_headers(extra_headers)
|
||||
owner.start_set_target_extra_http_headers(extra_headers, &global_extra_headers)
|
||||
}
|
||||
|
||||
pub(crate) fn start_replay_effective_network_request_policy_for_session_owner(
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
if matches!(
|
||||
self.session_route(session_id),
|
||||
Some(
|
||||
@@ -662,17 +720,18 @@ impl CdpConnection {
|
||||
) {
|
||||
return Ok(None);
|
||||
}
|
||||
let global_extra_headers = self.browser_global_overrides.extra_headers.clone();
|
||||
let Some(mut owner) = self.target_session_owner_mut(session_id) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
owner.start_replay_effective_network_request_policy()
|
||||
owner.start_replay_effective_network_request_policy(&global_extra_headers)
|
||||
}
|
||||
|
||||
pub(crate) fn start_set_base_user_agent_override_for_owner(
|
||||
&mut self,
|
||||
command_owner: &crate::conn::CommandOwnerScope,
|
||||
user_agent: Option<String>,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentResourceRuntimeUpdate>, String> {
|
||||
let browser_identity = user_agent.as_ref().map(|user_agent| {
|
||||
moli_browser_profile::BrowserIdentityProfile::new(
|
||||
user_agent.clone(),
|
||||
@@ -700,7 +759,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
browser_identity: Option<moli_browser_profile::BrowserIdentityProfile>,
|
||||
) -> Option<Result<Option<PendingPageCommand>, String>> {
|
||||
) -> Option<Result<Option<PendingDocumentResourceRuntimeUpdate>, String>> {
|
||||
let is_browser_session = matches!(
|
||||
self.session_route(session_id),
|
||||
Some(CdpSessionRoute::Browser)
|
||||
@@ -728,7 +787,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
browser_identity: Option<crate::conn::DevToolsBrowserIdentityOverride>,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentResourceRuntimeUpdate>, String> {
|
||||
let non_page_identity = browser_identity
|
||||
.as_ref()
|
||||
.map(crate::conn::DevToolsBrowserIdentityOverride::to_browser_identity);
|
||||
@@ -752,7 +811,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
enabled: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentResourceRuntimeUpdate>, String> {
|
||||
if session_id.is_none()
|
||||
|| matches!(
|
||||
self.session_route(session_id),
|
||||
@@ -779,7 +838,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
offline: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let owner = crate::conn::CommandOwnerScope::capture(self, session_id);
|
||||
self.start_set_network_offline_for_owner(&owner, offline)
|
||||
}
|
||||
@@ -788,7 +847,7 @@ impl CdpConnection {
|
||||
&mut self,
|
||||
command_owner: &crate::conn::CommandOwnerScope,
|
||||
offline: bool,
|
||||
) -> Result<Option<PendingPageCommand>, String> {
|
||||
) -> Result<Option<PendingDocumentPolicyUpdate>, String> {
|
||||
let Some(owner) = self.target_session_owner_mut_for_owner(command_owner) else {
|
||||
return Err("BrowserContextNotLoaded".to_owned());
|
||||
};
|
||||
@@ -801,8 +860,9 @@ mod tests {
|
||||
use super::*;
|
||||
|
||||
fn connection_with_background_attached_session() -> CdpConnection {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-background".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-background".to_owned());
|
||||
browser_context.register_page_target_url_fixture(
|
||||
"TID-background".to_owned(),
|
||||
Some("SID-background".to_owned()),
|
||||
@@ -816,6 +876,69 @@ mod tests {
|
||||
conn
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn browser_globals_apply_to_context_inserted_after_configuration() {
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.set_global_extra_headers(vec![("X-Browser".into(), "global".into())].into());
|
||||
conn.set_global_network_conditions(Some(crate::conn::EmulatedNetworkConditions::offline()));
|
||||
conn.set_global_geolocation_override(Some(
|
||||
crate::conn::EmulatedGeolocationOverrideState::Position(
|
||||
crate::conn::EmulatedGeolocationOverride {
|
||||
latitude: 12.5,
|
||||
longitude: 34.5,
|
||||
accuracy: 1.0,
|
||||
altitude: None,
|
||||
altitude_accuracy: None,
|
||||
heading: None,
|
||||
speed: None,
|
||||
},
|
||||
),
|
||||
));
|
||||
conn.set_global_cache_disabled(true);
|
||||
|
||||
let mut browser_context = conn.new_browser_context_fixture_for_test("BID-late".to_owned());
|
||||
browser_context.set_active_target_id("TID-late");
|
||||
browser_context.attach_active_session("SID-late");
|
||||
conn.insert_browser_context(browser_context);
|
||||
|
||||
let inputs = conn.navigation_load_inputs_for_session_owner(Some("SID-late"));
|
||||
assert_eq!(
|
||||
inputs.extra_http_headers,
|
||||
vec![("X-Browser".into(), "global".into())].into()
|
||||
);
|
||||
assert!(inputs.network_offline);
|
||||
// This fixture inserts the Context without registering its wire session.
|
||||
// Resolve the exact Page route for the native policy, just as the
|
||||
// former inspection-preload assertion did.
|
||||
let inputs = conn.navigation_load_inputs_for_owner(
|
||||
&crate::conn::CommandOwnerScope::for_route(CdpSessionRoute::PageTarget {
|
||||
browser_context_id: "BID-late".into(),
|
||||
target_id: "TID-late".into(),
|
||||
session_key: DevToolsSessionKey::Primary,
|
||||
}),
|
||||
);
|
||||
assert_eq!(inputs.navigator_overrides.online, Some(false));
|
||||
assert_eq!(
|
||||
inputs.navigator_overrides.geolocation,
|
||||
Some(moli_page_types::GeolocationPositionOverride {
|
||||
latitude: 12.5,
|
||||
longitude: 34.5,
|
||||
accuracy: 1.0,
|
||||
altitude: None,
|
||||
altitude_accuracy: None,
|
||||
heading: None,
|
||||
speed: None,
|
||||
})
|
||||
);
|
||||
assert!(
|
||||
conn.browser_context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.effective_policy_for_target("TID-late")
|
||||
.cache_disabled()
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn tls_policy_is_shared_by_page_sessions_and_primary_cleanup_reveals_context_default() {
|
||||
let mut conn = connection_with_background_attached_session();
|
||||
@@ -848,11 +971,14 @@ mod tests {
|
||||
("SID-attached", Some(false), false),
|
||||
("SID-background", None, true),
|
||||
] {
|
||||
conn.reset_primary_page_session_target_state_async(
|
||||
"BID-background",
|
||||
"TID-background",
|
||||
session,
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let context = conn.browser_context.as_mut().unwrap();
|
||||
context
|
||||
.reset_primary_page_session_target_state_async("TID-background", session)
|
||||
.await
|
||||
.unwrap();
|
||||
let target = context.page_target("TID-background").unwrap();
|
||||
assert!(target.is_session("SID-background"));
|
||||
assert_eq!(
|
||||
@@ -946,23 +1072,30 @@ mod tests {
|
||||
let mut conn = connection_with_background_attached_session();
|
||||
conn.start_set_network_offline_for_session_owner(Some("SID-attached"), true)
|
||||
.unwrap();
|
||||
let context = conn.browser_context.as_mut().unwrap();
|
||||
context
|
||||
.reset_primary_page_session_target_state_async("TID-background", "SID-attached")
|
||||
.await
|
||||
.unwrap();
|
||||
conn.reset_primary_page_session_target_state_async(
|
||||
"BID-background",
|
||||
"TID-background",
|
||||
"SID-attached",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let context = conn.browser_context.as_ref().unwrap();
|
||||
assert!(context.network_offline_for_target("TID-background"));
|
||||
context
|
||||
.reset_primary_page_session_target_state_async("TID-background", "SID-background")
|
||||
.await
|
||||
.unwrap();
|
||||
conn.reset_primary_page_session_target_state_async(
|
||||
"BID-background",
|
||||
"TID-background",
|
||||
"SID-background",
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
let context = conn.browser_context.as_ref().unwrap();
|
||||
assert!(!context.network_offline_for_target("TID-background"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn subresource_fetch_network_request_ids_are_connection_global_across_target_owners() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-mixed".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context = conn.new_browser_context_fixture_for_test("BID-mixed".to_owned());
|
||||
browser_context.set_active_target_id("TID-active".to_owned());
|
||||
browser_context.attach_active_session("SID-active".to_owned());
|
||||
browser_context.register_page_target_url_fixture(
|
||||
@@ -1216,8 +1349,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn network_target_listener_can_be_disabled_after_enable() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("BID-network".to_owned()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.browser_context =
|
||||
Some(conn.new_browser_context_fixture_for_test("BID-network".to_owned()));
|
||||
conn.browser_context
|
||||
.as_mut()
|
||||
.expect("browser context")
|
||||
|
||||
@@ -637,11 +637,9 @@ impl CdpConnection {
|
||||
let Some(browser_context) = self.browser_context_by_id(browser_context_id) else {
|
||||
return;
|
||||
};
|
||||
browser_context
|
||||
.renderer_runtime()
|
||||
.set_javascript_dialog_handler_enabled(
|
||||
browser_context_has_page_domain_enabled_session(browser_context),
|
||||
);
|
||||
browser_context.set_javascript_dialog_handler_enabled(
|
||||
browser_context_has_page_domain_enabled_session(browser_context),
|
||||
);
|
||||
}
|
||||
|
||||
pub(crate) fn set_console_enabled_for_session_owner(
|
||||
@@ -994,7 +992,7 @@ mod tests {
|
||||
for session in ["SID-attached", "SID-observer"] {
|
||||
assert!(context.assign_attached_session_to_target("TID-csp", session.to_owned()));
|
||||
}
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.install_browser_context_fixture_for_test(context);
|
||||
for (session, enabled, expected) in [
|
||||
("SID-primary", true, true),
|
||||
@@ -1211,13 +1209,12 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn page_domain_dialog_handler_tracks_the_session_owner_browser_context() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut conn = crate::test_support::connection();
|
||||
|
||||
let mut active = BrowserContext::new_with_page_for_test("BID-active", "TID-active");
|
||||
let mut active = conn.new_page_target_fixture_for_test("BID-active", "TID-active");
|
||||
active.set_active_target_id("TID-active".to_owned());
|
||||
let active_runtime = active.renderer_runtime();
|
||||
|
||||
let mut inactive = BrowserContext::new("BID-inactive".to_owned());
|
||||
let mut inactive = conn.new_browser_context_fixture_for_test("BID-inactive".to_owned());
|
||||
inactive.set_active_target_id("TID-inactive".to_owned());
|
||||
assert!(
|
||||
inactive
|
||||
@@ -1227,33 +1224,46 @@ mod tests {
|
||||
inactive
|
||||
.assign_attached_session_to_target("TID-inactive", "SID-inactive-b".to_owned(),)
|
||||
);
|
||||
let inactive_runtime = inactive.renderer_runtime();
|
||||
|
||||
conn.install_browser_context_fixture_for_test(active);
|
||||
conn.push_inactive_browser_context_fixture_for_test(inactive);
|
||||
|
||||
assert!(conn.set_page_domain_enabled_for_session_owner(Some("SID-inactive-a"), true));
|
||||
assert!(inactive_runtime.javascript_dialog_handler_enabled());
|
||||
assert!(
|
||||
!active_runtime.javascript_dialog_handler_enabled(),
|
||||
conn.browser_context_by_id("BID-inactive")
|
||||
.unwrap()
|
||||
.javascript_dialog_handler_enabled()
|
||||
);
|
||||
assert!(
|
||||
!conn
|
||||
.browser_context_by_id("BID-active")
|
||||
.unwrap()
|
||||
.javascript_dialog_handler_enabled(),
|
||||
"enabling an inactive target session must not mutate the active browser context"
|
||||
);
|
||||
|
||||
assert!(conn.set_page_domain_enabled_for_session_owner(Some("SID-inactive-b"), true));
|
||||
assert!(conn.disable_page_domain_for_session_owner(Some("SID-inactive-a")));
|
||||
assert!(
|
||||
inactive_runtime.javascript_dialog_handler_enabled(),
|
||||
conn.browser_context_by_id("BID-inactive")
|
||||
.unwrap()
|
||||
.javascript_dialog_handler_enabled(),
|
||||
"one frontend must not disable dialog handling while a peer remains subscribed"
|
||||
);
|
||||
|
||||
assert!(conn.disable_page_domain_for_session_owner(Some("SID-inactive-b")));
|
||||
assert!(!inactive_runtime.javascript_dialog_handler_enabled());
|
||||
assert!(
|
||||
!conn
|
||||
.browser_context_by_id("BID-inactive")
|
||||
.unwrap()
|
||||
.javascript_dialog_handler_enabled()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn file_dialog_opened_target_listener_can_be_disabled_after_enable() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("BID-file-dialog".to_owned()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.browser_context =
|
||||
Some(conn.new_browser_context_fixture_for_test("BID-file-dialog".to_owned()));
|
||||
conn.browser_context
|
||||
.as_mut()
|
||||
.expect("browser context")
|
||||
|
||||
@@ -437,7 +437,7 @@ mod tests {
|
||||
use moli_shared_worker::SharedWorkerInstanceId;
|
||||
use serde_json::json;
|
||||
|
||||
use crate::conn::{BrowserContext, CdpConnection, CdpSessionRoute, SharedWorkerTargetState};
|
||||
use crate::conn::{CdpSessionRoute, SharedWorkerTargetState};
|
||||
|
||||
#[test]
|
||||
fn runtime_listener_session_ids_are_worker_type_scoped() {
|
||||
@@ -457,8 +457,9 @@ mod tests {
|
||||
|
||||
#[tokio::test]
|
||||
async fn runtime_listener_enable_uses_shared_worker_target_session() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut browser_context = BrowserContext::new("BID-shared".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-shared".to_owned());
|
||||
browser_context.insert_shared_worker_target(SharedWorkerTargetState::new(
|
||||
RendererOwnerLocalHostId::new_for_testing(1),
|
||||
SharedWorkerInstanceId::from_u64(91),
|
||||
|
||||
@@ -99,12 +99,12 @@ impl CdpConnection {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::conn::BrowserContext;
|
||||
|
||||
#[test]
|
||||
fn empty_browser_context_does_not_materialize_a_page_owner() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("BID-empty-owner".to_owned()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.browser_context =
|
||||
Some(conn.new_browser_context_fixture_for_test("BID-empty-owner".to_owned()));
|
||||
|
||||
assert!(conn.target_session_owner(None).is_none());
|
||||
assert!(conn.accepts_unmaterialized_page_command(&CommandOwnerScope::capture(&conn, None)));
|
||||
@@ -117,11 +117,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn stale_page_route_is_not_reinterpreted_as_the_active_page() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new_with_page_for_test(
|
||||
"BID-stale-owner",
|
||||
"TID-live",
|
||||
));
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.browser_context =
|
||||
Some(conn.new_page_target_fixture_for_test("BID-stale-owner", "TID-live"));
|
||||
let stale_route = CdpSessionRoute::PageTarget {
|
||||
browser_context_id: "BID-stale-owner".to_owned(),
|
||||
target_id: "TID-stale".to_owned(),
|
||||
|
||||
@@ -26,10 +26,8 @@ use moli_core::page::{
|
||||
};
|
||||
#[cfg(test)]
|
||||
use moli_core::page::{
|
||||
Page, RendererInspectorSessionRestoreSnapshot, RendererPageCreationArtifacts,
|
||||
RendererServiceWorkerVersionStatus,
|
||||
RendererInspectorSessionRestoreSnapshot, RendererServiceWorkerVersionStatus,
|
||||
};
|
||||
use moli_core::runtime::RendererBrowserContextRuntimeOwnerAccess;
|
||||
use moli_fetch::BrowserNavigationRequestKind;
|
||||
use moli_page_types::DevToolsSessionKey;
|
||||
use url::Url;
|
||||
@@ -99,6 +97,7 @@ pub(super) struct TargetSessionStateMut<'a> {
|
||||
}
|
||||
|
||||
pub(crate) struct TargetNavigationRequestPreflight {
|
||||
pub(crate) web_contents: moli_core::browser::WebContentsHandle,
|
||||
pub(crate) frame_id: String,
|
||||
pub(crate) session_id: Option<String>,
|
||||
pub(crate) document_fetch_event_session_id: Option<String>,
|
||||
@@ -147,7 +146,6 @@ pub(crate) struct TargetNavigationLoadInputs {
|
||||
storage_handles: TargetNavigationStorageHandles,
|
||||
#[cfg(test)]
|
||||
pub(crate) root_frame_id: Option<String>,
|
||||
pub(crate) renderer_runtime: RendererBrowserContextRuntimeOwnerAccess,
|
||||
/// One identity for navigation request headers and the Document's Navigator.
|
||||
pub(crate) browser_identity_override: Option<moli_browser_profile::BrowserIdentityProfile>,
|
||||
pub(crate) http_proxy_override: Option<String>,
|
||||
@@ -176,8 +174,6 @@ pub(crate) struct TargetNavigationLoadInputs {
|
||||
#[cfg(test)]
|
||||
pub(crate) bypass_service_worker: bool,
|
||||
#[cfg(test)]
|
||||
pub(crate) cache_disabled: bool,
|
||||
#[cfg(test)]
|
||||
pub(crate) blocked_url_patterns: Vec<String>,
|
||||
#[cfg(test)]
|
||||
pub(crate) fetch_subresource_interception:
|
||||
@@ -191,6 +187,7 @@ pub(crate) struct TargetNavigationLoadInputs {
|
||||
fn prepared_document_inspection(
|
||||
context: &BrowserContext,
|
||||
target_id: &str,
|
||||
_browser_globals: &crate::conn::BrowserGlobalOverrides,
|
||||
) -> moli_renderer_v8::RendererPreparedDocumentInspectionConfiguration {
|
||||
let target = context
|
||||
.page_target(target_id)
|
||||
@@ -282,16 +279,20 @@ impl TargetNavigationLoadInputs {
|
||||
.then_some(report)
|
||||
}
|
||||
|
||||
fn from_browser_context_target(browser_context: &BrowserContext, target_id: &str) -> Self {
|
||||
fn from_browser_context_target(
|
||||
browser_context: &BrowserContext,
|
||||
target_id: &str,
|
||||
browser_globals: &crate::conn::BrowserGlobalOverrides,
|
||||
) -> Self {
|
||||
#[cfg(test)]
|
||||
let inspection = prepared_document_inspection(browser_context, target_id);
|
||||
let inspection = prepared_document_inspection(browser_context, target_id, browser_globals);
|
||||
|
||||
let effective_network_conditions = browser_context
|
||||
.target_emulation_policy(target_id)
|
||||
.expect("live WebContents")
|
||||
.network_conditions
|
||||
.or(browser_context.emulation_defaults().network_conditions)
|
||||
.or(browser_context.global_network_conditions);
|
||||
.or(browser_globals.network_conditions);
|
||||
let emulated_device_metrics = browser_context
|
||||
.target_emulation_policy(target_id)
|
||||
.expect("live WebContents")
|
||||
@@ -314,7 +315,6 @@ impl TargetNavigationLoadInputs {
|
||||
),
|
||||
#[cfg(test)]
|
||||
root_frame_id: Some(target_id.to_owned()),
|
||||
renderer_runtime: browser_context.renderer_runtime_owner_access(),
|
||||
browser_identity_override: effective_policy
|
||||
.browser_identity_override()
|
||||
.cloned()
|
||||
@@ -335,8 +335,10 @@ impl TargetNavigationLoadInputs {
|
||||
#[cfg(test)]
|
||||
runtime_inspector_session_restore_snapshots: inspection
|
||||
.runtime_inspector_session_restore_snapshots,
|
||||
extra_http_headers: browser_context
|
||||
.merged_extra_headers_for_target_policy(effective_policy.extra_headers()),
|
||||
extra_http_headers: browser_context.merged_extra_headers_for_target_policy(
|
||||
&browser_globals.extra_headers,
|
||||
effective_policy.extra_headers(),
|
||||
),
|
||||
script_execution_disabled: browser_context
|
||||
.target_emulation_policy(target_id)
|
||||
.expect("live WebContents")
|
||||
@@ -357,13 +359,11 @@ impl TargetNavigationLoadInputs {
|
||||
|| effective_network_conditions
|
||||
.is_some_and(|conditions| !conditions.navigator_online()),
|
||||
navigator_overrides: browser_context
|
||||
.navigator_overrides_for_target(target_id)
|
||||
.navigator_overrides_for_target(target_id, browser_globals)
|
||||
.expect("resolved Page target retains its navigator state"),
|
||||
#[cfg(test)]
|
||||
bypass_service_worker: effective_policy.bypass_service_worker(),
|
||||
#[cfg(test)]
|
||||
cache_disabled: effective_policy.cache_disabled(),
|
||||
#[cfg(test)]
|
||||
blocked_url_patterns: effective_policy.blocked_url_patterns().to_vec(),
|
||||
#[cfg(test)]
|
||||
fetch_subresource_interception: browser_context
|
||||
@@ -381,10 +381,12 @@ impl TargetNavigationLoadInputs {
|
||||
}
|
||||
}
|
||||
|
||||
fn from_browser_context_fallback(browser_context: &BrowserContext) -> Self {
|
||||
fn from_browser_context_fallback(
|
||||
browser_context: &BrowserContext,
|
||||
browser_globals: &crate::conn::BrowserGlobalOverrides,
|
||||
) -> Self {
|
||||
let mut inputs = TargetNavigationLoadInputs::no_loaded_browser_context(
|
||||
browser_context.page_storage_handles(),
|
||||
browser_context.renderer_runtime_owner_access(),
|
||||
);
|
||||
inputs.browser_context_id = Some(browser_context.id.clone());
|
||||
inputs.browser_identity_override =
|
||||
@@ -398,21 +400,20 @@ impl TargetNavigationLoadInputs {
|
||||
inputs.document_start_scripts =
|
||||
browser_context.default_document_start_script_descriptors();
|
||||
}
|
||||
inputs.extra_http_headers = browser_context.effective_extra_headers();
|
||||
inputs.extra_http_headers =
|
||||
browser_context.effective_extra_headers(&browser_globals.extra_headers);
|
||||
inputs.viewport_surface = browser_context
|
||||
.emulation_defaults()
|
||||
.device_metrics
|
||||
.as_ref()
|
||||
.map(|metrics| metrics.viewport_surface());
|
||||
inputs.document_activity = browser_context.active_document_activity();
|
||||
inputs.network_offline = browser_context.effective_active_network_offline();
|
||||
inputs.network_offline =
|
||||
browser_context.effective_active_network_offline(browser_globals.network_conditions);
|
||||
inputs
|
||||
}
|
||||
|
||||
fn no_loaded_browser_context(
|
||||
page_handles: BrowserContextPageStorageHandles,
|
||||
renderer_runtime: RendererBrowserContextRuntimeOwnerAccess,
|
||||
) -> Self {
|
||||
fn no_loaded_browser_context(page_handles: BrowserContextPageStorageHandles) -> Self {
|
||||
Self {
|
||||
redirect_chain: Vec::new(),
|
||||
redirect_headers: None,
|
||||
@@ -420,7 +421,6 @@ impl TargetNavigationLoadInputs {
|
||||
storage_handles: TargetNavigationStorageHandles::from_page_handles(page_handles),
|
||||
#[cfg(test)]
|
||||
root_frame_id: None,
|
||||
renderer_runtime,
|
||||
browser_identity_override: None,
|
||||
http_proxy_override: None,
|
||||
http_no_proxy_override: None,
|
||||
@@ -447,8 +447,6 @@ impl TargetNavigationLoadInputs {
|
||||
#[cfg(test)]
|
||||
bypass_service_worker: false,
|
||||
#[cfg(test)]
|
||||
cache_disabled: false,
|
||||
#[cfg(test)]
|
||||
blocked_url_patterns: Vec::new(),
|
||||
#[cfg(test)]
|
||||
fetch_subresource_interception: (false, None),
|
||||
@@ -604,7 +602,7 @@ impl<'a> TargetSessionOwnerRef<'a> {
|
||||
// Only a browser-owned network error Document diverges from the
|
||||
// user-visible Target/history URL.
|
||||
.filter(|url| url.as_str() == NETWORK_ERROR_PAGE_URL)
|
||||
.map(ToString::to_string)
|
||||
.map(|url| url.to_string())
|
||||
.unwrap_or_else(|| target.target_identity().url().to_owned());
|
||||
(
|
||||
target.target_id().to_owned(),
|
||||
@@ -638,10 +636,14 @@ impl<'a> TargetSessionOwnerRef<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
pub(super) fn navigation_load_inputs(&self) -> TargetNavigationLoadInputs {
|
||||
pub(super) fn navigation_load_inputs(
|
||||
&self,
|
||||
browser_globals: &crate::conn::BrowserGlobalOverrides,
|
||||
) -> TargetNavigationLoadInputs {
|
||||
TargetNavigationLoadInputs::from_browser_context_target(
|
||||
self.browser_context,
|
||||
&self.target_id,
|
||||
browser_globals,
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -837,8 +839,12 @@ impl<'a> TargetSessionOwnerMut<'a> {
|
||||
referrer: Option<&str>,
|
||||
is_data_url: bool,
|
||||
fallback_browser_identity: &moli_browser_profile::BrowserIdentityProfile,
|
||||
global_extra_headers: &moli_fetch::RequestHeaders,
|
||||
network_request_id_allocator: &mut ConnectionNetworkRequestIdAllocator,
|
||||
) -> Option<TargetNavigationRequestPreflight> {
|
||||
let web_contents = self
|
||||
.browser_context
|
||||
.web_contents_handle_for_target(&self.target_id)?;
|
||||
{
|
||||
let target = self.target();
|
||||
let frame_id = self.target_id.clone();
|
||||
@@ -851,15 +857,16 @@ impl<'a> TargetSessionOwnerMut<'a> {
|
||||
let effective_policy = self
|
||||
.browser_context
|
||||
.effective_policy_for_target(&self.target_id);
|
||||
let mut request_headers = self
|
||||
.browser_context
|
||||
.merged_extra_headers_for_target_policy(effective_policy.extra_headers());
|
||||
let user_agent = effective_policy
|
||||
let mut request_headers = self.browser_context.merged_extra_headers_for_target_policy(
|
||||
global_extra_headers,
|
||||
effective_policy.extra_headers(),
|
||||
);
|
||||
let identity = effective_policy
|
||||
.browser_identity_override()
|
||||
.cloned()
|
||||
.or_else(|| self.browser_context.default_browser_identity_override())
|
||||
.unwrap_or(fallback_browser_identity)
|
||||
.user_agent();
|
||||
apply_user_agent_header(&mut request_headers, user_agent);
|
||||
.unwrap_or_else(|| fallback_browser_identity.clone());
|
||||
apply_user_agent_header(&mut request_headers, identity.user_agent());
|
||||
apply_referrer_header(&mut request_headers, referrer);
|
||||
let fetch_config = target.fetch_owner.config_snapshot();
|
||||
let fetch_snapshot = fetch_config.subresource_interception_snapshot();
|
||||
@@ -930,6 +937,7 @@ impl<'a> TargetSessionOwnerMut<'a> {
|
||||
needs_fetch_navigation_request_id,
|
||||
);
|
||||
Some(TargetNavigationRequestPreflight {
|
||||
web_contents,
|
||||
frame_id,
|
||||
session_id: target_session_id,
|
||||
document_fetch_event_session_id,
|
||||
@@ -973,16 +981,18 @@ impl CdpConnection {
|
||||
None => self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.map(TargetNavigationLoadInputs::from_browser_context_fallback)
|
||||
.map(|context| {
|
||||
TargetNavigationLoadInputs::from_browser_context_fallback(
|
||||
context,
|
||||
&self.browser_global_overrides,
|
||||
)
|
||||
})
|
||||
.unwrap_or_else(|| {
|
||||
TargetNavigationLoadInputs::no_loaded_browser_context(
|
||||
self.initial_storage_partition.page_storage_handles(),
|
||||
self.standalone_navigation_engine
|
||||
.ensure()
|
||||
.browser_context_owner_access(),
|
||||
)
|
||||
}),
|
||||
Some(owner) => owner.navigation_load_inputs(),
|
||||
Some(owner) => owner.navigation_load_inputs(&self.browser_global_overrides),
|
||||
};
|
||||
#[cfg(test)]
|
||||
let inputs = {
|
||||
@@ -1001,7 +1011,11 @@ impl CdpConnection {
|
||||
owner: &CommandOwnerScope,
|
||||
) -> moli_renderer_v8::RendererPreparedDocumentInspectionConfiguration {
|
||||
match self.target_session_owner_ref_for_owner(owner) {
|
||||
Some(owner) => prepared_document_inspection(owner.browser_context, &owner.target_id),
|
||||
Some(owner) => prepared_document_inspection(
|
||||
owner.browser_context,
|
||||
&owner.target_id,
|
||||
&self.browser_global_overrides,
|
||||
),
|
||||
None => moli_renderer_v8::RendererPreparedDocumentInspectionConfiguration {
|
||||
document_start_scripts: self
|
||||
.browser_context
|
||||
@@ -1032,6 +1046,7 @@ impl CdpConnection {
|
||||
.global_browser_identity_override
|
||||
.clone()
|
||||
.unwrap_or_else(|| self.base_browser_identity.clone());
|
||||
let global_extra_headers = self.browser_global_overrides.extra_headers.clone();
|
||||
let mut network_request_id_allocator =
|
||||
std::mem::take(&mut self.network_request_id_allocator);
|
||||
let result = self
|
||||
@@ -1042,6 +1057,7 @@ impl CdpConnection {
|
||||
referrer,
|
||||
is_data_url,
|
||||
&fallback_browser_identity,
|
||||
&global_extra_headers,
|
||||
&mut network_request_id_allocator,
|
||||
)
|
||||
});
|
||||
@@ -1058,35 +1074,6 @@ impl CdpConnection {
|
||||
.register_pending_fetch_navigation_request(pending)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn start_loaded_document_navigation_for_owner(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
navigation: crate::conn::NavigationId,
|
||||
page: Page,
|
||||
destination: crate::conn::DocumentNavigationDestination,
|
||||
artifacts: &RendererPageCreationArtifacts,
|
||||
) -> Result<
|
||||
impl std::future::Future<Output = anyhow::Result<crate::conn::PreparedDocumentNavigation>>
|
||||
+ use<>,
|
||||
String,
|
||||
> {
|
||||
let (context_id, target_id) = self
|
||||
.resolved_page_owner_identity_for_owner(owner)
|
||||
.ok_or("navigation WebContents unavailable")?;
|
||||
self.browser_context_by_id(&context_id)
|
||||
.ok_or("navigation BrowserContext unavailable")?
|
||||
.start_loaded_document_navigation_for_target(
|
||||
&target_id,
|
||||
navigation,
|
||||
page,
|
||||
destination,
|
||||
artifacts,
|
||||
&self.permission_defaults,
|
||||
)
|
||||
.map_err(str::to_owned)
|
||||
}
|
||||
|
||||
pub(crate) fn commit_loaded_navigation(
|
||||
&mut self,
|
||||
prepared: crate::conn::PreparedDocumentNavigation,
|
||||
@@ -1109,12 +1096,18 @@ impl CdpConnection {
|
||||
.await
|
||||
}
|
||||
|
||||
async fn close_page_target_for_target_close_async(
|
||||
pub(crate) async fn close_web_contents_for_target_close_async(
|
||||
&mut self,
|
||||
target_id: &str,
|
||||
handle: moli_core::browser::WebContentsHandle,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
reason: &'static str,
|
||||
) -> Option<ClosedPageTarget> {
|
||||
let context = self.browser_context_by_browser_id(handle.context())?;
|
||||
if context.web_contents_handle_for_target(target_id) != Some(handle) {
|
||||
return None;
|
||||
}
|
||||
let was_selected = context.selected_web_contents_handle() == Some(handle);
|
||||
let primary_route = self.target_session_route_for_target_id(target_id)?;
|
||||
let primary_owner = CommandOwnerScope::for_route(primary_route);
|
||||
let session_owners = self
|
||||
@@ -1139,8 +1132,8 @@ impl CdpConnection {
|
||||
out.extend(claimed_await_events);
|
||||
|
||||
let (closing, primary_session_id, attached_session_ids, collected_network_data_artifacts) = {
|
||||
let browser_context = self.browser_context.as_mut()?;
|
||||
let (target, closing) = browser_context.take_page_target_for_close(target_id)?;
|
||||
let browser_context = self.browser_context_by_browser_id_mut(handle.context())?;
|
||||
let (target, closing) = browser_context.begin_web_contents_close(handle).ok()?;
|
||||
let collected_network_data_artifacts =
|
||||
target.runtime_slot.collected_network_data_artifacts();
|
||||
let attached_session_ids = target
|
||||
@@ -1159,48 +1152,37 @@ impl CdpConnection {
|
||||
closing.close_async().await;
|
||||
self.record_collected_network_data_artifacts(collected_network_data_artifacts);
|
||||
|
||||
Some(ClosedPageTarget {
|
||||
let closed = ClosedPageTarget {
|
||||
target_id: target_id.to_owned(),
|
||||
primary_session_id,
|
||||
attached_session_ids,
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) async fn close_background_page_target_for_target_close_async(
|
||||
&mut self,
|
||||
target_id: &str,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
reason: &'static str,
|
||||
) -> Option<ClosedPageTarget> {
|
||||
let is_background = self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.is_some_and(|browser_context| {
|
||||
browser_context.page_target(target_id).is_some()
|
||||
&& !browser_context.is_active_target(target_id)
|
||||
});
|
||||
if !is_background {
|
||||
return None;
|
||||
};
|
||||
if !was_selected {
|
||||
return Some(closed);
|
||||
}
|
||||
self.close_page_target_for_target_close_async(target_id, out, reason)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn close_active_page_target_for_target_close_async(
|
||||
&mut self,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
reason: &'static str,
|
||||
) -> Option<ClosedPageTarget> {
|
||||
let target_id = self.browser_context.as_ref()?.active_target_id_owned()?;
|
||||
let closed = self
|
||||
.close_page_target_for_target_close_async(&target_id, out, reason)
|
||||
.await?;
|
||||
let selected_target_id = if let Some(browser_context) = self.browser_context.as_mut() {
|
||||
browser_context.select_last_background_target_async().await
|
||||
let selected = self
|
||||
.browser_context_by_browser_id(handle.context())
|
||||
.and_then(|browser_context| {
|
||||
let target_id = browser_context.last_selectable_background_target_id()?;
|
||||
let handle = browser_context.web_contents_handle_for_target(&target_id)?;
|
||||
Some((target_id, handle))
|
||||
});
|
||||
let selected_target_id = if let Some((target_id, handle)) = selected {
|
||||
if let Err(error) = self.select_browser_web_contents_async(handle).await {
|
||||
tracing::warn!(%error, "failed to update selected WebContents surface after close");
|
||||
}
|
||||
Some(target_id)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
self.refresh_active_browser_context_loader();
|
||||
if self
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.is_some_and(|context| context.browser_context_id() == handle.context())
|
||||
{
|
||||
self.refresh_active_browser_context_loader();
|
||||
}
|
||||
if let Some(selected_target_id) = selected_target_id {
|
||||
self.notify_target_host_activated(&selected_target_id);
|
||||
out.extend(
|
||||
@@ -1238,7 +1220,10 @@ impl CdpConnection {
|
||||
if let Some(browser_context_id) = browser_context_id {
|
||||
let target = self
|
||||
.browser_context_by_id_mut(&browser_context_id)
|
||||
.and_then(|browser_context| browser_context.take_page_target_for_close(target_id));
|
||||
.and_then(|browser_context| {
|
||||
let handle = browser_context.web_contents_handle_for_target(target_id)?;
|
||||
browser_context.begin_web_contents_close(handle).ok()
|
||||
});
|
||||
if let Some((target, closing)) = target {
|
||||
page_session_ids.extend(
|
||||
target
|
||||
@@ -1380,11 +1365,9 @@ impl CdpConnection {
|
||||
owner: &CommandOwnerScope,
|
||||
state: WindowSurfaceState,
|
||||
) -> Option<()> {
|
||||
let owner = self.target_session_owner_mut_for_owner(owner)?;
|
||||
owner
|
||||
.browser_context
|
||||
.set_target_window_surface_state(&owner.target_id, state);
|
||||
Some(())
|
||||
let handle = self.browser_web_contents_for_owner(owner).ok()?;
|
||||
self.update_browser_window_surface(handle, Some(state), None, None, None, None)
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub(crate) fn set_window_surface_geometry_for_owner(
|
||||
@@ -1395,15 +1378,9 @@ impl CdpConnection {
|
||||
x: Option<i32>,
|
||||
y: Option<i32>,
|
||||
) -> Option<()> {
|
||||
let owner = self.target_session_owner_mut_for_owner(owner)?;
|
||||
owner.browser_context.set_target_window_surface_geometry(
|
||||
&owner.target_id,
|
||||
width,
|
||||
height,
|
||||
x,
|
||||
y,
|
||||
);
|
||||
Some(())
|
||||
let handle = self.browser_web_contents_for_owner(owner).ok()?;
|
||||
self.update_browser_window_surface(handle, None, width, height, x, y)
|
||||
.ok()
|
||||
}
|
||||
|
||||
pub(crate) fn target_owner_state_for_owner(
|
||||
@@ -1669,28 +1646,6 @@ impl CdpConnection {
|
||||
))
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn reserve_target_page_residence_identity_for_owner(
|
||||
&mut self,
|
||||
owner: &CommandOwnerScope,
|
||||
renderer_page: crate::conn::RendererPageResidenceIdentity,
|
||||
) -> Option<TargetPageResidenceIdentity> {
|
||||
let (browser_context_id, routed_target_id) = self.target_owner_identity_for_owner(owner)?;
|
||||
let target_id = routed_target_id.or_else(|| {
|
||||
self.browser_context_by_id(&browser_context_id)
|
||||
.and_then(|browser_context| browser_context.active_target_id())
|
||||
.map(str::to_owned)
|
||||
});
|
||||
let document_id = self
|
||||
.browser_context_by_id_mut(&browser_context_id)?
|
||||
.reserve_renderer_document_for_target(target_id.as_deref()?, renderer_page);
|
||||
Some(TargetPageResidenceIdentity::new(
|
||||
browser_context_id,
|
||||
target_id,
|
||||
document_id,
|
||||
))
|
||||
}
|
||||
|
||||
/// Checks that deferred Page-owned work still addresses the same target
|
||||
/// Page-slot residence from which it was captured.
|
||||
pub(crate) fn target_page_residence_identity_is_current(
|
||||
@@ -1703,16 +1658,6 @@ impl CdpConnection {
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn capture_document_lifetime_for_owner(
|
||||
&mut self,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Option<moli_core::browser::DocumentLifetimeObserver> {
|
||||
let owner = self.target_session_owner_mut_for_owner(owner)?;
|
||||
owner
|
||||
.browser_context
|
||||
.document_lifetime_observer_for_target(&owner.target_id)
|
||||
}
|
||||
|
||||
/// Captures the exact protocol attachment currently addressing a Page.
|
||||
pub(crate) fn target_page_protocol_attachment_identity_for_session(
|
||||
&self,
|
||||
@@ -2278,7 +2223,7 @@ impl CdpConnection {
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
destination: crate::conn::HistoryTraversalDestination,
|
||||
) -> Option<Result<crate::conn::ResolvedHistoryTraversal, &'static str>> {
|
||||
) -> Option<Result<crate::conn::ResolvedHistoryTraversal, String>> {
|
||||
let owner = self.target_session_owner_ref_for_owner(owner)?;
|
||||
owner
|
||||
.browser_context
|
||||
@@ -2418,8 +2363,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn generated_session_id_skips_caller_supplied_live_session() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-session-id".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-session-id".to_owned());
|
||||
browser_context.set_active_target_id("TID-session-id");
|
||||
browser_context.attach_active_session("SID-1".to_owned());
|
||||
conn.install_browser_context_fixture_for_test(browser_context);
|
||||
@@ -2511,8 +2457,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn renderer_runtime_inspector_session_id_tracks_owner_session_kind() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-owner-key".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-owner-key".to_owned());
|
||||
browser_context.set_active_target_id("TID-active".to_owned());
|
||||
browser_context.attach_active_session("SID-active-primary".to_owned());
|
||||
browser_context.set_active_document_fixture_for_test(1);
|
||||
@@ -2800,6 +2747,7 @@ mod tests {
|
||||
Some("https://referrer.example/"),
|
||||
false,
|
||||
&test_browser_identity("Moli/Test-UA"),
|
||||
&Default::default(),
|
||||
&mut network_request_id_allocator,
|
||||
)
|
||||
.expect("active preflight should prepare");
|
||||
@@ -2845,6 +2793,7 @@ mod tests {
|
||||
None,
|
||||
true,
|
||||
&test_browser_identity("Moli/Test-UA"),
|
||||
&Default::default(),
|
||||
&mut network_request_id_allocator,
|
||||
)
|
||||
.expect("active data URL preflight should prepare");
|
||||
@@ -2939,6 +2888,7 @@ mod tests {
|
||||
Some("https://referrer.example/"),
|
||||
false,
|
||||
&test_browser_identity("Moli/Test-UA"),
|
||||
&Default::default(),
|
||||
&mut network_request_id_allocator,
|
||||
)
|
||||
.expect("background preflight should prepare");
|
||||
@@ -3020,6 +2970,7 @@ mod tests {
|
||||
None,
|
||||
true,
|
||||
&test_browser_identity("Moli/Test-UA"),
|
||||
&Default::default(),
|
||||
&mut network_request_id_allocator,
|
||||
)
|
||||
.expect("background data URL preflight should prepare");
|
||||
@@ -3097,9 +3048,12 @@ mod tests {
|
||||
}],
|
||||
);
|
||||
}
|
||||
let web_contents = background
|
||||
.web_contents_handle_for_target("TID-background")
|
||||
.unwrap();
|
||||
assert!(
|
||||
background
|
||||
.start_target_fetch_interception_update("TID-background", true, None)
|
||||
.start_web_contents_fetch_interception_update(web_contents, true, None, false)
|
||||
.unwrap()
|
||||
.is_none()
|
||||
);
|
||||
@@ -3133,14 +3087,12 @@ mod tests {
|
||||
target_id: "TID-background".to_owned(),
|
||||
session_key: DevToolsSessionKey::Primary,
|
||||
};
|
||||
let inputs = owner.navigation_load_inputs();
|
||||
let inputs = owner.navigation_load_inputs(&Default::default());
|
||||
|
||||
assert_eq!(inputs.browser_context_id.as_deref(), Some("BID-background"));
|
||||
assert!(
|
||||
inputs
|
||||
.renderer_runtime
|
||||
.runtime()
|
||||
.shares_state_with(&background.renderer_runtime()),
|
||||
assert_eq!(
|
||||
background.page_navigation_browser_context_runtime_id_for_test("TID-background"),
|
||||
Some(background.renderer_runtime_id_for_test()),
|
||||
"background navigation must reuse the browser-context renderer runtime"
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -3230,7 +3182,8 @@ mod tests {
|
||||
"https://peer.example/".to_owned(),
|
||||
);
|
||||
background.set_active_target_id("TID-peer");
|
||||
let inspection = prepared_document_inspection(&background, "TID-background");
|
||||
let inspection =
|
||||
prepared_document_inspection(&background, "TID-background", &Default::default());
|
||||
assert_eq!(
|
||||
inspection.root_frame_projection_id.as_deref(),
|
||||
Some("TID-background")
|
||||
@@ -3240,7 +3193,7 @@ mod tests {
|
||||
assert!(sessions[0].protocol_configuration.runtime_frontend_enabled);
|
||||
assert_eq!(background.active_target_id(), Some("TID-peer"));
|
||||
assert!(
|
||||
prepared_document_inspection(&background, "TID-peer")
|
||||
prepared_document_inspection(&background, "TID-peer", &Default::default())
|
||||
.runtime_inspector_session_restore_snapshots
|
||||
.iter()
|
||||
.all(|session| !session.protocol_configuration.runtime_frontend_enabled),
|
||||
@@ -3300,43 +3253,25 @@ mod tests {
|
||||
#[tokio::test]
|
||||
async fn browser_context_commits_loaded_page_to_background_owner() {
|
||||
let mut ctx = TestContext::new();
|
||||
let loaded = ctx
|
||||
.conn
|
||||
.load_navigation_via_runtime_async("data:text/html,<title>background commit</title>")
|
||||
.await
|
||||
.expect("page should load");
|
||||
let page_url = Url::parse("https://nav.example/path").unwrap();
|
||||
let artifacts = loaded.page_creation_artifacts;
|
||||
let mut background = BrowserContext::new("BID-background".to_owned());
|
||||
let mut background = ctx
|
||||
.conn
|
||||
.new_browser_context_fixture_for_test("BID-background".to_owned());
|
||||
background.register_page_target_url_fixture(
|
||||
"TID-background".to_owned(),
|
||||
Some("SID-background".to_owned()),
|
||||
"about:blank".to_owned(),
|
||||
);
|
||||
let initial_attachment_id = background.target_document_id("TID-background");
|
||||
let navigation =
|
||||
background.begin_target_document_navigation("TID-background", "LOADER-nav".into());
|
||||
let prepared = background
|
||||
.start_loaded_document_navigation_for_target(
|
||||
"TID-background",
|
||||
navigation,
|
||||
loaded.page,
|
||||
crate::conn::DocumentNavigationDestination {
|
||||
url: page_url.clone(),
|
||||
security_origin: "https://nav.example".into(),
|
||||
secure_context_type: "Secure".into(),
|
||||
},
|
||||
&artifacts,
|
||||
&Default::default(),
|
||||
)
|
||||
.unwrap()
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let committed = background
|
||||
.commit_loaded_navigation(prepared)
|
||||
.expect("background Browser Document should commit");
|
||||
assert!(committed.inspection_projection.is_ok());
|
||||
ctx.conn
|
||||
.install_browser_context_fixture_for_test(background);
|
||||
ctx.install_buffered_navigation_fixture_for_session_owner(
|
||||
page_url.clone(),
|
||||
"<!doctype html><title>background commit</title>".to_owned(),
|
||||
Some("SID-background"),
|
||||
)
|
||||
.await;
|
||||
let background = ctx.conn.browser_context.as_ref().unwrap();
|
||||
|
||||
let target = background
|
||||
.background_target("TID-background")
|
||||
@@ -3358,13 +3293,13 @@ mod tests {
|
||||
);
|
||||
assert_eq!(target.target_identity().secure_context_type(), "Secure");
|
||||
assert!(!background.has_pending_document_navigation_for_target("TID-background"));
|
||||
committed.previous_document_retirement.close().await;
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn target_page_residence_identity_rejects_context_target_and_attachment_collisions() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-page-residence".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-page-residence".to_owned());
|
||||
browser_context.set_active_target_id("TID-page-residence");
|
||||
browser_context.attach_active_session("SID-page-residence");
|
||||
conn.install_browser_context_fixture_for_test(browser_context);
|
||||
@@ -3404,8 +3339,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn target_without_page_has_only_a_pending_page_residence() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-pending-residence".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-pending-residence".to_owned());
|
||||
browser_context.set_active_target_id("TID-pending-residence");
|
||||
browser_context.attach_active_session("SID-pending-residence".to_owned());
|
||||
conn.install_browser_context_fixture_for_test(browser_context);
|
||||
@@ -3439,8 +3375,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn implicit_active_route_freezes_the_concrete_target_in_page_residence() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-implicit-page-residence".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-implicit-page-residence".to_owned());
|
||||
browser_context.set_active_target_id("TID-original");
|
||||
conn.install_browser_context_fixture_for_test(browser_context);
|
||||
conn.set_document_fixture_for_owner_test(
|
||||
@@ -3470,8 +3407,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn connection_target_owner_reference_reads_and_mutates_active_and_background_state() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-1".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context = conn.new_browser_context_fixture_for_test("BID-1".to_owned());
|
||||
browser_context.set_active_target_id("TID-active".to_owned());
|
||||
browser_context.attach_active_session("SID-active".to_owned());
|
||||
browser_context.register_page_target_url_fixture(
|
||||
@@ -3527,8 +3464,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn subscribed_page_event_sessions_require_page_domain_enable_per_session() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-page-events".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-page-events".to_owned());
|
||||
browser_context.set_active_target_id("TID-page-events".to_owned());
|
||||
browser_context.attach_active_session("SID-primary".to_owned());
|
||||
assert!(
|
||||
@@ -3583,8 +3521,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn runtime_event_attachments_include_every_enabled_session_on_the_exact_page() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-runtime-events".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-runtime-events".to_owned());
|
||||
browser_context.set_active_target_id("TID-runtime-events".to_owned());
|
||||
browser_context.attach_active_session("SID-runtime-a".to_owned());
|
||||
assert!(
|
||||
@@ -3638,8 +3577,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn runtime_context_identity_includes_service_worker_without_page_owner_identity() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-worker-context".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-worker-context".to_owned());
|
||||
browser_context.insert_service_worker_target(ServiceWorkerTargetState::new(
|
||||
41,
|
||||
29,
|
||||
@@ -3683,8 +3623,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn connection_runtime_slot_reference_reads_and_mutates_active_background_and_attached_slots() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut active = BrowserContext::new_with_page_for_test("BID-active", "TID-active");
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut active = conn.new_page_target_fixture_for_test("BID-active", "TID-active");
|
||||
active.set_active_target_id("TID-active".to_owned());
|
||||
active.set_target_url("https://active.example/".to_owned());
|
||||
active.attach_active_session("SID-active".to_owned());
|
||||
@@ -3704,7 +3644,7 @@ mod tests {
|
||||
"SID-attached-background".to_owned()
|
||||
));
|
||||
|
||||
let mut inactive = BrowserContext::new("BID-inactive".to_owned());
|
||||
let mut inactive = conn.new_browser_context_fixture_for_test("BID-inactive".to_owned());
|
||||
inactive.set_active_target_id("TID-inactive".to_owned());
|
||||
inactive.set_target_url("https://inactive.example/".to_owned());
|
||||
inactive
|
||||
@@ -3820,8 +3760,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn background_dialog_scope_survives_default_session_state_folding() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-dialog-scope".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context =
|
||||
conn.new_browser_context_fixture_for_test("BID-dialog-scope".to_owned());
|
||||
browser_context.set_active_target_id("TID-active".to_owned());
|
||||
browser_context.register_page_target_url_fixture(
|
||||
"TID-background-dialog".to_owned(),
|
||||
|
||||
@@ -0,0 +1,705 @@
|
||||
use moli_core::{
|
||||
browser::{BrowserContextId, DocumentHandle, DocumentLifetimeObserver},
|
||||
page::{
|
||||
ChildFrameTreeSnapshot, DocumentCookieOwnerSnapshot, RendererAppManifestLoadPublication,
|
||||
RendererAutofillTriggerOutcome, RendererAutofillTriggerRequest,
|
||||
RendererCaptureScreencastFrameReply, RendererCaptureScreencastFrameRequest,
|
||||
RendererCaptureScreenshotReply, RendererCaptureScreenshotRequest,
|
||||
RendererCommandTurnOutput, RendererNetworkResourceLoadPreparation,
|
||||
RendererPageDiagnosticsSnapshot, RendererResourceTextSearchOutcome,
|
||||
RendererSetDocumentContentResult, SubresourceNetworkRecord,
|
||||
},
|
||||
};
|
||||
use std::sync::Arc;
|
||||
use url::Url;
|
||||
|
||||
use super::BrowserContext;
|
||||
use super::{
|
||||
BrowserAppManifestLoadPreparation, CompletedAppManifestLoadPreparation,
|
||||
CompletedAppManifestPublication, CompletedCaptureDocumentImage,
|
||||
CompletedCaptureDocumentScreencastFrame, CompletedCaptureDocumentSnapshot,
|
||||
CompletedChildFrameLifecycleWork, CompletedChildFrameNavigation,
|
||||
CompletedChildFrameTreeSnapshot, CompletedDocumentAutofillTrigger, CompletedDocumentBlobRead,
|
||||
CompletedDocumentCookieOwnerSnapshot, CompletedDocumentCspBypassUpdate,
|
||||
CompletedDocumentDiagnosticsSnapshot, CompletedDocumentFetchCommand,
|
||||
CompletedDocumentInputCommand, CompletedDocumentLifecycleStop, CompletedDocumentPolicyBatch,
|
||||
CompletedDocumentPolicyUpdate, CompletedDocumentResourceRuntimeUpdate,
|
||||
CompletedDocumentResourceTextSearch, CompletedDocumentStorageKeySnapshot,
|
||||
CompletedNavigationHistoryReset, CompletedNetworkResourceLoadPreparation,
|
||||
CompletedSetDocumentContent, CompletedTopLevelHistoryTraversal,
|
||||
CompletedTopLevelSameDocumentNavigation, DocumentFetchCommand, DocumentFetchCommandOutcome,
|
||||
DocumentPolicyUpdate, DocumentRuntimePolicyReconciliation, DocumentSnapshot, PageInputCommand,
|
||||
PendingAppManifestLoadPreparation, PendingAppManifestPublication, PendingCaptureDocumentImage,
|
||||
PendingCaptureDocumentScreencastFrame, PendingCaptureDocumentSnapshot,
|
||||
PendingChildFrameLifecycleWork, PendingChildFrameNavigation, PendingChildFrameTreeSnapshot,
|
||||
PendingDocumentAutofillTrigger, PendingDocumentBlobRead, PendingDocumentCookieOwnerSnapshot,
|
||||
PendingDocumentCspBypassUpdate, PendingDocumentDiagnosticsSnapshot,
|
||||
PendingDocumentFetchCommand, PendingDocumentInputCommand, PendingDocumentLifecycleStop,
|
||||
PendingDocumentPolicyBatch, PendingDocumentPolicyUpdate, PendingDocumentResourceTextSearch,
|
||||
PendingDocumentStorageKeySnapshot, PendingNavigationHistoryReset,
|
||||
PendingNetworkResourceLoadPreparation, PendingSetDocumentContent,
|
||||
PendingTopLevelHistoryTraversal, PendingTopLevelSameDocumentNavigation,
|
||||
};
|
||||
use crate::conn::{CdpConnection, CommandOwnerScope};
|
||||
|
||||
impl CdpConnection {
|
||||
#[cfg(test)]
|
||||
pub(crate) fn suspend_navigation_resource_owner_for_test(
|
||||
&self,
|
||||
handle: moli_core::browser::WebContentsHandle,
|
||||
) -> Result<impl std::future::Future<Output = ()> + use<>, String> {
|
||||
self.browser_context_handle_by_id(handle.context())
|
||||
.ok_or("BrowserContext unavailable")?
|
||||
.suspend_navigation_resource_owner_for_test(handle)
|
||||
}
|
||||
|
||||
pub(crate) fn browser_context_by_browser_id(
|
||||
&self,
|
||||
context: BrowserContextId,
|
||||
) -> Option<&BrowserContext> {
|
||||
self.browser_contexts()
|
||||
.find(|candidate| candidate.browser_context_id() == context)
|
||||
}
|
||||
|
||||
pub(in crate::conn) fn browser_context_by_browser_id_mut(
|
||||
&mut self,
|
||||
context: BrowserContextId,
|
||||
) -> Option<&mut BrowserContext> {
|
||||
self.browser_context
|
||||
.iter_mut()
|
||||
.chain(&mut self.inactive_browser_contexts)
|
||||
.find(|candidate| candidate.browser_context_id() == context)
|
||||
}
|
||||
|
||||
fn browser_context_handle_by_id(
|
||||
&self,
|
||||
context: BrowserContextId,
|
||||
) -> Option<&moli_core::browser::BrowserContextHandle> {
|
||||
Some(&self.browser_context_by_browser_id(context)?.browser_context)
|
||||
}
|
||||
|
||||
fn browser_context_handle_by_id_mut(
|
||||
&mut self,
|
||||
context: BrowserContextId,
|
||||
) -> Option<&mut moli_core::browser::BrowserContextHandle> {
|
||||
Some(
|
||||
&mut self
|
||||
.browser_context_by_browser_id_mut(context)?
|
||||
.browser_context,
|
||||
)
|
||||
}
|
||||
|
||||
/// DevTools projection boundary: resolve a session/target once into an
|
||||
/// exact physical Browser capability. Browser operations never receive the
|
||||
/// frontend identities used to find it.
|
||||
pub(crate) fn loaded_browser_document_for_owner(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Result<DocumentHandle, String> {
|
||||
let (context_id, target_id) = self
|
||||
.loaded_document_owner_identity_for_owner(owner)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?;
|
||||
self.browser_context_by_id(&context_id)
|
||||
.and_then(|context| context.document_handle_for_target(&target_id))
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn resolve_browser_document_for_owner(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Result<DocumentHandle, String> {
|
||||
self.ensure_document_accessible_for_owner(owner)?;
|
||||
self.loaded_browser_document_for_owner(owner)
|
||||
}
|
||||
|
||||
pub(crate) fn observe_browser_document_lifetime(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<DocumentLifetimeObserver, String> {
|
||||
self.browser_context_handle_by_id_mut(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.observe_document_lifetime(document)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_fetch_command(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
command: DocumentFetchCommand,
|
||||
) -> Result<PendingDocumentFetchCommand, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_fetch_command(document, command)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_fetch_command(
|
||||
&mut self,
|
||||
completed: CompletedDocumentFetchCommand,
|
||||
) -> Result<DocumentFetchCommandOutcome, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
match self.browser_context_handle_by_id_mut(context) {
|
||||
Some(context) => context.finish_document_fetch_command(completed),
|
||||
None if completed.is_interception_update() => {
|
||||
moli_core::browser::BrowserContext::finish_unobserved_document_fetch_interception_update(completed)
|
||||
}
|
||||
None => Err("NoDocumentLoaded".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_input_command(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
command: PageInputCommand,
|
||||
) -> Result<PendingDocumentInputCommand, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_input_command(document, command)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_input_command(
|
||||
&mut self,
|
||||
completed: CompletedDocumentInputCommand,
|
||||
) -> Result<RendererCommandTurnOutput, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_input_command(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_autofill_trigger(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
request: RendererAutofillTriggerRequest,
|
||||
) -> Result<PendingDocumentAutofillTrigger, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_autofill_trigger(document, request)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_autofill_trigger(
|
||||
&mut self,
|
||||
completed: CompletedDocumentAutofillTrigger,
|
||||
) -> Result<RendererAutofillTriggerOutcome, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_autofill_trigger(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_lifecycle_stop(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentLifecycleStop, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_lifecycle_stop(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_lifecycle_stop(
|
||||
&mut self,
|
||||
completed: CompletedDocumentLifecycleStop,
|
||||
) -> Result<RendererCommandTurnOutput, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_lifecycle_stop(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_diagnostics_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentDiagnosticsSnapshot, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_diagnostics_snapshot(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_diagnostics_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedDocumentDiagnosticsSnapshot,
|
||||
) -> Result<RendererPageDiagnosticsSnapshot, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_diagnostics_snapshot(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_child_frame_lifecycle_work(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
timeout: std::time::Duration,
|
||||
) -> Result<PendingChildFrameLifecycleWork, String> {
|
||||
self.browser_context_handle_by_id_mut(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_child_frame_lifecycle_work(document, timeout)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_child_frame_lifecycle_work(
|
||||
&mut self,
|
||||
completed: CompletedChildFrameLifecycleWork,
|
||||
) -> Result<(bool, RendererCommandTurnOutput), String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_child_frame_lifecycle_work(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_resource_runtime_update(
|
||||
&mut self,
|
||||
completed: CompletedDocumentResourceRuntimeUpdate,
|
||||
) -> Result<(), String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
match self.browser_context_handle_by_id_mut(context) {
|
||||
Some(context) => context.finish_document_resource_runtime_update(completed),
|
||||
None => moli_core::browser::BrowserContext::finish_unobserved_document_resource_runtime_update(completed),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn start_set_document_content(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
html: String,
|
||||
) -> Result<PendingSetDocumentContent, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_set_document_content(document, frame_id, html)
|
||||
}
|
||||
|
||||
pub(crate) fn set_document_javascript_dialog_handler_enabled(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
enabled: bool,
|
||||
) -> Result<(), String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.set_document_javascript_dialog_handler_enabled(document, enabled)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_policy_update(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
update: DocumentPolicyUpdate,
|
||||
) -> Result<PendingDocumentPolicyUpdate, String> {
|
||||
self.browser_context_handle_by_id_mut(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_policy_update(document, update)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_runtime_policy_reconciliation(
|
||||
&mut self,
|
||||
document: DocumentHandle,
|
||||
policy: DocumentRuntimePolicyReconciliation,
|
||||
) -> Result<PendingDocumentPolicyBatch, String> {
|
||||
Ok(self
|
||||
.browser_context_handle_by_id_mut(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_runtime_policy_reconciliation(document, policy))
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_policy_update(
|
||||
&mut self,
|
||||
completed: CompletedDocumentPolicyUpdate,
|
||||
) -> Result<(), String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_policy_update(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_policy_batch(
|
||||
&mut self,
|
||||
completed: CompletedDocumentPolicyBatch,
|
||||
) -> Result<(), String> {
|
||||
let context = completed.context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_policy_batch(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_set_document_content(
|
||||
&mut self,
|
||||
completed: CompletedSetDocumentContent,
|
||||
) -> Result<(RendererSetDocumentContentResult, RendererCommandTurnOutput), String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_set_document_content(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_top_level_same_document_navigation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
url: String,
|
||||
) -> Result<PendingTopLevelSameDocumentNavigation, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_top_level_same_document_navigation(document, url)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_top_level_same_document_navigation(
|
||||
&mut self,
|
||||
completed: CompletedTopLevelSameDocumentNavigation,
|
||||
) -> Result<(bool, RendererCommandTurnOutput), String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_top_level_same_document_navigation(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_top_level_history_traversal(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
delta: i64,
|
||||
) -> Result<PendingTopLevelHistoryTraversal, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_top_level_history_traversal(document, delta)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_top_level_history_traversal(
|
||||
&mut self,
|
||||
completed: CompletedTopLevelHistoryTraversal,
|
||||
) -> Result<bool, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_top_level_history_traversal(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_navigation_history_reset(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingNavigationHistoryReset, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_navigation_history_reset(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_navigation_history_reset(
|
||||
&mut self,
|
||||
completed: CompletedNavigationHistoryReset,
|
||||
) -> Result<bool, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_navigation_history_reset(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_child_frame_navigation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
url: String,
|
||||
) -> Result<PendingChildFrameNavigation, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_child_frame_navigation(document, frame_id, url)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_child_frame_navigation(
|
||||
&mut self,
|
||||
completed: CompletedChildFrameNavigation,
|
||||
) -> Result<(bool, RendererCommandTurnOutput), String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_child_frame_navigation(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_capture_document_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingCaptureDocumentSnapshot, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_capture_document_snapshot(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_capture_document_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedCaptureDocumentSnapshot,
|
||||
) -> Result<DocumentSnapshot, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_capture_document_snapshot(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_capture_document_image(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
request: RendererCaptureScreenshotRequest,
|
||||
) -> Result<PendingCaptureDocumentImage, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_capture_document_image(document, request)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_capture_document_image(
|
||||
&mut self,
|
||||
completed: CompletedCaptureDocumentImage,
|
||||
) -> Result<RendererCaptureScreenshotReply, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_capture_document_image(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_capture_document_screencast_frame(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
request: RendererCaptureScreencastFrameRequest,
|
||||
) -> Result<PendingCaptureDocumentScreencastFrame, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_capture_document_screencast_frame(document, request)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_capture_document_screencast_frame(
|
||||
&mut self,
|
||||
completed: CompletedCaptureDocumentScreencastFrame,
|
||||
) -> Result<RendererCaptureScreencastFrameReply, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_capture_document_screencast_frame(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_child_frame_document_resource_text_search(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
url: String,
|
||||
query: String,
|
||||
case_sensitive: bool,
|
||||
is_regex: bool,
|
||||
) -> Result<PendingDocumentResourceTextSearch, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_child_frame_document_resource_text_search(
|
||||
document,
|
||||
frame_id,
|
||||
url,
|
||||
query,
|
||||
case_sensitive,
|
||||
is_regex,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_text_search(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
text: String,
|
||||
query: String,
|
||||
case_sensitive: bool,
|
||||
is_regex: bool,
|
||||
) -> Result<PendingDocumentResourceTextSearch, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_text_search(document, text, query, case_sensitive, is_regex)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_resource_text_search(
|
||||
&mut self,
|
||||
completed: CompletedDocumentResourceTextSearch,
|
||||
) -> Result<RendererResourceTextSearchOutcome, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_resource_text_search(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn document_subresource_network_records(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<Vec<SubresourceNetworkRecord>, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.document_subresource_network_records(document)
|
||||
}
|
||||
|
||||
pub(crate) fn ensure_browser_document_current(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<(), String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.ensure_document_current(document)
|
||||
}
|
||||
|
||||
pub(crate) fn document_response_headers(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<Vec<(String, String)>, String> {
|
||||
Ok(self
|
||||
.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.document_response_headers(document)?
|
||||
.to_vec())
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_csp_bypass_update(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
bypass: bool,
|
||||
) -> Result<PendingDocumentCspBypassUpdate, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_csp_bypass_update(document, bypass)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_csp_bypass_update(
|
||||
&mut self,
|
||||
completed: CompletedDocumentCspBypassUpdate,
|
||||
) -> Result<(), String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_csp_bypass_update(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_storage_key_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentStorageKeySnapshot, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_storage_key_snapshot(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_storage_key_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedDocumentStorageKeySnapshot,
|
||||
) -> Result<String, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_storage_key_snapshot(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_child_frame_tree_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingChildFrameTreeSnapshot, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_child_frame_tree_snapshot(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_child_frame_tree_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedChildFrameTreeSnapshot,
|
||||
) -> Result<Vec<ChildFrameTreeSnapshot>, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_child_frame_tree_snapshot(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_cookie_owner_snapshot(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingDocumentCookieOwnerSnapshot, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_cookie_owner_snapshot(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_cookie_owner_snapshot(
|
||||
&mut self,
|
||||
completed: CompletedDocumentCookieOwnerSnapshot,
|
||||
) -> Result<DocumentCookieOwnerSnapshot, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_cookie_owner_snapshot(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_document_blob_read(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
uuid: String,
|
||||
) -> Result<PendingDocumentBlobRead, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_document_blob_read(document, uuid)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_document_blob_read(
|
||||
&mut self,
|
||||
completed: CompletedDocumentBlobRead,
|
||||
) -> Result<Option<Arc<[u8]>>, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_document_blob_read(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_network_resource_load_preparation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
frame_id: String,
|
||||
url: Url,
|
||||
disable_cache: bool,
|
||||
include_credentials: bool,
|
||||
) -> Result<PendingNetworkResourceLoadPreparation, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_network_resource_load_preparation(
|
||||
document,
|
||||
frame_id,
|
||||
url,
|
||||
disable_cache,
|
||||
include_credentials,
|
||||
)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_network_resource_load_preparation(
|
||||
&mut self,
|
||||
completed: CompletedNetworkResourceLoadPreparation,
|
||||
) -> Result<RendererNetworkResourceLoadPreparation, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_network_resource_load_preparation(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_app_manifest_load_preparation(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
) -> Result<PendingAppManifestLoadPreparation, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_app_manifest_load_preparation(document)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_app_manifest_load_preparation(
|
||||
&mut self,
|
||||
completed: CompletedAppManifestLoadPreparation,
|
||||
) -> Result<BrowserAppManifestLoadPreparation, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_app_manifest_load_preparation(completed)
|
||||
}
|
||||
|
||||
pub(crate) fn start_app_manifest_publication(
|
||||
&self,
|
||||
document: DocumentHandle,
|
||||
publication: RendererAppManifestLoadPublication,
|
||||
) -> Result<PendingAppManifestPublication, String> {
|
||||
self.browser_context_handle_by_id(document.web_contents().context())
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.start_app_manifest_publication(document, publication)
|
||||
}
|
||||
|
||||
pub(crate) fn finish_app_manifest_publication(
|
||||
&mut self,
|
||||
completed: CompletedAppManifestPublication,
|
||||
) -> Result<RendererCommandTurnOutput, String> {
|
||||
let context = completed.document().web_contents().context();
|
||||
self.browser_context_handle_by_id_mut(context)
|
||||
.ok_or_else(|| "NoDocumentLoaded".to_owned())?
|
||||
.finish_app_manifest_publication(completed)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
use moli_core::browser::WebContentsHandle;
|
||||
|
||||
use crate::conn::{
|
||||
CdpConnection, CommandOwnerScope, TargetPageResidenceIdentity, WindowSurface,
|
||||
WindowSurfaceState,
|
||||
};
|
||||
|
||||
impl CdpConnection {
|
||||
/// Resolves a frontend route once into a stable physical WebContents
|
||||
/// capability. Browser operations below never receive Target or Session
|
||||
/// identifiers.
|
||||
pub(crate) fn browser_web_contents_for_owner(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Result<WebContentsHandle, String> {
|
||||
let (context_id, target_id) = self
|
||||
.target_owner_identity_for_owner(owner)
|
||||
.ok_or_else(|| "BrowserContextNotLoaded".to_owned())?;
|
||||
let context = self
|
||||
.browser_context_by_id(&context_id)
|
||||
.ok_or_else(|| "BrowserContextNotLoaded".to_owned())?;
|
||||
match target_id {
|
||||
Some(target_id) => context
|
||||
.web_contents_handle_for_target(&target_id)
|
||||
.ok_or_else(|| "NoSuchTarget".to_owned()),
|
||||
None => context
|
||||
.selected_web_contents_handle()
|
||||
.ok_or_else(|| "NoSuchTarget".to_owned()),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn browser_web_contents_for_target(
|
||||
&self,
|
||||
target_id: &str,
|
||||
) -> Result<WebContentsHandle, String> {
|
||||
self.browser_contexts()
|
||||
.find_map(|context| context.web_contents_handle_for_target(target_id))
|
||||
.ok_or_else(|| "NoSuchTarget".to_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn browser_web_contents_for_page_residence(
|
||||
&self,
|
||||
page: &TargetPageResidenceIdentity,
|
||||
) -> Result<WebContentsHandle, String> {
|
||||
if !self.target_page_residence_identity_is_current(page) {
|
||||
return Err("WebContents unavailable".to_owned());
|
||||
}
|
||||
let target_id = page
|
||||
.target_id()
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())?;
|
||||
self.browser_context_by_id(page.browser_context_id())
|
||||
.and_then(|context| context.web_contents_handle_for_target(target_id))
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn browser_web_contents_for_window_id(
|
||||
&self,
|
||||
window_id: u64,
|
||||
) -> Result<WebContentsHandle, String> {
|
||||
self.browser_contexts()
|
||||
.find_map(|context| context.web_contents_handle_for_window_id(window_id))
|
||||
.ok_or_else(|| "UnknownWindowId".to_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn browser_window_surface(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<WindowSurface, String> {
|
||||
self.browser_context_by_browser_id(handle.context())
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())?
|
||||
.web_contents_window_surface(handle)
|
||||
}
|
||||
|
||||
pub(crate) fn crash_browser_web_contents_renderer_from_io(
|
||||
&self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
self.browser_context_by_browser_id(handle.context())
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())?
|
||||
.browser_context
|
||||
.crash_web_contents_renderer_from_io(handle)
|
||||
}
|
||||
|
||||
#[allow(clippy::too_many_arguments)]
|
||||
pub(crate) fn update_browser_window_surface(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
state: Option<WindowSurfaceState>,
|
||||
width: Option<u32>,
|
||||
height: Option<u32>,
|
||||
x: Option<i32>,
|
||||
y: Option<i32>,
|
||||
) -> Result<(), String> {
|
||||
self.browser_context_by_browser_id_mut(handle.context())
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())?
|
||||
.update_web_contents_window_surface(handle, state, width, height, x, y)
|
||||
}
|
||||
|
||||
pub(crate) async fn select_browser_web_contents_async(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
) -> Result<(), String> {
|
||||
let browser_globals = self.browser_global_overrides.clone();
|
||||
let pending = self
|
||||
.browser_context_by_browser_id_mut(handle.context())
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())?
|
||||
.start_select_web_contents(handle, &browser_globals)?;
|
||||
let completed = pending.wait().await;
|
||||
self.browser_context_by_browser_id_mut(handle.context())
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())?
|
||||
.finish_select_web_contents(completed)
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_browser_page_surface_async(
|
||||
&mut self,
|
||||
handle: WebContentsHandle,
|
||||
foreground: bool,
|
||||
) -> Result<bool, String> {
|
||||
let browser_globals = self.browser_global_overrides.clone();
|
||||
let pending = {
|
||||
let context = self
|
||||
.browser_context_by_browser_id_mut(handle.context())
|
||||
.ok_or_else(|| "WebContents unavailable".to_owned())?;
|
||||
let Some(document) = context.document_handle_for_web_contents(handle)? else {
|
||||
return Ok(false);
|
||||
};
|
||||
context.start_document_page_surface_update(
|
||||
document,
|
||||
foreground,
|
||||
browser_globals.network_conditions,
|
||||
browser_globals.geolocation.as_ref(),
|
||||
)?
|
||||
};
|
||||
let completed = pending.wait().await;
|
||||
self.finish_document_policy_batch(completed)?;
|
||||
Ok(true)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,86 @@
|
||||
use moli_core::browser::{BrowserContextId, ServiceWorkerCommand};
|
||||
use moli_shared_worker::SharedWorkerInstanceId;
|
||||
|
||||
use super::CdpConnection;
|
||||
|
||||
impl CdpConnection {
|
||||
pub(crate) fn browser_context_handle_for_devtools_id(
|
||||
&self,
|
||||
browser_context_id: &str,
|
||||
) -> Result<BrowserContextId, String> {
|
||||
self.browser_context_by_id(browser_context_id)
|
||||
.map(super::BrowserContext::browser_context_id)
|
||||
.ok_or_else(|| "BrowserContext unavailable".to_owned())
|
||||
}
|
||||
|
||||
pub(crate) fn execute_browser_service_worker_command(
|
||||
&self,
|
||||
context: BrowserContextId,
|
||||
command: ServiceWorkerCommand,
|
||||
) -> Result<(), String> {
|
||||
self.browser_context_by_browser_id(context)
|
||||
.ok_or_else(|| "BrowserContext unavailable".to_owned())?
|
||||
.execute_service_worker_command(command)
|
||||
}
|
||||
|
||||
pub(crate) fn close_browser_shared_worker(
|
||||
&self,
|
||||
context: BrowserContextId,
|
||||
instance_id: SharedWorkerInstanceId,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.browser_context_by_browser_id(context)
|
||||
.ok_or_else(|| "BrowserContext unavailable".to_owned())?
|
||||
.close_shared_worker(instance_id))
|
||||
}
|
||||
|
||||
pub(crate) fn close_browser_dedicated_worker(
|
||||
&self,
|
||||
context: BrowserContextId,
|
||||
instance_id: u64,
|
||||
) -> Result<bool, String> {
|
||||
Ok(self
|
||||
.browser_context_by_browser_id(context)
|
||||
.ok_or_else(|| "BrowserContext unavailable".to_owned())?
|
||||
.close_dedicated_worker(instance_id))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn service_worker_control_rejects_a_replaced_devtools_context_id() {
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.install_browser_context_fixture_for_test(
|
||||
conn.new_browser_context_fixture_for_test("BID-worker-control"),
|
||||
);
|
||||
let original = conn
|
||||
.browser_context_handle_for_devtools_id("BID-worker-control")
|
||||
.unwrap();
|
||||
|
||||
conn.install_browser_context_fixture_for_test(
|
||||
conn.new_browser_context_fixture_for_test("BID-worker-control"),
|
||||
);
|
||||
let replacement = conn
|
||||
.browser_context_handle_for_devtools_id("BID-worker-control")
|
||||
.unwrap();
|
||||
assert_ne!(original, replacement);
|
||||
|
||||
assert_eq!(
|
||||
conn.execute_browser_service_worker_command(
|
||||
original,
|
||||
ServiceWorkerCommand::SetForceUpdateOnPageLoad(true),
|
||||
),
|
||||
Err("BrowserContext unavailable".into())
|
||||
);
|
||||
assert!(
|
||||
!conn
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.service_worker_force_update_on_page_load()
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -102,7 +102,7 @@ impl CommandOwnerScope {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::conn::BrowserContext;
|
||||
|
||||
use crate::devtools_runtime::{
|
||||
DevToolsCommandContext, DevToolsProtocol, DevToolsSessionId, DevToolsTargetId,
|
||||
};
|
||||
@@ -118,8 +118,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn root_page_scope_freezes_the_concrete_active_target() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new("BID-scope".to_owned());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context = conn.new_browser_context_fixture_for_test("BID-scope".to_owned());
|
||||
browser_context.set_active_target_id("TID-original");
|
||||
conn.install_browser_context_fixture_for_test(browser_context);
|
||||
|
||||
@@ -137,7 +137,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn root_scope_without_a_browser_context_has_explicit_browser_authority() {
|
||||
let conn = CdpConnection::default();
|
||||
let conn = crate::test_support::connection();
|
||||
|
||||
let scope = CommandOwnerScope::capture(&conn, None);
|
||||
|
||||
@@ -146,8 +146,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn root_scope_without_a_page_has_explicit_browser_context_authority() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("BID-empty".to_owned()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.browser_context =
|
||||
Some(conn.new_browser_context_fixture_for_test("BID-empty".to_owned()));
|
||||
|
||||
let scope = CommandOwnerScope::capture(&conn, None);
|
||||
|
||||
@@ -161,8 +162,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn explicit_target_preserves_matching_attached_session_authority() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context = BrowserContext::new_with_page_for_test("BID-owner", "TID-owner");
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context = conn.new_page_target_fixture_for_test("BID-owner", "TID-owner");
|
||||
browser_context.attach_active_session("SID-primary");
|
||||
assert!(
|
||||
browser_context
|
||||
@@ -180,9 +181,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn explicit_target_routes_protocol_neutral_session_and_rejects_wrong_cdp_attachment() {
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut browser_context =
|
||||
BrowserContext::new_with_page_for_test("BID-owner", "TID-primary");
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut browser_context = conn.new_page_target_fixture_for_test("BID-owner", "TID-primary");
|
||||
browser_context.attach_active_session("SID-primary");
|
||||
assert!(browser_context.register_page_target_url_fixture(
|
||||
"TID-background".to_owned(),
|
||||
|
||||
@@ -7,8 +7,6 @@ use moli_cookie_jar::{
|
||||
};
|
||||
use url::Url;
|
||||
|
||||
#[cfg(test)]
|
||||
use moli_core::page::Page;
|
||||
use moli_core::page::{
|
||||
DocumentCookieBrowserContextSnapshot, DocumentCookieCacheLookupResult,
|
||||
DocumentCookieFacadeTelemetrySnapshot, DocumentCookieFirstOperation,
|
||||
@@ -983,8 +981,4 @@ impl BrowserContextCookieManagerSurface {
|
||||
pub(crate) fn clear_policy_browser_context_overrides(&mut self) -> bool {
|
||||
self.policy_surface.clear_browser_context_overrides()
|
||||
}
|
||||
|
||||
pub(crate) async fn apply_to_page_async(&self, page: &mut Page) {
|
||||
self.policy_surface.apply_to_page_async(page).await;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -198,22 +198,14 @@ fn browser_context_cookie_manager_surface_snapshot(
|
||||
let snapshot = browser_context.raw_cookie_manager_surface_snapshot();
|
||||
#[cfg(not(test))]
|
||||
let snapshot = BrowserContextCookieManagerSurfaceSnapshot::default();
|
||||
let current_document_url = browser_context
|
||||
.loaded_page()
|
||||
.map(|page| page.final_url().clone());
|
||||
let navigation_initiator_url = browser_context
|
||||
.loaded_page()
|
||||
.and_then(|page| page.navigation_initiator_url().cloned());
|
||||
let requested_document_url = browser_context
|
||||
.loaded_page()
|
||||
.map(|page| page.requested_url().clone());
|
||||
let navigation_was_redirected = browser_context
|
||||
.loaded_page()
|
||||
.is_some_and(|page| page.navigation_redirected());
|
||||
let navigation_redirect_count = browser_context
|
||||
.loaded_page()
|
||||
.map(|page| page.navigation_redirect_count())
|
||||
.unwrap_or(0);
|
||||
let navigation = browser_context.selected_document_navigation_metadata();
|
||||
let current_document_url = navigation.as_ref().map(|state| state.current_url.clone());
|
||||
let navigation_initiator_url = navigation
|
||||
.as_ref()
|
||||
.and_then(|state| state.initiator_url.clone());
|
||||
let requested_document_url = navigation.as_ref().map(|state| state.requested_url.clone());
|
||||
let navigation_was_redirected = navigation.as_ref().is_some_and(|state| state.redirected);
|
||||
let navigation_redirect_count = navigation.as_ref().map_or(0, |state| state.redirect_count);
|
||||
let (default_cookie_write_url, default_cookie_write_url_source) =
|
||||
browser_context.default_cookie_write_url_with_source();
|
||||
let structured_write_backend_status = browser_context_structured_cookie_write_backend_status();
|
||||
@@ -368,9 +360,7 @@ impl BrowserContext {
|
||||
return report;
|
||||
}
|
||||
|
||||
self.with_cookie_store_mut(|store| {
|
||||
store.upsert_with_request_url_report(cookie, request_url.as_ref(), CookieSource::Cdp)
|
||||
})
|
||||
self.store_cookie(cookie, request_url.as_ref(), CookieSource::Management)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@@ -546,12 +536,13 @@ impl BrowserContext {
|
||||
&mut self,
|
||||
) -> Option<DocumentCookieOwnerSnapshot> {
|
||||
let target_id = self.active_target_id_owned()?;
|
||||
let pending = self
|
||||
.start_document_cookie_owner_snapshot_for_target(&target_id)
|
||||
.ok()?;
|
||||
let completion = pending.wait().await.ok()?;
|
||||
self.finish_document_cookie_owner_snapshot_for_target(&target_id, completion)
|
||||
.ok()
|
||||
let document = self.document_handle_for_target(&target_id)?;
|
||||
let completion = self
|
||||
.start_document_cookie_owner_snapshot(document)
|
||||
.ok()?
|
||||
.wait()
|
||||
.await;
|
||||
self.finish_document_cookie_owner_snapshot(completion).ok()
|
||||
}
|
||||
|
||||
pub(super) fn default_cookie_write_url_with_source(
|
||||
@@ -562,8 +553,8 @@ impl BrowserContext {
|
||||
// document URL into every `set()` call. Prefer the live page URL when
|
||||
// one exists; otherwise fall back to the BrowserContext's current URL.
|
||||
if let Some(url) = self
|
||||
.loaded_page()
|
||||
.map(|page| page.final_url().clone())
|
||||
.selected_document_navigation_metadata()
|
||||
.map(|state| state.current_url)
|
||||
.filter(|url| matches!(url.scheme(), "http" | "https"))
|
||||
{
|
||||
return (
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
use moli_cookie_jar::{BrowserCookieFacadeContextOverrides, BrowserCookieFacadeOverrides};
|
||||
#[cfg(test)]
|
||||
use moli_core::page::Page;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||
pub(crate) struct BrowserContextDocumentCookiePolicySurfaceSnapshot {
|
||||
@@ -26,11 +24,6 @@ impl BrowserContextDocumentCookiePolicySurface {
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn overrides(&self) -> &BrowserCookieFacadeOverrides {
|
||||
&self.overrides
|
||||
}
|
||||
|
||||
pub(crate) fn cookies_enabled_override(&self) -> Option<bool> {
|
||||
self.overrides.cookies_enabled
|
||||
}
|
||||
@@ -95,17 +88,4 @@ impl BrowserContextDocumentCookiePolicySurface {
|
||||
overrides.storage_access_status = None;
|
||||
self.set_overrides(&overrides)
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) async fn apply_to_page_async(&self, page: &mut Page) {
|
||||
if self.overrides == BrowserCookieFacadeOverrides::default() {
|
||||
// See the sync variant above for why empty policy means clearing
|
||||
// the live document facade rather than applying empty overrides.
|
||||
let _ = page.clear_document_cookie_facade_overrides_async().await;
|
||||
} else {
|
||||
let _ = page
|
||||
.apply_document_cookie_facade_overrides_async(self.overrides())
|
||||
.await;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,5 @@
|
||||
use super::CdpConnection;
|
||||
use moli_core::browser::{DownloadBehavior, DownloadPolicy};
|
||||
use moli_core::browser::{BrowserContextId, DownloadBehavior, DownloadPolicy};
|
||||
use std::collections::HashMap;
|
||||
|
||||
pub(crate) fn parse_download_behavior(value: &str) -> Option<DownloadBehavior> {
|
||||
@@ -114,16 +114,18 @@ impl CdpConnection {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn download_policy_for_browser_context(
|
||||
&self,
|
||||
context_id: Option<&str>,
|
||||
) -> &DownloadPolicy {
|
||||
) -> DownloadPolicy {
|
||||
context_id
|
||||
.and_then(|id| self.browser_context_by_id(id))
|
||||
.and_then(|context| context.download_policy())
|
||||
.unwrap_or(&self.download_policy)
|
||||
.unwrap_or_else(|| self.download_policy.clone())
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
pub(crate) fn automation_download_events_enabled_for_context(
|
||||
&self,
|
||||
context_id: Option<&str>,
|
||||
@@ -134,6 +136,21 @@ impl CdpConnection {
|
||||
.unwrap_or(self.download_subscriptions.automation_events_enabled)
|
||||
}
|
||||
|
||||
pub(crate) fn download_configuration_for_browser_context(
|
||||
&self,
|
||||
context: BrowserContextId,
|
||||
) -> Option<(DownloadPolicy, bool)> {
|
||||
let context = self.browser_context_by_browser_id(context)?;
|
||||
Some((
|
||||
context
|
||||
.download_policy()
|
||||
.unwrap_or_else(|| self.download_policy.clone()),
|
||||
context
|
||||
.automation_download_events_enabled
|
||||
.unwrap_or(self.download_subscriptions.automation_events_enabled),
|
||||
))
|
||||
}
|
||||
|
||||
pub(crate) fn set_browser_download_events_enabled_for_session(
|
||||
&mut self,
|
||||
session_id: Option<&str>,
|
||||
|
||||
@@ -12,10 +12,10 @@ fn command(conn: &mut CdpConnection, method: &str, params: Value) {
|
||||
|
||||
#[test]
|
||||
fn download_policy_scopes_preserve_observer_shadowing_and_reset_fallback() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("first".into()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.browser_context = Some(conn.new_browser_context_fixture_for_test("first"));
|
||||
conn.inactive_browser_contexts
|
||||
.push(BrowserContext::new("second".into()));
|
||||
.push(conn.new_browser_context_fixture_for_test("second"));
|
||||
conn.configure_download_policy(
|
||||
None,
|
||||
DownloadPolicy {
|
||||
@@ -63,7 +63,7 @@ fn download_policy_scopes_preserve_observer_shadowing_and_reset_fallback() {
|
||||
conn.reset_download_policy(Some("first")).unwrap();
|
||||
assert_eq!(
|
||||
conn.download_policy_for_browser_context(Some("first")),
|
||||
&second
|
||||
second
|
||||
);
|
||||
assert!(conn.automation_download_events_enabled_for_context(Some("first")));
|
||||
|
||||
@@ -79,7 +79,7 @@ fn download_policy_scopes_preserve_observer_shadowing_and_reset_fallback() {
|
||||
conn.reset_download_policy(None).unwrap();
|
||||
assert_eq!(
|
||||
conn.download_policy_for_browser_context(Some("first")),
|
||||
&DownloadPolicy::default()
|
||||
DownloadPolicy::default()
|
||||
);
|
||||
assert!(!conn.automation_download_events_enabled_for_context(Some("first")));
|
||||
assert_eq!(
|
||||
@@ -91,7 +91,7 @@ fn download_policy_scopes_preserve_observer_shadowing_and_reset_fallback() {
|
||||
|
||||
#[test]
|
||||
fn download_configuration_is_lazy_and_missing_allow_path_remains_explicit() {
|
||||
let mut conn = CdpConnection::new_with_deferred_navigation_runtime(
|
||||
let mut conn = crate::test_support::connection_with_config(
|
||||
CdpInitialStoragePartition::memory(),
|
||||
NavigationRuntimeConfig::default(),
|
||||
);
|
||||
@@ -101,7 +101,10 @@ fn download_configuration_is_lazy_and_missing_allow_path_remains_explicit() {
|
||||
json!({"behavior": "allow"}),
|
||||
);
|
||||
assert!(conn.browser_context.is_none());
|
||||
assert!(!conn.standalone_navigation_engine.is_materialized());
|
||||
assert_eq!(
|
||||
conn.moli_memory_diagnostics()["isolateScope"]["estimatedRendererOwnerCount"],
|
||||
json!(0)
|
||||
);
|
||||
assert_eq!(
|
||||
conn.download_policy_for_browser_context(None).behavior,
|
||||
DownloadBehavior::Allow
|
||||
@@ -111,7 +114,7 @@ fn download_configuration_is_lazy_and_missing_allow_path_remains_explicit() {
|
||||
.download_path
|
||||
.is_none()
|
||||
);
|
||||
conn.browser_context = Some(BrowserContext::new("later".into()));
|
||||
conn.browser_context = Some(conn.new_browser_context_fixture_for_test("later"));
|
||||
assert_eq!(
|
||||
conn.download_policy_for_browser_context(Some("later"))
|
||||
.behavior,
|
||||
@@ -121,8 +124,8 @@ fn download_configuration_is_lazy_and_missing_allow_path_remains_explicit() {
|
||||
|
||||
#[test]
|
||||
fn download_policy_leaves_with_its_context_without_disposal_cleanup() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("same-context".into()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.browser_context = Some(conn.new_browser_context_fixture_for_test("same-context"));
|
||||
let policy = DownloadPolicy {
|
||||
behavior: DownloadBehavior::AllowAndName,
|
||||
download_path: Some("/owned".into()),
|
||||
@@ -133,13 +136,13 @@ fn download_policy_leaves_with_its_context_without_disposal_cleanup() {
|
||||
|
||||
let removed = conn
|
||||
.browser_context
|
||||
.replace(BrowserContext::new("same-context".into()))
|
||||
.replace(conn.new_browser_context_fixture_for_test("same-context"))
|
||||
.unwrap();
|
||||
assert_eq!(removed.download_policy(), Some(&policy));
|
||||
assert_eq!(removed.download_policy(), Some(policy));
|
||||
assert_eq!(removed.automation_download_events_enabled, Some(true));
|
||||
assert_eq!(
|
||||
conn.download_policy_for_browser_context(Some("same-context")),
|
||||
&DownloadPolicy::default()
|
||||
DownloadPolicy::default()
|
||||
);
|
||||
assert!(!conn.automation_download_events_enabled_for_context(Some("same-context")));
|
||||
assert_eq!(
|
||||
@@ -182,7 +185,7 @@ async fn detaching_a_download_observer_preserves_browser_context_policy() {
|
||||
assert_eq!(
|
||||
ctx.conn
|
||||
.download_policy_for_browser_context(Some("CTX-download")),
|
||||
&policy
|
||||
policy
|
||||
);
|
||||
assert!(
|
||||
!ctx.conn
|
||||
|
||||
+123
-103
@@ -1,5 +1,6 @@
|
||||
use moli_core::browser::{
|
||||
DownloadAccessError, DownloadBody, DownloadObservation, DownloadSnapshot, DownloadState,
|
||||
DownloadAccessError, DownloadBody, DownloadObservation, DownloadPolicy, DownloadSnapshot,
|
||||
DownloadState, WebContentsHandle,
|
||||
};
|
||||
use moli_core::page::RendererPendingDownloadActivation;
|
||||
use moli_fetch::Request;
|
||||
@@ -14,27 +15,31 @@ use super::{
|
||||
#[path = "downloads/lifecycle_tests.rs"]
|
||||
mod lifecycle_tests;
|
||||
|
||||
struct PendingDownloadOwnerContext {
|
||||
browser_context_id: String,
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
pub(crate) struct PreparedDownloadActivation {
|
||||
web_contents: WebContentsHandle,
|
||||
frame_id: String,
|
||||
request_headers: moli_fetch::RequestHeaders,
|
||||
initiator_url: Option<Url>,
|
||||
policy: DownloadPolicy,
|
||||
event_route: DownloadEventRoute,
|
||||
activation: RendererPendingDownloadActivation,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct DownloadEventRoute {
|
||||
browser_observers: Vec<BrowserDownloadObserver>,
|
||||
automation_events_enabled: bool,
|
||||
page_observers: Vec<PageDownloadObserver>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct BrowserDownloadObserver {
|
||||
session_id: Option<String>,
|
||||
subscription_generation: u64,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
#[derive(Debug, Eq, PartialEq)]
|
||||
struct PageDownloadObserver {
|
||||
session_id: Option<String>,
|
||||
subscription_generation: u64,
|
||||
@@ -49,56 +54,89 @@ impl DownloadEventRoute {
|
||||
}
|
||||
|
||||
impl CdpConnection {
|
||||
pub(crate) async fn handle_pending_download_activation_background_events_async(
|
||||
&mut self,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
pub(crate) fn prepare_download_activation_for_owner(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
activation: RendererPendingDownloadActivation,
|
||||
command_context: &mut CommandDispatchContext,
|
||||
) -> Result<(), String> {
|
||||
self.handle_pending_download_activation_with_event_route_async(
|
||||
out,
|
||||
owner,
|
||||
activation,
|
||||
true,
|
||||
command_context,
|
||||
)
|
||||
.await
|
||||
) -> Option<PreparedDownloadActivation> {
|
||||
let web_contents = self.browser_web_contents_for_owner(owner).ok()?;
|
||||
self.prepare_download_activation(owner, web_contents, activation)
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_pending_download_activation_inline_async(
|
||||
&mut self,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
pub(crate) fn prepare_download_activation(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
web_contents: WebContentsHandle,
|
||||
activation: RendererPendingDownloadActivation,
|
||||
command_context: &mut CommandDispatchContext,
|
||||
) -> Result<(), String> {
|
||||
self.handle_pending_download_activation_with_event_route_async(
|
||||
out,
|
||||
owner,
|
||||
) -> Option<PreparedDownloadActivation> {
|
||||
let context = self.browser_context_by_browser_id(web_contents.context())?;
|
||||
let frame_id = context
|
||||
.download_frame_id_for_web_contents(web_contents)?
|
||||
.to_owned();
|
||||
let request_headers = context.effective_extra_headers_for_target(
|
||||
&frame_id,
|
||||
&self.browser_global_overrides.extra_headers,
|
||||
);
|
||||
let initiator_url = context.target_document_url(&frame_id);
|
||||
let (policy, automation_events_enabled) =
|
||||
self.download_configuration_for_browser_context(web_contents.context())?;
|
||||
let event_route = self.download_event_route(owner, automation_events_enabled);
|
||||
Some(PreparedDownloadActivation {
|
||||
web_contents,
|
||||
frame_id,
|
||||
request_headers,
|
||||
initiator_url,
|
||||
policy,
|
||||
event_route,
|
||||
activation,
|
||||
false,
|
||||
command_context,
|
||||
)
|
||||
.await
|
||||
})
|
||||
}
|
||||
|
||||
async fn handle_pending_download_activation_with_event_route_async(
|
||||
pub(crate) async fn handle_prepared_download_activation_background_events_async(
|
||||
&mut self,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
owner: &CommandOwnerScope,
|
||||
activation: RendererPendingDownloadActivation,
|
||||
activation: PreparedDownloadActivation,
|
||||
command_context: &mut CommandDispatchContext,
|
||||
) -> Result<(), String> {
|
||||
self.handle_prepared_download_activation_async(out, activation, true, command_context)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(crate) async fn handle_prepared_download_activation_inline_async(
|
||||
&mut self,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
activation: PreparedDownloadActivation,
|
||||
command_context: &mut CommandDispatchContext,
|
||||
) -> Result<(), String> {
|
||||
self.handle_prepared_download_activation_async(out, activation, false, command_context)
|
||||
.await
|
||||
}
|
||||
|
||||
async fn handle_prepared_download_activation_async(
|
||||
&mut self,
|
||||
out: &mut Vec<BackgroundProtocolEvent>,
|
||||
prepared: PreparedDownloadActivation,
|
||||
allow_background_events: bool,
|
||||
command_context: &mut CommandDispatchContext,
|
||||
) -> Result<(), String> {
|
||||
let Some(context) = self.pending_download_owner_context(owner) else {
|
||||
let PreparedDownloadActivation {
|
||||
web_contents,
|
||||
frame_id,
|
||||
request_headers,
|
||||
initiator_url,
|
||||
policy,
|
||||
event_route,
|
||||
activation,
|
||||
} = prepared;
|
||||
let Some(context) = self.browser_context_by_browser_id(web_contents.context()) else {
|
||||
return Ok(());
|
||||
};
|
||||
let event_route = self.download_event_route(
|
||||
owner,
|
||||
self.automation_download_events_enabled_for_context(Some(&context.browser_context_id)),
|
||||
);
|
||||
let policy = self.download_policy_for_browser_context(Some(&context.browser_context_id));
|
||||
if context
|
||||
.download_frame_id_for_web_contents(web_contents)
|
||||
.is_none()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let observation = if policy.behavior.is_canceled_without_download() {
|
||||
if !event_route.has_observers() {
|
||||
return Ok(());
|
||||
@@ -117,15 +155,15 @@ impl CdpConnection {
|
||||
if policy.download_path.is_none() {
|
||||
return Ok(());
|
||||
}
|
||||
let default_policy = self.download_policy.clone();
|
||||
if let Some(response) = activation.response {
|
||||
let url = Url::parse(&response.final_url)
|
||||
.or_else(|_| Url::parse(&activation.url))
|
||||
.map_err(|error| format!("invalid download url: {error}"))?;
|
||||
self.browser_context_by_id_mut(&context.browser_context_id)
|
||||
.expect("download owner was resolved without yielding")
|
||||
self.browser_context_by_browser_id_mut(web_contents.context())
|
||||
.expect("exact download Context was resolved without yielding")
|
||||
.start_download_response(
|
||||
&default_policy,
|
||||
web_contents,
|
||||
&policy,
|
||||
url,
|
||||
response.headers,
|
||||
DownloadBody::Buffered(response.body),
|
||||
@@ -133,41 +171,36 @@ impl CdpConnection {
|
||||
} else {
|
||||
let mut request = Request::get(&activation.url)
|
||||
.map_err(|error| format!("invalid download url: {error}"))?;
|
||||
request.request_headers = context.request_headers;
|
||||
request.request_headers = request_headers;
|
||||
request = request
|
||||
.with_top_level_navigation_cookie_context()
|
||||
.with_page_network_policy()
|
||||
.with_request_origin(
|
||||
context
|
||||
.initiator_url
|
||||
initiator_url
|
||||
.as_ref()
|
||||
.map_or(moli_url::WebOrigin::Opaque, moli_url::WebOrigin::from_url),
|
||||
);
|
||||
if let Some(initiator_url) = &context.initiator_url {
|
||||
if let Some(initiator_url) = &initiator_url {
|
||||
request = request.with_initiator_url(initiator_url);
|
||||
}
|
||||
#[cfg(test)]
|
||||
self.ensure_page_navigation_engine_for_target(
|
||||
&context.browser_context_id,
|
||||
&context.frame_id,
|
||||
)
|
||||
.ok_or("navigation WebContents engine unavailable")?;
|
||||
let fetch_defaults = self.document_fetch_defaults();
|
||||
self.browser_context_by_id_mut(&context.browser_context_id)
|
||||
.expect("download owner was resolved without yielding")
|
||||
let browser_globals = self.browser_global_overrides.clone();
|
||||
self.browser_context_by_browser_id_mut(web_contents.context())
|
||||
.expect("exact download Context was resolved without yielding")
|
||||
.start_download_request(
|
||||
&context.frame_id,
|
||||
web_contents,
|
||||
fetch_defaults,
|
||||
&default_policy,
|
||||
&policy,
|
||||
request,
|
||||
activation.suggested_filename,
|
||||
&browser_globals,
|
||||
)?
|
||||
}
|
||||
};
|
||||
if let Some(observation) = observation {
|
||||
self.observe_download(
|
||||
DownloadProjection {
|
||||
frame_id: context.frame_id,
|
||||
frame_id,
|
||||
event_route,
|
||||
observation,
|
||||
started: false,
|
||||
@@ -189,24 +222,27 @@ impl CdpConnection {
|
||||
body_artifact: CompletedDownloadBodyArtifact,
|
||||
command_context: &mut CommandDispatchContext,
|
||||
) -> Result<(), String> {
|
||||
// The frozen initiating frame, not a subsequently selected Context or a
|
||||
// still-attached session, identifies the navigation's Browser residence.
|
||||
let Some(context_id) = self
|
||||
.browser_context_id_for_target(&state.frame_id)
|
||||
.map(str::to_owned)
|
||||
let web_contents = state.web_contents;
|
||||
let Some(context) = self.browser_context_by_browser_id(web_contents.context()) else {
|
||||
return Ok(());
|
||||
};
|
||||
if context
|
||||
.download_frame_id_for_web_contents(web_contents)
|
||||
.is_none()
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
let Some((policy, automation_events_enabled)) =
|
||||
self.download_configuration_for_browser_context(web_contents.context())
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
let event_route = self.download_event_route(
|
||||
&state.owner,
|
||||
self.automation_download_events_enabled_for_context(Some(&context_id)),
|
||||
);
|
||||
let default_policy = self.download_policy.clone();
|
||||
let event_route = self.download_event_route(&state.owner, automation_events_enabled);
|
||||
let (body, headers) = body_artifact.into_parts();
|
||||
let observation = self
|
||||
.browser_context_by_id_mut(&context_id)
|
||||
.expect("download owner was resolved without yielding")
|
||||
.start_download_response(&default_policy, final_url, headers, body)?;
|
||||
.browser_context_by_browser_id_mut(web_contents.context())
|
||||
.expect("exact navigation download Context was resolved without yielding")
|
||||
.start_download_response(web_contents, &policy, final_url, headers, body)?;
|
||||
if let Some(observation) = observation {
|
||||
self.observe_download(
|
||||
DownloadProjection {
|
||||
@@ -292,31 +328,6 @@ impl CdpConnection {
|
||||
self.open_global_io_stream(bytes)
|
||||
}
|
||||
|
||||
fn pending_download_owner_context(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> Option<PendingDownloadOwnerContext> {
|
||||
let (browser_context_id, target_id) = self.target_owner_identity_for_owner(owner)?;
|
||||
let browser_context = self.browser_context_by_id(&browser_context_id)?;
|
||||
let frame_id = target_id
|
||||
.clone()
|
||||
.or_else(|| browser_context.active_target_id_owned())
|
||||
.unwrap_or_else(|| "FRAME-0".to_owned());
|
||||
let request_headers = target_id
|
||||
.as_deref()
|
||||
.map(|target_id| browser_context.effective_extra_headers_for_target(target_id))
|
||||
.unwrap_or_else(|| browser_context.effective_extra_headers());
|
||||
let initiator_url = self
|
||||
.resolved_page_owner_identity_for_owner(owner)
|
||||
.and_then(|(_, target_id)| browser_context.target_document_url(&target_id).cloned());
|
||||
Some(PendingDownloadOwnerContext {
|
||||
browser_context_id,
|
||||
frame_id,
|
||||
request_headers,
|
||||
initiator_url,
|
||||
})
|
||||
}
|
||||
|
||||
fn download_event_route(
|
||||
&self,
|
||||
owner: &CommandOwnerScope,
|
||||
@@ -588,9 +599,10 @@ fn download_progress_event(
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use moli_core::browser::DownloadBehavior;
|
||||
use moli_core::page::RendererPendingDownloadActivation;
|
||||
|
||||
use crate::{
|
||||
conn::{BackgroundProtocolEvent, BrowserContext, CdpConnection, CommandOwnerScope},
|
||||
conn::{BackgroundProtocolEvent, BrowserContext, CommandOwnerScope},
|
||||
devtools_runtime::AutomationEvent,
|
||||
};
|
||||
|
||||
@@ -624,14 +636,15 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn background_download_uses_its_target_and_context_header_layers() {
|
||||
let mut connection = CdpConnection::new();
|
||||
let mut connection = crate::test_support::connection();
|
||||
let mut browser_context = BrowserContext::new("BID-download".to_owned());
|
||||
browser_context.set_active_target_id("TID-active");
|
||||
browser_context.set_default_extra_headers(
|
||||
vec![("X-Context-Default".to_owned(), "default".to_owned())].into(),
|
||||
);
|
||||
browser_context.global_extra_headers =
|
||||
vec![("X-Context-Global".to_owned(), "global".to_owned())].into();
|
||||
connection.set_global_extra_headers(
|
||||
vec![("X-Context-Global".to_owned(), "global".to_owned())].into(),
|
||||
);
|
||||
|
||||
assert!(browser_context.register_page_target_url_fixture(
|
||||
"TID-background".to_owned(),
|
||||
@@ -645,7 +658,14 @@ mod tests {
|
||||
connection.install_browser_context_fixture_for_test(browser_context);
|
||||
|
||||
let owner = connection
|
||||
.pending_download_owner_context(&CommandOwnerScope::for_session("SID-background"))
|
||||
.prepare_download_activation_for_owner(
|
||||
&CommandOwnerScope::for_session("SID-background"),
|
||||
RendererPendingDownloadActivation {
|
||||
url: "https://background.test/report.txt".to_owned(),
|
||||
suggested_filename: None,
|
||||
response: None,
|
||||
},
|
||||
)
|
||||
.expect("background session must resolve its Page target");
|
||||
assert_eq!(owner.frame_id, "TID-background");
|
||||
assert_eq!(
|
||||
@@ -795,7 +815,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn stale_browser_observer_does_not_suppress_automation_download_event() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.set_browser_download_events_enabled_for_session(Some("SID-browser"), true);
|
||||
let generation = conn.download_subscriptions.browser_event_observers()[0].1;
|
||||
let route = DownloadEventRoute {
|
||||
|
||||
@@ -25,8 +25,8 @@ impl Drop for TestDirectory {
|
||||
|
||||
fn fixture() -> (TestDirectory, CdpConnection) {
|
||||
let directory = TestDirectory::new();
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut source = BrowserContext::new("CTX-source".into());
|
||||
let mut conn = crate::test_support::connection();
|
||||
let mut source = conn.new_browser_context_fixture_for_test("CTX-source");
|
||||
source.set_active_target_id("TID-source");
|
||||
source.attach_active_session("SID-source");
|
||||
conn.install_browser_context_fixture_for_test(source);
|
||||
@@ -45,16 +45,22 @@ fn fixture() -> (TestDirectory, CdpConnection) {
|
||||
|
||||
fn projection(conn: &mut CdpConnection, body: DownloadBody) -> DownloadProjection {
|
||||
let owner = CommandOwnerScope::for_session("SID-source");
|
||||
let context = conn.pending_download_owner_context(&owner).unwrap();
|
||||
let event_route = conn.download_event_route(
|
||||
&owner,
|
||||
conn.automation_download_events_enabled_for_context(Some(&context.browser_context_id)),
|
||||
);
|
||||
let policy = conn.download_policy.clone();
|
||||
let web_contents = conn.browser_web_contents_for_owner(&owner).unwrap();
|
||||
let (policy, automation_events_enabled) = conn
|
||||
.download_configuration_for_browser_context(web_contents.context())
|
||||
.unwrap();
|
||||
let frame_id = conn
|
||||
.browser_context_by_browser_id(web_contents.context())
|
||||
.unwrap()
|
||||
.download_frame_id_for_web_contents(web_contents)
|
||||
.unwrap()
|
||||
.to_owned();
|
||||
let event_route = conn.download_event_route(&owner, automation_events_enabled);
|
||||
let observation = conn
|
||||
.browser_context_by_id_mut(&context.browser_context_id)
|
||||
.browser_context_by_browser_id_mut(web_contents.context())
|
||||
.unwrap()
|
||||
.start_download_response(
|
||||
web_contents,
|
||||
&policy,
|
||||
Url::parse("https://source.test/report.txt").unwrap(),
|
||||
Vec::new(),
|
||||
@@ -63,7 +69,7 @@ fn projection(conn: &mut CdpConnection, body: DownloadBody) -> DownloadProjectio
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
DownloadProjection {
|
||||
frame_id: context.frame_id,
|
||||
frame_id,
|
||||
event_route,
|
||||
observation,
|
||||
started: false,
|
||||
@@ -271,15 +277,16 @@ async fn retiring_context_cancels_download_and_new_same_wire_context_cannot_read
|
||||
chunks.send(b"partial".to_vec()).unwrap();
|
||||
wait_for(&mut monitor, |snapshot| snapshot.received_bytes == 7).await;
|
||||
assert_eq!(std::fs::read_dir(&directory.0).unwrap().count(), 1);
|
||||
drop(
|
||||
conn.remove_browser_context_by_id_restoring_active_async("CTX-source", None)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let removed = conn
|
||||
.remove_browser_context_by_id_restoring_active_async("CTX-source", None)
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(removed.remove_from_browser().unwrap());
|
||||
drop(removed);
|
||||
assert_eq!(terminal(&mut monitor).await.state, DownloadState::Canceled);
|
||||
assert!(cancel.is_cancelled());
|
||||
assert_eq!(std::fs::read_dir(&directory.0).unwrap().count(), 0);
|
||||
conn.insert_browser_context(BrowserContext::new("CTX-source".into()));
|
||||
conn.insert_browser_context(conn.new_browser_context_fixture_for_test("CTX-source"));
|
||||
assert_eq!(
|
||||
conn.cancel_download(monitor.guid()),
|
||||
Err("No download item found for the given GUID".into())
|
||||
@@ -289,17 +296,17 @@ async fn retiring_context_cancels_download_and_new_same_wire_context_cannot_read
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn navigation_download_uses_its_frozen_frame_after_session_detach_and_selection_change() {
|
||||
fn navigation_download_state(conn: &CdpConnection) -> NavigationDispatchState {
|
||||
use crate::conn::{
|
||||
NavigationRequestLoadPolicy, NavigationResultProjection,
|
||||
NavigationSourceDocumentSecurityContext,
|
||||
};
|
||||
|
||||
let (directory, mut conn) = fixture();
|
||||
let state = NavigationDispatchState {
|
||||
let owner = CommandOwnerScope::for_session("SID-source");
|
||||
NavigationDispatchState {
|
||||
navigate_id: None,
|
||||
owner: CommandOwnerScope::for_session("SID-source"),
|
||||
web_contents: conn.browser_web_contents_for_owner(&owner).unwrap(),
|
||||
owner,
|
||||
result_projection: NavigationResultProjection::Cdp(serde_json::json!({})),
|
||||
frame_id: "TID-source".into(),
|
||||
session_id: Some("SID-source".into()),
|
||||
@@ -319,7 +326,34 @@ async fn navigation_download_uses_its_frozen_frame_after_session_detach_and_sele
|
||||
"https://source.test".into(),
|
||||
"Secure".into(),
|
||||
),
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
async fn replace_source_download_context(conn: &mut CdpConnection, directory: &TestDirectory) {
|
||||
drop(
|
||||
conn.remove_browser_context_by_id_restoring_active_async("CTX-source", None)
|
||||
.await
|
||||
.unwrap(),
|
||||
);
|
||||
let mut replacement = conn.new_browser_context_fixture_for_test("CTX-source");
|
||||
replacement.set_active_target_id("TID-source");
|
||||
replacement.attach_active_session("SID-source");
|
||||
conn.install_browser_context_fixture_for_test(replacement);
|
||||
conn.configure_download_policy(
|
||||
Some("CTX-source"),
|
||||
DownloadPolicy {
|
||||
behavior: DownloadBehavior::AllowAndName,
|
||||
download_path: Some(directory.0.to_str().unwrap().to_owned()),
|
||||
},
|
||||
Some(true),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn navigation_download_uses_its_frozen_frame_after_session_detach_and_selection_change() {
|
||||
let (directory, mut conn) = fixture();
|
||||
let state = navigation_download_state(&conn);
|
||||
conn.detach_known_session_event_plan("TID-source", "SID-source", None, None);
|
||||
assert!(conn.target_owner_identity_for_owner(&state.owner).is_none());
|
||||
let source = conn
|
||||
@@ -363,3 +397,61 @@ async fn navigation_download_uses_its_frozen_frame_after_session_detach_and_sele
|
||||
);
|
||||
assert_eq!(conn.browser_context.as_ref().unwrap().id, "CTX-foreground");
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn navigation_download_does_not_enter_a_replacement_with_the_same_wire_identity() {
|
||||
let (directory, mut conn) = fixture();
|
||||
let state = navigation_download_state(&conn);
|
||||
replace_source_download_context(&mut conn, &directory).await;
|
||||
|
||||
let mut out = Vec::new();
|
||||
conn.handle_navigation_download_response_async(
|
||||
&mut out,
|
||||
&state,
|
||||
state.requested_url.clone(),
|
||||
CompletedDownloadBodyArtifact::from_body(
|
||||
DownloadBody::Buffered(b"stale navigation".to_vec()),
|
||||
Vec::new(),
|
||||
),
|
||||
&mut CommandDispatchContext::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(out.is_empty());
|
||||
assert_eq!(std::fs::read_dir(&directory.0).unwrap().count(), 0);
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn prepared_renderer_download_does_not_enter_a_replacement_web_contents() {
|
||||
let (directory, mut conn) = fixture();
|
||||
let owner = CommandOwnerScope::for_session("SID-source");
|
||||
let prepared = conn
|
||||
.prepare_download_activation_for_owner(
|
||||
&owner,
|
||||
RendererPendingDownloadActivation {
|
||||
url: "https://source.test/report.txt".to_owned(),
|
||||
suggested_filename: Some("report.txt".to_owned()),
|
||||
response: Some(moli_core::page::RendererPendingDownloadResponse {
|
||||
final_url: "https://source.test/report.txt".to_owned(),
|
||||
status: 200,
|
||||
headers: Vec::new(),
|
||||
body: b"stale renderer".to_vec(),
|
||||
}),
|
||||
},
|
||||
)
|
||||
.unwrap();
|
||||
replace_source_download_context(&mut conn, &directory).await;
|
||||
|
||||
let mut out = Vec::new();
|
||||
conn.handle_prepared_download_activation_background_events_async(
|
||||
&mut out,
|
||||
prepared,
|
||||
&mut CommandDispatchContext::default(),
|
||||
)
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
assert!(out.is_empty());
|
||||
assert_eq!(std::fs::read_dir(&directory.0).unwrap().count(), 0);
|
||||
}
|
||||
|
||||
+382
-1105
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
use serde_json::{Value, json};
|
||||
|
||||
use super::{BrowserContext, CdpCommandTaskStep, CommandOwnerScope};
|
||||
use super::{CdpCommandTaskStep, CommandOwnerScope};
|
||||
use crate::testing::TestContext;
|
||||
|
||||
mod document_agents;
|
||||
@@ -9,15 +9,62 @@ mod lifecycle;
|
||||
mod native_commands;
|
||||
mod runtime_agents;
|
||||
|
||||
pub(super) fn take_inspection_document(
|
||||
conn: &mut super::CdpConnection,
|
||||
pub(super) struct InspectionDocumentHandle {
|
||||
context: moli_core::browser::BrowserContextHandle,
|
||||
document: moli_core::browser::DocumentHandle,
|
||||
}
|
||||
|
||||
impl InspectionDocumentHandle {
|
||||
pub(super) async fn runtime_heap_usage_for_test(
|
||||
&self,
|
||||
) -> Result<moli_core::page::RendererRuntimeHeapUsage, String> {
|
||||
self.context
|
||||
.document_runtime_heap_usage_for_test(self.document)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) async fn evaluate_runtime_expression_for_test(
|
||||
&self,
|
||||
expression: &str,
|
||||
await_promise: bool,
|
||||
) -> Result<Value, String> {
|
||||
self.context
|
||||
.evaluate_document_expression_for_test(self.document, expression, await_promise)
|
||||
.await
|
||||
}
|
||||
|
||||
pub(super) fn document_title_for_test(&self) -> String {
|
||||
self.context
|
||||
.document_title(self.document)
|
||||
.expect("inspection document must remain current")
|
||||
}
|
||||
|
||||
pub(super) fn start_blob_bytes_for_uuid_for_test(
|
||||
&self,
|
||||
uuid: String,
|
||||
) -> Result<moli_core::browser::PendingDocumentBlobRead, String> {
|
||||
self.context.start_document_blob_read(self.document, uuid)
|
||||
}
|
||||
|
||||
pub(super) fn finish_blob_bytes_for_uuid_for_test(
|
||||
&self,
|
||||
completed: moli_core::browser::CompletedDocumentBlobRead,
|
||||
) -> Result<Option<std::sync::Arc<[u8]>>, String> {
|
||||
self.context.finish_document_blob_read(completed)
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn inspection_document_handle(
|
||||
conn: &super::CdpConnection,
|
||||
owner: &CommandOwnerScope,
|
||||
) -> super::state::DocumentHost {
|
||||
) -> InspectionDocumentHandle {
|
||||
let (context_id, target_id) = conn.resolved_page_owner_identity_for_owner(owner).unwrap();
|
||||
conn.browser_context_by_id_mut(&context_id)
|
||||
.unwrap()
|
||||
.take_document_host_for_inspection_test(&target_id)
|
||||
let (context, document) = conn
|
||||
.browser_context_by_id(&context_id)
|
||||
.unwrap()
|
||||
.inspection_document_handle_for_test(&target_id)
|
||||
.unwrap();
|
||||
InspectionDocumentHandle { context, document }
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@@ -68,7 +115,7 @@ async fn dom_inspection_rejects_frozen_replies_and_follow_ups_after_rebind() {
|
||||
};
|
||||
let completed = pending.wait().await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let old_document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let old_document = inspection_document_handle(&ctx.conn, &owner);
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
"data:text/html,<body><main id='inspected' data-phase='replacement'>new</main></body>",
|
||||
None,
|
||||
@@ -118,7 +165,9 @@ async fn dom_inspection_rejects_frozen_replies_and_follow_ups_after_rebind() {
|
||||
|
||||
async fn dom_context() -> TestContext {
|
||||
let mut ctx = TestContext::new();
|
||||
let mut context = BrowserContext::new("BID-dom-inspection".into());
|
||||
let mut context = ctx
|
||||
.conn
|
||||
.new_browser_context_fixture_for_test("BID-dom-inspection");
|
||||
context.set_active_target_id("TID-dom-inspection");
|
||||
ctx.conn.install_browser_context_fixture_for_test(context);
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
@@ -131,8 +180,8 @@ async fn dom_context() -> TestContext {
|
||||
async fn dom_inspection_round_trip(start_before_move: bool) {
|
||||
let mut ctx = dom_context().await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let take_document = |ctx: &mut TestContext| take_inspection_document(&mut ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| take_document(&mut ctx));
|
||||
let document_handle = |ctx: &TestContext| inspection_document_handle(&ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| document_handle(&ctx));
|
||||
let raw = json!({"id": 1, "method": "DOM.getDocument", "params": {"depth": -1}}).to_string();
|
||||
let step = ctx.conn.start_command_dispatch(&raw);
|
||||
assert!(
|
||||
@@ -140,9 +189,9 @@ async fn dom_inspection_round_trip(start_before_move: bool) {
|
||||
"DOM inspection must start on the live binding without a Protocol Document"
|
||||
);
|
||||
if start_before_move {
|
||||
document = Some(take_document(&mut ctx));
|
||||
document = Some(document_handle(&ctx));
|
||||
}
|
||||
let mut document = document.unwrap();
|
||||
let document = document.unwrap();
|
||||
let (messages, _) = ctx.complete_command_task_step_for_test(step).await;
|
||||
let response = messages
|
||||
.iter()
|
||||
@@ -227,8 +276,7 @@ async fn dom_inspection_round_trip(start_before_move: bool) {
|
||||
)
|
||||
.await;
|
||||
let actual = document
|
||||
.page
|
||||
.evaluate_runtime_expression_without_navigation_follow_with_await_async(
|
||||
.evaluate_runtime_expression_for_test(
|
||||
"document.getElementById('inspected').getAttribute('data-phase')",
|
||||
false,
|
||||
)
|
||||
@@ -239,7 +287,7 @@ async fn dom_inspection_round_trip(start_before_move: bool) {
|
||||
json!("after"),
|
||||
"the DOM mutation must reach the actual renderer"
|
||||
);
|
||||
assert!(!ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
assert!(ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
}
|
||||
|
||||
async fn dom_command(ctx: &mut TestContext, id: u64, method: &str, params: Value) -> Value {
|
||||
|
||||
@@ -32,7 +32,9 @@ async fn dom_snapshot_completes_without_protocol_document() {
|
||||
|
||||
async fn document_agent_context() -> TestContext {
|
||||
let mut ctx = TestContext::new();
|
||||
let mut context = BrowserContext::new("BID-document-agents".into());
|
||||
let mut context = ctx
|
||||
.conn
|
||||
.new_browser_context_fixture_for_test("BID-document-agents");
|
||||
context.set_active_target_id("TID-document-agents");
|
||||
ctx.conn.install_browser_context_fixture_for_test(context);
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
@@ -45,8 +47,8 @@ async fn document_agent_context() -> TestContext {
|
||||
async fn document_agent_round_trip(method: &str, start_before_move: bool) {
|
||||
let mut ctx = document_agent_context().await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let take_document = |ctx: &mut TestContext| take_inspection_document(&mut ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| take_document(&mut ctx));
|
||||
let document_handle = |ctx: &mut TestContext| inspection_document_handle(&ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| document_handle(&mut ctx));
|
||||
let step = ctx.conn.start_command_dispatch(
|
||||
&json!({"id": 1, "method": method, "params": {"computedStyles": ["color"]}}).to_string(),
|
||||
);
|
||||
@@ -55,9 +57,9 @@ async fn document_agent_round_trip(method: &str, start_before_move: bool) {
|
||||
"{method} must start on its live binding"
|
||||
);
|
||||
if start_before_move {
|
||||
document = Some(take_document(&mut ctx));
|
||||
document = Some(document_handle(&mut ctx));
|
||||
}
|
||||
let mut document = document.unwrap();
|
||||
let document = document.unwrap();
|
||||
let (messages, _) = ctx.complete_command_task_step_for_test(step).await;
|
||||
let response = messages
|
||||
.iter()
|
||||
@@ -118,8 +120,7 @@ async fn document_agent_round_trip(method: &str, start_before_move: bool) {
|
||||
json!("button { color: rgb(4, 5, 6); }")
|
||||
);
|
||||
let actual = document
|
||||
.page
|
||||
.evaluate_runtime_expression_without_navigation_follow_with_await_async(
|
||||
.evaluate_runtime_expression_for_test(
|
||||
"getComputedStyle(document.getElementById('inspected')).color",
|
||||
false,
|
||||
)
|
||||
@@ -188,7 +189,7 @@ async fn document_agent_round_trip(method: &str, start_before_move: bool) {
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
assert!(!ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
assert!(ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@@ -216,7 +217,7 @@ async fn document_agents_reject_old_completion_and_follow_up_after_rebind() {
|
||||
};
|
||||
let completed = pending.wait().await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let old_document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let old_document = inspection_document_handle(&ctx.conn, &owner);
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
"data:text/html,<button>Replacement</button>",
|
||||
None,
|
||||
|
||||
@@ -37,8 +37,8 @@ async fn blob_inspection_round_trip(start_before_move: bool) {
|
||||
ctx.conn.commit_declared_session_fixtures_for_test();
|
||||
let object_id = blob_object(&mut ctx, Some(session)).await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, Some(session));
|
||||
let take_document = |ctx: &mut TestContext| take_inspection_document(&mut ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| take_document(&mut ctx));
|
||||
let document_handle = |ctx: &mut TestContext| inspection_document_handle(&ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| document_handle(&mut ctx));
|
||||
let raw = json!({"id": 2, "sessionId": session, "method": "IO.resolveBlob", "params": {
|
||||
"objectId": object_id,
|
||||
}})
|
||||
@@ -49,9 +49,9 @@ async fn blob_inspection_round_trip(start_before_move: bool) {
|
||||
"Blob inspection must start through the live session binding without a Protocol Document"
|
||||
);
|
||||
if start_before_move {
|
||||
document = Some(take_document(&mut ctx));
|
||||
document = Some(document_handle(&mut ctx));
|
||||
}
|
||||
let mut document = document.unwrap();
|
||||
let document = document.unwrap();
|
||||
let (messages, _) = ctx.complete_command_task_step_for_test(step).await;
|
||||
let response = messages
|
||||
.iter()
|
||||
@@ -63,22 +63,19 @@ async fn blob_inspection_round_trip(start_before_move: bool) {
|
||||
);
|
||||
let uuid = response["result"]["uuid"].as_str().unwrap();
|
||||
let completed = document
|
||||
.page
|
||||
.start_blob_bytes_for_uuid(uuid.to_owned())
|
||||
.start_blob_bytes_for_uuid_for_test(uuid.to_owned())
|
||||
.unwrap()
|
||||
.wait()
|
||||
.await
|
||||
.unwrap();
|
||||
.await;
|
||||
let bytes = document
|
||||
.page
|
||||
.finish_blob_bytes_for_uuid(completed)
|
||||
.finish_blob_bytes_for_uuid_for_test(completed)
|
||||
.unwrap()
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
&*bytes, b"bound blob",
|
||||
"the reply must resolve the actual session object"
|
||||
);
|
||||
assert!(!ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
assert!(ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
@@ -100,7 +97,7 @@ async fn blob_inspection_rejects_frozen_reply_after_rebind() {
|
||||
};
|
||||
let completed = pending.wait().await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let old_document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let old_document = inspection_document_handle(&ctx.conn, &owner);
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
"data:text/html,<title>replacement</title>",
|
||||
None,
|
||||
|
||||
@@ -21,10 +21,9 @@ async fn session_detach_reaches_renderer_without_protocol_document() {
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
// Both child sessions have already used the binding. Browser-owned work
|
||||
// must not inherit either frontend or resurrect its V8 session on detach.
|
||||
let mut document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let document = inspection_document_handle(&ctx.conn, &owner);
|
||||
let before = document
|
||||
.page
|
||||
.runtime_heap_usage_async()
|
||||
.runtime_heap_usage_for_test()
|
||||
.await
|
||||
.unwrap()
|
||||
.moli
|
||||
@@ -47,8 +46,7 @@ async fn session_detach_reaches_renderer_without_protocol_document() {
|
||||
// This real owner command is sequenced after detach finalization. The
|
||||
// frontend acknowledgement alone cannot prove that V8 cleanup happened.
|
||||
let after = document
|
||||
.page
|
||||
.runtime_heap_usage_async()
|
||||
.runtime_heap_usage_for_test()
|
||||
.await
|
||||
.unwrap()
|
||||
.moli
|
||||
@@ -66,6 +64,6 @@ async fn session_detach_reaches_renderer_without_protocol_document() {
|
||||
.await;
|
||||
let surviving = ctx.take_response_by_id(4);
|
||||
assert_eq!(surviving["result"]["result"]["value"], json!("value"));
|
||||
assert!(!ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
assert!(ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
drop(document);
|
||||
}
|
||||
|
||||
@@ -55,15 +55,16 @@ async fn native_history_reset_completion_survives_selection_and_session_detach()
|
||||
panic!("history reset must execute on the Browser Document");
|
||||
};
|
||||
let completed = pending.wait().await;
|
||||
assert!(
|
||||
assert!({
|
||||
let handle = ctx
|
||||
.conn
|
||||
.browser_web_contents_for_target("TID-native-background")
|
||||
.unwrap();
|
||||
ctx.conn
|
||||
.browser_context
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.select_page_target_async("TID-native-background")
|
||||
.select_browser_web_contents_async(handle)
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
.is_ok()
|
||||
});
|
||||
let peer = ctx
|
||||
.conn
|
||||
.browser_context
|
||||
@@ -119,15 +120,14 @@ async fn native_input_completion_keeps_its_document_after_selection_and_session_
|
||||
panic!("input must be admitted to the original Browser document");
|
||||
};
|
||||
let completed = pending.wait().await;
|
||||
assert!(
|
||||
ctx.conn
|
||||
.browser_context
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.select_page_target_async("TID-native-background")
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
let handle = ctx
|
||||
.conn
|
||||
.browser_web_contents_for_target("TID-native-background")
|
||||
.unwrap();
|
||||
ctx.conn
|
||||
.select_browser_web_contents_async(handle)
|
||||
.await
|
||||
.unwrap();
|
||||
ctx.process_async(
|
||||
json!({"id": 23, "method": "Target.detachFromTarget", "params": {
|
||||
"targetId": "TID-dom-inspection", "sessionId": "SID-native-original",
|
||||
@@ -177,15 +177,14 @@ async fn native_diagnostics_completion_keeps_exact_documents_after_selection_and
|
||||
.wait()
|
||||
.await
|
||||
.unwrap();
|
||||
assert!(
|
||||
ctx.conn
|
||||
.browser_context
|
||||
.as_mut()
|
||||
.unwrap()
|
||||
.select_page_target_async("TID-native-background")
|
||||
.await
|
||||
.unwrap()
|
||||
);
|
||||
let handle = ctx
|
||||
.conn
|
||||
.browser_web_contents_for_target("TID-native-background")
|
||||
.unwrap();
|
||||
ctx.conn
|
||||
.select_browser_web_contents_async(handle)
|
||||
.await
|
||||
.unwrap();
|
||||
ctx.process_async(
|
||||
json!({"id": 31, "method": "Target.detachFromTarget", "params": {
|
||||
"targetId": "TID-dom-inspection", "sessionId": "SID-native-original",
|
||||
@@ -257,14 +256,19 @@ async fn browser_native_commands_do_not_require_a_live_inspector_session() {
|
||||
}})).await;
|
||||
assert!(ctx.take_response_by_id(1).get("error").is_none());
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let (context_id, target_id) = ctx.conn.resolve_document_command_owner(&owner).unwrap();
|
||||
let (context_id, target_id) = ctx
|
||||
.conn
|
||||
.loaded_document_owner_identity_for_owner(&owner)
|
||||
.unwrap();
|
||||
let context = ctx.conn.browser_context_by_id(&context_id).unwrap();
|
||||
let residence = context
|
||||
.target_renderer_page_residence_identity(&target_id)
|
||||
.unwrap();
|
||||
let pending_capture = context
|
||||
.start_capture_screenshot_with_request_for_target(
|
||||
&target_id,
|
||||
let document = ctx.conn.resolve_browser_document_for_owner(&owner).unwrap();
|
||||
let pending_capture = ctx
|
||||
.conn
|
||||
.start_capture_document_image(
|
||||
document,
|
||||
moli_core::page::RendererCaptureScreenshotRequest::viewport_png(),
|
||||
)
|
||||
.unwrap();
|
||||
@@ -279,15 +283,18 @@ async fn browser_native_commands_do_not_require_a_live_inspector_session() {
|
||||
.await
|
||||
.unwrap();
|
||||
|
||||
let completion = pending_capture.wait().await.unwrap();
|
||||
let context = ctx.conn.browser_context_by_id_mut(&context_id).unwrap();
|
||||
assert!(residence.matches_residence(completion.page_state().renderer_residence()));
|
||||
let completion = pending_capture.wait().await;
|
||||
assert!(
|
||||
context
|
||||
.finish_capture_screenshot_for_target(&target_id, completion)
|
||||
.is_ok(),
|
||||
ctx.conn.finish_capture_document_image(completion).is_ok(),
|
||||
"session detach must not cancel already admitted Browser capture"
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.conn
|
||||
.browser_context_by_id(&context_id)
|
||||
.unwrap()
|
||||
.target_renderer_page_residence_identity(&target_id),
|
||||
Some(residence)
|
||||
);
|
||||
|
||||
ctx.process_async(json!({"id": 2, "method": "Input.insertText", "params": {"text": "native"}}))
|
||||
.await;
|
||||
@@ -357,3 +364,93 @@ async fn native_capture_rejects_a_foreign_page_without_devtools_attachments() {
|
||||
.unwrap();
|
||||
assert!(first.finish_capture_screenshot(completion).is_ok());
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn completed_browser_document_command_cannot_retarget_a_replacement_document() {
|
||||
let mut ctx = dom_context().await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let document = ctx.conn.resolve_browser_document_for_owner(&owner).unwrap();
|
||||
let completed = ctx
|
||||
.conn
|
||||
.start_capture_document_snapshot(document)
|
||||
.unwrap()
|
||||
.wait()
|
||||
.await;
|
||||
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
"data:text/html,<title>replacement-document</title>",
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
assert!(matches!(
|
||||
ctx.conn.finish_capture_document_snapshot(completed),
|
||||
Err(error) if error == "Document changed"
|
||||
));
|
||||
assert_eq!(
|
||||
ctx.conn
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.target_document_title("TID-dom-inspection")
|
||||
.as_deref(),
|
||||
Some("replacement-document")
|
||||
);
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn app_manifest_fetch_cannot_publish_into_a_replacement_document() {
|
||||
let mut ctx = dom_context().await;
|
||||
ctx.install_buffered_navigation_fixture_for_session_owner(
|
||||
url::Url::parse("https://manifest.example/page").unwrap(),
|
||||
r#"<!doctype html><link rel="manifest" href="data:application/manifest+json,%7B%7D"><title>manifest-owner</title>"#
|
||||
.into(),
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let raw = json!({"id": 801, "method": "Page.getAppManifest"}).to_string();
|
||||
let CdpCommandTaskStep::Pending(prepare) = ctx.conn.start_command_dispatch(&raw) else {
|
||||
panic!("app manifest inspection must start on the original document");
|
||||
};
|
||||
let CdpCommandTaskStep::Pending(fetch) = ctx
|
||||
.conn
|
||||
.complete_pending_command_dispatch(prepare.wait().await)
|
||||
.await
|
||||
else {
|
||||
panic!("an external app manifest must enter the browser fetch stage");
|
||||
};
|
||||
let fetched = fetch.wait().await;
|
||||
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
"data:text/html,<title>replacement-manifest-owner</title>",
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
|
||||
let CdpCommandTaskStep::Complete(outcome) =
|
||||
ctx.conn.complete_pending_command_dispatch(fetched).await
|
||||
else {
|
||||
panic!("a stale app manifest fetch must not publish into the replacement document");
|
||||
};
|
||||
let (messages, _) = ctx.route_completed_command_outcome_for_test(outcome).await;
|
||||
let response = messages
|
||||
.iter()
|
||||
.find(|message| message["id"] == json!(801))
|
||||
.expect("app manifest response");
|
||||
assert_eq!(response["error"]["code"], json!(-32000), "{response}");
|
||||
assert_eq!(
|
||||
response["error"]["message"],
|
||||
json!("Failed to publish app manifest result: Document changed"),
|
||||
"the fetched result must retain its originating Browser Document: {response}"
|
||||
);
|
||||
assert_eq!(
|
||||
ctx.conn
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.target_document_title("TID-dom-inspection")
|
||||
.as_deref(),
|
||||
Some("replacement-manifest-owner")
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,7 +4,7 @@ use super::*;
|
||||
async fn runtime_controls_start_without_protocol_document() {
|
||||
let mut ctx = dom_context().await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let document = inspection_document_handle(&ctx.conn, &owner);
|
||||
for (id, method) in (1..).zip([
|
||||
"Runtime.enable",
|
||||
"Runtime.disable",
|
||||
@@ -37,7 +37,7 @@ async fn runtime_controls_start_without_protocol_document() {
|
||||
};
|
||||
assert!(response.get("error").is_none(), "{method}: {response}");
|
||||
}
|
||||
assert!(!ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
assert!(ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
drop(document);
|
||||
}
|
||||
|
||||
@@ -46,7 +46,7 @@ async fn preloads_register_run_remove_and_replay_without_protocol_document() {
|
||||
let mut ctx = dom_context().await;
|
||||
dom_command(&mut ctx, 1, "Runtime.enable", json!({})).await;
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let document = inspection_document_handle(&ctx.conn, &owner);
|
||||
dom_command(
|
||||
&mut ctx,
|
||||
2,
|
||||
@@ -113,7 +113,7 @@ async fn preloads_register_run_remove_and_replay_without_protocol_document() {
|
||||
"realm creation must consume the renderer's updated preload registry"
|
||||
);
|
||||
}
|
||||
assert!(!ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
assert!(ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
drop(document);
|
||||
}
|
||||
|
||||
@@ -128,13 +128,13 @@ async fn isolated_world_restarts_on_replacement_binding_without_protocol_documen
|
||||
);
|
||||
assert!(matches!(&step, CdpCommandTaskStep::Pending(_)));
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let old_document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let old_document = inspection_document_handle(&ctx.conn, &owner);
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
"data:text/html,<main id='inspected'>replacement</main>",
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let replacement = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let replacement = inspection_document_handle(&ctx.conn, &owner);
|
||||
let (messages, _) = ctx.complete_command_task_step_for_test(step).await;
|
||||
let response = messages
|
||||
.iter()
|
||||
@@ -169,7 +169,7 @@ async fn session_preload_cleanup_updates_renderer_without_protocol_document() {
|
||||
}})).await;
|
||||
ctx.expect_result(1, json!({"identifier": "1"}), Some("SID-preload-cleanup"));
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, Some("SID-preload-cleanup"));
|
||||
let document = take_inspection_document(&mut ctx.conn, &owner);
|
||||
let document = inspection_document_handle(&ctx.conn, &owner);
|
||||
ctx.process_async(
|
||||
json!({"id": 10, "method": "Target.detachFromTarget", "params": {
|
||||
"targetId": "TID-dom-inspection", "sessionId": "SID-preload-cleanup",
|
||||
@@ -315,8 +315,8 @@ async fn runtime_agent_round_trip(method: &str, start_before_move: bool) {
|
||||
})).await["result"]["objectId"].clone();
|
||||
assert!(object.is_string());
|
||||
let owner = CommandOwnerScope::capture(&ctx.conn, None);
|
||||
let take_document = |ctx: &mut TestContext| take_inspection_document(&mut ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| take_document(&mut ctx));
|
||||
let document_handle = |ctx: &mut TestContext| inspection_document_handle(&ctx.conn, &owner);
|
||||
let mut document = (!start_before_move).then(|| document_handle(&mut ctx));
|
||||
let params = match method {
|
||||
"Runtime.addBinding" => json!({"name": "inspectBinding"}),
|
||||
"Page.createIsolatedWorld" => {
|
||||
@@ -333,7 +333,7 @@ async fn runtime_agent_round_trip(method: &str, start_before_move: bool) {
|
||||
"{method} must dispatch to the live binding"
|
||||
);
|
||||
if start_before_move {
|
||||
document = Some(take_document(&mut ctx));
|
||||
document = Some(document_handle(&mut ctx));
|
||||
}
|
||||
let document = document.unwrap();
|
||||
let (messages, _) = ctx.complete_command_task_step_for_test(step).await;
|
||||
@@ -451,6 +451,6 @@ async fn runtime_agent_round_trip(method: &str, start_before_move: bool) {
|
||||
}
|
||||
_ => unreachable!(),
|
||||
}
|
||||
assert!(!ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
assert!(ctx.conn.has_loaded_page_for_owner(&owner));
|
||||
drop(document);
|
||||
}
|
||||
|
||||
@@ -259,7 +259,7 @@ mod tests {
|
||||
false,
|
||||
crate::conn::CapturedBody::from_string("candidate".to_owned()),
|
||||
);
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.install_browser_context_fixture_for_test(context);
|
||||
let owner = CommandOwnerScope::for_route(crate::conn::CdpSessionRoute::PageTarget {
|
||||
browser_context_id: "BID-rollback".to_owned(),
|
||||
@@ -289,7 +289,7 @@ mod tests {
|
||||
let mut context = BrowserContext::new("BID-cleanup".to_owned());
|
||||
context.set_active_target_id("TID-cleanup");
|
||||
context.begin_active_target_initial_empty_document("about:blank".to_owned());
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.install_browser_context_fixture_for_test(context);
|
||||
let owner = CommandOwnerScope::for_route(crate::conn::CdpSessionRoute::PageTarget {
|
||||
browser_context_id: "BID-cleanup".to_owned(),
|
||||
@@ -421,7 +421,7 @@ mod tests {
|
||||
let second = context
|
||||
.start_document_navigation_for_target("TID-second", "LOADER-shared".to_owned())
|
||||
.unwrap();
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.install_browser_context_fixture_for_test(context);
|
||||
let owner = CommandOwnerScope::for_route(crate::conn::CdpSessionRoute::PageTarget {
|
||||
browser_context_id: "BID-navigation-route".to_owned(),
|
||||
@@ -471,7 +471,7 @@ mod tests {
|
||||
"TID-background",
|
||||
"SID-background-attached".to_owned(),
|
||||
));
|
||||
let mut conn = CdpConnection::default();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.install_browser_context_fixture_for_test(browser_context);
|
||||
|
||||
let filtered = conn.filter_renderer_inspector_batches_for_target_owner(
|
||||
@@ -499,14 +499,9 @@ mod tests {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn inspector_state_updates_require_the_current_attachment_and_agent() {
|
||||
let mut ctx = TestContext::new();
|
||||
let page = ctx
|
||||
let mut browser_context = ctx
|
||||
.conn
|
||||
.load_page_via_runtime_async(
|
||||
"data:text/html,<title>inspector-state-source-validation</title>",
|
||||
)
|
||||
.await
|
||||
.expect("state source validation page should load");
|
||||
let mut browser_context = BrowserContext::new("BID-state-route".to_owned());
|
||||
.new_browser_context_fixture_for_test("BID-state-route");
|
||||
browser_context.set_active_target_id("TID-state-route".to_owned());
|
||||
browser_context.attach_active_session("SID-state-primary".to_owned());
|
||||
assert!(
|
||||
@@ -515,14 +510,21 @@ mod tests {
|
||||
"SID-state-attached".to_owned(),
|
||||
)
|
||||
);
|
||||
browser_context.set_loaded_page_async(page).await;
|
||||
ctx.conn
|
||||
.install_browser_context_fixture_for_test(browser_context);
|
||||
let _ = ctx
|
||||
.conn
|
||||
.install_navigation_fixture_for_session_owner_for_test(
|
||||
"data:text/html,<title>inspector-state-source-validation</title>",
|
||||
None,
|
||||
)
|
||||
.await;
|
||||
let browser_context = ctx.conn.browser_context.as_ref().expect("browser context");
|
||||
let current = browser_context
|
||||
.active_page_target()
|
||||
.runtime_slot
|
||||
.current_renderer_attachment()
|
||||
.expect("installed page should have a renderer attachment");
|
||||
ctx.conn
|
||||
.install_browser_context_fixture_for_test(browser_context);
|
||||
|
||||
let accepted_state = V8InspectorSessionState::from_bytes(vec![1, 2, 3]);
|
||||
let mut accepted = batch(DevToolsSessionKey::Primary);
|
||||
|
||||
@@ -1,57 +1,4 @@
|
||||
use std::{error::Error, fmt};
|
||||
pub(crate) use moli_core::browser::{NavigationNetworkError, NavigationRequestBlocked};
|
||||
|
||||
use url::Url;
|
||||
|
||||
/// Network policy failures produced by the navigation request loaders.
|
||||
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
|
||||
pub(crate) enum NavigationNetworkErrorKind {
|
||||
InternetDisconnected,
|
||||
}
|
||||
|
||||
impl NavigationNetworkErrorKind {
|
||||
pub(crate) fn error_text(self) -> &'static str {
|
||||
match self {
|
||||
Self::InternetDisconnected => "net::ERR_INTERNET_DISCONNECTED",
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A navigation request rejected by emulated offline network policy.
|
||||
///
|
||||
/// Transport failures retain `moli_fetch::NetworkFetchFailureContext` and use
|
||||
/// their existing handling paths; this cause currently represents offline
|
||||
/// enforcement only.
|
||||
///
|
||||
/// This is an error cause rather than display context, so callers can recover
|
||||
/// its request identity through `anyhow::Error::downcast_ref` after adding
|
||||
/// diagnostic context along the navigation pipeline.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NavigationNetworkError {
|
||||
pub(crate) kind: NavigationNetworkErrorKind,
|
||||
pub(crate) unreachable_url: Url,
|
||||
pub(crate) request_method: String,
|
||||
pub(crate) request_headers: moli_fetch::RequestHeaders,
|
||||
}
|
||||
|
||||
impl fmt::Display for NavigationNetworkError {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str(self.kind.error_text())
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for NavigationNetworkError {}
|
||||
|
||||
/// A navigation request rejected by `Network.setBlockedURLs`.
|
||||
///
|
||||
/// The protocol boundary preserves its canonical error text independently of
|
||||
/// diagnostic context on other navigation failures.
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct NavigationRequestBlocked;
|
||||
|
||||
impl fmt::Display for NavigationRequestBlocked {
|
||||
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
formatter.write_str("net::ERR_BLOCKED_BY_CLIENT")
|
||||
}
|
||||
}
|
||||
|
||||
impl Error for NavigationRequestBlocked {}
|
||||
#[cfg(test)]
|
||||
pub(crate) use moli_core::browser::NavigationNetworkErrorKind;
|
||||
|
||||
@@ -4381,7 +4381,7 @@ mod tests {
|
||||
BackgroundServiceWorkerVersion, NavigationBackgroundEvent, PageScreencastFrameMetadata,
|
||||
RuntimeInspectorResponseReady, build_event,
|
||||
};
|
||||
use crate::conn::{BrowserContext, CdpConnection, DevToolsDocumentLifecycleWaitKey};
|
||||
use crate::conn::{BrowserContext, DevToolsDocumentLifecycleWaitKey};
|
||||
use moli_core::{
|
||||
PageId, RendererRuntimeInspectorAsyncCompletion,
|
||||
page::{
|
||||
@@ -4429,7 +4429,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn page_download_envelope_rejects_disabled_and_reenabled_subscription() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.install_default_browser_target();
|
||||
assert!(conn.set_page_domain_enabled_for_session_owner(None, true));
|
||||
let first_generation = conn
|
||||
@@ -4474,7 +4474,7 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn browser_download_route_guard_rejects_detached_and_reenabled_subscription() {
|
||||
let mut conn = CdpConnection::new();
|
||||
let mut conn = crate::test_support::connection();
|
||||
conn.set_browser_download_events_enabled_for_session(Some("SID-browser"), true);
|
||||
let first_generation = conn.download_subscriptions.browser_event_observers()[0].1;
|
||||
let event = BackgroundProtocolEvent::browser_download_progress(
|
||||
|
||||
@@ -27,10 +27,11 @@ fn permission_settings(conn: &CdpConnection, context: &str) -> Vec<String> {
|
||||
|
||||
#[test]
|
||||
fn permission_scope_order_tracks_writes_instead_of_fixed_scope_precedence() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("BID-first".into()));
|
||||
conn.inactive_browser_contexts
|
||||
.push(BrowserContext::new("BID-second".into()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
let first = conn.new_browser_context_fixture_for_test("BID-first");
|
||||
let second = conn.new_browser_context_fixture_for_test("BID-second");
|
||||
conn.install_browser_context_fixture_for_test(first);
|
||||
conn.push_inactive_browser_context_fixture_for_test(second);
|
||||
|
||||
set_permission(&mut conn, None, "denied");
|
||||
set_permission(&mut conn, Some("BID-first"), "granted");
|
||||
@@ -52,23 +53,27 @@ fn permission_scope_order_tracks_writes_instead_of_fixed_scope_precedence() {
|
||||
permission_settings(&conn, "BID-first"),
|
||||
["prompt", "denied"]
|
||||
);
|
||||
conn.inactive_browser_contexts
|
||||
.push(BrowserContext::new("BID-later".into()));
|
||||
let later = conn.new_browser_context_fixture_for_test("BID-later");
|
||||
conn.push_inactive_browser_context_fixture_for_test(later);
|
||||
assert_eq!(permission_settings(&conn, "BID-later"), ["prompt"]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_resets_preserve_other_scopes_and_do_not_materialize_pages() {
|
||||
let mut conn = CdpConnection::new_with_deferred_navigation_runtime(
|
||||
let mut conn = crate::test_support::connection_with_config(
|
||||
CdpInitialStoragePartition::memory(),
|
||||
NavigationRuntimeConfig::default(),
|
||||
);
|
||||
set_permission(&mut conn, None, "denied");
|
||||
assert!(conn.browser_context.is_none());
|
||||
assert!(!conn.standalone_navigation_engine.is_materialized());
|
||||
conn.browser_context = Some(BrowserContext::new("BID-first".into()));
|
||||
conn.inactive_browser_contexts
|
||||
.push(BrowserContext::new("BID-second".into()));
|
||||
assert_eq!(
|
||||
conn.moli_memory_diagnostics()["isolateScope"]["estimatedRendererOwnerCount"],
|
||||
json!(0)
|
||||
);
|
||||
let first = conn.new_browser_context_fixture_for_test("BID-first");
|
||||
let second = conn.new_browser_context_fixture_for_test("BID-second");
|
||||
conn.install_browser_context_fixture_for_test(first);
|
||||
conn.push_inactive_browser_context_fixture_for_test(second);
|
||||
set_permission(&mut conn, Some("BID-first"), "granted");
|
||||
set_permission(&mut conn, Some("BID-second"), "prompt");
|
||||
|
||||
@@ -89,22 +94,25 @@ fn permission_resets_preserve_other_scopes_and_do_not_materialize_pages() {
|
||||
conn.browser_contexts()
|
||||
.all(|context| context.page_targets.is_empty())
|
||||
);
|
||||
assert!(!conn.standalone_navigation_engine.is_materialized());
|
||||
assert_eq!(
|
||||
conn.moli_memory_diagnostics()["isolateScope"]["estimatedRendererOwnerCount"],
|
||||
json!(0)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn permission_rules_leave_the_connection_with_their_context() {
|
||||
let mut conn = CdpConnection::default();
|
||||
conn.browser_context = Some(BrowserContext::new("same-context".into()));
|
||||
let mut conn = crate::test_support::connection();
|
||||
let original = conn.new_browser_context_fixture_for_test("same-context");
|
||||
conn.install_browser_context_fixture_for_test(original);
|
||||
set_permission(&mut conn, None, "denied");
|
||||
set_permission(&mut conn, Some("same-context"), "granted");
|
||||
assert_eq!(conn.permission_override_count(), 2);
|
||||
|
||||
// No disposal handler or ID-keyed cleanup is allowed to be necessary.
|
||||
let removed = conn
|
||||
.browser_context
|
||||
.replace(BrowserContext::new("same-context".into()))
|
||||
.unwrap();
|
||||
let removed = conn.browser_context.take().unwrap();
|
||||
let replacement = conn.new_browser_context_fixture_for_test("same-context");
|
||||
conn.install_browser_context_fixture_for_test(replacement);
|
||||
assert_ne!(
|
||||
removed.browser_context_id(),
|
||||
conn.browser_context.as_ref().unwrap().browser_context_id()
|
||||
@@ -113,7 +121,7 @@ fn permission_rules_leave_the_connection_with_their_context() {
|
||||
assert_eq!(conn.permission_override_count(), 1);
|
||||
assert_eq!(
|
||||
removed
|
||||
.permission_snapshot(&conn.permission_defaults)
|
||||
.permission_snapshot()
|
||||
.iter()
|
||||
.map(|entry| entry.setting.as_str())
|
||||
.collect::<Vec<_>>(),
|
||||
@@ -140,7 +148,9 @@ async fn current_permission(ctx: &mut TestContext) -> String {
|
||||
#[tokio::test(flavor = "multi_thread")]
|
||||
async fn permission_update_order_and_context_reset_reach_live_and_replacement_documents() {
|
||||
let mut ctx = TestContext::new();
|
||||
let mut context = BrowserContext::new("BID-permission-live".into());
|
||||
let mut context = ctx
|
||||
.conn
|
||||
.new_browser_context_fixture_for_test("BID-permission-live");
|
||||
context.set_active_target_id("TID-permission-live");
|
||||
ctx.conn.install_browser_context_fixture_for_test(context);
|
||||
ctx.install_navigation_fixture_for_session_owner("data:text/html,<title>first</title>", None)
|
||||
@@ -171,7 +181,9 @@ async fn permission_update_order_and_context_reset_reach_live_and_replacement_do
|
||||
}
|
||||
|
||||
async fn loaded_permission_context(ctx: &mut TestContext, title: &str) {
|
||||
let mut context = BrowserContext::new("BID-permission-live".into());
|
||||
let mut context = ctx
|
||||
.conn
|
||||
.new_browser_context_fixture_for_test("BID-permission-live");
|
||||
context.set_active_target_id("TID-permission-live");
|
||||
ctx.conn.install_browser_context_fixture_for_test(context);
|
||||
ctx.install_navigation_fixture_for_session_owner(
|
||||
@@ -226,9 +238,8 @@ async fn permission_completion_preserves_its_ack_without_observing_a_replacement
|
||||
.browser_context
|
||||
.as_ref()
|
||||
.unwrap()
|
||||
.loaded_page()
|
||||
.unwrap()
|
||||
.document_title(),
|
||||
.loaded_document_title_for_test()
|
||||
.unwrap(),
|
||||
"replacement"
|
||||
);
|
||||
assert_eq!(current_permission(&mut ctx).await, "denied");
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user