Skip to content

Commit

Permalink
fix(engine): fixes #973 to support cloneNode with ie11 devtool comment (
Browse files Browse the repository at this point in the history
#974)

* fix(engine): fixes #973 to support cloneNode with ie11 devtool comment
* fix(engine): fix for devtool logging of -bash on a root custom element
* fix(engine): more guarding around isNodeSlotted for inserted nodes
  • Loading branch information
caridy authored Jan 17, 2019
1 parent 6eb7eda commit 4931eec
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 27 deletions.
6 changes: 3 additions & 3 deletions packages/@lwc/engine/src/faux-shadow/custom-element.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,7 @@ import { getActiveElement, handleFocusIn, handleFocus, ignoreFocusIn, ignoreFocu
import { HTMLElementConstructor } from "../framework/base-bridge-element";
import { createStaticNodeList } from "../shared/static-node-list";
import { createStaticHTMLCollection } from "../shared/static-html-collection";

const hasNativeSymbolsSupport = Symbol('x').toString() === 'Symbol(x)';
import { hasNativeSymbolsSupport, isExternalChildNodeAccessorFlagOn } from "./node";

export function PatchedCustomElement(Base: HTMLElement): HTMLElementConstructor {
const Ctor = PatchedElement(Base) as HTMLElementConstructor;
Expand Down Expand Up @@ -98,10 +97,11 @@ export function PatchedCustomElement(Base: HTMLElement): HTMLElementConstructor
get childNodes(this: HTMLElement): NodeListOf<Node & Element> {
const owner = getNodeOwner(this);
const childNodes = isNull(owner) ? [] : getAllMatches(owner, getFilteredChildNodes(this));
if (process.env.NODE_ENV !== 'production' && isFalse(hasNativeSymbolsSupport)) {
if (process.env.NODE_ENV !== 'production' && isFalse(hasNativeSymbolsSupport) && isExternalChildNodeAccessorFlagOn()) {
// inserting a comment node as the first childNode to trick the IE11
// DevTool to show the content of the shadowRoot, this should only happen
// in dev-mode and in IE11 (which we detect by looking at the symbol).
// Plus it should only be in place if we know it is an external invoker.
ArrayUnshift.call(childNodes, getIE11FakeShadowRootPlaceholder(this));
}
return createStaticNodeList(childNodes);
Expand Down
64 changes: 53 additions & 11 deletions packages/@lwc/engine/src/faux-shadow/node.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
forEach,
getPrototypeOf,
setPrototypeOf,
isFalse,
} from '../shared/language';
import {
parentNodeGetter,
Expand All @@ -31,6 +32,8 @@ import { getShadowRoot } from './shadow-root';
const OwnerKey = '$$OwnerKey$$';
const OwnKey = '$$OwnKey$$';

export const hasNativeSymbolsSupport = Symbol('x').toString() === 'Symbol(x)';

export function getNodeOwnerKey(node: Node): number | undefined {
return node[OwnerKey];
}
Expand All @@ -40,12 +43,18 @@ export function setNodeOwnerKey(node: Node, key: number) {
}

export function getNodeNearestOwnerKey(node: Node): number | undefined {
let ownerKey: number | undefined;
let ownerNode: Node | null = node;
// search for the first element with owner identity (just in case of manually inserted elements)
while (!isNull(node) && isUndefined((ownerKey = node[OwnerKey]))) {
node = parentNodeGetter.call(node);
while (!isNull(ownerNode)) {
if (!isUndefined(ownerNode[OwnerKey])) {
return ownerNode[OwnerKey];
} else if (!isUndefined(ownerNode[OwnKey])) {
// perf optimization:
// root elements have ownKey but not ownerKey, we don't need to walk up anymore
return;
}
ownerNode = parentNodeGetter.call(ownerNode);
}
return ownerKey;
}

export function getNodeKey(node: Node): number | undefined {
Expand All @@ -66,7 +75,7 @@ const portalObserverConfig: MutationObserverInit = {
};

function patchPortalElement(node: Node, ownerKey: number, shadowToken: string | undefined) {
// If node aleady has an ownerkey, we can skip
// If node already has an ownerKey, we can skip
// Note: checking if a node as any ownerKey is not enough
// because this element could be moved from one
// shadow to another
Expand All @@ -76,7 +85,7 @@ function patchPortalElement(node: Node, ownerKey: number, shadowToken: string |
setNodeOwnerKey(node, ownerKey);
if (node instanceof Element) {
setCSSToken(node, shadowToken);
const { childNodes } = node;
const childNodes = getInternalChildNodes(node);
for (let i = 0, len = childNodes.length; i < len; i += 1) {
const child = childNodes[i];
patchPortalElement(child, ownerKey, shadowToken);
Expand Down Expand Up @@ -171,17 +180,17 @@ export function PatchedNode(node: Node): NodeConstructor {
throw new TypeError('Illegal constructor');
}
hasChildNodes(this: Node, ) {
return this.childNodes.length > 0;
return getInternalChildNodes(this).length > 0;
}
// @ts-ignore until [email protected]
get firstChild(this: Node): ChildNode | null {
const { childNodes } = this;
const childNodes = getInternalChildNodes(this);
// @ts-ignore until [email protected]
return childNodes[0] || null;
}
// @ts-ignore until [email protected]
get lastChild(this: Node): ChildNode | null {
const { childNodes } = this;
const childNodes = getInternalChildNodes(this);
// @ts-ignore until [email protected]
return childNodes[childNodes.length - 1] || null;
}
Expand All @@ -202,7 +211,7 @@ export function PatchedNode(node: Node): NodeConstructor {
return children.item(children.length - 1) || null;
}
get assignedSlot(this: Node): HTMLElement | null {
const parentNode: HTMLElement = nativeParentNodeGetter.call(this);
const parentNode = nativeParentNodeGetter.call(this);
/**
* if it doesn't have a parent node,
* or the parent is not an slot element
Expand Down Expand Up @@ -263,7 +272,7 @@ export function PatchedNode(node: Node): NodeConstructor {
return clone;
}

const childNodes = this.childNodes;
const childNodes = getInternalChildNodes(this);
for (let i = 0, len = childNodes.length; i < len; i += 1) {
clone.appendChild(childNodes[i].cloneNode(true));
}
Expand All @@ -276,3 +285,36 @@ export function PatchedNode(node: Node): NodeConstructor {
setPrototypeOf(PatchedNodeClass.prototype, Ctor.prototype);
return (PatchedNodeClass as any) as NodeConstructor;
}

let internalChildNodeAccessorFlag = false;

/**
* These 2 methods are providing a machinery to understand who is accessing the
* .childNodes member property of a node. If it is used from inside the synthetic shadow
* or from an external invoker. This helps to produce the right output in one very peculiar
* case, the IE11 debugging comment for shadowRoot representation on the devtool.
*/
export function isExternalChildNodeAccessorFlagOn(): boolean {
return !internalChildNodeAccessorFlag;
}
export const getInternalChildNodes = (process.env.NODE_ENV !== 'production' && isFalse(hasNativeSymbolsSupport)) ?
function(node: Node): NodeListOf<ChildNode> {
internalChildNodeAccessorFlag = true;
let childNodes;
let error = null;
try {
childNodes = node.childNodes;
} catch (e) {
// childNodes accessor should never throw, but just in case!
error = e;
} finally {
internalChildNodeAccessorFlag = false;
if (!isNull(error)) {
// re-throwing after restoring the state machinery for setInternalChildNodeAccessorFlag
throw error; // tslint:disable-line
}
}
return childNodes;
} : function(node: Node): NodeListOf<ChildNode> {
return node.childNodes;
};
12 changes: 7 additions & 5 deletions packages/@lwc/engine/src/faux-shadow/shadow-root.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { createStaticHTMLCollection } from "../shared/static-html-collection";
import { getOuterHTML } from "../3rdparty/polymer/outer-html";
import { retarget } from "../3rdparty/polymer/retarget";
import { pathComposer } from "../3rdparty/polymer/path-composer";
import { getInternalChildNodes } from "./node";

const HostKey = createFieldName('host');
const ShadowRootKey = createFieldName('shadowRoot');
Expand Down Expand Up @@ -269,15 +270,15 @@ const NodePatchDescriptors = {
enumerable: true,
configurable: true,
get(this: SyntheticShadowRootInterface): ChildNode | null {
const { childNodes } = this;
const childNodes = getInternalChildNodes(this);
return childNodes[0] || null;
},
},
lastChild: {
enumerable: true,
configurable: true,
get(this: SyntheticShadowRootInterface): ChildNode | null {
const { childNodes } = this;
const childNodes = getInternalChildNodes(this);
return childNodes[childNodes.length - 1] || null;
},
},
Expand All @@ -286,7 +287,8 @@ const NodePatchDescriptors = {
enumerable: true,
configurable: true,
value(this: SyntheticShadowRootInterface): boolean {
return this.childNodes.length > 0;
const childNodes = getInternalChildNodes(this);
return childNodes.length > 0;
},
},
isConnected: {
Expand Down Expand Up @@ -360,7 +362,7 @@ const NodePatchDescriptors = {
enumerable: true,
configurable: true,
get(this: SyntheticShadowRootInterface): string {
const { childNodes } = this;
const childNodes = getInternalChildNodes(this);
let textContent = '';
for (let i = 0, len = childNodes.length; i < len; i += 1) {
textContent += getTextContent(childNodes[i]);
Expand All @@ -383,7 +385,7 @@ const ElementPatchDescriptors = {
enumerable: true,
configurable: true,
get(this: SyntheticShadowRootInterface): string {
const { childNodes } = this;
const childNodes = getInternalChildNodes(this);
let innerHTML = '';
for (let i = 0, len = childNodes.length; i < len; i += 1) {
innerHTML += getOuterHTML(childNodes[i]);
Expand Down
18 changes: 10 additions & 8 deletions packages/@lwc/engine/src/faux-shadow/traverse.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
getNodeKey,
getNodeNearestOwnerKey,
PatchedNode,
getInternalChildNodes,
} from "./node";
import {
childNodesGetter as nativeChildNodesGetter,
Expand Down Expand Up @@ -43,15 +44,16 @@ export function getNodeOwner(node: Node): HTMLElement | null {
if (isUndefined(ownerKey)) {
return null;
}
let nodeOwner: Node | null = node;
// At this point, node is a valid node with owner identity, now we need to find the owner node
// search for a custom element with a VM that owns the first element with owner identity attached to it
while (!isNull(node) && (getNodeKey(node) !== ownerKey)) {
node = parentNodeGetter.call(node);
while (!isNull(nodeOwner) && (getNodeKey(nodeOwner) !== ownerKey)) {
nodeOwner = parentNodeGetter.call(nodeOwner);
}
if (isNull(node)) {
if (isNull(nodeOwner)) {
return null;
}
return node as HTMLElement;
return nodeOwner as HTMLElement;
}

export function isSlotElement(elm: Element): boolean {
Expand All @@ -76,15 +78,15 @@ export function isNodeSlotted(host: Element, node: Node): boolean {
}
const hostKey = getNodeKey(host);
// just in case the provided node is not an element
let currentElement: Element = node instanceof Element ? node : parentElementGetter.call(node);
let currentElement = node instanceof Element ? node : parentElementGetter.call(node);
while (!isNull(currentElement) && currentElement !== host) {
const elmOwnerKey = getNodeNearestOwnerKey(currentElement);
const parent: Element = parentElementGetter.call(currentElement);
const parent = parentElementGetter.call(currentElement);
if (elmOwnerKey === hostKey) {
// we have reached a host's node element, and only if
// that element is an slot, then the node is considered slotted
return isSlotElement(currentElement);
} else if (parent !== host && getNodeNearestOwnerKey(parent) !== elmOwnerKey) {
} else if (!isNull(parent) && parent !== host && getNodeNearestOwnerKey(parent) !== elmOwnerKey) {
// we are crossing a boundary of some sort since the elm and its parent
// have different owner key. for slotted elements, this is only possible
// if the parent happens to be a slot that is not owned by the host
Expand Down Expand Up @@ -268,7 +270,7 @@ export function PatchedElement(elm: HTMLElement): HTMLElementConstructor {
return createStaticNodeList(lightDomQuerySelectorAll(this, selectors));
}
get innerHTML(this: Element): string {
const { childNodes } = this;
const childNodes = getInternalChildNodes(this);
let innerHTML = '';
for (let i = 0, len = childNodes.length; i < len; i += 1) {
innerHTML += getOuterHTML(childNodes[i]);
Expand Down

0 comments on commit 4931eec

Please sign in to comment.