use anyhow::Result; use moli_core::runtime::{Browser, BrowserConfig}; use moli_test_support::FixtureServer; use serde_json::{Value, json}; use tokio::time::Duration; use url::Url; async fn child_navigation_lifecycle(via: &str, kind: &str, depth: usize) -> Result { let server = FixtureServer::spawn().await?; let browser = Browser::new(BrowserConfig::default())?; let markup = format!( r#""# ); let mut url = Url::parse(&server.url("/compat/child-dynamic-markup-document"))?; url.query_pairs_mut().append_pair("markup", &markup); let result = tokio::time::timeout(Duration::from_secs(10), async { let mut page = browser.fetch(url.as_str()).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["unrelatedUnchanged"], true, "{via}/{kind}: {result}"); assert_eq!(result["staleTimerRan"], false, "{via}/{kind}: {result}"); assert_eq!( result["syntheticDispatchCalled"], false, "{via}/{kind}: {result}" ); assert_eq!(result["newHidden"], false, "{via}/{kind}: {result}"); assert_eq!( result["unloadNavigationRan"], false, "{via}/{kind}: {result}" ); Ok(result) } #[tokio::test(flavor = "multi_thread")] async fn child_javascript_url_string_unloads_without_beforeunload() -> Result<()> { for via in ["location", "src", "anchor"] { for depth in [0, 2] { let result = child_navigation_lifecycle(via, "string", depth).await?; assert_eq!(result["sameDocument"], false); assert_eq!(result["loads"], 1); assert_eq!(result["text"], "replacement"); assert_eq!(result["children"], 0); let events = result["events"].as_array().unwrap(); let labels = std::iter::once("target".to_owned()) .chain((0..depth).map(|index| format!("descendant-{index}"))); for label in labels { let actual: Vec<_> = events .iter() .filter(|event| event.as_str().unwrap().starts_with(&format!("{label}:"))) .cloned() .collect(); assert_eq!( actual, vec![ json!(format!("{label}:pagehide")), json!(format!("{label}:visibilitychange")), json!(format!("{label}:unload")), ], "{via}/{depth}: {result}" ); } assert_eq!(events.len(), 3 * (depth + 1), "{via}/{depth}: {result}"); } } Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn child_javascript_url_non_string_preserves_document_and_descendants() -> Result<()> { for via in ["location", "src", "anchor"] { let result = child_navigation_lifecycle(via, "undefined", 2).await?; assert_eq!(result["sameDocument"], true); assert_eq!(result["loads"], 0); assert_eq!(result["events"], json!([])); assert_eq!(result["children"], 1); } Ok(()) } #[tokio::test(flavor = "multi_thread")] async fn ordinary_child_navigation_unloads_descendants_and_updates_visibility() -> Result<()> { for via in ["location", "src", "anchor"] { for depth in [0, 2] { let result = child_navigation_lifecycle(via, "network", depth).await?; assert_eq!(result["sameDocument"], false); assert_eq!(result["loads"], 1); assert_eq!(result["text"], "network"); assert_eq!(result["oldHidden"], true); let events = result["events"].as_array().unwrap(); let labels: Vec<_> = std::iter::once("target".to_owned()) .chain((0..depth).map(|index| format!("descendant-{index}"))) .collect(); // Cancellation checks for every document precede actual unloads. assert!( events .iter() .take(labels.len()) .all(|event| event.as_str().unwrap().ends_with(":beforeunload")), "{via}/{depth}: {result}" ); for label in &labels { let actual: Vec<_> = events .iter() .filter(|event| event.as_str().unwrap().starts_with(&format!("{label}:"))) .cloned() .collect(); assert_eq!( actual, vec![ json!(format!("{label}:beforeunload")), json!(format!("{label}:pagehide")), json!(format!("{label}:visibilitychange")), json!(format!("{label}:unload")) ], "{via}/{depth}: {result}" ); assert!( result["visibility"].as_array().unwrap().contains(&json!({ "label": label, "hidden": true, "state": "hidden", "trusted": true, "bubbles": true, "cancelable": false, "documentTarget": true })), "{via}/{depth}: {result}" ); assert!( result["bubbledVisibility"] .as_array() .unwrap() .contains(&json!(label)) ); } assert_eq!(events.len(), 4 * labels.len(), "{via}/{depth}: {result}"); assert_eq!(result["visibility"].as_array().unwrap().len(), labels.len()); assert_eq!( result["bubbledVisibility"].as_array().unwrap().len(), labels.len() ); } } Ok(()) }