use anyhow::Result; use moli_core::runtime::{Browser, BrowserConfig}; use moli_test_support::FixtureServer; use serde_json::Value; use tokio::time::Duration; use url::Url; fn markup_url(server: &FixtureServer, markup: &str) -> String { let mut url = Url::parse(&server.url("/compat/child-dynamic-markup-document")).unwrap(); url.query_pairs_mut().append_pair("markup", markup); url.into() } async fn child_javascript_url_navigation(action: &str, other_target: bool) -> Result { let server = FixtureServer::spawn().await?; let browser = Browser::new(BrowserConfig::default())?; let source = format!( r#"
go"# ); let parent = format!( r#""#, serde_json::to_string(&markup_url(&server, &source))?.replace("", "<\\/script>") ); let result = tokio::time::timeout(Duration::from_secs(10), async { let mut page = browser.fetch(&markup_url(&server, &parent)).await?; page.evaluate_runtime_expression_with_await_async( "finished.then(value => JSON.stringify(value))", true, ) .await }) .await??; let result: Value = serde_json::from_str(result["value"].as_str().unwrap())?; server.shutdown().await; assert_eq!(result[0]["documentChanged"], true, "{action}: {result}"); assert_eq!(result[0]["scriptContinued"], true, "{action}: {result}"); Ok(result) } #[tokio::test(flavor = "multi_thread")] async fn child_javascript_url_string_does_not_overtake_its_form_submission() -> Result<()> { for method in ["get", "post"] { for target in ["", "_self", "source"] { let action = format!( "document.forms[0].method = {method:?}; \ document.forms[0].target = {target:?}; document.forms[0].submit();" ); let result = child_javascript_url_navigation(&action, false).await?; // Observe the first load after the javascript URL. A temporary // replacement document would fire an extra load at the old URL. assert_eq!(result[0]["path"], "/net/echo", "{action}: {result}"); let response: Value = serde_json::from_str(result[0]["body"].as_str().unwrap())?; assert_eq!(response["method"], method.to_uppercase()); if method == "post" { assert_eq!(result[0]["search"], ""); } else { assert_eq!(result[0]["search"], "?q=submitted"); } } } Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn child_javascript_url_string_does_not_overtake_its_location_navigation() -> Result<()> { let result = child_javascript_url_navigation("location.href = '/net/echo?q=location';", false).await?; assert_eq!(result[0]["path"], "/net/echo", "{result}"); assert_eq!(result[0]["search"], "?q=location"); let result = child_javascript_url_navigation( "location.href = \"javascript:'successor'\";", false, ) .await?; assert_eq!(result[0]["bodyId"], "successor", "{result}"); assert_eq!(result[0]["body"], "successor"); Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn child_javascript_url_string_survives_navigation_to_another_frame() -> Result<()> { for method in ["get", "post"] { let action = format!( "document.forms[0].method = {method:?}; \ document.forms[0].target = 'other'; document.forms[0].submit();" ); let result = child_javascript_url_navigation(&action, true).await?; assert_eq!(result[0]["bodyId"], "completion", "{action}: {result}"); assert_eq!(result[0]["body"], "replacement"); assert_eq!(result[1]["method"], method.to_uppercase()); } Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn child_javascript_url_string_survives_without_a_new_cross_document_navigation() -> Result<()> { for action in [ "document.body.dataset.changed = 'yes';", "location.hash = 'fragment';", "navigation.onnavigate = event => event.preventDefault(); document.forms[0].submit();", ] { let result = child_javascript_url_navigation(action, false).await?; assert_eq!(result[0]["bodyId"], "completion", "{action}: {result}"); assert_eq!(result[0]["body"], "replacement"); } Ok(()) }