use std::sync::Arc;
use std::sync::mpsc::Sender;
use blitz_traits::navigation::{NavigationOptions, NavigationProvider};
use blitz_traits::net::{AbortController, AbortSignal, Url};
use blitz_traits::node_id::NodeId;
use blitz_traits::shell::ShellProvider;
use crate::document::DocumentEvent;
use crate::net::{DocumentSrcHandler, ResourceHandler, stamped_request};
use crate::{BaseDocument, DocumentConfig, local_name};
pub(crate) const MAX_SUBDOCUMENT_DEPTH: usize = 10;
pub(crate) struct IframeLoad {
pub(crate) request_id: Option<usize>,
pub(crate) abort_controller: AbortController,
}
struct IframeNavigationProvider {
node_id: NodeId,
tx: Sender<DocumentEvent>,
shell_provider: Arc<dyn ShellProvider>,
}
impl NavigationProvider for IframeNavigationProvider {
fn navigate_to(&self, options: NavigationOptions) {
let _ = self.tx.send(DocumentEvent::NavigateIframe {
node_id: self.node_id,
url: options.url,
});
self.shell_provider.request_redraw();
}
}
impl BaseDocument {
fn iframe_document_config(
&self,
node_id: NodeId,
base_url: Option<String>,
abort_signal: AbortSignal,
) -> DocumentConfig {
DocumentConfig {
viewport: None,
base_url,
ua_stylesheets: None,
net_provider: Some(self.net_provider.clone()),
navigation_provider: Some(Arc::new(IframeNavigationProvider {
node_id,
tx: self.tx.clone(),
shell_provider: self.shell_provider.clone(),
})),
shell_provider: Some(self.shell_provider.clone()),
html_parser_provider: Some(self.html_parser_provider.clone()),
font_ctx: Some(self.font_ctx.lock().unwrap().clone()),
media_type: Some(self.media_type.clone()),
style_threading: self.style_threading,
incremental: Some(self.incremental_layout),
abort_signal: Some(abort_signal),
subdocument_depth: self.subdocument_depth + 1,
}
}
fn new_iframe_generation(&mut self, node_id: NodeId, request_id: Option<usize>) -> AbortSignal {
if let Some(load) = self.iframe_loads.remove(&node_id) {
load.abort_controller.abort();
}
let abort_controller = AbortController::default();
let signal = abort_controller.signal.clone();
self.iframe_loads.insert(
node_id,
IframeLoad {
request_id,
abort_controller,
},
);
signal
}
pub(crate) fn attach_iframe_document(
&mut self,
node_id: NodeId,
html: &str,
base_url: Option<String>,
abort_signal: AbortSignal,
) {
let config = self.iframe_document_config(node_id, base_url, abort_signal);
let sub_doc = self
.html_parser_provider
.clone()
.parse_document(html, config);
self.set_sub_document(node_id, sub_doc);
self.shell_provider.request_redraw();
}
pub(crate) fn load_iframe_srcdoc(&mut self, node_id: NodeId, srcdoc: &str) {
let signal = self.new_iframe_generation(node_id, None);
let base_url = Some(self.url.to_string());
self.attach_iframe_document(node_id, srcdoc, base_url, signal);
}
pub(crate) fn start_iframe_load(&mut self, node_id: NodeId, url: Url) {
let handler = ResourceHandler::new(
self.tx.clone(),
self.id(),
Some(node_id),
self.shell_provider.clone(),
DocumentSrcHandler,
);
let signal = self.new_iframe_generation(node_id, Some(handler.request_id()));
self.net_provider.fetch(
self.id(),
stamped_request(url, Some(&signal)),
Box::new(handler),
);
}
pub(crate) fn navigate_iframe(&mut self, node_id: NodeId, url: Url) {
let Some(node) = self.get_node(node_id) else {
return;
};
if !node.flags.is_in_document() {
return;
}
self.start_iframe_load(node_id, url);
}
pub(crate) fn apply_iframe_html(
&mut self,
node_id: NodeId,
request_id: usize,
resolved_url: Option<String>,
html: &str,
) {
let is_current = self
.iframe_loads
.get(&node_id)
.is_some_and(|load| load.request_id == Some(request_id));
if !is_current {
return;
}
let load = self.iframe_loads.get_mut(&node_id).unwrap();
load.request_id = None;
let signal = load.abort_controller.signal.clone();
let node_is_iframe = self
.get_node(node_id)
.and_then(|node| node.element_data())
.is_some_and(|el| el.name.local == local_name!("iframe"));
if !node_is_iframe {
return;
}
self.attach_iframe_document(node_id, html, resolved_url, signal);
}
}