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 @@
-
-
+
+