forked from DevExpress/testcafe
-
Notifications
You must be signed in to change notification settings - Fork 0
/
dom.ts
247 lines (176 loc) · 7.8 KB
/
dom.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
import nativeMethods from '../native-methods';
const NOT_CONTENT_EDITABLE_ELEMENTS_RE = /^(select|option|applet|area|audio|canvas|datalist|keygen|map|meter|object|progress|source|track|video|img)$/;
const INPUT_ELEMENTS_RE = /^(input|textarea|button)$/;
// NOTE: save this classes in hammerhead and local native methods
const HTMLOptionElement = window.HTMLOptionElement;
const ShadowRoot = window.ShadowRoot;
const Text = window.Text;
const HTMLMapElement = window.HTMLMapElement;
const HTMLAreaElement = window.HTMLAreaElement;
const HTMLSelectElement = window.HTMLSelectElement;
const HTMLHtmlElement = window.HTMLHtmlElement;
const HTMLBodyElement = window.HTMLBodyElement;
const ProcessingInstruction = window.ProcessingInstruction;
export function isSelectElement (el: unknown): el is HTMLSelectElement {
return el instanceof HTMLSelectElement;
}
export function isShadowRoot (root: unknown): root is ShadowRoot {
return root instanceof ShadowRoot;
}
export function isDomElement (el: unknown): el is Element {
return el instanceof nativeMethods.elementClass;
}
export function isTextNode (node: unknown): node is Text {
return node instanceof Text;
}
export function isOptionElement (el: unknown): el is HTMLOptionElement {
return el instanceof HTMLOptionElement;
}
export function isBodyElement (el: unknown): el is HTMLBodyElement {
return el instanceof HTMLBodyElement;
}
export function isHtmlElement (el: unknown): el is HTMLHtmlElement {
return el instanceof HTMLHtmlElement;
}
export function getTagName (el: Element): string {
// NOTE: Check for tagName being a string, because it may be a function in an Angular app (T175340).
return el && typeof el.tagName === 'string' ? el.tagName.toLowerCase() : '';
}
function getParent (el: Element): Element | null {
el = el.assignedSlot || el;
// @ts-ignore
return el.parentNode || el.host; // eslint-disable-line no-restricted-properties
}
export function matches (el: Element, selector: string): boolean {
if (!isElementNode(el))
return false;
return nativeMethods.matches.call(el, selector);
}
export function getParents (el: Element, selector?: string): Element[] {
const parents = [];
let parent = getParent(el);
while (parent) {
if (!selector && isElementNode(parent) || selector && matches(parent, selector))
parents.push(parent);
parent = getParent(parent);
}
return parents;
}
export function getActiveElement (): Element {
let activeElement = document.activeElement || document.body; // eslint-disable-line no-restricted-properties
while (activeElement.shadowRoot?.activeElement) // eslint-disable-line no-restricted-properties
activeElement = activeElement.shadowRoot.activeElement; // eslint-disable-line no-restricted-properties
return activeElement;
}
export function getSelectParent (el: Node): HTMLSelectElement | null {
const parent = el.parentNode; // eslint-disable-line no-restricted-properties
return closest(parent as Element, 'select') as HTMLSelectElement;
}
export function isMapElement (el: unknown): el is HTMLMapElement | HTMLAreaElement {
return el instanceof HTMLMapElement || el instanceof HTMLAreaElement;
}
export function isSVGElement (instance: any): instance is SVGElement {
return instance instanceof nativeMethods.svgElementClass;
}
function isAlwaysNotEditableElement (el: Element): boolean {
const tagName = getTagName(el);
return !!tagName && (NOT_CONTENT_EDITABLE_ELEMENTS_RE.test(tagName) || INPUT_ELEMENTS_RE.test(tagName));
}
export function findDocument (el: Node): Document {
if ('documentElement' in el)
return el;
if (el.ownerDocument && el.ownerDocument.defaultView)
return el.ownerDocument;
const parent = isElementNode(el) && el.parentNode; // eslint-disable-line no-restricted-properties
return parent ? findDocument(parent) : document;
}
export function isContentEditableElement (el: Node): boolean {
const element = isTextNode(el) ? el.parentElement || el.parentNode : el; // eslint-disable-line no-restricted-properties
if (!element)
return false;
// @ts-ignore
const isContentEditable = element.isContentEditable &&
!isAlwaysNotEditableElement(element as Element);
return isRenderedNode(element) && (isContentEditable || findDocument(el).designMode === 'on');
}
export function closest (el: Element, selector: string): Element | null {
if (!isElementNode(el))
return null;
return nativeMethods.closest.call(el, selector);
}
export function getMapContainer (el: HTMLElement): Element | null {
const closestMap = closest(el, 'map');
const closestMapName = nativeMethods.getAttribute.call(closestMap, 'name');
const containerSelector = '[usemap="#' + closestMapName + '"]';
return nativeMethods.querySelector.call(findDocument(el), containerSelector);
}
export function getSelectVisibleChildren (select: HTMLSelectElement): Node[] {
const collection = nativeMethods.querySelectorAll.call(select, 'optgroup, option');
return Array.prototype.slice.call(collection);
}
export function getChildVisibleIndex (select: HTMLSelectElement, child: Node): number {
const childrenArray = getSelectVisibleChildren(select);
return childrenArray.indexOf(child);
}
export function findParent (node: Node, includeSelf: boolean, predicate: (el: Node) => boolean): Node | null {
let resultNode = includeSelf ? node : node.parentNode; // eslint-disable-line no-restricted-properties
if (typeof predicate !== 'function')
return resultNode;
while (resultNode) {
if (predicate(resultNode))
return resultNode;
resultNode = resultNode.parentNode; // eslint-disable-line no-restricted-properties
}
return null;
}
export function isElementNode (node: Node | null): node is Element {
return !!node && node.nodeType === nativeMethods.Node.ELEMENT_NODE;
}
export function isRenderedNode (node: Node): boolean {
const nodeName = node.nodeName;
return !(node instanceof ProcessingInstruction) && nodeName !== '#comment' && nodeName !== 'SCRIPT' && nodeName !== 'STYLE';
}
let scrollbarSize = NaN;
export function getScrollbarSize (): number {
if (!isNaN(scrollbarSize))
return scrollbarSize;
const scrollDiv = document.createElement('div');
scrollDiv.style.height = '100px';
scrollDiv.style.overflow = 'scroll';
scrollDiv.style.position = 'absolute';
scrollDiv.style.top = '-9999px';
scrollDiv.style.width = '100px';
document.body.appendChild(scrollDiv);
scrollbarSize = scrollDiv.offsetWidth - scrollDiv.clientWidth;
document.body.removeChild(scrollDiv);
return scrollbarSize;
}
export function isElementInIframe (el: Element | Document, currentDocument?: Document): boolean {
const doc = currentDocument || findDocument(el);
return window.document !== doc;
}
export function isIframeElement (el: Element): boolean {
return el instanceof HTMLIFrameElement;
}
export function isWindow (el: Node | Window | Document): el is Window {
return 'pageYOffset' in el;
}
export function isDocument (el: Node | Window | Document): el is Document {
return 'defaultView' in el;
}
function getFrameElement (win: Window): HTMLFrameElement | HTMLIFrameElement | null {
try {
return win.frameElement as HTMLFrameElement | HTMLIFrameElement;
}
catch (e) {
return null;
}
}
export function getIframeByElement (el: Element | Document): HTMLFrameElement | HTMLIFrameElement | null {
const doc = isDocument(el) ? el : el.ownerDocument;
const win = doc.defaultView;
return win && getFrameElement(win);
}
export function isSVGElementOrChild (el: Element): el is SVGElement {
return !!closest(el, 'svg');
}