diff --git a/src/idiomorph.js b/src/idiomorph.js index bbd972e..333a2e3 100644 --- a/src/idiomorph.js +++ b/src/idiomorph.js @@ -1019,7 +1019,7 @@ var Idiomorph = (function () { throw `Do not understand how to morph head style ${headStyle}`; } - const doc = oldNode.ownerDocument; + const doc = ownerDocumentOf(oldNode); return { target: oldNode, @@ -1452,6 +1452,27 @@ var Idiomorph = (function () { }; })(); + /** + * `
` is [LegacyOverrideBuiltIns], so a named control such as + * `` installs an own property on the form that + * shadows `Node.prototype.ownerDocument`. Read through the prototype getter + * instead, which is also realm-safe: it brand-checks the internal slot + * rather than the realm the node was created in. + */ + /** @type {((this: Node) => Document) | undefined} */ + let ownerDocumentGetter; + + /** @param {Node} node @returns {Document} */ + const ownerDocumentOf = (node) => { + // Resolve lazily so importing the module does not require a DOM. + ownerDocumentGetter ??= /** @type {(this: Node) => Document} */ ( + /** @type {PropertyDescriptor} */ ( + Object.getOwnPropertyDescriptor(Node.prototype, "ownerDocument") + ).get + ); + return ownerDocumentGetter.call(node); + }; + //============================================================================= // This is what ends up becoming the Idiomorph global object //============================================================================= diff --git a/test/core.js b/test/core.js index d543b5f..8fea55f 100644 --- a/test/core.js +++ b/test/core.js @@ -1,4 +1,14 @@ describe("Core morphing tests", function () { + it("can load before DOM constructors are available", async function () { + const source = await (await fetch("/src/idiomorph.js")).text(); + const load = new Function( + "Node", + "Element", + source + "; return Idiomorph;", + ); + load(undefined, undefined).morph.should.be.a("function"); + }); + setup(); it("morphs outerHTML by default", function () { let initial = make(""); diff --git a/test/restore-focus.js b/test/restore-focus.js index 916af6b..4811a96 100644 --- a/test/restore-focus.js +++ b/test/restore-focus.js @@ -43,6 +43,35 @@ describe("Option to forcibly restore focus after morph", function () { focused.selectionEnd.should.equal(2); }); + it("restores focus and selection state when the target form shadows ownerDocument", function () { + // a is [LegacyOverrideBuiltIns]: a named control installs an own + // property that shadows Node.prototype.ownerDocument + let form = make(`
`); + getWorkArea().append(form); + form.innerHTML = ` +
+ `; + for (const elt of form.querySelectorAll("input")) { + elt.parentElement.moveBefore = undefined; + } + let input = document.getElementById("focused"); + input.focus(); + input.setSelectionRange(1, 2); + + Idiomorph.morph( + form, + ` +
+ `, + { morphStyle: "innerHTML" }, + ); + + let focused = document.getElementById("focused"); + (document.activeElement === focused).should.equal(true); + focused.selectionStart.should.equal(1); + focused.selectionEnd.should.equal(2); + }); + it("restores focus and selection state with outerHTML morphStyle", function () { const div = make(`