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

+30
View File
@@ -3080,6 +3080,36 @@ if (typeof Document !== 'undefined' && !Document.prototype.importNode) {
Document.prototype.importNode = function(node, deep) { return node?.cloneNode(!!deep) || null; };
}
// Document.elementFromPoint / elementsFromPoint — no layout engine, so this is a stub:
// in-viewport coords return <body> (or <html> as fallback), out-of-viewport returns null.
// Wrong-but-non-throwing beats "undefined", which traps ad/analytics bootstraps in retry loops
// (see issue #63).
if (typeof Document !== 'undefined' && !Document.prototype.elementFromPoint) {
Document.prototype.elementFromPoint = function(x, y) {
if (typeof x !== 'number' || typeof y !== 'number' || !isFinite(x) || !isFinite(y)) {
return null;
}
var w = (typeof window !== 'undefined' && window.innerWidth) || 0;
var h = (typeof window !== 'undefined' && window.innerHeight) || 0;
if (x < 0 || y < 0 || x > w || y > h) {
return null;
}
return this.body || this.documentElement || null;
};
Document.prototype.elementsFromPoint = function(x, y) {
var el = this.elementFromPoint(x, y);
return el ? [el] : [];
};
}
if (typeof ShadowRoot !== 'undefined' && !ShadowRoot.prototype.elementFromPoint) {
ShadowRoot.prototype.elementFromPoint = function(x, y) {
return Document.prototype.elementFromPoint.call(globalThis.document || this, x, y);
};
ShadowRoot.prototype.elementsFromPoint = function(x, y) {
return Document.prototype.elementsFromPoint.call(globalThis.document || this, x, y);
};
}
globalThis.__obscura_init = function() {
_fpSeed = Date.now() ^ (Math.random() * 0xFFFFFFFF >>> 0);
_fpCache = null;