Skip to content

Commit d394c0a

Browse files
authored
fix: Make dom findUpUntil util work within iframes (#99)
1 parent ba1e797 commit d394c0a

File tree

4 files changed

+82
-7
lines changed

4 files changed

+82
-7
lines changed
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
import { isNode, isHTMLElement, isSVGElement } from '../element-types';
5+
6+
test('an HTMLElement is recognized as a Node and HTMLElement', () => {
7+
const div = document.createElement('div');
8+
expect(isNode(div)).toBe(true);
9+
expect(isHTMLElement(div)).toBe(true);
10+
expect(isSVGElement(div)).toBe(false);
11+
});
12+
13+
test('an SVGElement is recognized as a Node and SVGElement', () => {
14+
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
15+
expect(isNode(rect)).toBe(true);
16+
expect(isHTMLElement(rect)).toBe(false);
17+
expect(isSVGElement(rect)).toBe(true);
18+
});
19+
20+
test('an object is recognized as Node', () => {
21+
expect(isNode({ nodeType: 3, nodeName: '', parentNode: {} })).toBe(true);
22+
});
23+
24+
test('an object is recognized as HTMLElement', () => {
25+
const node = { nodeType: 1, nodeName: '', parentNode: {} };
26+
expect(isHTMLElement({ ...node, style: {}, ownerDocument: {} })).toBe(true);
27+
expect(isHTMLElement({ ...node, style: {}, ownerDocument: {}, ownerSVGElement: {} })).toBe(false);
28+
});
29+
30+
test('an object is recognized as SVGElement', () => {
31+
const node = { nodeType: 1, nodeName: '', parentNode: {} };
32+
expect(isSVGElement({ ...node, style: {}, ownerDocument: {} })).toBe(false);
33+
expect(isSVGElement({ ...node, style: {}, ownerDocument: {}, ownerSVGElement: {} })).toBe(true);
34+
});

src/dom/element-types.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// The instanceof Node/HTMLElement/SVGElement checks can fail if the target element
5+
// belongs to a different window than the respective type.
6+
7+
export function isNode(target: unknown): target is Node {
8+
return (
9+
target instanceof Node ||
10+
(target !== null &&
11+
typeof target === 'object' &&
12+
'nodeType' in target &&
13+
typeof target.nodeType === 'number' &&
14+
'nodeName' in target &&
15+
typeof target.nodeName === 'string' &&
16+
'parentNode' in target &&
17+
typeof target.parentNode === 'object')
18+
);
19+
}
20+
21+
export function isHTMLElement(target: unknown): target is HTMLElement {
22+
return (
23+
target instanceof HTMLElement ||
24+
(isNode(target) &&
25+
target.nodeType === Node.ELEMENT_NODE &&
26+
'style' in target &&
27+
typeof target.style === 'object' &&
28+
typeof target.ownerDocument === 'object' &&
29+
!isSVGElement(target))
30+
);
31+
}
32+
33+
export function isSVGElement(target: unknown): target is SVGElement {
34+
return (
35+
target instanceof SVGElement ||
36+
(isNode(target) &&
37+
target.nodeType === Node.ELEMENT_NODE &&
38+
'ownerSVGElement' in target &&
39+
typeof target.ownerSVGElement === 'object')
40+
);
41+
}

src/dom/find-up-until.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { isHTMLElement } from './element-types';
5+
46
/**
57
* Checks if the current element or any of its parent is matched with `test` function.
68
*
@@ -20,9 +22,9 @@ export default function findUpUntil(from: HTMLElement, test: (element: HTMLEleme
2022
while (current && !test(current)) {
2123
current = current.parentElement;
2224
// If a component is used within an svg (i.e. as foreignObject), then it will
23-
// have some ancestor elements that are SVGElement. We want to skip those,
25+
// have some ancestor nodes that are SVGElement. We want to skip those,
2426
// as they have very different properties to HTMLElements.
25-
while (current && !(current instanceof HTMLElement)) {
27+
while (current && !isHTMLElement(current)) {
2628
current = (current as Element).parentElement;
2729
}
2830
}

src/dom/node-contains.ts

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,16 @@
11
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
22
// SPDX-License-Identifier: Apache-2.0
33

4+
import { isNode } from './element-types';
5+
46
/**
57
* Checks whether the given node is a parent of the other descendant node.
68
* @param parent Parent node
79
* @param descendant Node that is checked to be a descendant of the parent node
810
*/
911
export default function nodeContains(parent: Node | null, descendant: Node | EventTarget | null) {
10-
// ('nodeType' in descendant) is a workaround to check if descendant is a node
11-
// Node interface is tied to the window it's created in, if the descendant was moved to an iframe after it was created,
12-
// descendant instanceof Node will be false since Node has a different window
13-
if (!parent || !descendant || !('nodeType' in descendant)) {
12+
if (!parent || !descendant || !isNode(descendant)) {
1413
return false;
1514
}
16-
1715
return parent.contains(descendant);
1816
}

0 commit comments

Comments
 (0)