1 /*
2 * Copyright (c) 2002-2026 Gargoyle Software Inc.
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 * https://www.apache.org/licenses/LICENSE-2.0
8 *
9 * Unless required by applicable law or agreed to in writing, software
10 * distributed under the License is distributed on an "AS IS" BASIS,
11 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 * See the License for the specific language governing permissions and
13 * limitations under the License.
14 */
15 package org.htmlunit.html;
16
17 import java.util.ArrayList;
18 import java.util.List;
19
20 import org.htmlunit.javascript.host.event.KeyboardEvent;
21
22 /**
23 * Represents a sequence of keyboard actions—such as typed characters, key presses,
24 * and key releases—used to simulate realistic user input on HTML elements.
25 * <p>
26 * A {@code Keyboard} instance acts as a keystroke recorder that queues key actions
27 * and maintains modifier state ({@code SHIFT}, {@code CONTROL}, {@code ALT}) across
28 * sequential events. When executed against an element via {@code element.type(keyboard)},
29 * it drives the full browser event lifecycle ({@code keydown}, {@code keypress},
30 * {@code input}, and {@code keyup}) and mutates DOM text values accordingly.
31 * </p>
32 *
33 * <h3>Core Features</h3>
34 * <ul>
35 * <li><b>Character and Key Code Queuing:</b> Supports typing printable characters as well
36 * as holding and releasing raw key codes (e.g., navigation, {@code BACK_SPACE}, or modifier keys).</li>
37 * <li><b>Modifier State Persistence:</b> Tracks modifier keys so subsequent keystrokes inherit
38 * the active modifier flags during JavaScript event dispatching.</li>
39 * <li><b>Caret Positioning:</b> Supports configured cursor placement ({@link #isStartAtEnd()})
40 * to position the selection caret at the end of input fields before typing begins.</li>
41 * </ul>
42 *
43 * @author Ahmed Ashour
44 * @author Ronald Brill
45 */
46 public class Keyboard {
47
48 /**
49 * Represents a single keyboard input action.
50 *
51 * <p>A {@code KeyAction} is either a typed character ({@link TypedChar})
52 * or a key-code press or release ({@link KeyCodeAction}).
53 * </p>
54 */
55 public sealed interface KeyAction permits TypedChar, KeyCodeAction {
56 // tagging interface
57 }
58
59 /**
60 * A character typed directly, e.g. via {@link Keyboard#type(char)}.
61 *
62 * @param ch the typed character
63 */
64 public record TypedChar(char ch) implements KeyAction { }
65
66 /**
67 * A key-code press or release, e.g. via {@link Keyboard#press(int)}
68 * or {@link Keyboard#release(int)}.
69 *
70 * @param keyCode the DOM key code
71 * @param pressed {@code true} if the key is being pressed,
72 * {@code false} if it is being released
73 */
74 public record KeyCodeAction(int keyCode, boolean pressed) implements KeyAction { }
75
76 private final List<KeyAction> keys_ = new ArrayList<>();
77 private final boolean startAtEnd_;
78
79 /**
80 * Creates a new instance.
81 */
82 public Keyboard() {
83 this(false);
84 }
85
86 /**
87 * Creates a new instance, specifying whether typing should start at the text end or not.
88 * @param startAtEnd whether typing should start at the text end or not
89 */
90 public Keyboard(final boolean startAtEnd) {
91 startAtEnd_ = startAtEnd;
92 }
93
94 /**
95 * Types the specified character.
96 * @param ch the character
97 */
98 public void type(final char ch) {
99 keys_.add(new TypedChar(ch));
100 }
101
102 /**
103 * Press the specified key code (without releasing it).
104 * <p>
105 * An example of predefined values is
106 * {@link org.htmlunit.javascript.host.event.KeyboardEvent#DOM_VK_PAGE_DOWN}.
107 * </p>
108 *
109 * @param keyCode the key code
110 */
111 public void press(final int keyCode) {
112 if (keyCode >= KeyboardEvent.DOM_VK_A && keyCode <= KeyboardEvent.DOM_VK_Z) {
113 throw new IllegalArgumentException("For key code " + keyCode + ", use type(char) instead");
114 }
115 keys_.add(new KeyCodeAction(keyCode, true));
116 }
117
118 /**
119 * Releases the specified key code.
120 * <p>
121 * An example of predefined values is
122 * {@link org.htmlunit.javascript.host.event.KeyboardEvent#DOM_VK_PAGE_DOWN}.
123 * </p>
124 *
125 * @param keyCode the key code.
126 */
127 public void release(final int keyCode) {
128 if (keyCode >= KeyboardEvent.DOM_VK_A && keyCode <= KeyboardEvent.DOM_VK_Z) {
129 throw new IllegalArgumentException("For key code " + keyCode + ", use type(char) instead");
130 }
131 keys_.add(new KeyCodeAction(keyCode, false));
132 }
133
134 /**
135 * Clears all keys.
136 */
137 public void clear() {
138 keys_.clear();
139 }
140
141 /**
142 * Returns the keys.
143 *
144 * @return the key actions
145 */
146 List<KeyAction> getKeys() {
147 return keys_;
148 }
149
150 /**
151 * Returns whether typing should start at the text end or not.
152 * @return whether typing should start at the text end or not
153 */
154 public boolean isStartAtEnd() {
155 return startAtEnd_;
156 }
157 }