@@ -30,44 +30,92 @@ import (
30
30
"github.com/dop251/goja"
31
31
"github.com/grafana/xk6-browser/api"
32
32
"github.com/grafana/xk6-browser/keyboardlayout"
33
- k6common "go.k6.io/k6/js/common"
34
33
)
35
34
36
- // Ensure Keyboard implements the api.Keyboard interface
37
35
var _ api.Keyboard = & Keyboard {}
38
36
39
37
const (
40
- ModifierKeyAlt int64 = 1
41
- ModifierKeyControl = 2
42
- ModifierKeyMeta = 4
43
- ModifierKeyShift = 8
38
+ ModifierKeyAlt int64 = 1 << iota
39
+ ModifierKeyControl
40
+ ModifierKeyMeta
41
+ ModifierKeyShift
44
42
)
45
43
46
- // Keyboard represents a keyboard input device
44
+ // Keyboard represents a keyboard input device.
45
+ // Each Page has a publicly accessible Keyboard.
47
46
type Keyboard struct {
48
- ctx context.Context
49
- session * Session
50
- modifiers int64
51
- pressedKeys map [int64 ]bool
52
- layoutName string
47
+ ctx context.Context
48
+ session * Session
49
+
50
+ modifiers int64 // like shift, alt, ctrl, ...
51
+ pressedKeys map [int64 ]bool // tracks keys through down() and up()
52
+ layoutName string // us by default
53
+ layout keyboardlayout.KeyboardLayout
53
54
}
54
55
55
- // NewKeyboard creates a new keyboard
56
+ // NewKeyboard returns a new keyboard with a "us" layout.
56
57
func NewKeyboard (ctx context.Context , session * Session ) * Keyboard {
57
- k := & Keyboard {
58
+ return & Keyboard {
58
59
ctx : ctx ,
59
60
session : session ,
60
- modifiers : 0 ,
61
61
pressedKeys : make (map [int64 ]bool ),
62
62
layoutName : "us" ,
63
+ layout : keyboardlayout .GetKeyboardLayout ("us" ),
64
+ }
65
+ }
66
+
67
+ // Down sends a key down message to a session target.
68
+ func (k * Keyboard ) Down (key string ) {
69
+ if err := k .down (key ); err != nil {
70
+ k6Throw (k .ctx , "cannot send key down: %w" , err )
71
+ }
72
+ }
73
+
74
+ // Up sends a key up message to a session target.
75
+ func (k * Keyboard ) Up (key string ) {
76
+ if err := k .up (key ); err != nil {
77
+ k6Throw (k .ctx , "cannot send key up: %w" , err )
78
+ }
79
+ }
80
+
81
+ // Press sends a key press message to a session target.
82
+ // It delays the action if `Delay` option is specified.
83
+ // A press message consists of successive key down and up messages.
84
+ func (k * Keyboard ) Press (key string , opts goja.Value ) {
85
+ kbdOpts := NewKeyboardOptions ()
86
+ if err := kbdOpts .Parse (k .ctx , opts ); err != nil {
87
+ k6Throw (k .ctx , "cannot parse keyboard options: %w" , err )
88
+ }
89
+ if err := k .press (key , kbdOpts ); err != nil {
90
+ k6Throw (k .ctx , "cannot press key: %w" , err )
91
+ }
92
+ }
93
+
94
+ // InsertText inserts a text without dispatching key events.
95
+ func (k * Keyboard ) InsertText (text string ) {
96
+ if err := k .insertText (text ); err != nil {
97
+ k6Throw (k .ctx , "cannot insert text: %w" , err )
98
+ }
99
+ }
100
+
101
+ // Type sends a press message to a session target for each character in text.
102
+ // It delays the action if `Delay` option is specified.
103
+ //
104
+ // It sends an insertText message if a character is not among
105
+ // valid characters in the keyboard's layout.
106
+ func (k * Keyboard ) Type (text string , opts goja.Value ) {
107
+ kbdOpts := NewKeyboardOptions ()
108
+ if err := kbdOpts .Parse (k .ctx , opts ); err != nil {
109
+ k6Throw (k .ctx , "cannot parse keyboard options: %w" , err )
110
+ }
111
+ if err := k .typ (text , kbdOpts ); err != nil {
112
+ k6Throw (k .ctx , "cannot type text: %w" , err )
63
113
}
64
- return k
65
114
}
66
115
67
116
func (k * Keyboard ) down (key string ) error {
68
117
keyInput := keyboardlayout .KeyInput (key )
69
- layout := keyboardlayout .GetKeyboardLayout (k .layoutName )
70
- if _ , ok := layout .ValidKeys [keyInput ]; ! ok {
118
+ if _ , ok := k .layout .ValidKeys [keyInput ]; ! ok {
71
119
return fmt .Errorf ("%q is not a valid key for layout %q" , key , k .layoutName )
72
120
}
73
121
@@ -93,7 +141,30 @@ func (k *Keyboard) down(key string) error {
93
141
WithUnmodifiedText (text ).
94
142
WithAutoRepeat (autoRepeat )
95
143
if err := action .Do (cdp .WithExecutor (k .ctx , k .session )); err != nil {
96
- return fmt .Errorf ("unable to mouse down: %w" , err )
144
+ return fmt .Errorf ("cannot execute dispatch key event down: %w" , err )
145
+ }
146
+
147
+ return nil
148
+ }
149
+
150
+ func (k * Keyboard ) up (key string ) error {
151
+ keyInput := keyboardlayout .KeyInput (key )
152
+ if _ , ok := k .layout .ValidKeys [keyInput ]; ! ok {
153
+ return fmt .Errorf ("'%s' is not a valid key for layout '%s'" , key , k .layoutName )
154
+ }
155
+
156
+ keyDef := k .keyDefinitionFromKey (keyInput )
157
+ k .modifiers &= ^ k .modifierBitFromKeyName (keyDef .Key )
158
+ delete (k .pressedKeys , keyDef .KeyCode )
159
+
160
+ action := input .DispatchKeyEvent (input .KeyUp ).
161
+ WithModifiers (input .Modifier (k .modifiers )).
162
+ WithKey (keyDef .Key ).
163
+ WithWindowsVirtualKeyCode (keyDef .KeyCode ).
164
+ WithCode (keyDef .Code ).
165
+ WithLocation (keyDef .Location )
166
+ if err := action .Do (cdp .WithExecutor (k .ctx , k .session )); err != nil {
167
+ return fmt .Errorf ("cannot execute dispatch key event up: %w" , err )
97
168
}
98
169
99
170
return nil
@@ -102,206 +173,107 @@ func (k *Keyboard) down(key string) error {
102
173
func (k * Keyboard ) insertText (text string ) error {
103
174
action := input .InsertText (text )
104
175
if err := action .Do (cdp .WithExecutor (k .ctx , k .session )); err != nil {
105
- return fmt .Errorf ("unable to send character : %w" , err )
176
+ return fmt .Errorf ("cannot execute insert text : %w" , err )
106
177
}
107
178
return nil
108
179
}
109
180
110
- func (k * Keyboard ) keyDefinitionFromKey (keyString keyboardlayout.KeyInput ) keyboardlayout.KeyDefinition {
181
+ func (k * Keyboard ) keyDefinitionFromKey (key keyboardlayout.KeyInput ) keyboardlayout.KeyDefinition {
111
182
shift := k .modifiers & ModifierKeyShift
112
- keyDef := keyboardlayout.KeyDefinition {}
113
- layout := keyboardlayout .GetKeyboardLayout (k .layoutName )
114
- code := keyString
115
- srcKeyDef , ok := layout .Keys [keyString ]
116
183
184
+ // Find directly from the keyboard layout
185
+ srcKeyDef , ok := k .layout .Keys [key ]
186
+ // Try to find based on key value instead of code
117
187
if ! ok {
118
- // Find based on key value instead of code
119
- for k , v := range layout .Keys {
120
- if v .Key == string (keyString ) {
121
- code = k
122
- srcKeyDef = v
123
- break
124
- }
125
- }
188
+ srcKeyDef , ok = k .layout .KeyDefinition (key )
189
+ }
190
+ // Try to find with the shift key value
191
+ if ! ok {
192
+ srcKeyDef = k .layout .ShiftKeyDefinition (key )
193
+ shift = k .modifiers | ModifierKeyShift
126
194
}
127
195
196
+ var keyDef keyboardlayout.KeyDefinition
128
197
if srcKeyDef .Key != "" {
129
198
keyDef .Key = srcKeyDef .Key
130
- }
131
- if shift != 0 && srcKeyDef .ShiftKey != "" {
132
- keyDef .Key = srcKeyDef .ShiftKey
133
- }
134
- if srcKeyDef .KeyCode != 0 {
135
- keyDef .KeyCode = srcKeyDef .KeyCode
199
+ keyDef .Text = srcKeyDef .Key
136
200
}
137
201
if shift != 0 && srcKeyDef .ShiftKeyCode != 0 {
138
202
keyDef .KeyCode = srcKeyDef .ShiftKeyCode
139
203
}
140
204
if srcKeyDef .KeyCode != 0 {
141
205
keyDef .KeyCode = srcKeyDef .KeyCode
142
206
}
143
- if code != "" {
144
- keyDef .Code = string (code )
207
+ if key != "" {
208
+ keyDef .Code = string (key )
145
209
}
146
210
if srcKeyDef .Location != 0 {
147
211
keyDef .Location = srcKeyDef .Location
148
212
}
149
- if len (srcKeyDef .Key ) == 1 {
150
- keyDef .Text = srcKeyDef .Key
151
- }
152
213
if srcKeyDef .Text != "" {
153
214
keyDef .Text = srcKeyDef .Text
154
215
}
155
- if shift != 0 && keyDef .ShiftKey != "" {
156
- keyDef .Text = keyDef .ShiftKey
216
+ if shift != 0 && srcKeyDef .ShiftKey != "" {
217
+ keyDef .Key = srcKeyDef .ShiftKey
218
+ keyDef .Text = srcKeyDef .ShiftKey
157
219
}
158
-
159
220
// If any modifiers besides shift are pressed, no text should be sent
160
221
if k .modifiers & ^ ModifierKeyShift != 0 {
161
222
keyDef .Text = ""
162
223
}
163
-
164
224
return keyDef
165
225
}
166
226
167
227
func (k * Keyboard ) modifierBitFromKeyName (key string ) int64 {
168
- if key == "Alt" {
228
+ switch key {
229
+ case "Alt" :
169
230
return ModifierKeyAlt
170
- }
171
- if key == "Control" {
231
+ case "Control" :
172
232
return ModifierKeyControl
173
- }
174
- if key == "Meta" {
233
+ case "Meta" :
175
234
return ModifierKeyMeta
176
- }
177
- if key == "Shift" {
235
+ case "Shift" :
178
236
return ModifierKeyShift
179
237
}
180
238
return 0
181
239
}
182
240
183
241
func (k * Keyboard ) press (key string , opts * KeyboardOptions ) error {
184
- err := k .down (key )
185
- if err != nil {
186
- return err
187
- }
188
242
if opts .Delay != 0 {
189
- t := time .NewTimer (time .Duration (opts .Delay * time .Hour . Milliseconds ()) )
243
+ t := time .NewTimer (time .Duration (opts .Delay ) * time .Millisecond )
190
244
select {
191
245
case <- k .ctx .Done ():
192
246
t .Stop ()
193
247
case <- t .C :
194
248
}
195
249
}
196
- err = k .up (key )
197
- if err != nil {
198
- return err
250
+ if err := k .down (key ); err != nil {
251
+ return fmt .Errorf ("cannot do key down: %w" , err )
199
252
}
200
- return nil
253
+ return k . up ( key )
201
254
}
202
255
203
256
func (k * Keyboard ) typ (text string , opts * KeyboardOptions ) error {
204
257
layout := keyboardlayout .GetKeyboardLayout (k .layoutName )
205
258
for _ , c := range text {
259
+ if opts .Delay != 0 {
260
+ t := time .NewTimer (time .Duration (opts .Delay ) * time .Millisecond )
261
+ select {
262
+ case <- k .ctx .Done ():
263
+ t .Stop ()
264
+ case <- t .C :
265
+ }
266
+ }
206
267
keyInput := keyboardlayout .KeyInput (c )
207
268
if _ , ok := layout .ValidKeys [keyInput ]; ok {
208
- err := k .press (string (c ), opts )
209
- if err != nil {
210
- return nil
211
- }
212
- } else {
213
- if opts .Delay != 0 {
214
- t := time .NewTimer (time .Duration (opts .Delay * time .Hour .Milliseconds ()))
215
- select {
216
- case <- k .ctx .Done ():
217
- t .Stop ()
218
- case <- t .C :
219
- }
220
- }
221
- err := k .insertText (string (c ))
222
- if err != nil {
223
- return nil
269
+ if err := k .press (string (c ), opts ); err != nil {
270
+ return fmt .Errorf ("cannot press key: %w" , err )
224
271
}
272
+ continue
273
+ }
274
+ if err := k .insertText (string (c )); err != nil {
275
+ return fmt .Errorf ("cannot insert text: %w" , err )
225
276
}
226
277
}
227
278
return nil
228
279
}
229
-
230
- func (k * Keyboard ) up (key string ) error {
231
- keyInput := keyboardlayout .KeyInput (key )
232
- layout := keyboardlayout .GetKeyboardLayout (k .layoutName )
233
- if _ , ok := layout .ValidKeys [keyInput ]; ! ok {
234
- return fmt .Errorf ("'%s' is not a valid key for layout '%s'" , key , k .layoutName )
235
- }
236
-
237
- keyDef := k .keyDefinitionFromKey (keyInput )
238
- k .modifiers &= ^ k .modifierBitFromKeyName (keyDef .Key )
239
- delete (k .pressedKeys , keyDef .KeyCode )
240
-
241
- action := input .DispatchKeyEvent (input .KeyUp ).
242
- WithModifiers (input .Modifier (k .modifiers )).
243
- WithKey (keyDef .Key ).
244
- WithWindowsVirtualKeyCode (keyDef .KeyCode ).
245
- WithCode (keyDef .Code ).
246
- WithLocation (keyDef .Location )
247
- if err := action .Do (cdp .WithExecutor (k .ctx , k .session )); err != nil {
248
- return fmt .Errorf ("unable to mouse down: %w" , err )
249
- }
250
-
251
- return nil
252
- }
253
-
254
- // Down
255
- func (k * Keyboard ) Down (key string ) {
256
- rt := k6common .GetRuntime (k .ctx )
257
- err := k .down (key )
258
- if err != nil {
259
- k6common .Throw (rt , err )
260
- }
261
- }
262
-
263
- // Press
264
- func (k * Keyboard ) Press (key string , opts goja.Value ) {
265
- rt := k6common .GetRuntime (k .ctx )
266
- kbdOpts := NewKeyboardOptions ()
267
- if err := kbdOpts .Parse (k .ctx , opts ); err != nil {
268
- k6common .Throw (rt , fmt .Errorf ("failed parsing options: %w" , err ))
269
- }
270
-
271
- err := k .press (key , kbdOpts )
272
- if err != nil {
273
- k6common .Throw (rt , err )
274
- }
275
- }
276
-
277
- // InsertText
278
- func (k * Keyboard ) InsertText (text string ) {
279
- rt := k6common .GetRuntime (k .ctx )
280
- err := k .insertText (text )
281
- if err != nil {
282
- k6common .Throw (rt , err )
283
- }
284
- }
285
-
286
- // Type
287
- func (k * Keyboard ) Type (text string , opts goja.Value ) {
288
- rt := k6common .GetRuntime (k .ctx )
289
- kbdOpts := NewKeyboardOptions ()
290
- if err := kbdOpts .Parse (k .ctx , opts ); err != nil {
291
- k6common .Throw (rt , fmt .Errorf ("failed parsing options: %w" , err ))
292
- }
293
-
294
- err := k .typ (text , kbdOpts )
295
- if err != nil {
296
- k6common .Throw (rt , err )
297
- }
298
- }
299
-
300
- // Up
301
- func (k * Keyboard ) Up (key string ) {
302
- rt := k6common .GetRuntime (k .ctx )
303
- err := k .up (key )
304
- if err != nil {
305
- k6common .Throw (rt , err )
306
- }
307
- }
0 commit comments