From 8dd83920c45da55ab41e515ad771d0d8bced1656 Mon Sep 17 00:00:00 2001 From: Pierre-Marie Dartus Date: Tue, 22 May 2018 16:28:27 -0700 Subject: [PATCH] fix: getRootNode when composed is false and the element is the root (#337) ## Details Fix error throw in getRootNode when the component is the root of the component tree. ## Does this PR introduce a breaking change? * [ ] Yes * [X] No --- .../src/framework/__tests__/dom.spec.ts | 58 ++-------------- packages/lwc-engine/src/framework/dom.ts | 66 +++++++++++++------ 2 files changed, 53 insertions(+), 71 deletions(-) diff --git a/packages/lwc-engine/src/framework/__tests__/dom.spec.ts b/packages/lwc-engine/src/framework/__tests__/dom.spec.ts index 3236d3cce4..e0bced4c07 100644 --- a/packages/lwc-engine/src/framework/__tests__/dom.spec.ts +++ b/packages/lwc-engine/src/framework/__tests__/dom.spec.ts @@ -50,34 +50,12 @@ describe('dom', () => { }); it('should return correct value from self', () => { - class Parent extends Element { - handleFoo(evt) { - expect(evt.target).toBe(this.template.querySelector('x-foo')); - } - - render() { - return ($api, $cmp) => { - return [ - $api.h( - 'div', - { - on: { - foo: $api.b($cmp.handleFoo) - }, - key: 0, - }, - [] - ) - ] - } - } - } - + class Parent extends Element {} const elm = createElement('x-parent', { is: Parent }); document.body.appendChild(elm); + const match = getRootNode.call(elm, { composed: true }); - // We can't assert against document directly, because - // for some reasons, jest is locking up with document here + expect(match.nodeName).toBe('#document'); }); }); @@ -129,34 +107,12 @@ describe('dom', () => { }); it('should return correct value from self', () => { - class Parent extends Element { - handleFoo(evt) { - expect(evt.target).toBe(this.template.querySelector('x-foo')); - } - - render() { - return ($api, $cmp) => { - return [ - $api.h( - 'div', - { - on: { - foo: $api.b($cmp.handleFoo) - }, - key: 0, - }, - [] - ) - ] - } - } - } - + class Parent extends Element {} const elm = createElement('x-parent', { is: Parent }); document.body.appendChild(elm); - const match = getRootNode.call(elm, { composed: true }); - // We can't assert against document directly, because - // for some reasons, jest is locking up with document here + + const match = getRootNode.call(elm, { composed: false }); + expect(match.nodeName).toBe('#document'); }); }); diff --git a/packages/lwc-engine/src/framework/dom.ts b/packages/lwc-engine/src/framework/dom.ts index cec6272e9b..68122bd85d 100644 --- a/packages/lwc-engine/src/framework/dom.ts +++ b/packages/lwc-engine/src/framework/dom.ts @@ -9,6 +9,7 @@ import { getOwnPropertyDescriptor, isUndefined, isNull, + isTrue, defineProperties, } from './language'; import { ViewModelReflection } from "./utils"; @@ -35,34 +36,59 @@ const { compareDocumentPosition, } = Node.prototype; -function findShadowRoot(node) { - let root = node; - while (isUndefined(root[ViewModelReflection])) { - root = root.parentNode; +/** + * Returns the context shadow included root. + */ +function findShadowRoot(node: Node): Node { + // We need to ensure that the parent element is present before accessing it. + if (isNull(node.parentNode)) { + return node; } - return root; + + // In the case of LWC, the root and the host element are the same things. Therefor, + // when calling findShadowRoot on the a host element we want to return the parent host + // element and not the current host element. + node = node.parentNode; + while ( + !isNull(node.parentNode) && + isUndefined(node[ViewModelReflection]) + ) { + node = node.parentNode; + } + + return node; } -function findComposedRootNode(node: Node) { - while (node !== document) { - const parent = node.parentNode; - if (isNull(parent)) { - return node; - } - node = parent; +/** + * Returns the context root beyond the shadow root. + * + * It doesn't returns actually the root but the host. This approximation is sufficiant + * in our case. + */ +function findComposedRootNode(node: Node): Node { + while (!isNull(node.parentNode)) { + node = node.parentNode; } + return node; } -// TODO: once we start using the real shadowDOM, we can rely on: -// const { getRootNode } = Node.prototype; -// for now, we need to provide a dummy implementation to provide retargeting -function getRootNode(this: Node, options: Record | undefined): Node { +/** + * Dummy implementation of the Node.prototype.getRootNode. + * Spec: https://dom.spec.whatwg.org/#dom-node-getrootnode + * + * TODO: Once we start using the real shadowDOM, this method should be replaced by: + * const { getRootNode } = Node.prototype; + */ +function getRootNode( + this: Node, + options?: { composed?: boolean } +): Node { const composed: boolean = isUndefined(options) ? false : !!options.composed; - if (!composed) { - return findShadowRoot(this.parentNode); // this is not quite the root (it is the host), but for us is sufficient - } - return findComposedRootNode(this); + + return isTrue(composed) ? + findComposedRootNode(this) : + findShadowRoot(this); } export function isChildNode(root: Element, node: Node): boolean {