DDoS-Guard's view.js challenge calls Element.prototype.insertAdjacentText and dies with 'TypeError: t.insertAdjacentText is not a function', blocking any DDoS-Guard-protected site (e.g. pikabu.ru). insertAdjacentElement was missing too. insertAdjacentHTML was already implemented, so this just lifts the same insertion pattern for Text-node and Element insertion semantics. Both methods mirror the spec: case-insensitive position keywords, four insertion points (beforebegin/afterbegin/beforeend/afterend), and insertAdjacentElement returns the inserted element (or null when beforebegin/afterend has no parent). Registered with _markNative so Function.prototype.toString keeps reporting [native code] — consistent with the existing Element.prototype.insertAdjacentHTML stealth registration.
This commit is contained in:
1 parent
4a00a6ac2c
commit
38fa85946c
2 files changed
+121
-1
No files matched your search
Vendored
+47
-1
@@ -1111,6 +1111,51 @@ class Element extends Node {
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Same insertion semantics as insertAdjacentHTML but inserts a Text node
|
||||
// instead of parsing markup — that distinction is the whole point of the API
|
||||
// (DDoS-Guard, reCAPTCHA, and others use it precisely because it does NOT
|
||||
// parse HTML). Position string is case-insensitive per spec. See issue #285.
|
||||
insertAdjacentText(position, text) {
|
||||
const parent = this.parentNode;
|
||||
const node = document.createTextNode(String(text));
|
||||
switch (String(position).toLowerCase()) {
|
||||
case 'beforebegin':
|
||||
if (parent) parent.insertBefore(node, this);
|
||||
break;
|
||||
case 'afterbegin':
|
||||
this.insertBefore(node, this.firstChild);
|
||||
break;
|
||||
case 'beforeend':
|
||||
this.appendChild(node);
|
||||
break;
|
||||
case 'afterend':
|
||||
if (parent) parent.insertBefore(node, this.nextSibling);
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Per spec, returns the inserted element, or null when position is
|
||||
// beforebegin/afterend and this element has no parent (so the node could
|
||||
// not be placed). See issue #285.
|
||||
insertAdjacentElement(position, element) {
|
||||
const parent = this.parentNode;
|
||||
switch (String(position).toLowerCase()) {
|
||||
case 'beforebegin':
|
||||
if (!parent) return null;
|
||||
parent.insertBefore(element, this);
|
||||
return element;
|
||||
case 'afterbegin':
|
||||
this.insertBefore(element, this.firstChild);
|
||||
return element;
|
||||
case 'beforeend':
|
||||
this.appendChild(element);
|
||||
return element;
|
||||
case 'afterend':
|
||||
if (!parent) return null;
|
||||
parent.insertBefore(element, this.nextSibling);
|
||||
return element;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
addEventListener(type, handler, opts) {
|
||||
const key = this._nid;
|
||||
if (!_eventRegistry[key]) _eventRegistry[key] = {};
|
||||
@@ -4823,7 +4868,8 @@ _markNative(globalThis.Selection);
|
||||
Element.prototype.focus, Element.prototype.blur,
|
||||
Element.prototype.showPopover, Element.prototype.hidePopover, Element.prototype.togglePopover,
|
||||
Element.prototype.cloneNode, Element.prototype.attachShadow,
|
||||
Element.prototype.insertAdjacentHTML, Element.prototype.scrollIntoView,
|
||||
Element.prototype.insertAdjacentHTML, Element.prototype.insertAdjacentText,
|
||||
Element.prototype.insertAdjacentElement, Element.prototype.scrollIntoView,
|
||||
Element.prototype.append, Element.prototype.prepend, Element.prototype.remove,
|
||||
Element.prototype.before, Element.prototype.after, Element.prototype.replaceWith,
|
||||
HTMLFormElement.prototype.reset,
|
||||
|
||||
@@ -2357,6 +2357,80 @@ mod tests {
|
||||
/// Setting an ARIA reflection property must write through to the
|
||||
/// underlying attribute so frameworks that toggle state via
|
||||
/// `el.ariaExpanded = 'true'` actually update the DOM.
|
||||
/// Regression test for #285: DDoS-Guard's challenge calls
|
||||
/// `t.insertAdjacentText(...)` and dies with `TypeError: ... is not a
|
||||
/// function` because `Element.prototype.insertAdjacentText` was missing.
|
||||
/// Verify all four positions place a Text node (NOT parsed HTML) at the
|
||||
/// right spot. Tests `insertAdjacentText` exists, is callable, and that
|
||||
/// inserted content remains literal text — angle brackets must not be
|
||||
/// parsed as markup, which is the whole point of the API.
|
||||
#[test]
|
||||
fn element_insert_adjacent_text_polyfill() {
|
||||
let mut rt = setup_runtime(r#"<div id="p"><span id="t">X</span></div>"#);
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const t = document.getElementById('t');
|
||||
t.insertAdjacentText('afterbegin', 'AB');
|
||||
t.insertAdjacentText('beforeend', 'BE');
|
||||
t.insertAdjacentText('beforebegin', 'BB');
|
||||
t.insertAdjacentText('afterend', 'AE');
|
||||
t.insertAdjacentText('beforeend', '<b>raw</b>');
|
||||
return [
|
||||
typeof Element.prototype.insertAdjacentText,
|
||||
document.getElementById('p').textContent,
|
||||
t.getElementsByTagName('b').length,
|
||||
];
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!(["function", "BBABXBE<b>raw</b>AE", 0])
|
||||
);
|
||||
}
|
||||
|
||||
/// Regression test for #285: `Element.prototype.insertAdjacentElement`
|
||||
/// was missing alongside `insertAdjacentText`. Verify all four positions
|
||||
/// place the given element correctly and that the inserted element is
|
||||
/// returned (per spec — that's the contract callers rely on for chaining).
|
||||
#[test]
|
||||
fn element_insert_adjacent_element_polyfill() {
|
||||
let mut rt = setup_runtime(r#"<div id="p"><span id="t">X</span></div>"#);
|
||||
let result = rt
|
||||
.evaluate(
|
||||
r#"
|
||||
const t = document.getElementById('t');
|
||||
const before = document.createElement('b'); before.id = 'before';
|
||||
const after = document.createElement('i'); after.id = 'after';
|
||||
const inside = document.createElement('em'); inside.id = 'inside';
|
||||
const last = document.createElement('u'); last.id = 'last';
|
||||
const r1 = t.insertAdjacentElement('beforebegin', before);
|
||||
const r2 = t.insertAdjacentElement('afterend', after);
|
||||
const r3 = t.insertAdjacentElement('afterbegin', inside);
|
||||
const r4 = t.insertAdjacentElement('beforeend', last);
|
||||
const siblings = Array.from(document.getElementById('p').children).map(c => c.id);
|
||||
const inT = Array.from(t.children).map(c => c.id);
|
||||
return [
|
||||
typeof Element.prototype.insertAdjacentElement,
|
||||
r1 === before && r2 === after && r3 === inside && r4 === last,
|
||||
siblings,
|
||||
inT,
|
||||
];
|
||||
"#,
|
||||
)
|
||||
.unwrap();
|
||||
assert_eq!(
|
||||
result,
|
||||
serde_json::json!([
|
||||
"function",
|
||||
true,
|
||||
["before", "t", "after"],
|
||||
["inside", "last"]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn element_aria_reflection_setters_write_through() {
|
||||
let mut rt = setup_runtime(r#"<div id="d"></div>"#);
|
||||
|
||||
Reference in new issue
Block a user