1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.html;
16
17 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_ADD;
18 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_BACK_SPACE;
19 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_DECIMAL;
20 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_DELETE;
21 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_DIVIDE;
22 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_DOWN;
23 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_END;
24 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_EQUALS;
25 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_HOME;
26 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_LEFT;
27 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_MULTIPLY;
28 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_NUMPAD0;
29 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_NUMPAD9;
30 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_RIGHT;
31 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SEMICOLON;
32 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SEPARATOR;
33 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SPACE;
34 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_SUBTRACT;
35 import static org.htmlunit.javascript.host.event.KeyboardEvent.DOM_VK_UP;
36
37 import java.io.Serializable;
38 import java.util.HashMap;
39 import java.util.Map;
40
41 import org.htmlunit.ClipboardHandler;
42 import org.htmlunit.html.impl.SelectionDelegate;
43
44
45
46
47
48
49
50
51
52 class DoTypeProcessor implements Serializable {
53
54 private static final Map<Integer, Character> SPECIAL_KEYS_MAP_ = new HashMap<>();
55
56
57
58
59 private final DomNode domNode_;
60
61 static {
62 SPECIAL_KEYS_MAP_.put(DOM_VK_ADD, '+');
63 SPECIAL_KEYS_MAP_.put(DOM_VK_DECIMAL, '.');
64 SPECIAL_KEYS_MAP_.put(DOM_VK_DIVIDE, '/');
65 SPECIAL_KEYS_MAP_.put(DOM_VK_EQUALS, '=');
66 SPECIAL_KEYS_MAP_.put(DOM_VK_MULTIPLY, '*');
67 SPECIAL_KEYS_MAP_.put(DOM_VK_SEMICOLON, ';');
68 SPECIAL_KEYS_MAP_.put(DOM_VK_SEPARATOR, ',');
69 SPECIAL_KEYS_MAP_.put(DOM_VK_SPACE, ' ');
70 SPECIAL_KEYS_MAP_.put(DOM_VK_SUBTRACT, '-');
71
72 for (int i = DOM_VK_NUMPAD0; i <= DOM_VK_NUMPAD9; i++) {
73 SPECIAL_KEYS_MAP_.put(i, (char) ('0' + i - DOM_VK_NUMPAD0));
74 }
75 }
76
77 DoTypeProcessor(final DomNode domNode) {
78 domNode_ = domNode;
79 }
80
81 void doType(final String currentValue, final SelectionDelegate selectionDelegate,
82 final char c, final HtmlElement element, final boolean lastType) {
83
84 int selectionStart = selectionDelegate.getSelectionStart();
85 selectionStart = Math.max(0, Math.min(selectionStart, currentValue.length()));
86
87 int selectionEnd = selectionDelegate.getSelectionEnd();
88 selectionEnd = Math.max(selectionStart, Math.min(selectionEnd, currentValue.length()));
89
90 final StringBuilder newValue = new StringBuilder(currentValue);
91 if (c == '\b') {
92 if (selectionStart > 0) {
93 newValue.deleteCharAt(selectionStart - 1);
94 selectionStart--;
95 selectionEnd--;
96 }
97 }
98 else if (acceptChar(c)) {
99 final boolean ctrlKey = element.isCtrlPressed();
100 if (ctrlKey && (c == 'C' || c == 'c')) {
101 final ClipboardHandler clipboardHandler = element.getPage().getWebClient().getClipboardHandler();
102 if (clipboardHandler != null) {
103 final String content = newValue.substring(selectionStart, selectionEnd);
104 clipboardHandler.setClipboardContent(content);
105 }
106 }
107 else if (ctrlKey && (c == 'V' || c == 'v')) {
108 final ClipboardHandler clipboardHandler = element.getPage().getWebClient().getClipboardHandler();
109 if (clipboardHandler != null) {
110 final String content = clipboardHandler.getClipboardContent();
111 add(newValue, content, selectionStart, selectionEnd);
112 selectionStart += content.length();
113 selectionEnd = selectionStart;
114 }
115 }
116 else if (ctrlKey && (c == 'X' || c == 'x')) {
117 final ClipboardHandler clipboardHandler = element.getPage().getWebClient().getClipboardHandler();
118 if (clipboardHandler != null) {
119 final String content = newValue.substring(selectionStart, selectionEnd);
120 clipboardHandler.setClipboardContent(content);
121 newValue.delete(selectionStart, selectionEnd);
122 selectionEnd = selectionStart;
123 }
124 }
125 else if (ctrlKey && (c == 'A' || c == 'a')) {
126 selectionStart = 0;
127 selectionEnd = newValue.length();
128 }
129 else {
130 add(newValue, c, selectionStart, selectionEnd);
131 selectionStart++;
132 selectionEnd = selectionStart;
133 }
134 }
135
136 typeDone(newValue.toString(), lastType);
137
138 selectionDelegate.setSelectionStart(selectionStart);
139 selectionDelegate.setSelectionEnd(selectionEnd);
140 }
141
142 private static void add(final StringBuilder newValue, final char c, final int selectionStart,
143 final int selectionEnd) {
144 if (selectionStart == newValue.length()) {
145 newValue.append(c);
146 }
147 else {
148 newValue.replace(selectionStart, selectionEnd, Character.toString(c));
149 }
150 }
151
152 private static void add(final StringBuilder newValue, final String string, final int selectionStart,
153 final int selectionEnd) {
154 if (selectionStart == newValue.length()) {
155 newValue.append(string);
156 }
157 else {
158 newValue.replace(selectionStart, selectionEnd, string);
159 }
160 }
161
162 private void typeDone(final String newValue, final boolean notifyAttributeChangeListeners) {
163 if (domNode_ instanceof DomText text) {
164 text.setData(newValue);
165 }
166 else {
167 ((HtmlElement) domNode_).typeDone(newValue, notifyAttributeChangeListeners);
168 }
169 }
170
171 private boolean acceptChar(final char ch) {
172 if (domNode_ instanceof DomText text) {
173 return text.acceptChar(ch);
174 }
175 return ((HtmlElement) domNode_).acceptChar(ch);
176 }
177
178 void doType(final String currentValue, final SelectionDelegate selectionDelegate,
179 final int keyCode, final HtmlElement element, final boolean lastType) {
180
181 int selectionStart = selectionDelegate.getSelectionStart();
182 selectionStart = Math.max(0, Math.min(selectionStart, currentValue.length()));
183
184 int selectionEnd = selectionDelegate.getSelectionEnd();
185 selectionEnd = Math.max(selectionStart, Math.min(selectionEnd, currentValue.length()));
186
187 final Character ch = SPECIAL_KEYS_MAP_.get(keyCode);
188 if (ch != null) {
189 doType(currentValue, selectionDelegate, ch, element, lastType);
190 return;
191 }
192
193 final StringBuilder newValue = new StringBuilder(currentValue);
194 switch (keyCode) {
195 case DOM_VK_BACK_SPACE:
196 if (selectionEnd != selectionStart) {
197 newValue.delete(selectionStart, selectionEnd);
198 selectionEnd = selectionStart;
199 break;
200 }
201
202 if (selectionStart > 0) {
203 if (element.isCtrlPressed()) {
204 int targetStart = selectionStart;
205 while (targetStart > 0 && Character.isWhitespace(newValue.charAt(targetStart - 1))) {
206 targetStart--;
207 }
208 while (targetStart > 0 && !Character.isWhitespace(newValue.charAt(targetStart - 1))) {
209 targetStart--;
210 }
211 newValue.delete(targetStart, selectionStart);
212 selectionStart = targetStart;
213 }
214 else {
215 newValue.deleteCharAt(selectionStart - 1);
216 selectionStart--;
217 }
218 }
219 break;
220
221 case DOM_VK_LEFT:
222 if (element.isCtrlPressed()) {
223 int targetPos = selectionStart;
224
225 while (targetPos > 0 && Character.isWhitespace(newValue.charAt(targetPos - 1))) {
226 targetPos--;
227 }
228
229 while (targetPos > 0 && !Character.isWhitespace(newValue.charAt(targetPos - 1))) {
230 targetPos--;
231 }
232 selectionStart = targetPos;
233 }
234 else if (element.isShiftPressed()) {
235 if (selectionStart > 0) {
236 selectionStart--;
237 }
238 }
239 else if (selectionStart > 0) {
240 selectionStart--;
241 }
242 break;
243
244 case DOM_VK_RIGHT:
245 if (element.isCtrlPressed()) {
246 int targetPos = selectionStart;
247
248 while (targetPos < newValue.length() && !Character.isWhitespace(newValue.charAt(targetPos))) {
249 targetPos++;
250 }
251
252 while (targetPos < newValue.length() && Character.isWhitespace(newValue.charAt(targetPos))) {
253 targetPos++;
254 }
255 if (element.isShiftPressed()) {
256 selectionEnd = targetPos;
257 }
258 else {
259 selectionStart = targetPos;
260 }
261 }
262 else if (element.isShiftPressed()) {
263 if (selectionEnd < newValue.length()) {
264 selectionEnd++;
265 }
266 }
267 else if (selectionStart < newValue.length()) {
268 selectionStart++;
269 }
270 break;
271
272 case DOM_VK_UP:
273 final int lastLfUp = currentValue.lastIndexOf('\n', selectionStart - 1);
274 final int lastCrUp = currentValue.lastIndexOf('\r', selectionStart - 1);
275 final int lastBreakUp = Math.max(lastLfUp, lastCrUp);
276
277 if (lastBreakUp == -1) {
278 selectionStart = 0;
279 }
280 else {
281 final int currentLineStart = lastBreakUp + 1;
282 final int column = selectionStart - currentLineStart;
283
284 final int prevLineEnd = (lastBreakUp > 0 && newValue.charAt(lastBreakUp) == '\n'
285 && newValue.charAt(lastBreakUp - 1) == '\r') ? lastBreakUp - 1 : lastBreakUp;
286
287 final int prevLf = currentValue.lastIndexOf('\n', prevLineEnd - 1);
288 final int prevCr = currentValue.lastIndexOf('\r', prevLineEnd - 1);
289 final int prevBreak = Math.max(prevLf, prevCr);
290
291 final int prevLineStart = (prevBreak == -1) ? 0 : prevBreak + 1;
292 final int prevLineLength = prevLineEnd - prevLineStart;
293
294 selectionStart = prevLineStart + Math.min(column, prevLineLength);
295 }
296 break;
297
298 case DOM_VK_DOWN:
299 final int lastLfDown = currentValue.lastIndexOf('\n', selectionStart - 1);
300 final int lastCrDown = currentValue.lastIndexOf('\r', selectionStart - 1);
301 final int lastBreakDown = Math.max(lastLfDown, lastCrDown);
302 final int currentLineStartDown = (lastBreakDown == -1) ? 0 : lastBreakDown + 1;
303 final int columnDown = selectionStart - currentLineStartDown;
304
305 final int nextLfDown = currentValue.indexOf('\n', selectionStart);
306 final int nextCrDown = currentValue.indexOf('\r', selectionStart);
307 final int nextBreakDown;
308 if (nextLfDown == -1) {
309 nextBreakDown = nextCrDown;
310 }
311 else if (nextCrDown == -1) {
312 nextBreakDown = nextLfDown;
313 }
314 else {
315 nextBreakDown = Math.min(nextLfDown, nextCrDown);
316 }
317
318 final int targetDown;
319 if (nextBreakDown == -1) {
320 targetDown = newValue.length();
321 }
322 else {
323 int nextLineStart = nextBreakDown + 1;
324 if (newValue.charAt(nextBreakDown) == '\r' && nextLineStart < newValue.length()
325 && newValue.charAt(nextLineStart) == '\n') {
326 nextLineStart++;
327 }
328
329 final int followingLf = currentValue.indexOf('\n', nextLineStart);
330 final int followingCr = currentValue.indexOf('\r', nextLineStart);
331 final int followingBreak;
332 if (followingLf == -1) {
333 followingBreak = followingCr;
334 }
335 else if (followingCr == -1) {
336 followingBreak = followingLf;
337 }
338 else {
339 followingBreak = Math.min(followingLf, followingCr);
340 }
341
342 final int nextLineEnd = (followingBreak == -1) ? newValue.length() : followingBreak;
343 final int nextLineLength = nextLineEnd - nextLineStart;
344
345 targetDown = nextLineStart + Math.min(columnDown, nextLineLength);
346 }
347
348 if (element.isShiftPressed()) {
349 selectionEnd = targetDown;
350 }
351 else {
352 selectionStart = targetDown;
353 }
354 break;
355
356 case DOM_VK_HOME:
357 final int targetHome;
358 if (element.isCtrlPressed()) {
359 targetHome = 0;
360 }
361 else {
362 final int lastLf = currentValue.lastIndexOf('\n', selectionStart - 1);
363 final int lastCr = currentValue.lastIndexOf('\r', selectionStart - 1);
364
365
366 final int lastBreak = Math.max(lastLf, lastCr);
367 targetHome = (lastBreak == -1) ? 0 : lastBreak + 1;
368 }
369
370 if (element.isShiftPressed()) {
371 selectionStart = targetHome;
372 }
373 else {
374 selectionStart = targetHome;
375 selectionEnd = targetHome;
376 }
377 break;
378
379 case DOM_VK_END:
380 int targetEnd;
381 if (element.isCtrlPressed()) {
382 targetEnd = newValue.length();
383 }
384 else {
385 final int nextLf = currentValue.indexOf('\n', selectionStart);
386 final int nextCr = currentValue.indexOf('\r', selectionStart);
387 if (nextLf == -1) {
388 targetEnd = nextCr;
389 }
390 else if (nextCr == -1) {
391 targetEnd = nextLf;
392 }
393 else {
394 targetEnd = Math.min(nextLf, nextCr);
395 }
396 if (targetEnd == -1) {
397 targetEnd = newValue.length();
398 }
399 }
400
401 if (element.isShiftPressed()) {
402 selectionEnd = targetEnd;
403 }
404 else {
405 selectionStart = targetEnd;
406 }
407 break;
408
409 case DOM_VK_DELETE:
410 if (selectionEnd != selectionStart) {
411 newValue.delete(selectionStart, selectionEnd);
412 selectionEnd = selectionStart;
413 break;
414 }
415
416 if (selectionStart < newValue.length()) {
417 if (element.isCtrlPressed()) {
418 int delTargetEnd = selectionStart;
419
420 while (delTargetEnd < newValue.length()
421 && Character.isWhitespace(newValue.charAt(delTargetEnd))) {
422 delTargetEnd++;
423 }
424
425 while (delTargetEnd < newValue.length()
426 && !Character.isWhitespace(newValue.charAt(delTargetEnd))) {
427 delTargetEnd++;
428 }
429
430 while (delTargetEnd < newValue.length()
431 && Character.isWhitespace(newValue.charAt(delTargetEnd))) {
432 delTargetEnd++;
433 }
434 newValue.delete(selectionStart, delTargetEnd);
435 }
436 else {
437 newValue.deleteCharAt(selectionStart);
438 }
439 }
440 break;
441
442 default:
443 return;
444 }
445
446 if (!element.isShiftPressed()) {
447 selectionEnd = selectionStart;
448 }
449
450 typeDone(newValue.toString(), lastType);
451
452 selectionDelegate.setSelectionStart(selectionStart);
453 selectionDelegate.setSelectionEnd(selectionEnd);
454 }
455 }