View Javadoc
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 }