Skip to content

Commit

Permalink
fix: Make dom findUpUntil util work within iframes (#99)
Browse files Browse the repository at this point in the history
  • Loading branch information
pan-kot authored Sep 20, 2024
1 parent ba1e797 commit d394c0a
Show file tree
Hide file tree
Showing 4 changed files with 82 additions and 7 deletions.
34 changes: 34 additions & 0 deletions src/dom/__tests__/element-types.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { isNode, isHTMLElement, isSVGElement } from '../element-types';

test('an HTMLElement is recognized as a Node and HTMLElement', () => {
const div = document.createElement('div');
expect(isNode(div)).toBe(true);
expect(isHTMLElement(div)).toBe(true);
expect(isSVGElement(div)).toBe(false);
});

test('an SVGElement is recognized as a Node and SVGElement', () => {
const rect = document.createElementNS('http://www.w3.org/2000/svg', 'rect');
expect(isNode(rect)).toBe(true);
expect(isHTMLElement(rect)).toBe(false);
expect(isSVGElement(rect)).toBe(true);
});

test('an object is recognized as Node', () => {
expect(isNode({ nodeType: 3, nodeName: '', parentNode: {} })).toBe(true);
});

test('an object is recognized as HTMLElement', () => {
const node = { nodeType: 1, nodeName: '', parentNode: {} };
expect(isHTMLElement({ ...node, style: {}, ownerDocument: {} })).toBe(true);
expect(isHTMLElement({ ...node, style: {}, ownerDocument: {}, ownerSVGElement: {} })).toBe(false);
});

test('an object is recognized as SVGElement', () => {
const node = { nodeType: 1, nodeName: '', parentNode: {} };
expect(isSVGElement({ ...node, style: {}, ownerDocument: {} })).toBe(false);
expect(isSVGElement({ ...node, style: {}, ownerDocument: {}, ownerSVGElement: {} })).toBe(true);
});
41 changes: 41 additions & 0 deletions src/dom/element-types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

// The instanceof Node/HTMLElement/SVGElement checks can fail if the target element
// belongs to a different window than the respective type.

export function isNode(target: unknown): target is Node {
return (
target instanceof Node ||
(target !== null &&
typeof target === 'object' &&
'nodeType' in target &&
typeof target.nodeType === 'number' &&
'nodeName' in target &&
typeof target.nodeName === 'string' &&
'parentNode' in target &&
typeof target.parentNode === 'object')
);
}

export function isHTMLElement(target: unknown): target is HTMLElement {
return (
target instanceof HTMLElement ||
(isNode(target) &&
target.nodeType === Node.ELEMENT_NODE &&
'style' in target &&
typeof target.style === 'object' &&
typeof target.ownerDocument === 'object' &&
!isSVGElement(target))
);
}

export function isSVGElement(target: unknown): target is SVGElement {
return (
target instanceof SVGElement ||
(isNode(target) &&
target.nodeType === Node.ELEMENT_NODE &&
'ownerSVGElement' in target &&
typeof target.ownerSVGElement === 'object')
);
}
6 changes: 4 additions & 2 deletions src/dom/find-up-until.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { isHTMLElement } from './element-types';

/**
* Checks if the current element or any of its parent is matched with `test` function.
*
Expand All @@ -20,9 +22,9 @@ export default function findUpUntil(from: HTMLElement, test: (element: HTMLEleme
while (current && !test(current)) {
current = current.parentElement;
// If a component is used within an svg (i.e. as foreignObject), then it will
// have some ancestor elements that are SVGElement. We want to skip those,
// have some ancestor nodes that are SVGElement. We want to skip those,
// as they have very different properties to HTMLElements.
while (current && !(current instanceof HTMLElement)) {
while (current && !isHTMLElement(current)) {
current = (current as Element).parentElement;
}
}
Expand Down
8 changes: 3 additions & 5 deletions src/dom/node-contains.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
// Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
// SPDX-License-Identifier: Apache-2.0

import { isNode } from './element-types';

/**
* Checks whether the given node is a parent of the other descendant node.
* @param parent Parent node
* @param descendant Node that is checked to be a descendant of the parent node
*/
export default function nodeContains(parent: Node | null, descendant: Node | EventTarget | null) {
// ('nodeType' in descendant) is a workaround to check if descendant is a node
// Node interface is tied to the window it's created in, if the descendant was moved to an iframe after it was created,
// descendant instanceof Node will be false since Node has a different window
if (!parent || !descendant || !('nodeType' in descendant)) {
if (!parent || !descendant || !isNode(descendant)) {
return false;
}

return parent.contains(descendant);
}

0 comments on commit d394c0a

Please sign in to comment.