Skip to content

Commit

Permalink
Allow insertAdjacentHTML without a parent element sometimes
Browse files Browse the repository at this point in the history
That is, when using "afterbegin" and "beforeend", a parent element that is the document or is null should be allowed, per spec. This also refactors the algorithm to follow the spec more closely.

Closes #1676.
  • Loading branch information
domenic committed Dec 18, 2016
1 parent 139b9d7 commit 60555c0
Show file tree
Hide file tree
Showing 2 changed files with 68 additions and 18 deletions.
44 changes: 30 additions & 14 deletions lib/jsdom/living/nodes/Element-impl.js
Original file line number Diff line number Diff line change
Expand Up @@ -423,37 +423,53 @@ class ElementImpl extends NodeImpl {
}];
}

// https://w3c.github.io/DOM-Parsing/#dom-element-insertadjacenthtml
insertAdjacentHTML(position, text) {
position = position.toLowerCase();

if (this.parentNode === null || this.parentNode.nodeType === NODE_TYPE.DOCUMENT_NODE) {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, "Cannot insert HTML adjacent to parent-less " +
"nodes or children of document nodes.");
let context;
switch (position) {
case "beforebegin":
case "afterend": {
context = this.parentNode;
if (context === null || context.nodeType === NODE_TYPE.DOCUMENT_NODE) {
throw new DOMException(DOMException.NO_MODIFICATION_ALLOWED_ERR, "Cannot insert HTML adjacent to " +
"parent-less nodes or children of document nodes.");
}
break;
}
case "afterbegin":
case "beforeend": {
context = this;
break;
}
default: {
throw new DOMException(DOMException.SYNTAX_ERR, "Must provide one of \"beforebegin\", \"afterend\", " +
"\"afterbegin\", or \"beforeend\".");
}
}

// TODO: use context for parsing instead of a <template>.
const fragment = this.ownerDocument.createElement("template");
fragment.innerHTML = text;

switch (position) {
case "beforebegin":
case "beforebegin": {
this.parentNode.insertBefore(fragment.content, this);
break;

case "afterbegin":
}
case "afterbegin": {
this.insertBefore(fragment.content, this.firstChild);
break;

case "beforeend":
}
case "beforeend": {
this.appendChild(fragment.content);
break;

case "afterend":
}
case "afterend": {
this.parentNode.insertBefore(fragment.content, this.nextSibling);
break;

default:
throw new DOMException(DOMException.SYNTAX_ERR, "The value provided is not one of 'beforebegin', " +
"'afterbegin', 'beforeend', or 'afterend'.");
}
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,54 @@
}, "Throws when given an invalid position");

test(() => {
// A detached element does not have a parent node.
const el = document.createElement("span");

assert_throws("NO_MODIFICATION_ALLOWED_ERR", () => {
el.insertAdjacentHTML("beforebegin", "test");
});
}, "Throws when inserting into an element whose parent is null");
}, "Throws when inserting using \"beforebegin\" into an element whose parent is null");

test(() => {
const el = document.createElement("span");

assert_throws("NO_MODIFICATION_ALLOWED_ERR", () => {
el.insertAdjacentHTML("afterend", "test");
});
}, "Throws when inserting using \"afterend\" into an element whose parent is null");

test(() => {
const el = document.createElement("span");

el.insertAdjacentHTML("afterbegin", "foo");
el.insertAdjacentHTML("beforeend", "bar");

assert_equals(el.textContent, "foobar");
}, "Does not throw when inserting using \"afterbegin\" and \"beforeend\" into an element whose parent is null");

test(() => {
// The parent node of the html element is a document.
const el = document.querySelector("html");

assert_throws("NO_MODIFICATION_ALLOWED_ERR", () => {
el.insertAdjacentHTML("beforebegin", "test");
});
}, "Throws when inserting into an element whose parent is a document");
}, "Throws when inserting using \"beforebegin\" into an element whose parent is a document");

test(() => {
const el = document.querySelector("html");

assert_throws("NO_MODIFICATION_ALLOWED_ERR", () => {
el.insertAdjacentHTML("afterend", "test");
});
}, "Throws when inserting using \"afterend\" into an element whose parent is a document");

test(() => {
const el = document.querySelector("html");

el.insertAdjacentHTML("afterbegin", "foo");
el.insertAdjacentHTML("beforeend", "bar");

assert_true(el.textContent.startsWith("foo"));
assert_true(el.textContent.endsWith("bar"));
}, "Does not throw when inserting using \"afterbegin\" and \"beforeend\" into an element whose parent is a document");

</script>

0 comments on commit 60555c0

Please sign in to comment.