diff --git a/packages/lwc-engine/src/faux-shadow/__tests__/shadow-root.spec.ts b/packages/lwc-engine/src/faux-shadow/__tests__/shadow-root.spec.ts index b92b3bb1be..1ee68d60a4 100644 --- a/packages/lwc-engine/src/faux-shadow/__tests__/shadow-root.spec.ts +++ b/packages/lwc-engine/src/faux-shadow/__tests__/shadow-root.spec.ts @@ -4,7 +4,23 @@ import { getHostShadowRoot } from '../../framework/html-element'; describe('root', () => { describe('integration', () => { - it.skip('should support this.template.host', () => {}); + it('should support template.host', () => { + const html = compileTemplate(` + + `); + class Parent extends LightningElement { + getHost() { + return this.template.host; + } + render() { + return html; + } + } + Parent.publicMethods = ['getHost']; + const elm = createElement('x-parent', { is: Parent }); + expect(elm.getHost()).toBe(elm); + expect(elm.shadowRoot.host).toBe(elm); + }); it('should support this.template.mode', () => { class MyComponent extends LightningElement {} diff --git a/packages/lwc-engine/src/faux-shadow/custom-element.ts b/packages/lwc-engine/src/faux-shadow/custom-element.ts index 5fa55029b8..9c75de931c 100644 --- a/packages/lwc-engine/src/faux-shadow/custom-element.ts +++ b/packages/lwc-engine/src/faux-shadow/custom-element.ts @@ -1,5 +1,5 @@ import { defineProperties } from "../shared/language"; -import { attachShadow, getShadowRoot } from "./shadow-root"; +import { attachShadow, getShadowRoot, SyntheticShadowRoot } from "./shadow-root"; import { addCustomElementEventListener, removeCustomElementEventListener } from "./events"; function addEventListenerPatchedValue(this: EventTarget, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { @@ -10,7 +10,7 @@ function removeEventListenerPatchedValue(this: EventTarget, type: string, listen removeCustomElementEventListener(this as HTMLElement, type, listener, options); } -function attachShadowGetter(this: HTMLElement, options: ShadowRootInit): ShadowRoot { +function attachShadowGetter(this: HTMLElement, options: ShadowRootInit): SyntheticShadowRoot { return attachShadow(this, options); } diff --git a/packages/lwc-engine/src/faux-shadow/document.ts b/packages/lwc-engine/src/faux-shadow/document.ts index b600ffafab..e10806366f 100644 --- a/packages/lwc-engine/src/faux-shadow/document.ts +++ b/packages/lwc-engine/src/faux-shadow/document.ts @@ -7,7 +7,8 @@ const { createElementNS, createTextNode, createComment, -} = document; + elementsFromPoint, +} = Document.prototype; export { createElement, @@ -15,4 +16,5 @@ export { createTextNode, createComment, DocumentPrototypeActiveElement, + elementsFromPoint, }; diff --git a/packages/lwc-engine/src/faux-shadow/events.ts b/packages/lwc-engine/src/faux-shadow/events.ts index 44c0c0d4ca..e5a2024389 100644 --- a/packages/lwc-engine/src/faux-shadow/events.ts +++ b/packages/lwc-engine/src/faux-shadow/events.ts @@ -9,7 +9,7 @@ import { } from "./node"; import { ArraySlice, ArraySplice, ArrayIndexOf, create, ArrayPush, isUndefined, isFunction, getOwnPropertyDescriptor, defineProperties, toString, forEach, defineProperty, isFalse } from "../shared/language"; import { compareDocumentPosition, DOCUMENT_POSITION_CONTAINED_BY, getNodeOwnerKey, getNodeKey } from "./node"; -import { getHost } from "./shadow-root"; +import { getHost, SyntheticShadowRoot } from "./shadow-root"; interface WrappedListener extends EventListener { placement: EventListenerContext; @@ -178,7 +178,7 @@ function getEventMap(elm: HTMLElement): ListenerMap { const shadowRootEventListenerMap: WeakMap = new WeakMap(); -function getWrappedShadowRootListener(sr: ShadowRoot, listener: EventListener): WrappedListener { +function getWrappedShadowRootListener(sr: SyntheticShadowRoot, listener: EventListener): WrappedListener { if (!isFunction(listener)) { throw new TypeError(); // avoiding problems with non-valid listeners } @@ -357,7 +357,7 @@ export function removeCustomElementEventListener(elm: HTMLElement, type: string, detachDOMListener(elm, type, wrappedListener); } -export function addShadowRootEventListener(sr: ShadowRoot, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { +export function addShadowRootEventListener(sr: SyntheticShadowRoot, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { if (process.env.NODE_ENV !== 'production') { assert.invariant(isFunction(listener), `Invalid second argument for this.template.addEventListener() in ${toString(sr)} for event "${type}". Expected an EventListener but received ${listener}.`); // TODO: issue #420 @@ -374,7 +374,7 @@ export function addShadowRootEventListener(sr: ShadowRoot, type: string, listene attachDOMListener(elm, type, wrappedListener); } -export function removeShadowRootEventListener(sr: ShadowRoot, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { +export function removeShadowRootEventListener(sr: SyntheticShadowRoot, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { const elm = getHost(sr); const wrappedListener = getWrappedShadowRootListener(sr, listener); detachDOMListener(elm, type, wrappedListener); diff --git a/packages/lwc-engine/src/faux-shadow/shadow-root.ts b/packages/lwc-engine/src/faux-shadow/shadow-root.ts index 69e2dff33a..5b6140e184 100644 --- a/packages/lwc-engine/src/faux-shadow/shadow-root.ts +++ b/packages/lwc-engine/src/faux-shadow/shadow-root.ts @@ -1,29 +1,28 @@ import assert from "../shared/assert"; -import { create, assign, isUndefined, getOwnPropertyDescriptor, ArrayReduce, isNull } from "../shared/language"; +import { isFalse, create, isUndefined, getOwnPropertyDescriptor, ArrayReduce, isNull, defineProperty, defineProperties, setPrototypeOf } from "../shared/language"; import { addShadowRootEventListener, removeShadowRootEventListener } from "./events"; import { shadowRootQuerySelector, shadowRootQuerySelectorAll, shadowRootChildNodes, isNodeOwnedBy } from "./traverse"; import { getInternalField, setInternalField, createFieldName } from "../shared/fields"; import { getInnerHTML } from "../3rdparty/polymer/inner-html"; import { getTextContent } from "../3rdparty/polymer/text-content"; -import { compareDocumentPosition, DOCUMENT_POSITION_CONTAINED_BY } from "./node"; +import { compareDocumentPosition, DOCUMENT_POSITION_CONTAINED_BY, getNodeOwnerKey } from "./node"; // it is ok to import from the polyfill since they always go hand-to-hand anyways. import { ElementPrototypeAriaPropertyNames } from "../polyfills/aria-properties/polyfill"; -import { DocumentPrototypeActiveElement } from "./document"; - -let ArtificialShadowRootPrototype; +import { DocumentPrototypeActiveElement, elementsFromPoint } from "./document"; +import { getNodeKey } from "../framework/vm"; const HostKey = createFieldName('host'); const ShadowRootKey = createFieldName('shadowRoot'); const isNativeShadowRootAvailable = typeof (window as any).ShadowRoot !== "undefined"; -export function getHost(root: ShadowRoot): HTMLElement { +export function getHost(root: SyntheticShadowRoot): HTMLElement { if (process.env.NODE_ENV !== 'production') { assert.invariant(root[HostKey], `A 'ShadowRoot' node must be attached to an 'HTMLElement' node.`); } return root[HostKey]; } -export function getShadowRoot(elm: HTMLElement): ShadowRoot { +export function getShadowRoot(elm: HTMLElement): SyntheticShadowRoot { if (process.env.NODE_ENV !== 'production') { assert.invariant(getInternalField(elm, ShadowRootKey), `A Custom Element with a shadow attached must be provided as the first argument.`); } @@ -46,24 +45,21 @@ function createShadowRootAOMDescriptorMap(): PropertyDescriptorMap { }, create(null)); } -export function attachShadow(elm: HTMLElement, options: ShadowRootInit): ShadowRoot { +let ShadowRootPrototypePatched = false; +export function attachShadow(elm: HTMLElement, options: ShadowRootInit): SyntheticShadowRoot { if (getInternalField(elm, ShadowRootKey)) { throw new Error(`Failed to execute 'attachShadow' on 'Element': Shadow root cannot be created on a host which already hosts a shadow tree.`); } const { mode } = options; - if (isUndefined(ArtificialShadowRootPrototype)) { - // Adding AOM properties to the faux shadow root prototype - // Note: lazy creation to avoid circular deps - assign(ArtificialShadowRootDescriptors, createShadowRootAOMDescriptorMap()); - ArtificialShadowRootPrototype = create(null, ArtificialShadowRootDescriptors); - } - const sr = create(ArtificialShadowRootPrototype, { - mode: { - get() { return mode; }, - enumerable: true, - configurable: true, - }, - }) as ShadowRoot; + + // These cannot be patched when module is loaded because + // Element.prototype needs to be patched first, which happens + // after this module is executed + if (isFalse(ShadowRootPrototypePatched)) { + ShadowRootPrototypePatched = true; + defineProperties(SyntheticShadowRoot.prototype, createShadowRootAOMDescriptorMap()); + } + const sr = new SyntheticShadowRoot(mode); setInternalField(sr, HostKey, elm); setInternalField(elm, ShadowRootKey, sr); // expose the shadow via a hidden symbol for testing purposes @@ -73,21 +69,21 @@ export function attachShadow(elm: HTMLElement, options: ShadowRootInit): ShadowR return sr; } -function patchedShadowRootChildNodesGetter(this: ShadowRoot): Element[] { +function patchedShadowRootChildNodesGetter(this: SyntheticShadowRoot): Element[] { return shadowRootChildNodes(this); } -function patchedShadowRootFirstChildGetter(this: ShadowRoot): Node | null { +function patchedShadowRootFirstChildGetter(this: SyntheticShadowRoot): Node | null { const { childNodes } = this; return childNodes[0] || null; } -function patchedShadowRootLastChildGetter(this: ShadowRoot): Node | null { +function patchedShadowRootLastChildGetter(this: SyntheticShadowRoot): Node | null { const { childNodes } = this; return childNodes[childNodes.length - 1] || null; } -function patchedShadowRootInnerHTMLGetter(this: ShadowRoot): string { +function patchedShadowRootInnerHTMLGetter(this: SyntheticShadowRoot): string { const { childNodes } = this; let innerHTML = ''; for (let i = 0, len = childNodes.length; i < len; i += 1) { @@ -96,7 +92,7 @@ function patchedShadowRootInnerHTMLGetter(this: ShadowRoot): string { return innerHTML; } -function patchedShadowRootTextContentGetter(this: ShadowRoot): string { +function patchedShadowRootTextContentGetter(this: SyntheticShadowRoot): string { const { childNodes } = this; let textContent = ''; for (let i = 0, len = childNodes.length; i < len; i += 1) { @@ -105,7 +101,7 @@ function patchedShadowRootTextContentGetter(this: ShadowRoot): string { return textContent; } -function activeElementGetter(this: ShadowRoot): Element | null { +function activeElementGetter(this: SyntheticShadowRoot): Element | null { const activeElement = DocumentPrototypeActiveElement.call(document); if (isNull(activeElement)) { return activeElement; @@ -117,124 +113,117 @@ function activeElementGetter(this: ShadowRoot): Element | null { isNodeOwnedBy(host, activeElement) ? activeElement : null; } -function hostGetter(this: ShadowRoot): HTMLElement { - return getHost(this); -} +export class SyntheticShadowRoot { + constructor(mode: string) { + defineProperty(this, 'mode', { + get() { + return mode; + }, + configurable: true, + enumerable: true, + }); + } + get nodeType() { + return 11; + } + get host() { + return getHost(this); + } + get activeElement() { + return activeElementGetter.call(this); + } + get firstChild() { + return patchedShadowRootFirstChildGetter.call(this); + } + get lastChild() { + return patchedShadowRootLastChildGetter.call(this); + } + get innerHTML() { + return patchedShadowRootInnerHTMLGetter.call(this); + } + get textContent() { + return patchedShadowRootTextContentGetter.call(this); + } + get childNodes() { + return patchedShadowRootChildNodesGetter.call(this); + } + get delegatesFocus() { + return false; + } + get parentNode() { + return null; + } + hasChildNodes() { + return this.childNodes.length > 0; + } + querySelector(selector: string) { + const node = shadowRootQuerySelector(this, selector); + return node as Element; + } + querySelectorAll(selector: string) { + const nodeList = shadowRootQuerySelectorAll(this, selector); + return nodeList; + } + addEventListener(type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { + addShadowRootEventListener(this, type, listener, options); + } + removeEventListener(type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { + removeShadowRootEventListener(this, type, listener, options); + } + compareDocumentPosition(otherNode: Node | SyntheticShadowRoot) { + // this API might be called with proxies + const host = getHost(this); + if (this === otherNode) { + // it is the root itself + return 0; + } + if (this.contains(otherNode as Node)) { + // it belongs to the shadow root instance + return 20; // 10100 === DOCUMENT_POSITION_FOLLOWING & DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + } else if (compareDocumentPosition.call(host, otherNode) & DOCUMENT_POSITION_CONTAINED_BY) { + // it is a child element but does not belong to the shadow root instance + return 37; // 100101 === DOCUMENT_POSITION_DISCONNECTED & DOCUMENT_POSITION_FOLLOWING & DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + } else { + // it is not a descendant + return 35; // 100011 === DOCUMENT_POSITION_DISCONNECTED & DOCUMENT_POSITION_PRECEDING & DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC + } + } + contains(otherNode: Node) { + // this API might be called with proxies + const host = getHost(this); + // must be child of the host and owned by it. + return (compareDocumentPosition.call(host, otherNode) & DOCUMENT_POSITION_CONTAINED_BY) !== 0 && + isNodeOwnedBy(host, otherNode); + } + toString() { + return `[object ShadowRoot]`; + } -const ArtificialShadowRootDescriptors: PropertyDescriptorMap = { - host: { - get: hostGetter, - enumerable: true, - configurable: true, - }, - activeElement: { - get: activeElementGetter, - enumerable: true, - configurable: true, - }, - firstChild: { - get: patchedShadowRootFirstChildGetter, - enumerable: true, - configurable: true, - }, - lastChild: { - get: patchedShadowRootLastChildGetter, - enumerable: true, - configurable: true, - }, - innerHTML: { - get: patchedShadowRootInnerHTMLGetter, - enumerable: true, - configurable: true, - }, - textContent: { - get: patchedShadowRootTextContentGetter, - enumerable: true, - configurable: true, - }, - childNodes: { - get: patchedShadowRootChildNodesGetter, - enumerable: true, - configurable: true, - }, - delegatesFocus: { - value: false, - enumerable: true, - configurable: true, - }, - hasChildNodes: { - value(this: ShadowRoot): boolean { - return this.childNodes.length > 0; - }, - enumerable: true, - configurable: true, - }, - querySelector: { - value(this: ShadowRoot, selector: string): Element | null { - const node = shadowRootQuerySelector(this, selector); - return node as Element; - }, - enumerable: true, - configurable: true, - }, - querySelectorAll: { - value(this: ShadowRoot, selector: string): Element[] { - const nodeList = shadowRootQuerySelectorAll(this, selector); - return nodeList; - }, - enumerable: true, - configurable: true, - }, - addEventListener: { - value(this: ShadowRoot, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { - addShadowRootEventListener(this, type, listener, options); - }, - enumerable: true, - configurable: true, - }, - removeEventListener: { - value(this: ShadowRoot, type: string, listener: EventListener, options?: boolean | AddEventListenerOptions) { - removeShadowRootEventListener(this, type, listener, options); - }, - enumerable: true, - configurable: true, - }, - compareDocumentPosition: { - value(this: ShadowRoot, otherNode: Node): number { - // this API might be called with proxies - const host = getHost(this); - if (this === otherNode) { - // it is the root itself - return 0; + // Same functionality as document.elementFromPoint + // but we should only return elements that the shadow owns, + // or are ancestors of the shadow + elementFromPoint(left: number, top: number) { + const elements = elementsFromPoint.call(document, left, top); + const hostKey = getNodeKey(this.host); + let topElement = null; + for (let i = elements.length - 1; i >= 0; i -= 1) { + const el = elements[i]; + const elementOwnerKey = getNodeOwnerKey(el); + if (elementOwnerKey === hostKey) { + topElement = el; + } else if (topElement === null && isUndefined(elementOwnerKey)) { + // This should handle any global elements that are ancestors + // of our current shadow + topElement = el; } - if (this.contains(otherNode)) { - // it belongs to the shadow root instance - return 20; // 10100 === DOCUMENT_POSITION_FOLLOWING & DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC - } else if (compareDocumentPosition.call(host, otherNode) & DOCUMENT_POSITION_CONTAINED_BY) { - // it is a child element but does not belong to the shadow root instance - return 37; // 100101 === DOCUMENT_POSITION_DISCONNECTED & DOCUMENT_POSITION_FOLLOWING & DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC - } else { - // it is not a descendant - return 35; // 100011 === DOCUMENT_POSITION_DISCONNECTED & DOCUMENT_POSITION_PRECEDING & DOCUMENT_POSITION_IMPLEMENTATION_SPECIFIC - } - }, - enumerable: true, - configurable: true, - }, - contains: { - value(this: ShadowRoot, otherNode: Node): boolean { - // this API might be called with proxies - const host = getHost(this); - // must be child of the host and owned by it. - return (compareDocumentPosition.call(host, otherNode) & DOCUMENT_POSITION_CONTAINED_BY) !== 0 && - isNodeOwnedBy(host, otherNode); - }, - enumerable: true, - configurable: true, - }, - toString: { - value() { - return `[object ShadowRoot]`; - }, - }, -}; + } + return topElement; + } +} + +// Is native ShadowDom is available on window, +// we need to make sure that our synthetic shadow dom +// passed instanceof checks against window.ShadowDom +if (isNativeShadowRootAvailable) { + setPrototypeOf(SyntheticShadowRoot.prototype, (window as any).ShadowRoot.prototype); +} diff --git a/packages/lwc-engine/src/faux-shadow/traverse.ts b/packages/lwc-engine/src/faux-shadow/traverse.ts index 4581ca6f3f..273cbec517 100644 --- a/packages/lwc-engine/src/faux-shadow/traverse.ts +++ b/packages/lwc-engine/src/faux-shadow/traverse.ts @@ -26,7 +26,7 @@ import { getOwnPropertyDescriptor, isNull } from "../shared/language"; import { getOuterHTML } from "../3rdparty/polymer/outer-html"; import { getTextContent } from "../3rdparty/polymer/text-content"; import { getInnerHTML } from "../3rdparty/polymer/inner-html"; -import { getHost, getShadowRoot } from "./shadow-root"; +import { getHost, getShadowRoot, SyntheticShadowRoot } from "./shadow-root"; const iFrameContentWindowGetter: (this: HTMLIFrameElement) => Window = getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow')!.get!; @@ -63,7 +63,7 @@ export function isNodeOwnedBy(owner: HTMLElement, node: Node): boolean { return isUndefined(ownerKey) || getNodeKey(owner) === ownerKey; } -function getShadowParent(node: HTMLElement, value: undefined | HTMLElement): ShadowRoot | HTMLElement | null { +function getShadowParent(node: HTMLElement, value: undefined | HTMLElement): SyntheticShadowRoot | HTMLElement | null { const owner = getNodeOwner(node); if (value === owner) { // walking up via parent chain might end up in the shadow root element @@ -86,7 +86,7 @@ function getShadowParent(node: HTMLElement, value: undefined | HTMLElement): Sha return null; } -function parentNodeDescriptorValue(this: HTMLElement): HTMLElement | ShadowRoot | null { +function parentNodeDescriptorValue(this: HTMLElement): HTMLElement | SyntheticShadowRoot | null { const value = nativeParentNodeGetter.call(this); if (isNull(value)) { return value; @@ -105,7 +105,7 @@ function parentElementDescriptorValue(this: HTMLElement): HTMLElement | null { return parentNode; } -export function shadowRootChildNodes(root: ShadowRoot) { +export function shadowRootChildNodes(root: SyntheticShadowRoot) { const elm = getHost(root); return getAllMatches(elm, nativeChildNodesGetter.call(elm)); } @@ -159,13 +159,13 @@ function lightDomQuerySelectorValue(this: HTMLElement, selector: string): Elemen return lightDomQuerySelector(this, selector); } -export function shadowRootQuerySelector(root: ShadowRoot, selector: string): Element | null { +export function shadowRootQuerySelector(root: SyntheticShadowRoot, selector: string): Element | null { const elm = getHost(root); const nodeList = nativeQuerySelectorAll.call(elm, selector); return getFirstMatch(elm, nodeList); } -export function shadowRootQuerySelectorAll(root: ShadowRoot, selector: string): Element[] { +export function shadowRootQuerySelectorAll(root: SyntheticShadowRoot, selector: string): Element[] { const elm = getHost(root); const nodeList = nativeQuerySelectorAll.call(elm, selector); return getAllMatches(elm, nodeList); diff --git a/packages/lwc-engine/src/framework/html-properties.ts b/packages/lwc-engine/src/framework/html-properties.ts index d129d1a2fb..f0bb27237a 100644 --- a/packages/lwc-engine/src/framework/html-properties.ts +++ b/packages/lwc-engine/src/framework/html-properties.ts @@ -8,6 +8,8 @@ import { defaultDefHTMLPropertyNames } from "./attributes"; import { ElementPrototypeAriaPropertyNames } from '../polyfills/aria-properties/polyfill'; // Initialization Routines +import "../polyfills/document-element-from-point/main"; +import "../polyfills/shadow-root/main"; import "../polyfills/proxy-concat/main"; import "../polyfills/click-event-composed/main"; // must come before event-composed import "../polyfills/event-composed/main"; diff --git a/packages/lwc-engine/src/framework/restrictions.ts b/packages/lwc-engine/src/framework/restrictions.ts index bac0b4dda5..9f4347a6fe 100644 --- a/packages/lwc-engine/src/framework/restrictions.ts +++ b/packages/lwc-engine/src/framework/restrictions.ts @@ -84,11 +84,6 @@ function getShadowRootRestrictionsDescriptors(sr: ShadowRoot): PropertyDescripto return originalQuerySelectorAll.apply(this, arguments); } }, - host: { - get() { - throw new Error(`Disallowed property "host" in ShadowRoot.`); - }, - }, ownerDocument: { get() { throw new Error(`Disallowed property "ownerDocument" in ShadowRoot.`); @@ -109,7 +104,6 @@ function getShadowRootRestrictionsDescriptors(sr: ShadowRoot): PropertyDescripto insertBefore: 0, getElementById: 0, getSelection: 0, - elementFromPoint: 0, elementsFromPoint: 0, }; forEach.call(getOwnPropertyNames(BlackListedShadowRootMethods), (methodName: string) => { diff --git a/packages/lwc-engine/src/polyfills/document-element-from-point/README.md b/packages/lwc-engine/src/polyfills/document-element-from-point/README.md new file mode 100644 index 0000000000..f2e51ecb4a --- /dev/null +++ b/packages/lwc-engine/src/polyfills/document-element-from-point/README.md @@ -0,0 +1,6 @@ +# document.elementFromPoint polyfill + +This polyfill is needed to make `document.elementFromPoint` aware of our synthetic shadow roots. + +- Polyfill will only return root nodes from LWC trees +- Polyfill works correctly in both Synthetic and Native Shadow Dom modes diff --git a/packages/lwc-engine/src/polyfills/document-element-from-point/detect.ts b/packages/lwc-engine/src/polyfills/document-element-from-point/detect.ts new file mode 100644 index 0000000000..c3ef2123a6 --- /dev/null +++ b/packages/lwc-engine/src/polyfills/document-element-from-point/detect.ts @@ -0,0 +1,3 @@ +export default function detect(): boolean { + return true; +} diff --git a/packages/lwc-engine/src/polyfills/document-element-from-point/main.ts b/packages/lwc-engine/src/polyfills/document-element-from-point/main.ts new file mode 100644 index 0000000000..50f64525b4 --- /dev/null +++ b/packages/lwc-engine/src/polyfills/document-element-from-point/main.ts @@ -0,0 +1,6 @@ +import detect from './detect'; +import apply from './polyfill'; + +if (detect()) { + apply(); +} diff --git a/packages/lwc-engine/src/polyfills/document-element-from-point/polyfill.ts b/packages/lwc-engine/src/polyfills/document-element-from-point/polyfill.ts new file mode 100644 index 0000000000..e1a80bcad8 --- /dev/null +++ b/packages/lwc-engine/src/polyfills/document-element-from-point/polyfill.ts @@ -0,0 +1,22 @@ +import { elementsFromPoint } from "../../faux-shadow/document"; +import { getNodeOwnerKey } from "../../framework/vm"; +import { isUndefined } from "../../shared/language"; + +export default function apply() { + function elemFromPoint(left: number, top: number): Element | null { + const elements = elementsFromPoint.call(document, left, top); + const { length } = elements; + let match = null; + for (let i = length - 1; i >= 0; i -= 1) { + const el = elements[i]; + const ownerKey = getNodeOwnerKey(el); + if (isUndefined(ownerKey)) { + match = elements[i]; + } + } + return match; + } + + // https://github.com/Microsoft/TypeScript/issues/14139 + document.elementFromPoint = elemFromPoint as (left: number, top: number) => Element; +} diff --git a/packages/lwc-engine/src/polyfills/shadow-root/README.md b/packages/lwc-engine/src/polyfills/shadow-root/README.md new file mode 100644 index 0000000000..8b9bf1d6e6 --- /dev/null +++ b/packages/lwc-engine/src/polyfills/shadow-root/README.md @@ -0,0 +1,3 @@ +# shadow root polyfill + +This polyfill attaches our internal Synthetic Shadow Root to the Window diff --git a/packages/lwc-engine/src/polyfills/shadow-root/detect.ts b/packages/lwc-engine/src/polyfills/shadow-root/detect.ts new file mode 100644 index 0000000000..d37f420a37 --- /dev/null +++ b/packages/lwc-engine/src/polyfills/shadow-root/detect.ts @@ -0,0 +1,3 @@ +export default function detect(): boolean { + return typeof (window as any).ShadowRoot === 'undefined'; +} diff --git a/packages/lwc-engine/src/polyfills/shadow-root/main.ts b/packages/lwc-engine/src/polyfills/shadow-root/main.ts new file mode 100644 index 0000000000..50f64525b4 --- /dev/null +++ b/packages/lwc-engine/src/polyfills/shadow-root/main.ts @@ -0,0 +1,6 @@ +import detect from './detect'; +import apply from './polyfill'; + +if (detect()) { + apply(); +} diff --git a/packages/lwc-engine/src/polyfills/shadow-root/polyfill.ts b/packages/lwc-engine/src/polyfills/shadow-root/polyfill.ts new file mode 100644 index 0000000000..5bb0659798 --- /dev/null +++ b/packages/lwc-engine/src/polyfills/shadow-root/polyfill.ts @@ -0,0 +1,5 @@ +import { SyntheticShadowRoot } from "../../faux-shadow/shadow-root"; + +export default function apply() { + (window as any).ShadowRoot = SyntheticShadowRoot; +} diff --git a/packages/lwc-integration/scripts/wdio.sauce.conf.js b/packages/lwc-integration/scripts/wdio.sauce.conf.js index 9e9e8fa290..0db40e8ffa 100644 --- a/packages/lwc-integration/scripts/wdio.sauce.conf.js +++ b/packages/lwc-integration/scripts/wdio.sauce.conf.js @@ -49,7 +49,8 @@ const compatBrowsers = [ commonName: 'ie11', browserName: 'internet explorer', platform: 'Windows 10', - version: '11.103' + version: '11.103', + iedriverVersion: '3.4.0' }, { // ideally this would be 10.1 (or latest 10.x available) but there is diff --git a/packages/lwc-integration/src/components/attributes/test-attributes-suite/attributes-suite.spec.js b/packages/lwc-integration/src/components/attributes/test-attributes-suite/attributes-suite.spec.js index 0c8705a526..bc6672268c 100644 --- a/packages/lwc-integration/src/components/attributes/test-attributes-suite/attributes-suite.spec.js +++ b/packages/lwc-integration/src/components/attributes/test-attributes-suite/attributes-suite.spec.js @@ -5,7 +5,6 @@ describe('test pre dom-insertion setAttribute and removeAttribute functionality' before(() => { browser.url(URL); - }); it('should set user defined attribute value', () => { diff --git a/packages/lwc-integration/src/components/attributes/test-attributes-suite/integration/attributes-suite/attributes-suite.js b/packages/lwc-integration/src/components/attributes/test-attributes-suite/integration/attributes-suite/attributes-suite.js index 661a5ce843..0fa33e6330 100644 --- a/packages/lwc-integration/src/components/attributes/test-attributes-suite/integration/attributes-suite/attributes-suite.js +++ b/packages/lwc-integration/src/components/attributes/test-attributes-suite/integration/attributes-suite/attributes-suite.js @@ -14,8 +14,6 @@ export default class AttributesSuite extends LightningElement { childElm.setAttribute('tabindex', '4'); childElm.setAttribute('title', 'im child title'); childElm.removeAttribute('tabindex'); - - const hostElm = document.querySelector('#childhost'); - hostElm.appendChild(childElm); + document.body.appendChild(childElm); } } diff --git a/packages/lwc-integration/src/components/events/test-composed-events/composed-events.spec.js b/packages/lwc-integration/src/components/events/test-composed-events/composed-events.spec.js index 5857644d81..21d444bc30 100644 --- a/packages/lwc-integration/src/components/events/test-composed-events/composed-events.spec.js +++ b/packages/lwc-integration/src/components/events/test-composed-events/composed-events.spec.js @@ -7,12 +7,12 @@ describe('Composed events', () => { }); it('should dispatch Event on the custom element', function () { - browser.element('integration-child').click(); + browser.click('.composed-event'); assert.ok(browser.element('.event-received-indicator')); }); it('should dispatch CustomEvent on the custom element', function () { - browser.element('integration-child').click(); + browser.click('.composed-custom-event'); assert.ok(browser.element('.custom-event-received-indicator')); }); }); diff --git a/packages/lwc-integration/src/components/events/test-composed-events/integration/child/child.html b/packages/lwc-integration/src/components/events/test-composed-events/integration/child/child.html index 1405bb4ca2..6d996c719e 100644 --- a/packages/lwc-integration/src/components/events/test-composed-events/integration/child/child.html +++ b/packages/lwc-integration/src/components/events/test-composed-events/integration/child/child.html @@ -1,4 +1,4 @@