Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(engine): dom patching #688

Merged
Merged
Show file tree
Hide file tree
Changes from 10 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 0 additions & 23 deletions packages/lwc-engine/src/faux-shadow/__tests__/iframe.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,27 +111,4 @@ describe('wrapped iframe window', () => {
expect(contentWindow.blur).toHaveBeenCalled();
});
});

describe('unwrapping', () => {
it('should return original object', () => {
const myComponentTmpl = compileTemplate(`
<template>
<iframe src="https://salesforce.com"></iframe>
</template>
`);
class MyComponent extends LightningElement {
render() {
return myComponentTmpl;
}
}

const elm = createElement('x-foo', { is: MyComponent });
document.body.appendChild(elm);

const nativeIframeContentWindow = document.querySelector('iframe').contentWindow;
const wrappedIframe = getHostShadowRoot(elm).querySelector('iframe'); // will return monkey patched contentWindow
const contentWindowGetter = Object.getOwnPropertyDescriptor(HTMLIFrameElement.prototype, 'contentWindow').get;
expect(nativeIframeContentWindow === contentWindowGetter.call(wrappedIframe)).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -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(`
<template></template>
`);
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 {}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -646,7 +646,7 @@ describe('#childNodes', () => {
expect(() => {
const child = getHostShadowRoot(elm).querySelector('div');
const childNodes = child.childNodes;
}).toLogWarning(`childNodes on [object HTMLDivElement] returns a live NodeList which is not stable. Use querySelectorAll instead.`);
}).toLogWarning(`Discouraged access to property 'childNodes' on 'Node': It returns a live NodeList and should not be relied upon. Instead, use 'querySelectorAll' which returns a static NodeList.`);
});

it('should return correct elements for custom elements when no children present', () => {
Expand Down
13 changes: 11 additions & 2 deletions packages/lwc-engine/src/faux-shadow/custom-element.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { defineProperties } from "../shared/language";
import { attachShadow } 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) {
Expand All @@ -10,10 +10,14 @@ 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);
}

function shadowRootGetter(this: HTMLElement) {
return getShadowRoot(this);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this should take into consideration the mode

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to match native.

}

const CustomElementPatchDescriptors: PropertyDescriptorMap = {
attachShadow: {
value: attachShadowGetter,
Expand All @@ -31,6 +35,11 @@ const CustomElementPatchDescriptors: PropertyDescriptorMap = {
configurable: true,
enumerable: true,
},
shadowRoot: {
get: shadowRootGetter,
configurable: true,
enumerable: true,
}
};

export function patchCustomElement(elm: HTMLElement) {
Expand Down
4 changes: 3 additions & 1 deletion packages/lwc-engine/src/faux-shadow/document.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@ const {
createElementNS,
createTextNode,
createComment,
} = document;
elementsFromPoint,
} = Document.prototype;
Copy link
Contributor

@caridy caridy Oct 10, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

any particular reason for this change? document => Document.prototype?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not really, just mostly just to match other patterns.


export {
createElement,
createElementNS,
createTextNode,
createComment,
DocumentPrototypeActiveElement,
elementsFromPoint,
};
30 changes: 7 additions & 23 deletions packages/lwc-engine/src/faux-shadow/events.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,9 @@ import {
getRootNode,
parentNodeGetter,
} from "./node";
import { ArraySlice, ArraySplice, ArrayIndexOf, create, ArrayPush, isUndefined, isFunction, getOwnPropertyDescriptor, defineProperties, isNull, toString, forEach, defineProperty, isFalse } from "../shared/language";
import { patchShadowDomTraversalMethods } from "./traverse";
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;
Expand All @@ -33,18 +32,6 @@ const eventCurrentTargetGetter: (this: Event) => Element | null = getOwnProperty
const GET_ROOT_NODE_CONFIG_FALSE = { composed: false };

const EventPatchDescriptors: PropertyDescriptorMap = {
currentTarget: {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

very nice!

get(this: Event): EventTarget | null {
const currentTarget: EventTarget = eventCurrentTargetGetter.call(this);
if (isNull(currentTarget) || isUndefined(getNodeOwnerKey(currentTarget as Node))) {
// event is already beyond the boundaries of our controlled shadow roots
return currentTarget;
}
return patchShadowDomTraversalMethods(currentTarget as Element);
},
enumerable: true,
configurable: true,
},
target: {
get(this: Event): EventTarget {
const currentTarget: EventTarget = eventCurrentTargetGetter.call(this);
Expand All @@ -71,7 +58,7 @@ const EventPatchDescriptors: PropertyDescriptorMap = {
const eventContext = eventToContextMap.get(this);
// Executing event listener on component, target is always currentTarget
if (eventContext === EventListenerContext.CUSTOM_ELEMENT_LISTENER) {
return patchShadowDomTraversalMethods(currentTarget as Element);
return currentTarget as Element;
}
const currentTargetRootNode = getRootNode.call(currentTarget, GET_ROOT_NODE_CONFIG_FALSE); // x-child

Expand Down Expand Up @@ -160,10 +147,7 @@ const EventPatchDescriptors: PropertyDescriptorMap = {
* while the event is patched because the component is listening for it internally
* via this.addEventListener('click') in constructor or something similar
*/
if (isUndefined(getNodeOwnerKey(closestTarget as Node))) {
return closestTarget;
}
return patchShadowDomTraversalMethods(closestTarget as Element);
return closestTarget as Element;
pmdartus marked this conversation as resolved.
Show resolved Hide resolved
},
enumerable: true,
configurable: true,
Expand Down Expand Up @@ -194,7 +178,7 @@ function getEventMap(elm: HTMLElement): ListenerMap {

const shadowRootEventListenerMap: WeakMap<EventListener, WrappedListener> = 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
}
Expand Down Expand Up @@ -373,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
Expand All @@ -390,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);
Expand Down
4 changes: 0 additions & 4 deletions packages/lwc-engine/src/faux-shadow/faux.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,6 @@ export { patchEvent } from "./events";

export { patchCustomElement } from "./custom-element";

export {
unwrap as getRawNode,
} from "./traverse-membrane";

export {
lightDomQuerySelectorAll,
lightDomQuerySelector,
Expand Down
Loading