cuprite 0.14.1 → 0.14.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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;