fix(js): add elementFromPoint / elementsFromPoint stubs (#63) (#75)

document.elementFromPoint(x, y) was undefined, which caused Google
Publisher Tag, viewability libs and any code doing hit-testing to
throw on bootstrap and trigger retry loops in the surrounding
framework. This is one of the throwing APIs feeding into the wedge
described in #62.

Without a layout engine we can't return the actual topmost element,
but a non-throwing stub is strictly better than "undefined" for the
headless-scraping use case:

- coords inside the viewport return document.body (or
  document.documentElement as a fallback) so callers that just want
  a hit target stop throwing
- coords outside the viewport (negative, > innerWidth/innerHeight,
  NaN/Infinity) return null per spec
- elementsFromPoint mirrors the same shape but returns a one- or
  zero-length array
- ShadowRoot.prototype gets the same treatment for completeness

Five new unit tests in obscura-js cover function presence, in-viewport
hit, out-of-viewport coords, non-numeric input, and the array shape.

Generated by Ora Studio
Vibe coded by ousamabenyounes

Co-authored-by: Ora Studio <[email protected]>
This commit is contained in:
Ben YounesandOra Studio authored and GitHub committed 2026-05-03 17:19:17 +02:00
1 parent d50dd0f553
commit 111150a4f8
2 files changed
+75

No files matched your search

+45
View File
@@ -1480,4 +1480,49 @@ mod tests {
assert!(html.contains("<p>Test</p>"));
}
#[test]
fn test_element_from_point_is_function() {
let mut rt = setup_runtime("<html><body></body></html>");
let kind = rt.evaluate("typeof document.elementFromPoint").unwrap();
assert_eq!(kind, serde_json::json!("function"));
let kind2 = rt.evaluate("typeof document.elementsFromPoint").unwrap();
assert_eq!(kind2, serde_json::json!("function"));
}
#[test]
fn test_element_from_point_in_viewport_returns_body() {
let mut rt = setup_runtime("<html><body><h1>Hi</h1></body></html>");
let tag = rt.evaluate("document.elementFromPoint(10, 10)?.tagName").unwrap();
assert_eq!(tag, serde_json::json!("BODY"));
}
#[test]
fn test_element_from_point_out_of_viewport_returns_null() {
let mut rt = setup_runtime("<html><body></body></html>");
let neg_x = rt.evaluate("document.elementFromPoint(-1, 10)").unwrap();
assert_eq!(neg_x, serde_json::Value::Null);
let neg_y = rt.evaluate("document.elementFromPoint(10, -1)").unwrap();
assert_eq!(neg_y, serde_json::Value::Null);
let huge = rt.evaluate("document.elementFromPoint(99999, 99999)").unwrap();
assert_eq!(huge, serde_json::Value::Null);
}
#[test]
fn test_elements_from_point_returns_array() {
let mut rt = setup_runtime("<html><body></body></html>");
let len_in = rt.evaluate("document.elementsFromPoint(10, 10).length").unwrap();
assert_eq!(len_in.as_f64().unwrap() as i64, 1);
let len_out = rt.evaluate("document.elementsFromPoint(-1, -1).length").unwrap();
assert_eq!(len_out.as_f64().unwrap() as i64, 0);
}
#[test]
fn test_element_from_point_non_numeric_returns_null() {
let mut rt = setup_runtime("<html><body></body></html>");
let nan = rt.evaluate("document.elementFromPoint(NaN, 10)").unwrap();
assert_eq!(nan, serde_json::Value::Null);
let inf = rt.evaluate("document.elementFromPoint(Infinity, 10)").unwrap();
assert_eq!(inf, serde_json::Value::Null);
}
}