Implement document title and referrer semantics
This commit is contained in:
1 parent
b21b194d09
commit
edb1785313
4 files changed
+320
-14
No files matched your search
@@ -74,6 +74,10 @@ pub struct ObscuraState {
|
||||
/// encoding override for `<a>`/`<area>` hrefs in legacy-charset documents.
|
||||
pub encoding: String,
|
||||
pub title: String,
|
||||
/// URL of the document that initiated this document's navigation. Direct
|
||||
/// browser/API navigations leave this empty; document-initiated
|
||||
/// navigations set it to the source document URL.
|
||||
pub referrer: String,
|
||||
pub blocked_urls: Vec<String>,
|
||||
pub cookie_jar: Option<Arc<CookieJar>>,
|
||||
pub http_client: Option<Arc<ObscuraHttpClient>>,
|
||||
@@ -141,6 +145,7 @@ impl ObscuraState {
|
||||
url: "about:blank".to_string(),
|
||||
encoding: "UTF-8".to_string(),
|
||||
title: String::new(),
|
||||
referrer: String::new(),
|
||||
blocked_urls: Vec::new(),
|
||||
cookie_jar: None,
|
||||
http_client: None,
|
||||
@@ -350,8 +355,26 @@ fn op_dom_inner(state: &OpState, cmd: String, arg1: String, arg2: String) -> Str
|
||||
|
||||
match cmd.as_str() {
|
||||
"document_node_id" => dom.document().index().to_string(),
|
||||
"document_title" => serde_json::to_string(&gs.title).unwrap_or("\"\"".into()),
|
||||
"document_title" => {
|
||||
// The DOM is authoritative after parsing. In particular, script
|
||||
// changes through title.textContent must be reflected by
|
||||
// document.title, not hidden behind the navigation-time snapshot.
|
||||
let title = dom
|
||||
.query_selector("title")
|
||||
.ok()
|
||||
.flatten()
|
||||
.map(|title_id| {
|
||||
dom.text_content(title_id)
|
||||
.split(|ch| matches!(ch, '\t' | '\n' | '\u{000C}' | '\r' | ' '))
|
||||
.filter(|part| !part.is_empty())
|
||||
.collect::<Vec<_>>()
|
||||
.join(" ")
|
||||
})
|
||||
.unwrap_or_default();
|
||||
serde_json::to_string(&title).unwrap_or("\"\"".into())
|
||||
}
|
||||
"document_url" => serde_json::to_string(&gs.url).unwrap_or("\"\"".into()),
|
||||
"document_referrer" => serde_json::to_string(&gs.referrer).unwrap_or("\"\"".into()),
|
||||
"document_encoding" => serde_json::to_string(&gs.encoding).unwrap_or("\"UTF-8\"".into()),
|
||||
"document_element" => {
|
||||
for cid in dom.children(dom.document()) {
|
||||
|
||||
@@ -255,6 +255,14 @@ impl ObscuraJsRuntime {
|
||||
self.state.borrow_mut().title = title.to_string();
|
||||
}
|
||||
|
||||
/// Set the source document URL exposed as `document.referrer`. Navigation
|
||||
/// owns this value; it is not derived from the current URL because direct
|
||||
/// navigations and document-initiated navigations have different
|
||||
/// referrer semantics.
|
||||
pub fn set_referrer(&self, referrer: &str) {
|
||||
self.state.borrow_mut().referrer = referrer.to_string();
|
||||
}
|
||||
|
||||
pub fn set_blocked_urls(&self, patterns: Vec<String>) {
|
||||
self.state.borrow_mut().blocked_urls = patterns;
|
||||
}
|
||||
@@ -2244,7 +2252,91 @@ mod tests {
|
||||
fn test_document_title() {
|
||||
let mut rt = setup_runtime("<html><head><title>Test</title></head><body></body></html>");
|
||||
let title = rt.evaluate("document.title").unwrap();
|
||||
assert_eq!(title, serde_json::json!("Test Page"));
|
||||
assert_eq!(title, serde_json::json!("Test"));
|
||||
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
(function() {
|
||||
document.title = "A <new> title";
|
||||
return [
|
||||
document.title,
|
||||
document.querySelector("head > title").textContent,
|
||||
document.querySelectorAll("title").length
|
||||
];
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!(["A <new> title", "A <new> title", 1])
|
||||
);
|
||||
|
||||
let normalized = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
(function() {
|
||||
document.querySelector("title").textContent = " live\n\tDOM title ";
|
||||
return document.title;
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(normalized, serde_json::json!("live DOM title"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_title_setter_creates_missing_title_element() {
|
||||
let mut rt = setup_runtime("<html><body><main>content</main></body></html>");
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
(function() {
|
||||
document.title = "Created";
|
||||
return [
|
||||
document.title,
|
||||
document.head.tagName,
|
||||
document.head.firstElementChild.tagName,
|
||||
document.head.firstElementChild.textContent,
|
||||
document.documentElement.firstElementChild === document.head
|
||||
];
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!(["Created", "HEAD", "TITLE", "Created", true])
|
||||
);
|
||||
|
||||
let detached = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
(function() {
|
||||
const doc = document.implementation.createHTMLDocument();
|
||||
doc.title = " Detached title ";
|
||||
return [doc.title, doc.querySelector("title").textContent, doc.referrer];
|
||||
})()
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
detached,
|
||||
serde_json::json!(["Detached title", " Detached title ", ""])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn document_referrer_has_explicit_navigation_state() {
|
||||
let mut rt = setup_runtime("<html><body></body></html>");
|
||||
assert_eq!(rt.evaluate("document.referrer").unwrap(), serde_json::json!(""));
|
||||
|
||||
rt.set_referrer("https://source.example/path?q=1");
|
||||
assert_eq!(
|
||||
rt.evaluate("document.referrer").unwrap(),
|
||||
serde_json::json!("https://source.example/path?q=1")
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -6052,7 +6144,7 @@ mod tests {
|
||||
let result = rt
|
||||
.call_function_on("() => document.title", None, &[], true)
|
||||
.await.unwrap();
|
||||
assert_eq!(result.value.unwrap(), serde_json::json!("Test Page"));
|
||||
assert_eq!(result.value.unwrap(), serde_json::json!("Test"));
|
||||
}
|
||||
|
||||
#[tokio::test(flavor = "current_thread")]
|
||||
|
||||
Reference in new issue
Block a user