use crate::browser::Browser;
use crate::error::RenderError;
use decant_core::Cookie;
use std::time::Duration;
use url::Url;
const RENDER_TIMEOUT_SECS: u64 = 15;
pub async fn render_html(
browser: &Browser,
url: &Url,
cookies: &[Cookie],
wait_ms: u64,
) -> Result<String, RenderError> {
tokio::time::timeout(
Duration::from_secs(RENDER_TIMEOUT_SECS),
render_html_inner(browser, url, cookies, wait_ms),
)
.await
.unwrap_or_else(|_| {
Err(RenderError::Page {
url: url.to_string(),
message: format!(
"Render timed out after {RENDER_TIMEOUT_SECS}s — \
falling back to static fetch"
),
})
})
}
async fn render_html_inner(
browser: &Browser,
url: &Url,
cookies: &[Cookie],
wait_ms: u64,
) -> Result<String, RenderError> {
let inner_browser = browser.inner();
let page = tokio::time::timeout(
Duration::from_secs(5),
inner_browser.new_page("about:blank"),
)
.await
.map_err(|_| RenderError::Page {
url: url.to_string(),
message: "Timed out opening new tab".into(),
})?
.map_err(|e| RenderError::Page {
url: url.to_string(),
message: format!("Failed to open new page: {e}"),
})?;
if !cookies.is_empty() {
use chromiumoxide::cdp::browser_protocol::network::CookieParam;
use chromiumoxide::cdp::browser_protocol::network::SetCookiesParams;
let mut cdp_cookies = Vec::new();
for c in cookies {
let domain = if c.domain.is_empty() {
url.host_str().map(|h| h.to_string())
} else {
Some(c.domain.clone())
};
let param = CookieParam::builder()
.name(c.name.clone())
.value(c.value.clone())
.url(url.to_string())
.domain(domain.unwrap_or_default())
.path("/")
.secure(url.scheme() == "https")
.build()
.map_err(|e| RenderError::Page {
url: url.to_string(),
message: format!("Failed to build CookieParam: {e}"),
})?;
cdp_cookies.push(param);
}
page.execute(SetCookiesParams::new(cdp_cookies))
.await
.map_err(|e| RenderError::Page {
url: url.to_string(),
message: format!("Failed to set cookies: {e}"),
})?;
}
match tokio::time::timeout(Duration::from_secs(5), page.goto(url.as_str())).await {
Ok(Ok(_)) => {}
Ok(Err(e)) => {
tracing::warn!("Navigation error for {url}: {e}, attempting content extraction");
}
Err(_) => {
tracing::warn!("Navigation timed out for {url}, attempting content extraction");
}
}
let pause = wait_ms.max(500);
tokio::time::sleep(Duration::from_millis(pause)).await;
let html = tokio::time::timeout(Duration::from_secs(5), page.content())
.await
.map_err(|_| RenderError::Page {
url: url.to_string(),
message: "Timed out extracting page content".into(),
})?
.map_err(|e| RenderError::Page {
url: url.to_string(),
message: format!("Failed to get page content: {e}"),
})?;
let _ = page.close().await;
Ok(html)
}