cuprite 0.14.1 → 0.14.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,478 @@
1
+ class InvalidSelector extends Error {}
2
+ class TimedOutPromise extends Error {}
3
+ class MouseEventFailed extends Error {}
4
+
5
+ const EVENTS = {
6
+ FOCUS: ["blur", "focus", "focusin", "focusout"],
7
+ MOUSE: ["click", "dblclick", "mousedown", "mouseenter", "mouseleave",
8
+ "mousemove", "mouseover", "mouseout", "mouseup", "contextmenu"],
9
+ FORM: ["submit"]
10
+ }
11
+
12
+ class Cuprite {
13
+ constructor() {
14
+ this._json = JSON; // In case someone overrides it like mootools
15
+ }
16
+
17
+ find(method, selector, within = document) {
18
+ try {
19
+ let results = [];
20
+
21
+ if (method == "xpath") {
22
+ let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
23
+ for (let i = 0; i < xpath.snapshotLength; i++) {
24
+ results.push(xpath.snapshotItem(i));
25
+ }
26
+ } else {
27
+ results = Array.from(within.querySelectorAll(selector));
28
+ }
29
+
30
+ return results;
31
+ } catch (error) {
32
+ // DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code
33
+ if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {
34
+ throw new InvalidSelector;
35
+ } else {
36
+ throw error;
37
+ }
38
+ }
39
+ }
40
+
41
+ parents(node) {
42
+ let nodes = [];
43
+ let parent = node.parentNode;
44
+ while (parent != document && parent !== null) {
45
+ nodes.push(parent);
46
+ parent = parent.parentNode;
47
+ }
48
+ return nodes;
49
+ }
50
+
51
+ visibleText(node) {
52
+ if (this.isVisible(node)) {
53
+ if (node.nodeName == "TEXTAREA") {
54
+ return node.textContent;
55
+ } else {
56
+ if (node instanceof SVGElement) {
57
+ return node.textContent;
58
+ } else {
59
+ return node.innerText;
60
+ }
61
+ }
62
+ }
63
+ }
64
+
65
+ isVisible(node) {
66
+ let mapName, style;
67
+ // if node is area, check visibility of relevant image
68
+ if (node.tagName === "AREA") {
69
+ mapName = document.evaluate("./ancestor::map/@name", node, null, XPathResult.STRING_TYPE, null).stringValue;
70
+ node = document.querySelector(`img[usemap="#${mapName}"]`);
71
+ if (node == null) {
72
+ return false;
73
+ }
74
+ }
75
+
76
+ while (node) {
77
+ style = window.getComputedStyle(node);
78
+ if (style.display === "none" || style.visibility === "hidden" || parseFloat(style.opacity) === 0) {
79
+ return false;
80
+ }
81
+ node = node.parentElement;
82
+ }
83
+
84
+ return true;
85
+ }
86
+
87
+
88
+ isDisabled(node) {
89
+ let xpath = "parent::optgroup[@disabled] | \
90
+ ancestor::select[@disabled] | \
91
+ parent::fieldset[@disabled] | \
92
+ ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
93
+
94
+ return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
95
+ }
96
+
97
+ path(node) {
98
+ let nodes = [node];
99
+ let parent = node.parentNode;
100
+ while (parent !== document && parent !== null) {
101
+ nodes.unshift(parent);
102
+ parent = parent.parentNode;
103
+ }
104
+
105
+ let selectors = nodes.map(node => {
106
+ let prevSiblings = [];
107
+ let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
108
+
109
+ for (let i = 0; i < xpath.snapshotLength; i++) {
110
+ prevSiblings.push(xpath.snapshotItem(i));
111
+ }
112
+
113
+ return `${node.tagName}[${(prevSiblings.length + 1)}]`;
114
+ });
115
+
116
+ return `//${selectors.join("/")}`;
117
+ }
118
+
119
+ set(node, value) {
120
+ if (node.readOnly) return;
121
+
122
+ if (node.maxLength >= 0) {
123
+ value = value.substr(0, node.maxLength);
124
+ }
125
+
126
+ let valueBefore = node.value;
127
+
128
+ this.trigger(node, "focus");
129
+ this.setValue(node, "");
130
+
131
+ if (node.type == "number" || node.type == "date" || node.type == "range") {
132
+ this.setValue(node, value);
133
+ this.input(node);
134
+ } else if (node.type == "time") {
135
+ this.setValue(node, new Date(value).toTimeString().split(" ")[0]);
136
+ this.input(node);
137
+ } else if (node.type == "datetime-local") {
138
+ value = new Date(value);
139
+ let year = value.getFullYear();
140
+ let month = ("0" + (value.getMonth() + 1)).slice(-2);
141
+ let date = ("0" + value.getDate()).slice(-2);
142
+ let hour = ("0" + value.getHours()).slice(-2);
143
+ let min = ("0" + value.getMinutes()).slice(-2);
144
+ let sec = ("0" + value.getSeconds()).slice(-2);
145
+ this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);
146
+ this.input(node);
147
+ } else {
148
+ for (let i = 0; i < value.length; i++) {
149
+ let char = value[i];
150
+ let keyCode = this.characterToKeyCode(char);
151
+ // call the following functions in order, if one returns false (preventDefault),
152
+ // stop the call chain
153
+ [
154
+ () => this.keyupdowned(node, "keydown", keyCode),
155
+ () => this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)),
156
+ () => {
157
+ this.setValue(node, node.value + char)
158
+ this.input(node)
159
+ }
160
+ ].some(fn => fn())
161
+
162
+ this.keyupdowned(node, "keyup", keyCode);
163
+ }
164
+ }
165
+
166
+ if (valueBefore !== node.value) {
167
+ this.changed(node);
168
+ }
169
+ this.trigger(node, "blur");
170
+ }
171
+
172
+ setValue(node, value) {
173
+ let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
174
+ let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
175
+
176
+ if (node.tagName.toLowerCase() === 'input') {
177
+ return nativeInputValueSetter.call(node, value);
178
+ }
179
+ return nativeTextareaValueSetter.call(node, value);
180
+ }
181
+
182
+ input(node) {
183
+ let event = document.createEvent("HTMLEvents");
184
+ event.initEvent("input", true, false);
185
+ node.dispatchEvent(event);
186
+ }
187
+
188
+ /**
189
+ * @return {boolean} false when an event handler called preventDefault()
190
+ */
191
+ keyupdowned(node, eventName, keyCode) {
192
+ let event = document.createEvent("UIEvents");
193
+ event.initEvent(eventName, true, true);
194
+ event.keyCode = keyCode;
195
+ event.charCode = 0;
196
+ return !node.dispatchEvent(event);
197
+ }
198
+
199
+ /**
200
+ * @return {boolean} false when an event handler called preventDefault()
201
+ */
202
+ keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
203
+ event = document.createEvent("UIEvents");
204
+ event.initEvent("keypress", true, true);
205
+ event.window = window;
206
+ event.altKey = altKey;
207
+ event.ctrlKey = ctrlKey;
208
+ event.shiftKey = shiftKey;
209
+ event.metaKey = metaKey;
210
+ event.keyCode = keyCode;
211
+ event.charCode = charCode;
212
+ return !node.dispatchEvent(event);
213
+ }
214
+
215
+ characterToKeyCode(char) {
216
+ const specialKeys = {
217
+ 96: 192, // `
218
+ 45: 189, // -
219
+ 61: 187, // =
220
+ 91: 219, // [
221
+ 93: 221, // ]
222
+ 92: 220, // \
223
+ 59: 186, // ;
224
+ 39: 222, // '
225
+ 44: 188, // ,
226
+ 46: 190, // .
227
+ 47: 191, // /
228
+ 127: 46, // delete
229
+ 126: 192, // ~
230
+ 33: 49, // !
231
+ 64: 50, // @
232
+ 35: 51, // #
233
+ 36: 52, // $
234
+ 37: 53, // %
235
+ 94: 54, // ^
236
+ 38: 55, // &
237
+ 42: 56, // *
238
+ 40: 57, // (
239
+ 41: 48, // )
240
+ 95: 189, // _
241
+ 43: 187, // +
242
+ 123: 219, // {
243
+ 125: 221, // }
244
+ 124: 220, // |
245
+ 58: 186, // :
246
+ 34: 222, // "
247
+ 60: 188, // <
248
+ 62: 190, // >
249
+ 63: 191, // ?
250
+ }
251
+
252
+ let code = char.toUpperCase().charCodeAt(0);
253
+ return specialKeys[code] || code;
254
+ }
255
+
256
+ scrollIntoViewport(node) {
257
+ let areaImage = this._getAreaImage(node);
258
+
259
+ if (areaImage) {
260
+ return this.scrollIntoViewport(areaImage);
261
+ } else {
262
+ node.scrollIntoViewIfNeeded();
263
+
264
+ if (!this._isInViewport(node)) {
265
+ node.scrollIntoView({block: "center", inline: "center", behavior: "instant"});
266
+ return this._isInViewport(node);
267
+ }
268
+
269
+ return true;
270
+ }
271
+ }
272
+
273
+ mouseEventTest(node, name, x, y) {
274
+ let frameOffset = this._frameOffset();
275
+ x -= frameOffset.left;
276
+ y -= frameOffset.top;
277
+
278
+ let element = document.elementFromPoint(x, y);
279
+
280
+ let el = element;
281
+ while (el) {
282
+ if (el == node) {
283
+ return true;
284
+ } else {
285
+ el = el.parentNode;
286
+ }
287
+ }
288
+
289
+ let selector = element && this._getSelector(element) || "none";
290
+ throw new MouseEventFailed([name, selector, x, y].join(", "));
291
+ }
292
+
293
+ _getAreaImage(node) {
294
+ if ("area" == node.tagName.toLowerCase()) {
295
+ let map = node.parentNode;
296
+ if (map.tagName.toLowerCase() != "map") {
297
+ throw new Error("the area is not within a map");
298
+ }
299
+
300
+ let mapName = map.getAttribute("name");
301
+ if (typeof mapName === "undefined" || mapName === null) {
302
+ throw new Error("area's parent map must have a name");
303
+ }
304
+
305
+ mapName = `#${mapName.toLowerCase()}`;
306
+ let imageNode = this.find("css", `img[usemap='${mapName}']`)[0];
307
+ if (typeof imageNode === "undefined" || imageNode === null) {
308
+ throw new Error("no image matches the map");
309
+ }
310
+
311
+ return imageNode;
312
+ }
313
+ }
314
+
315
+ _frameOffset() {
316
+ let win = window;
317
+ let offset = { top: 0, left: 0 };
318
+
319
+ while (win.frameElement) {
320
+ let rect = win.frameElement.getClientRects()[0];
321
+ let style = win.getComputedStyle(win.frameElement);
322
+ win = win.parent;
323
+
324
+ offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10)
325
+ offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10)
326
+ }
327
+
328
+ return offset;
329
+ }
330
+
331
+ _getSelector(el) {
332
+ let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + " " : "";
333
+ selector += el.tagName.toLowerCase();
334
+ if (el.id) { selector += `#${el.id}` };
335
+ el.classList.forEach(c => selector += `.${c}`);
336
+ return selector;
337
+ }
338
+
339
+ _isInViewport(node) {
340
+ let rect = node.getBoundingClientRect();
341
+ return rect.top >= 0 &&
342
+ rect.left >= 0 &&
343
+ rect.bottom <= window.innerHeight &&
344
+ rect.right <= window.innerWidth;
345
+ }
346
+
347
+ select(node, value) {
348
+ if (this.isDisabled(node)) {
349
+ return false;
350
+ } else if (value == false && !node.parentNode.multiple) {
351
+ return false;
352
+ } else {
353
+ this.trigger(node.parentNode, "focus");
354
+
355
+ node.selected = value;
356
+ this.changed(node);
357
+
358
+ this.trigger(node.parentNode, "blur");
359
+ return true;
360
+ }
361
+ }
362
+
363
+ changed(node) {
364
+ let element;
365
+ let event = document.createEvent("HTMLEvents");
366
+ event.initEvent("change", true, false);
367
+
368
+ // In the case of an OPTION tag, the change event should come
369
+ // from the parent SELECT
370
+ if (node.nodeName == "OPTION") {
371
+ element = node.parentNode
372
+ if (element.nodeName == "OPTGROUP") {
373
+ element = element.parentNode
374
+ }
375
+ element
376
+ } else {
377
+ element = node
378
+ }
379
+
380
+ element.dispatchEvent(event)
381
+ }
382
+
383
+ trigger(node, name, options = {}) {
384
+ let event;
385
+
386
+ if (EVENTS.MOUSE.indexOf(name) != -1) {
387
+ event = document.createEvent("MouseEvent");
388
+ event.initMouseEvent(
389
+ name, true, true, window, 0,
390
+ options["screenX"] || 0, options["screenY"] || 0,
391
+ options["clientX"] || 0, options["clientY"] || 0,
392
+ options["ctrlKey"] || false,
393
+ options["altKey"] || false,
394
+ options["shiftKey"] || false,
395
+ options["metaKey"] || false,
396
+ options["button"] || 0, null
397
+ )
398
+ } else if (EVENTS.FOCUS.indexOf(name) != -1) {
399
+ event = this.obtainEvent(name);
400
+ } else if (EVENTS.FORM.indexOf(name) != -1) {
401
+ event = this.obtainEvent(name);
402
+ } else {
403
+ throw "Unknown event";
404
+ }
405
+
406
+ node.dispatchEvent(event);
407
+ }
408
+
409
+ obtainEvent(name) {
410
+ let event = document.createEvent("HTMLEvents");
411
+ event.initEvent(name, true, true);
412
+ return event;
413
+ }
414
+
415
+ getAttributes(node) {
416
+ let attrs = {};
417
+ for (let i = 0, len = node.attributes.length; i < len; i++) {
418
+ let attr = node.attributes[i];
419
+ attrs[attr.name] = attr.value.replace("\n", "\\n");
420
+ }
421
+
422
+ return this._json.stringify(attrs);
423
+ }
424
+
425
+ getAttribute(node, name) {
426
+ if (name == "checked" || name == "selected") {
427
+ return node[name];
428
+ } else {
429
+ return node.getAttribute(name);
430
+ }
431
+ }
432
+
433
+ value(node) {
434
+ if (node.tagName == "SELECT" && node.multiple) {
435
+ let result = []
436
+
437
+ for (let i = 0, len = node.children.length; i < len; i++) {
438
+ let option = node.children[i];
439
+ if (option.selected) {
440
+ result.push(option.value);
441
+ }
442
+ }
443
+
444
+ return result;
445
+ } else {
446
+ return node.value;
447
+ }
448
+ }
449
+
450
+ deleteText(node) {
451
+ let range = document.createRange();
452
+ range.selectNodeContents(node);
453
+ window.getSelection().removeAllRanges();
454
+ window.getSelection().addRange(range);
455
+ window.getSelection().deleteFromDocument();
456
+ }
457
+
458
+ containsSelection(node) {
459
+ let selectedNode = document.getSelection().focusNode;
460
+
461
+ if (!selectedNode) {
462
+ return false;
463
+ }
464
+
465
+ if (selectedNode.nodeType == 3) {
466
+ selectedNode = selectedNode.parentNode;
467
+ }
468
+
469
+ return node.contains(selectedNode);
470
+ }
471
+
472
+ // This command is purely for testing error handling
473
+ browserError() {
474
+ throw new Error("zomg");
475
+ }
476
+ }
477
+
478
+ window._cuprite = new Cuprite;