cuprite 0.18 → 0.20
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.
- checksums.yaml +4 -4
- data/README.md +69 -37
- data/lib/capybara/cuprite/browser/deprecations.rb +26 -0
- data/lib/capybara/cuprite/browser/extensions.rb +59 -0
- data/lib/capybara/cuprite/browser.rb +28 -231
- data/lib/capybara/cuprite/deprecation.rb +13 -0
- data/lib/capybara/cuprite/devices.rb +188 -0
- data/lib/capybara/cuprite/driver/deprecations.rb +198 -0
- data/lib/capybara/cuprite/driver/extensions.rb +13 -0
- data/lib/capybara/cuprite/driver.rb +68 -201
- data/lib/capybara/cuprite/finder.rb +22 -0
- data/lib/capybara/cuprite/javascripts/drag.js +100 -103
- data/lib/capybara/cuprite/javascripts/index.js +511 -489
- data/lib/capybara/cuprite/node/deprecations.rb +43 -0
- data/lib/capybara/cuprite/node/extensions.rb +27 -0
- data/lib/capybara/cuprite/node.rb +140 -116
- data/lib/capybara/cuprite/options.rb +19 -0
- data/lib/capybara/cuprite/page/frames.rb +47 -0
- data/lib/capybara/cuprite/page/modals.rb +118 -0
- data/lib/capybara/cuprite/page.rb +9 -207
- data/lib/capybara/cuprite/version.rb +1 -1
- data/lib/capybara/cuprite.rb +1 -0
- metadata +16 -6
- data/lib/capybara/cuprite/cookie.rb +0 -47
|
@@ -1,606 +1,628 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
constructor() {
|
|
14
|
-
this._json = JSON; // In case someone overrides it like mootools
|
|
1
|
+
(() => {
|
|
2
|
+
if (window._cuprite) return;
|
|
3
|
+
|
|
4
|
+
class InvalidSelector extends Error {}
|
|
5
|
+
class TimedOutPromise extends Error {}
|
|
6
|
+
class MouseEventFailed extends Error {}
|
|
7
|
+
|
|
8
|
+
const EVENTS = {
|
|
9
|
+
FOCUS: ["blur", "focus", "focusin", "focusout"],
|
|
10
|
+
MOUSE: ["click", "dblclick", "mousedown", "mouseenter", "mouseleave",
|
|
11
|
+
"mousemove", "mouseover", "mouseout", "mouseup", "contextmenu"],
|
|
12
|
+
FORM: ["submit"]
|
|
15
13
|
}
|
|
16
14
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
class Cuprite {
|
|
16
|
+
constructor() {
|
|
17
|
+
this._json = JSON; // In case someone overrides it like mootools
|
|
18
|
+
}
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
20
|
+
find(method, selector, within = document) {
|
|
21
|
+
try {
|
|
22
|
+
let results = [];
|
|
23
|
+
|
|
24
|
+
if (method == "xpath") {
|
|
25
|
+
let xpath = document.evaluate(selector, within, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
26
|
+
for (let i = 0; i < xpath.snapshotLength; i++) {
|
|
27
|
+
results.push(xpath.snapshotItem(i));
|
|
28
|
+
}
|
|
29
|
+
} else {
|
|
30
|
+
results = Array.from(within.querySelectorAll(selector));
|
|
25
31
|
}
|
|
26
|
-
} else {
|
|
27
|
-
results = Array.from(within.querySelectorAll(selector));
|
|
28
|
-
}
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
33
|
+
return results;
|
|
34
|
+
} catch (error) {
|
|
35
|
+
// DOMException.INVALID_EXPRESSION_ERR is undefined, using pure code
|
|
36
|
+
if (error.code == DOMException.SYNTAX_ERR || error.code == 51) {
|
|
37
|
+
throw new InvalidSelector;
|
|
38
|
+
} else {
|
|
39
|
+
throw error;
|
|
40
|
+
}
|
|
37
41
|
}
|
|
38
42
|
}
|
|
39
|
-
}
|
|
40
43
|
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
}
|
|
48
|
-
return nodes;
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
visibleText(node) {
|
|
52
|
-
if (this.isVisible(node)) {
|
|
53
|
-
if (node.nodeName == "TEXTAREA") {
|
|
54
|
-
return node.textContent;
|
|
55
|
-
}
|
|
56
|
-
if (node instanceof SVGElement) {
|
|
57
|
-
return node.textContent;
|
|
44
|
+
parents(node) {
|
|
45
|
+
let nodes = [];
|
|
46
|
+
let parent = node.parentNode;
|
|
47
|
+
while (parent != document && parent !== null) {
|
|
48
|
+
nodes.push(parent);
|
|
49
|
+
parent = parent.parentNode;
|
|
58
50
|
}
|
|
59
|
-
|
|
60
|
-
return Array.from(node.children)
|
|
61
|
-
.map(child => this.visibleText(child))
|
|
62
|
-
.filter(text => text)
|
|
63
|
-
.join(" ");
|
|
64
|
-
}
|
|
65
|
-
return node.innerText;
|
|
51
|
+
return nodes;
|
|
66
52
|
}
|
|
67
|
-
}
|
|
68
53
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
visibleText(node) {
|
|
55
|
+
if (this.isVisible(node)) {
|
|
56
|
+
if (node.nodeName == "TEXTAREA") {
|
|
57
|
+
return node.textContent;
|
|
58
|
+
}
|
|
59
|
+
if (node instanceof SVGElement) {
|
|
60
|
+
return node.textContent;
|
|
61
|
+
}
|
|
62
|
+
if (node instanceof ShadowRoot) {
|
|
63
|
+
return Array.from(node.children)
|
|
64
|
+
.map(child => this.visibleText(child))
|
|
65
|
+
.filter(text => text)
|
|
66
|
+
.join(" ");
|
|
67
|
+
}
|
|
68
|
+
return node.innerText;
|
|
77
69
|
}
|
|
78
70
|
}
|
|
79
71
|
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
72
|
+
isVisible(node) {
|
|
73
|
+
let mapName, style;
|
|
74
|
+
// if node is area, check visibility of relevant image
|
|
75
|
+
if (node.tagName === "AREA") {
|
|
76
|
+
mapName = document.evaluate("./ancestor::map/@name", node, null, XPathResult.STRING_TYPE, null).stringValue;
|
|
77
|
+
node = document.querySelector(`img[usemap="#${mapName}"]`);
|
|
78
|
+
if (node == null) {
|
|
86
79
|
return false;
|
|
87
80
|
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
let ancestorOpacityApplies = true;
|
|
88
84
|
|
|
89
|
-
|
|
90
|
-
if (
|
|
91
|
-
|
|
92
|
-
|
|
85
|
+
while (node) {
|
|
86
|
+
if (node instanceof ShadowRoot) {
|
|
87
|
+
node = node.host;
|
|
88
|
+
} else {
|
|
89
|
+
style = window.getComputedStyle(node);
|
|
90
|
+
if (style.display === "none" || style.visibility === "hidden" || (ancestorOpacityApplies && parseFloat(style.opacity) === 0)) {
|
|
93
91
|
return false;
|
|
94
92
|
}
|
|
95
|
-
}
|
|
96
93
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
// A node matching :modal or :popover-open is a top-layer element, so
|
|
95
|
+
// an ancestor's opacity no longer applies to it once shown.
|
|
96
|
+
if (ancestorOpacityApplies) {
|
|
97
|
+
try {
|
|
98
|
+
if (node.matches(":modal, :popover-open")) {
|
|
99
|
+
ancestorOpacityApplies = false;
|
|
100
|
+
}
|
|
101
|
+
} catch (e) {}
|
|
102
|
+
}
|
|
100
103
|
|
|
101
|
-
|
|
102
|
-
|
|
104
|
+
let parent = node.parentElement;
|
|
105
|
+
if (parent && parent.tagName === "DETAILS" && !parent.open) {
|
|
106
|
+
// In a closed <details> only the first <summary> (and its subtree) is rendered.
|
|
107
|
+
if (node !== parent.querySelector(":scope > summary")) {
|
|
108
|
+
return false;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
103
111
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
parent::fieldset[@disabled] | \
|
|
108
|
-
ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
|
|
112
|
+
node = parent ?? (node.getRootNode() instanceof ShadowRoot && node.getRootNode());
|
|
113
|
+
}
|
|
114
|
+
}
|
|
109
115
|
|
|
110
|
-
|
|
111
|
-
|
|
116
|
+
return true;
|
|
117
|
+
}
|
|
112
118
|
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
119
|
+
isDisabled(node) {
|
|
120
|
+
let xpath = "parent::optgroup[@disabled] | \
|
|
121
|
+
ancestor::select[@disabled] | \
|
|
122
|
+
parent::fieldset[@disabled] | \
|
|
123
|
+
ancestor::*[not(self::legend) or preceding-sibling::legend][parent::fieldset[@disabled]]";
|
|
117
124
|
|
|
118
|
-
|
|
119
|
-
let parent = node.parentNode;
|
|
120
|
-
while (parent !== document && parent !== null) {
|
|
121
|
-
nodes.unshift(parent);
|
|
122
|
-
parent = parent.parentNode;
|
|
125
|
+
return node.disabled || document.evaluate(xpath, node, null, XPathResult.BOOLEAN_TYPE, null).booleanValue;
|
|
123
126
|
}
|
|
124
127
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
+
path(node) {
|
|
129
|
+
if (node.getRootNode && node.getRootNode() instanceof ShadowRoot) {
|
|
130
|
+
return "(: Shadow DOM element - no XPath :)";
|
|
131
|
+
};
|
|
128
132
|
|
|
129
|
-
|
|
130
|
-
|
|
133
|
+
let nodes = [node];
|
|
134
|
+
let parent = node.parentNode;
|
|
135
|
+
while (parent !== document && parent !== null) {
|
|
136
|
+
nodes.unshift(parent);
|
|
137
|
+
parent = parent.parentNode;
|
|
131
138
|
}
|
|
132
139
|
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
return `//${selectors.join("/")}`;
|
|
137
|
-
}
|
|
140
|
+
let selectors = nodes.map(node => {
|
|
141
|
+
let prevSiblings = [];
|
|
142
|
+
let xpath = document.evaluate(`./preceding-sibling::${node.tagName}`, node, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
|
|
138
143
|
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
* @param {Element} node
|
|
143
|
-
* @return {boolean} true if the node is obscured, false otherwise
|
|
144
|
-
*/
|
|
145
|
-
isObscured(node) {
|
|
146
|
-
let win = window;
|
|
147
|
-
let rect = node.getBoundingClientRect();
|
|
148
|
-
let px = rect.left + rect.width / 2;
|
|
149
|
-
let py = rect.top + rect.height / 2;
|
|
144
|
+
for (let i = 0; i < xpath.snapshotLength; i++) {
|
|
145
|
+
prevSiblings.push(xpath.snapshotItem(i));
|
|
146
|
+
}
|
|
150
147
|
|
|
151
|
-
|
|
152
|
-
|
|
148
|
+
return `${node.tagName}[${(prevSiblings.length + 1)}]`;
|
|
149
|
+
});
|
|
153
150
|
|
|
154
|
-
|
|
151
|
+
return `//${selectors.join("/")}`;
|
|
152
|
+
}
|
|
155
153
|
|
|
156
|
-
|
|
157
|
-
|
|
154
|
+
/**
|
|
155
|
+
* Returns true if the node is obscured in the viewport.
|
|
156
|
+
*
|
|
157
|
+
* @param {Element} node
|
|
158
|
+
* @return {boolean} true if the node is obscured, false otherwise
|
|
159
|
+
*/
|
|
160
|
+
isObscured(node) {
|
|
161
|
+
let win = window;
|
|
162
|
+
let rect = node.getBoundingClientRect();
|
|
163
|
+
let px = rect.left + rect.width / 2;
|
|
164
|
+
let py = rect.top + rect.height / 2;
|
|
165
|
+
|
|
166
|
+
while (win) {
|
|
167
|
+
let topNode = win.document.elementFromPoint(px, py);
|
|
168
|
+
|
|
169
|
+
if (node !== topNode && !node.contains(topNode)) return true;
|
|
170
|
+
|
|
171
|
+
node = win.frameElement;
|
|
172
|
+
if (!node) return false;
|
|
173
|
+
|
|
174
|
+
rect = node.getBoundingClientRect();
|
|
175
|
+
px = rect.left + px;
|
|
176
|
+
py = rect.top + py;
|
|
177
|
+
win = win.parent;
|
|
178
|
+
}
|
|
158
179
|
|
|
159
|
-
|
|
160
|
-
px = rect.left + px;
|
|
161
|
-
py = rect.top + py;
|
|
162
|
-
win = win.parent;
|
|
180
|
+
return false;
|
|
163
181
|
}
|
|
164
182
|
|
|
165
|
-
|
|
166
|
-
|
|
183
|
+
set(node, value) {
|
|
184
|
+
if (node.readOnly) return;
|
|
167
185
|
|
|
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
|
-
|
|
186
|
+
if (node.maxLength >= 0) {
|
|
187
|
+
value = value.substr(0, node.maxLength);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
let valueBefore = node.value;
|
|
191
|
+
|
|
192
|
+
node.focus();
|
|
193
|
+
this.trigger(node, "focus");
|
|
194
|
+
this.setValue(node, "");
|
|
195
|
+
|
|
196
|
+
if (node.type == "number" || node.type == "date" || node.type == "range" || node.type == "time" || node.type == "month" || node.type == "week") {
|
|
197
|
+
this.setValue(node, value);
|
|
198
|
+
this.input(node);
|
|
199
|
+
} else if (node.type == "datetime-local") {
|
|
200
|
+
value = new Date(value);
|
|
201
|
+
let year = value.getFullYear();
|
|
202
|
+
let month = ("0" + (value.getMonth() + 1)).slice(-2);
|
|
203
|
+
let date = ("0" + value.getDate()).slice(-2);
|
|
204
|
+
let hour = ("0" + value.getHours()).slice(-2);
|
|
205
|
+
let min = ("0" + value.getMinutes()).slice(-2);
|
|
206
|
+
let sec = ("0" + value.getSeconds()).slice(-2);
|
|
207
|
+
this.setValue(node, `${year}-${month}-${date}T${hour}:${min}:${sec}`);
|
|
208
|
+
this.input(node);
|
|
209
|
+
} else {
|
|
210
|
+
for (let i = 0; i < value.length; i++) {
|
|
211
|
+
let char = value[i];
|
|
212
|
+
let keyCode = this.characterToKeyCode(char);
|
|
213
|
+
// call the following functions in order, if one returns false (preventDefault),
|
|
214
|
+
// stop the call chain
|
|
215
|
+
[
|
|
216
|
+
() => this.keyupdowned(node, "keydown", char, keyCode),
|
|
217
|
+
() => this.keypressed(node, false, false, false, false, char.charCodeAt(0), char.charCodeAt(0)),
|
|
218
|
+
() => {
|
|
219
|
+
this.setValue(node, node.value + char)
|
|
220
|
+
this.input(node)
|
|
221
|
+
}
|
|
222
|
+
].some(fn => fn())
|
|
223
|
+
|
|
224
|
+
this.keyupdowned(node, "keyup", char, keyCode);
|
|
225
|
+
}
|
|
208
226
|
|
|
209
|
-
|
|
227
|
+
if (valueBefore !== "" && node.value === "") {
|
|
228
|
+
this.input(node, "deleteContentBackward");
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
if (valueBefore !== node.value) {
|
|
233
|
+
this.changed(node);
|
|
210
234
|
}
|
|
235
|
+
this.trigger(node, "blur");
|
|
211
236
|
}
|
|
212
237
|
|
|
213
|
-
|
|
214
|
-
|
|
238
|
+
setValue(node, value) {
|
|
239
|
+
let nativeInputValueSetter = Object.getOwnPropertyDescriptor(window.HTMLInputElement.prototype, "value").set;
|
|
240
|
+
let nativeTextareaValueSetter = Object.getOwnPropertyDescriptor(window.HTMLTextAreaElement.prototype, "value").set;
|
|
241
|
+
|
|
242
|
+
if (node.tagName.toLowerCase() === 'input') {
|
|
243
|
+
return nativeInputValueSetter.call(node, value);
|
|
244
|
+
}
|
|
245
|
+
return nativeTextareaValueSetter.call(node, value);
|
|
215
246
|
}
|
|
216
|
-
this.trigger(node, "blur");
|
|
217
|
-
}
|
|
218
247
|
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
248
|
+
input(node, inputType = "insertText") {
|
|
249
|
+
let event = new InputEvent("input", { inputType: inputType, bubbles: true, cancelable: false });
|
|
250
|
+
node.dispatchEvent(event);
|
|
251
|
+
}
|
|
222
252
|
|
|
223
|
-
|
|
224
|
-
|
|
253
|
+
/**
|
|
254
|
+
* @return {boolean} false when an event handler called preventDefault()
|
|
255
|
+
*/
|
|
256
|
+
keyupdowned(node, eventName, char, keyCode) {
|
|
257
|
+
let event = new KeyboardEvent(
|
|
258
|
+
eventName, {
|
|
259
|
+
bubbles: true,
|
|
260
|
+
cancelable: true,
|
|
261
|
+
key: char,
|
|
262
|
+
keyCode: keyCode
|
|
263
|
+
}
|
|
264
|
+
);
|
|
265
|
+
return !node.dispatchEvent(event);
|
|
225
266
|
}
|
|
226
|
-
return nativeTextareaValueSetter.call(node, value);
|
|
227
|
-
}
|
|
228
267
|
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
268
|
+
/**
|
|
269
|
+
* @return {boolean} false when an event handler called preventDefault()
|
|
270
|
+
*/
|
|
271
|
+
keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
|
|
272
|
+
let event = new KeyboardEvent(
|
|
273
|
+
"keypress", {
|
|
274
|
+
bubbles: true,
|
|
275
|
+
cancelable: true,
|
|
276
|
+
altKey: altKey,
|
|
277
|
+
ctrlKey: ctrlKey,
|
|
278
|
+
shiftKey: shiftKey,
|
|
279
|
+
metaKey: metaKey,
|
|
280
|
+
keyCode: keyCode,
|
|
281
|
+
charCode: charCode
|
|
282
|
+
}
|
|
283
|
+
);
|
|
284
|
+
return !node.dispatchEvent(event);
|
|
285
|
+
}
|
|
233
286
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
287
|
+
characterToKeyCode(char) {
|
|
288
|
+
const specialKeys = {
|
|
289
|
+
96: 192, // `
|
|
290
|
+
45: 189, // -
|
|
291
|
+
61: 187, // =
|
|
292
|
+
91: 219, // [
|
|
293
|
+
93: 221, // ]
|
|
294
|
+
92: 220, // \
|
|
295
|
+
59: 186, // ;
|
|
296
|
+
39: 222, // '
|
|
297
|
+
44: 188, // ,
|
|
298
|
+
46: 190, // .
|
|
299
|
+
47: 191, // /
|
|
300
|
+
127: 46, // delete
|
|
301
|
+
126: 192, // ~
|
|
302
|
+
33: 49, // !
|
|
303
|
+
64: 50, // @
|
|
304
|
+
35: 51, // #
|
|
305
|
+
36: 52, // $
|
|
306
|
+
37: 53, // %
|
|
307
|
+
94: 54, // ^
|
|
308
|
+
38: 55, // &
|
|
309
|
+
42: 56, // *
|
|
310
|
+
40: 57, // (
|
|
311
|
+
41: 48, // )
|
|
312
|
+
95: 189, // _
|
|
313
|
+
43: 187, // +
|
|
314
|
+
123: 219, // {
|
|
315
|
+
125: 221, // }
|
|
316
|
+
124: 220, // |
|
|
317
|
+
58: 186, // :
|
|
318
|
+
34: 222, // "
|
|
319
|
+
60: 188, // <
|
|
320
|
+
62: 190, // >
|
|
321
|
+
63: 191, // ?
|
|
244
322
|
}
|
|
245
|
-
);
|
|
246
|
-
return !node.dispatchEvent(event);
|
|
247
|
-
}
|
|
248
323
|
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
keypressed(node, altKey, ctrlKey, shiftKey, metaKey, keyCode, charCode) {
|
|
253
|
-
let event = new KeyboardEvent(
|
|
254
|
-
"keypress", {
|
|
255
|
-
bubbles: true,
|
|
256
|
-
cancelable: true,
|
|
257
|
-
altKey: altKey,
|
|
258
|
-
ctrlKey: ctrlKey,
|
|
259
|
-
shiftKey: shiftKey,
|
|
260
|
-
metaKey: metaKey,
|
|
261
|
-
keyCode: keyCode,
|
|
262
|
-
charCode: charCode
|
|
263
|
-
}
|
|
264
|
-
);
|
|
265
|
-
return !node.dispatchEvent(event);
|
|
266
|
-
}
|
|
324
|
+
let code = char.toUpperCase().charCodeAt(0);
|
|
325
|
+
return specialKeys[code] || code;
|
|
326
|
+
}
|
|
267
327
|
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
96: 192, // `
|
|
271
|
-
45: 189, // -
|
|
272
|
-
61: 187, // =
|
|
273
|
-
91: 219, // [
|
|
274
|
-
93: 221, // ]
|
|
275
|
-
92: 220, // \
|
|
276
|
-
59: 186, // ;
|
|
277
|
-
39: 222, // '
|
|
278
|
-
44: 188, // ,
|
|
279
|
-
46: 190, // .
|
|
280
|
-
47: 191, // /
|
|
281
|
-
127: 46, // delete
|
|
282
|
-
126: 192, // ~
|
|
283
|
-
33: 49, // !
|
|
284
|
-
64: 50, // @
|
|
285
|
-
35: 51, // #
|
|
286
|
-
36: 52, // $
|
|
287
|
-
37: 53, // %
|
|
288
|
-
94: 54, // ^
|
|
289
|
-
38: 55, // &
|
|
290
|
-
42: 56, // *
|
|
291
|
-
40: 57, // (
|
|
292
|
-
41: 48, // )
|
|
293
|
-
95: 189, // _
|
|
294
|
-
43: 187, // +
|
|
295
|
-
123: 219, // {
|
|
296
|
-
125: 221, // }
|
|
297
|
-
124: 220, // |
|
|
298
|
-
58: 186, // :
|
|
299
|
-
34: 222, // "
|
|
300
|
-
60: 188, // <
|
|
301
|
-
62: 190, // >
|
|
302
|
-
63: 191, // ?
|
|
303
|
-
}
|
|
304
|
-
|
|
305
|
-
let code = char.toUpperCase().charCodeAt(0);
|
|
306
|
-
return specialKeys[code] || code;
|
|
307
|
-
}
|
|
328
|
+
scrollIntoViewport(node) {
|
|
329
|
+
let areaImage = this._getAreaImage(node);
|
|
308
330
|
|
|
309
|
-
|
|
310
|
-
|
|
331
|
+
if (areaImage) {
|
|
332
|
+
return this.scrollIntoViewport(areaImage);
|
|
333
|
+
} else {
|
|
334
|
+
node.scrollIntoViewIfNeeded();
|
|
311
335
|
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
336
|
+
if (!this._isInViewport(node)) {
|
|
337
|
+
node.scrollIntoView({block: "center", inline: "center", behavior: "instant"});
|
|
338
|
+
return this._isInViewport(node);
|
|
339
|
+
}
|
|
316
340
|
|
|
317
|
-
|
|
318
|
-
node.scrollIntoView({block: "center", inline: "center", behavior: "instant"});
|
|
319
|
-
return this._isInViewport(node);
|
|
341
|
+
return true;
|
|
320
342
|
}
|
|
321
|
-
|
|
322
|
-
return true;
|
|
323
343
|
}
|
|
324
|
-
}
|
|
325
344
|
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
345
|
+
mouseEventTest(node, name, x, y) {
|
|
346
|
+
let frameOffset = this._frameOffset();
|
|
347
|
+
x -= frameOffset.left;
|
|
348
|
+
y -= frameOffset.top;
|
|
330
349
|
|
|
331
|
-
|
|
350
|
+
let element = node.getRootNode().elementFromPoint(x, y);
|
|
332
351
|
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
352
|
+
let el = element;
|
|
353
|
+
while (el) {
|
|
354
|
+
if (el == node) {
|
|
355
|
+
return true;
|
|
356
|
+
} else {
|
|
357
|
+
el = el.parentNode;
|
|
358
|
+
}
|
|
339
359
|
}
|
|
360
|
+
|
|
361
|
+
let selector = element && this._getSelector(element) || "none";
|
|
362
|
+
throw new MouseEventFailed([name, selector, x, y].join(", "));
|
|
340
363
|
}
|
|
341
364
|
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
365
|
+
_getAreaImage(node) {
|
|
366
|
+
if ("area" == node.tagName.toLowerCase()) {
|
|
367
|
+
let map = node.parentNode;
|
|
368
|
+
if (map.tagName.toLowerCase() != "map") {
|
|
369
|
+
throw new Error("the area is not within a map");
|
|
370
|
+
}
|
|
345
371
|
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
throw new Error("the area is not within a map");
|
|
351
|
-
}
|
|
372
|
+
let mapName = map.getAttribute("name");
|
|
373
|
+
if (typeof mapName === "undefined" || mapName === null) {
|
|
374
|
+
throw new Error("area's parent map must have a name");
|
|
375
|
+
}
|
|
352
376
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
377
|
+
mapName = `#${mapName.toLowerCase()}`;
|
|
378
|
+
let imageNode = this.find("css", `img[usemap='${mapName}']`)[0];
|
|
379
|
+
if (typeof imageNode === "undefined" || imageNode === null) {
|
|
380
|
+
throw new Error("no image matches the map");
|
|
381
|
+
}
|
|
357
382
|
|
|
358
|
-
|
|
359
|
-
let imageNode = this.find("css", `img[usemap='${mapName}']`)[0];
|
|
360
|
-
if (typeof imageNode === "undefined" || imageNode === null) {
|
|
361
|
-
throw new Error("no image matches the map");
|
|
383
|
+
return imageNode;
|
|
362
384
|
}
|
|
363
|
-
|
|
364
|
-
return imageNode;
|
|
365
385
|
}
|
|
366
|
-
}
|
|
367
386
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
387
|
+
_frameOffset() {
|
|
388
|
+
let win = window;
|
|
389
|
+
let offset = { top: 0, left: 0 };
|
|
371
390
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
391
|
+
while (win.frameElement) {
|
|
392
|
+
let rect = win.frameElement.getClientRects()[0];
|
|
393
|
+
let style = win.getComputedStyle(win.frameElement);
|
|
394
|
+
win = win.parent;
|
|
395
|
+
|
|
396
|
+
offset.top += rect.top + parseInt(style.getPropertyValue("padding-top"), 10)
|
|
397
|
+
offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10)
|
|
398
|
+
}
|
|
376
399
|
|
|
377
|
-
offset
|
|
378
|
-
offset.left += rect.left + parseInt(style.getPropertyValue("padding-left"), 10)
|
|
400
|
+
return offset;
|
|
379
401
|
}
|
|
380
402
|
|
|
381
|
-
|
|
382
|
-
|
|
403
|
+
_getSelector(el) {
|
|
404
|
+
let selector = (el.tagName != 'HTML') ? this._getSelector(el.parentNode) + " " : "";
|
|
405
|
+
selector += el.tagName.toLowerCase();
|
|
406
|
+
if (el.id) { selector += `#${el.id}` };
|
|
407
|
+
el.classList.forEach(c => selector += `.${c}`);
|
|
408
|
+
return selector;
|
|
409
|
+
}
|
|
383
410
|
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
selector += el.tagName.toLowerCase();
|
|
387
|
-
if (el.id) { selector += `#${el.id}` };
|
|
388
|
-
el.classList.forEach(c => selector += `.${c}`);
|
|
389
|
-
return selector;
|
|
390
|
-
}
|
|
411
|
+
_isInViewport(node) {
|
|
412
|
+
let rect = node.getBoundingClientRect();
|
|
391
413
|
|
|
392
|
-
|
|
393
|
-
|
|
414
|
+
let inViewport = rect.top >= 0 &&
|
|
415
|
+
rect.left >= 0 &&
|
|
416
|
+
rect.bottom <= window.innerHeight &&
|
|
417
|
+
rect.right <= window.innerWidth;
|
|
394
418
|
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
419
|
+
if (inViewport) {
|
|
420
|
+
// check if obscured by another element
|
|
421
|
+
let x = rect.width/2;
|
|
422
|
+
let y = rect.height/2 ;
|
|
399
423
|
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
424
|
+
let px = rect.left + x,
|
|
425
|
+
py = rect.top + y,
|
|
426
|
+
e = document.elementFromPoint(px, py);
|
|
427
|
+
return node == e;
|
|
428
|
+
}
|
|
404
429
|
|
|
405
|
-
|
|
406
|
-
py = rect.top + y,
|
|
407
|
-
e = document.elementFromPoint(px, py);
|
|
408
|
-
return node == e;
|
|
430
|
+
return false;
|
|
409
431
|
}
|
|
410
432
|
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
} else {
|
|
420
|
-
let parentNode = node.parentNode;
|
|
421
|
-
this.trigger(parentNode, "focus");
|
|
433
|
+
select(node, value) {
|
|
434
|
+
if (this.isDisabled(node)) {
|
|
435
|
+
return false;
|
|
436
|
+
} else if (value == false && !node.parentNode.multiple) {
|
|
437
|
+
return false;
|
|
438
|
+
} else {
|
|
439
|
+
let selectNode = node.closest("select");
|
|
440
|
+
this.trigger(selectNode, "focus");
|
|
422
441
|
|
|
423
|
-
|
|
424
|
-
|
|
442
|
+
node.selected = value;
|
|
443
|
+
selectNode.dispatchEvent(new Event("input", { bubbles: true, composed: true }));
|
|
444
|
+
this.changed(node);
|
|
425
445
|
|
|
426
|
-
|
|
427
|
-
|
|
446
|
+
this.trigger(selectNode, "blur");
|
|
447
|
+
return true;
|
|
448
|
+
}
|
|
428
449
|
}
|
|
429
|
-
}
|
|
430
450
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
451
|
+
changed(node) {
|
|
452
|
+
let element;
|
|
453
|
+
let event = document.createEvent("HTMLEvents");
|
|
454
|
+
event.initEvent("change", true, false);
|
|
455
|
+
|
|
456
|
+
// In the case of an OPTION tag, the change event should come
|
|
457
|
+
// from the parent SELECT
|
|
458
|
+
if (node.nodeName == "OPTION") {
|
|
459
|
+
element = node.parentNode
|
|
460
|
+
if (element.nodeName == "OPTGROUP") {
|
|
461
|
+
element = element.parentNode
|
|
462
|
+
}
|
|
463
|
+
element
|
|
464
|
+
} else {
|
|
465
|
+
element = node
|
|
442
466
|
}
|
|
443
|
-
element
|
|
444
|
-
} else {
|
|
445
|
-
element = node
|
|
446
|
-
}
|
|
447
467
|
|
|
448
|
-
|
|
449
|
-
|
|
468
|
+
element.dispatchEvent(event)
|
|
469
|
+
}
|
|
450
470
|
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
}
|
|
471
|
+
trigger(node, name, options = {}) {
|
|
472
|
+
let event;
|
|
473
|
+
|
|
474
|
+
if (EVENTS.MOUSE.indexOf(name) != -1) {
|
|
475
|
+
event = document.createEvent("MouseEvent");
|
|
476
|
+
event.initMouseEvent(
|
|
477
|
+
name, true, true, window, 0,
|
|
478
|
+
options["screenX"] || 0, options["screenY"] || 0,
|
|
479
|
+
options["clientX"] || 0, options["clientY"] || 0,
|
|
480
|
+
options["ctrlKey"] || false,
|
|
481
|
+
options["altKey"] || false,
|
|
482
|
+
options["shiftKey"] || false,
|
|
483
|
+
options["metaKey"] || false,
|
|
484
|
+
options["button"] || 0, null
|
|
485
|
+
)
|
|
486
|
+
} else if (EVENTS.FOCUS.indexOf(name) != -1) {
|
|
487
|
+
event = new FocusEvent(name, { bubbles: true, cancelable: true });
|
|
488
|
+
} else if (EVENTS.FORM.indexOf(name) != -1) {
|
|
489
|
+
event = this.obtainEvent(name);
|
|
490
|
+
} else if (name == "input") {
|
|
491
|
+
event = new InputEvent(name, { inputType: "insertText", bubbles: true, composed: true });
|
|
492
|
+
} else {
|
|
493
|
+
event = new Event(name, { bubbles: true, cancelable: true });
|
|
494
|
+
}
|
|
476
495
|
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
event.initEvent(name, true, true);
|
|
480
|
-
return event;
|
|
481
|
-
}
|
|
496
|
+
node.dispatchEvent(event);
|
|
497
|
+
}
|
|
482
498
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
attrs[attr.name] = attr.value.replace("\n", "\\n");
|
|
499
|
+
obtainEvent(name) {
|
|
500
|
+
let event = document.createEvent("HTMLEvents");
|
|
501
|
+
event.initEvent(name, true, true);
|
|
502
|
+
return event;
|
|
488
503
|
}
|
|
489
504
|
|
|
490
|
-
|
|
491
|
-
|
|
505
|
+
getAttributes(node) {
|
|
506
|
+
let attrs = {};
|
|
507
|
+
for (let i = 0, len = node.attributes.length; i < len; i++) {
|
|
508
|
+
let attr = node.attributes[i];
|
|
509
|
+
attrs[attr.name] = attr.value.replace("\n", "\\n");
|
|
510
|
+
}
|
|
492
511
|
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
512
|
+
return this._json.stringify(attrs);
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
getAttribute(node, name) {
|
|
516
|
+
if (name == "checked" || name == "selected") {
|
|
517
|
+
return node[name];
|
|
518
|
+
} else {
|
|
519
|
+
return node.getAttribute(name);
|
|
520
|
+
}
|
|
498
521
|
}
|
|
499
|
-
}
|
|
500
522
|
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
523
|
+
value(node) {
|
|
524
|
+
if (node.tagName == "SELECT" && node.multiple) {
|
|
525
|
+
let result = []
|
|
504
526
|
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
527
|
+
for (let i = 0, len = node.children.length; i < len; i++) {
|
|
528
|
+
let option = node.children[i];
|
|
529
|
+
if (option.selected) {
|
|
530
|
+
result.push(option.value);
|
|
531
|
+
}
|
|
509
532
|
}
|
|
533
|
+
|
|
534
|
+
return result;
|
|
535
|
+
} else {
|
|
536
|
+
return node.value;
|
|
510
537
|
}
|
|
538
|
+
}
|
|
511
539
|
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
540
|
+
deleteText(node) {
|
|
541
|
+
let range = document.createRange();
|
|
542
|
+
range.selectNodeContents(node);
|
|
543
|
+
window.getSelection().removeAllRanges();
|
|
544
|
+
window.getSelection().addRange(range);
|
|
545
|
+
window.getSelection().deleteFromDocument();
|
|
515
546
|
}
|
|
516
|
-
}
|
|
517
547
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
window.getSelection().removeAllRanges();
|
|
522
|
-
window.getSelection().addRange(range);
|
|
523
|
-
window.getSelection().deleteFromDocument();
|
|
524
|
-
}
|
|
548
|
+
hasFocus(node) {
|
|
549
|
+
return document.activeElement === node;
|
|
550
|
+
}
|
|
525
551
|
|
|
526
|
-
|
|
527
|
-
|
|
552
|
+
containsSelection(node) {
|
|
553
|
+
let selectedNode = document.getSelection().focusNode;
|
|
528
554
|
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
555
|
+
if (!selectedNode) {
|
|
556
|
+
return false;
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
if (selectedNode.nodeType == 3) {
|
|
560
|
+
selectedNode = selectedNode.parentNode;
|
|
561
|
+
}
|
|
532
562
|
|
|
533
|
-
|
|
534
|
-
selectedNode = selectedNode.parentNode;
|
|
563
|
+
return node.contains(selectedNode);
|
|
535
564
|
}
|
|
536
565
|
|
|
537
|
-
|
|
538
|
-
|
|
566
|
+
dragMousedownTracker() {
|
|
567
|
+
window._cupriteMousedownPrevented = null;
|
|
568
|
+
document.addEventListener('mousedown', ev => {
|
|
569
|
+
window._cupriteMousedownPrevented = ev.defaultPrevented;
|
|
570
|
+
}, { once: true, passive: true });
|
|
571
|
+
}
|
|
539
572
|
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
}, { once: true, passive: true });
|
|
545
|
-
}
|
|
573
|
+
legacyDragCheck(node) {
|
|
574
|
+
if ([true, null].indexOf(window._cupriteMousedownPrevented) >= 0) {
|
|
575
|
+
return true;
|
|
576
|
+
}
|
|
546
577
|
|
|
547
|
-
|
|
548
|
-
|
|
578
|
+
let el = node;
|
|
579
|
+
do {
|
|
580
|
+
if (el.draggable) return false;
|
|
581
|
+
} while (el = el.parentElement);
|
|
549
582
|
return true;
|
|
550
583
|
}
|
|
551
584
|
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
let opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
|
563
|
-
for (let i = 0; i < strings.length; i++) {
|
|
564
|
-
if (dt.items) {
|
|
565
|
-
dt.items.add(strings[i]["data"], strings[i]["type"]);
|
|
566
|
-
} else {
|
|
567
|
-
dt.setData(strings[i]["type"], strings[i]["data"]);
|
|
585
|
+
// Ported from Capybara's Selenium html5_drag.rb (DROP_STRING).
|
|
586
|
+
dropString(strings, target) {
|
|
587
|
+
let dt = new DataTransfer();
|
|
588
|
+
let opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
|
589
|
+
for (let i = 0; i < strings.length; i++) {
|
|
590
|
+
if (dt.items) {
|
|
591
|
+
dt.items.add(strings[i]["data"], strings[i]["type"]);
|
|
592
|
+
} else {
|
|
593
|
+
dt.setData(strings[i]["type"], strings[i]["data"]);
|
|
594
|
+
}
|
|
568
595
|
}
|
|
596
|
+
target.dispatchEvent(new DragEvent("drop", opts));
|
|
569
597
|
}
|
|
570
|
-
target.dispatchEvent(new DragEvent("drop", opts));
|
|
571
|
-
}
|
|
572
598
|
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
599
|
+
// Ported from Capybara's Selenium html5_drag.rb (ATTACH_FILE).
|
|
600
|
+
attachDropInput() {
|
|
601
|
+
document.getElementById("_cuprite_drop_file")?.remove();
|
|
602
|
+
let input = document.createElement("INPUT");
|
|
603
|
+
input.type = "file";
|
|
604
|
+
input.id = "_cuprite_drop_file";
|
|
605
|
+
input.multiple = true;
|
|
606
|
+
document.body.appendChild(input);
|
|
607
|
+
}
|
|
582
608
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
609
|
+
// Ported from Capybara's Selenium html5_drag.rb (DROP_FILE).
|
|
610
|
+
dropFile(target) {
|
|
611
|
+
let input = document.getElementById("_cuprite_drop_file");
|
|
612
|
+
let files = input.files;
|
|
613
|
+
let dt = new DataTransfer();
|
|
614
|
+
let opts = { cancelable: true, bubbles: true, dataTransfer: dt };
|
|
615
|
+
input.parentElement.removeChild(input);
|
|
616
|
+
if (dt.items) {
|
|
617
|
+
for (let i = 0; i < files.length; i++) {
|
|
618
|
+
dt.items.add(files[i]);
|
|
619
|
+
}
|
|
620
|
+
} else {
|
|
621
|
+
Object.defineProperty(dt, "files", { value: files, writable: false });
|
|
593
622
|
}
|
|
594
|
-
|
|
595
|
-
Object.defineProperty(dt, "files", { value: files, writable: false });
|
|
623
|
+
target.dispatchEvent(new DragEvent("drop", opts));
|
|
596
624
|
}
|
|
597
|
-
target.dispatchEvent(new DragEvent("drop", opts));
|
|
598
|
-
}
|
|
599
|
-
|
|
600
|
-
// This command is purely for testing error handling
|
|
601
|
-
browserError() {
|
|
602
|
-
throw new Error("zomg");
|
|
603
625
|
}
|
|
604
|
-
}
|
|
605
626
|
|
|
606
|
-
window._cuprite = new Cuprite;
|
|
627
|
+
window._cuprite = new Cuprite;
|
|
628
|
+
})();
|