diff --git a/compat/src/portals.js b/compat/src/portals.js index ddbcdbd0dd..bce59b95eb 100644 --- a/compat/src/portals.js +++ b/compat/src/portals.js @@ -51,7 +51,8 @@ function Portal(props) { removeChild(child) { this.childNodes.splice(this.childNodes.indexOf(child) >>> 1, 1); _this._container.removeChild(child); - } + }, + ownerDocument: container.ownerDocument }; } diff --git a/src/component.js b/src/component.js index 287f3233f4..888ac9137b 100644 --- a/src/component.js +++ b/src/component.js @@ -124,7 +124,8 @@ function renderComponent(component) { let oldVNode = component._vnode, oldDom = oldVNode._dom, commitQueue = [], - refQueue = []; + refQueue = [], + parentDom = component._parentDom; if (component._parentDom) { const newVNode = assign({}, oldVNode); @@ -141,7 +142,8 @@ function renderComponent(component) { commitQueue, oldDom == null ? getDomSibling(oldVNode) : oldDom, !!(oldVNode._flags & MODE_HYDRATE), - refQueue + refQueue, + parentDom.ownerDocument ); newVNode._original = oldVNode._original; diff --git a/src/diff/children.js b/src/diff/children.js index ba2730478c..aef78b675f 100644 --- a/src/diff/children.js +++ b/src/diff/children.js @@ -25,6 +25,7 @@ import { getDomSibling } from '../component'; * siblings. In most cases, it starts out as `oldChildren[0]._dom`. * @param {boolean} isHydrating Whether or not we are in hydration * @param {any[]} refQueue an array of elements needed to invoke refs + * @param {Document} doc The owner document of the parentNode */ export function diffChildren( parentDom, @@ -37,7 +38,8 @@ export function diffChildren( commitQueue, oldDom, isHydrating, - refQueue + refQueue, + doc ) { let i, /** @type {VNode} */ @@ -86,7 +88,8 @@ export function diffChildren( commitQueue, oldDom, isHydrating, - refQueue + refQueue, + doc ); // Adjust DOM nodes diff --git a/src/diff/index.js b/src/diff/index.js index 287f94344d..fc5aa3efc6 100644 --- a/src/diff/index.js +++ b/src/diff/index.js @@ -28,6 +28,7 @@ import options from '../options'; * siblings. In most cases, it starts out as `oldChildren[0]._dom`. * @param {boolean} isHydrating Whether or not we are in hydration * @param {any[]} refQueue an array of elements needed to invoke refs + * @param {Document} doc The owner document of the parentNode */ export function diff( parentDom, @@ -39,7 +40,8 @@ export function diff( commitQueue, oldDom, isHydrating, - refQueue + refQueue, + doc ) { /** @type {any} */ let tmp, @@ -254,7 +256,8 @@ export function diff( commitQueue, oldDom, isHydrating, - refQueue + refQueue, + doc ); c.base = newVNode._dom; @@ -304,7 +307,8 @@ export function diff( excessDomChildren, commitQueue, isHydrating, - refQueue + refQueue, + doc, ); } @@ -353,6 +357,7 @@ export function commitRoot(commitQueue, root, refQueue) { * to invoke in commitRoot * @param {boolean} isHydrating Whether or not we are in hydration * @param {any[]} refQueue an array of elements needed to invoke refs + * @param {Document} doc The owner document of the parentNode * @returns {PreactElement} */ function diffElementNodes( @@ -364,7 +369,8 @@ function diffElementNodes( excessDomChildren, commitQueue, isHydrating, - refQueue + refQueue, + doc ) { let oldProps = oldVNode.props; let newProps = newVNode.props; @@ -408,10 +414,10 @@ function diffElementNodes( if (dom == null) { if (nodeType === null) { - return document.createTextNode(newProps); + return doc.createTextNode(newProps); } - dom = document.createElementNS( + dom = doc.createElementNS( namespace, nodeType, newProps.is && newProps @@ -424,6 +430,7 @@ function diffElementNodes( options._hydrationMismatch(newVNode, excessDomChildren); isHydrating = false; } + // we created a new parent, so none of the previously attached children can be reused: excessDomChildren = null; } @@ -517,7 +524,8 @@ function diffElementNodes( ? excessDomChildren[0] : oldVNode._children && getDomSibling(oldVNode, 0), isHydrating, - refQueue + refQueue, + doc ); // Remove children that are not part of any vnode. diff --git a/src/index.d.ts b/src/index.d.ts index ab372b3c2a..d733f4ba3d 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -292,6 +292,7 @@ interface ContainerNode { readonly parentNode: ContainerNode | null; readonly firstChild: ContainerNode | null; readonly childNodes: ArrayLike; + readonly ownerDocument: Document | null; contains(other: ContainerNode | null): boolean; insertBefore(node: ContainerNode, child: ContainerNode | null): ContainerNode; diff --git a/src/render.js b/src/render.js index e0fec45424..6b9fd13ad7 100644 --- a/src/render.js +++ b/src/render.js @@ -56,7 +56,8 @@ export function render(vnode, parentDom, replaceNode) { ? oldVNode._dom : parentDom.firstChild, isHydrating, - refQueue + refQueue, + parentDom.ownerDocument ); // Flush all queued effects diff --git a/test/browser/getOwnerDocument.test.js b/test/browser/getOwnerDocument.test.js new file mode 100644 index 0000000000..b67123af14 --- /dev/null +++ b/test/browser/getOwnerDocument.test.js @@ -0,0 +1,50 @@ +import { createElement, render } from 'preact'; +import { setupScratch, teardown } from '../_util/helpers'; + +/** @jsx createElement */ + +describe('parentDom.ownerDocument', () => { + /** @type {HTMLDivElement} */ + let scratch; + + before(() => { + scratch = setupScratch(); + }); + + after(() => { + teardown(scratch); + }); + + it.skip('should reference the correct document from the parent node', () => { + let iframe = document.createElement('iframe'); + + scratch.appendChild(iframe); + + let iframeDoc = iframe.contentDocument; + + iframeDoc.write( + '
' + ); + + iframeDoc.close(); + + let rootTextSpy = sinon.spy(document, 'createTextNode'); + let rootElementSpy = sinon.spy(document, 'createElement'); + + let iframeTextSpy = sinon.spy(iframeDoc, 'createTextNode'); + let iframeElementSpy = sinon.spy(iframeDoc, 'createElement'); + + let iframeRootNode = iframeDoc.querySelector('div'); + + render(Hello, iframeRootNode); + + expect(rootTextSpy).not.to.be.called; + expect(rootElementSpy).not.to.be.called; + expect(iframeTextSpy).to.be.called; + expect(iframeElementSpy).to.be.called; + + expect(iframeRootNode.textContent).to.be.equal('Hello'); + expect(iframeRootNode.firstChild.ownerDocument).to.be.equal(iframeDoc); + expect(iframeRootNode.firstChild.ownerDocument).to.not.be.equal(document); + }); +});