1
2
3
4
5
6
7
8
9
10
11
12
13
14
15 package org.htmlunit.javascript.host.dom;
16
17 import java.util.HashSet;
18
19 import org.apache.commons.logging.LogFactory;
20 import org.htmlunit.SgmlPage;
21 import org.htmlunit.WebClient;
22 import org.htmlunit.html.DomDocumentFragment;
23 import org.htmlunit.html.DomNode;
24 import org.htmlunit.html.DomText;
25 import org.htmlunit.html.impl.SimpleRange;
26 import org.htmlunit.javascript.HtmlUnitScriptable;
27 import org.htmlunit.javascript.JavaScriptEngine;
28 import org.htmlunit.javascript.configuration.JsxClass;
29 import org.htmlunit.javascript.configuration.JsxConstant;
30 import org.htmlunit.javascript.configuration.JsxConstructor;
31 import org.htmlunit.javascript.configuration.JsxFunction;
32 import org.htmlunit.javascript.configuration.JsxGetter;
33 import org.htmlunit.javascript.host.DOMRect;
34 import org.htmlunit.javascript.host.DOMRectList;
35 import org.htmlunit.javascript.host.html.HTMLElement;
36
37
38
39
40
41
42
43
44
45
46
47
48 @JsxClass
49 public class Range extends AbstractRange {
50
51
52 @JsxConstant
53 public static final int START_TO_START = 0;
54
55
56 @JsxConstant
57 public static final int START_TO_END = 1;
58
59
60 @JsxConstant
61 public static final int END_TO_END = 2;
62
63
64 @JsxConstant
65 public static final int END_TO_START = 3;
66
67
68
69
70 public Range() {
71 super();
72 }
73
74
75
76
77 @Override
78 @JsxConstructor
79 public void jsConstructor() {
80 super.jsConstructor();
81 }
82
83
84
85
86
87 public Range(final Document document) {
88 super(document, document, 0, 0);
89 }
90
91 Range(final SimpleRange simpleRange) {
92 super(simpleRange.getStartContainer().getScriptableObject(),
93 simpleRange.getEndContainer().getScriptableObject(),
94 simpleRange.getStartOffset(),
95 simpleRange.getEndOffset());
96 }
97
98
99
100
101
102
103 @JsxFunction
104 public void setStart(final Node refNode, final int offset) {
105 if (refNode == null) {
106 throw JavaScriptEngine.reportRuntimeError("It is illegal to call Range.setStart() with a null node.");
107 }
108 internSetStartContainer(refNode);
109 internSetStartOffset(offset);
110 }
111
112
113
114
115
116 @JsxFunction
117 public void setStartAfter(final Node refNode) {
118 if (refNode == null) {
119 throw JavaScriptEngine.reportRuntimeError("It is illegal to call Range.setStartAfter() with a null node.");
120 }
121 internSetStartContainer(refNode.getParent());
122 internSetStartOffset(getPositionInContainer(refNode) + 1);
123 }
124
125
126
127
128
129 @JsxFunction
130 public void setStartBefore(final Node refNode) {
131 if (refNode == null) {
132 throw JavaScriptEngine.reportRuntimeError("It is illegal to call Range.setStartBefore() with a null node.");
133 }
134 internSetStartContainer(refNode.getParent());
135 internSetStartOffset(getPositionInContainer(refNode));
136 }
137
138 private static int getPositionInContainer(final Node refNode) {
139 int i = 0;
140 Node node = refNode;
141 while (node.getPreviousSibling() != null) {
142 node = node.getPreviousSibling();
143 i++;
144 }
145 return i;
146 }
147
148
149
150
151
152
153 @JsxFunction
154 public void setEnd(final Node refNode, final int offset) {
155 if (refNode == null) {
156 throw JavaScriptEngine.reportRuntimeError("It is illegal to call Range.setEnd() with a null node.");
157 }
158 internSetEndContainer(refNode);
159 internSetEndOffset(offset);
160 }
161
162
163
164
165
166 @JsxFunction
167 public void setEndAfter(final Node refNode) {
168 if (refNode == null) {
169 throw JavaScriptEngine.reportRuntimeError("It is illegal to call Range.setEndAfter() with a null node.");
170 }
171 internSetEndContainer(refNode.getParent());
172 internSetEndOffset(getPositionInContainer(refNode) + 1);
173 }
174
175
176
177
178
179 @JsxFunction
180 public void setEndBefore(final Node refNode) {
181 if (refNode == null) {
182 throw JavaScriptEngine.reportRuntimeError("It is illegal to call Range.setEndBefore() with a null node.");
183 }
184 internSetEndContainer(refNode.getParent());
185 internSetEndOffset(getPositionInContainer(refNode));
186 }
187
188
189
190
191
192 @JsxFunction
193 public void selectNodeContents(final Node refNode) {
194 internSetStartContainer(refNode);
195 internSetStartOffset(0);
196 internSetEndContainer(refNode);
197 internSetEndOffset(refNode.getChildNodes().getLength());
198 }
199
200
201
202
203
204 @JsxFunction
205 public void selectNode(final Node refNode) {
206 setStartBefore(refNode);
207 setEndAfter(refNode);
208 }
209
210
211
212
213
214 @JsxFunction
215 public void collapse(final boolean toStart) {
216 if (toStart) {
217 internSetEndContainer(internGetStartContainer());
218 internSetEndOffset(internGetStartOffset());
219 }
220 else {
221 internSetStartContainer(internGetEndContainer());
222 internSetStartOffset(internGetEndOffset());
223 }
224 }
225
226
227
228
229
230 @JsxGetter
231 public Object getCommonAncestorContainer() {
232 final HashSet<Node> startAncestors = new HashSet<>();
233 Node ancestor = internGetStartContainer();
234 while (ancestor != null) {
235 startAncestors.add(ancestor);
236 ancestor = ancestor.getParent();
237 }
238
239 ancestor = internGetEndContainer();
240 while (ancestor != null) {
241 if (startAncestors.contains(ancestor)) {
242 return ancestor;
243 }
244 ancestor = ancestor.getParent();
245 }
246
247 return JavaScriptEngine.UNDEFINED;
248 }
249
250
251
252
253
254
255
256
257 @JsxFunction
258 public HtmlUnitScriptable createContextualFragment(final String valueAsString) {
259 final SgmlPage page = internGetStartContainer().getDomNodeOrDie().getPage();
260 final DomDocumentFragment fragment = new DomDocumentFragment(page);
261 try {
262 final WebClient webClient = page.getWebClient();
263 webClient.getPageCreator().getHtmlParser()
264 .parseFragment(webClient, fragment,
265 internGetStartContainer().getDomNodeOrDie(), valueAsString, false);
266 }
267 catch (final Exception e) {
268 LogFactory.getLog(Range.class).error("Unexpected exception occurred in createContextualFragment", e);
269 throw JavaScriptEngine.reportRuntimeError("Unexpected exception occurred in createContextualFragment: "
270 + e.getMessage());
271 }
272
273 return fragment.getScriptableObject();
274 }
275
276
277
278
279
280 @JsxFunction
281 public HtmlUnitScriptable extractContents() {
282 try {
283 return getSimpleRange().extractContents().getScriptableObject();
284 }
285 catch (final IllegalStateException e) {
286 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
287 }
288 }
289
290
291
292
293
294
295
296
297 @JsxFunction
298 public int compareBoundaryPoints(final int how, final Range sourceRange) {
299 final Node nodeForThis;
300 final int offsetForThis;
301 final int containingModifier;
302 if (START_TO_START == how || END_TO_START == how) {
303 nodeForThis = internGetStartContainer();
304 offsetForThis = internGetStartOffset();
305 containingModifier = 1;
306 }
307 else {
308 nodeForThis = internGetEndContainer();
309 offsetForThis = internGetEndOffset();
310 containingModifier = -1;
311 }
312
313 final Node nodeForOther;
314 final int offsetForOther;
315 if (START_TO_END == how || START_TO_START == how) {
316 nodeForOther = sourceRange.internGetStartContainer();
317 offsetForOther = sourceRange.internGetStartOffset();
318 }
319 else {
320 nodeForOther = sourceRange.internGetEndContainer();
321 offsetForOther = sourceRange.internGetEndOffset();
322 }
323
324 if (nodeForThis == nodeForOther) {
325 if (offsetForThis < offsetForOther) {
326 return -1;
327 }
328 else if (offsetForThis > offsetForOther) {
329 return 1;
330 }
331 return 0;
332 }
333
334 final byte nodeComparision = (byte) nodeForThis.compareDocumentPosition(nodeForOther);
335 if ((nodeComparision & Node.DOCUMENT_POSITION_CONTAINED_BY) != 0) {
336 return -1 * containingModifier;
337 }
338 else if ((nodeComparision & Node.DOCUMENT_POSITION_PRECEDING) != 0) {
339 return -1;
340 }
341
342
343 return 1;
344 }
345
346
347
348
349
350 @JsxFunction
351 public HtmlUnitScriptable cloneContents() {
352 try {
353 return getSimpleRange().cloneContents().getScriptableObject();
354 }
355 catch (final IllegalStateException e) {
356 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
357 }
358 }
359
360
361
362
363 @JsxFunction
364 public void deleteContents() {
365 try {
366 getSimpleRange().deleteContents();
367 }
368 catch (final IllegalStateException e) {
369 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
370 }
371 }
372
373
374
375
376
377
378 @JsxFunction
379 public void insertNode(final Node newNode) {
380 try {
381 getSimpleRange().insertNode(newNode.getDomNodeOrDie());
382 }
383 catch (final IllegalStateException e) {
384 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
385 }
386 }
387
388
389
390
391
392 @JsxFunction
393 public void surroundContents(final Node newNode) {
394 try {
395 getSimpleRange().surroundContents(newNode.getDomNodeOrDie());
396 }
397 catch (final IllegalStateException e) {
398 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
399 }
400 }
401
402
403
404
405
406 @JsxFunction
407 public Range cloneRange() {
408 try {
409 return new Range(getSimpleRange().cloneRange());
410 }
411 catch (final IllegalStateException e) {
412 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
413 }
414 }
415
416
417
418
419 @JsxFunction
420 public void detach() {
421
422 }
423
424
425
426
427
428 @JsxFunction(functionName = "toString")
429 public String jsToString() {
430 try {
431 return getSimpleRange().toString();
432 }
433 catch (final IllegalStateException e) {
434 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
435 }
436 }
437
438
439
440
441
442
443 @JsxFunction
444 public DOMRectList getClientRects() {
445 final DOMRectList rectList = new DOMRectList();
446 rectList.setParentScope(getParentScope());
447 rectList.setPrototype(getPrototype(rectList.getClass()));
448
449 try {
450
451 for (final DomNode node : getSimpleRange().containedNodes()) {
452 final HtmlUnitScriptable scriptable = node.getScriptableObject();
453 if (scriptable instanceof HTMLElement element) {
454 final DOMRect rect = element.getBoundingClientRect();
455 rect.setParentScope(getParentScope());
456 rect.setPrototype(getPrototype(rect.getClass()));
457 rectList.add(rect);
458 }
459 else if (node instanceof DomText) {
460
461 final DomNode parent = node.getParentNode();
462 if (parent != null) {
463 final HtmlUnitScriptable parentScriptable = parent.getScriptableObject();
464 if (parentScriptable instanceof HTMLElement parentElement) {
465 final DOMRect rect = parentElement.getBoundingClientRect();
466 rect.setParentScope(getParentScope());
467 rect.setPrototype(getPrototype(rect.getClass()));
468 rectList.add(rect);
469 }
470 }
471 }
472 }
473
474 return rectList;
475 }
476 catch (final IllegalStateException e) {
477 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
478 }
479 }
480
481
482
483
484
485
486 @JsxFunction
487 public DOMRect getBoundingClientRect() {
488 final DOMRect rect = new DOMRect(Integer.MAX_VALUE, Integer.MAX_VALUE, 0, 0);
489 rect.setParentScope(getParentScope());
490 rect.setPrototype(getPrototype(rect.getClass()));
491
492 try {
493
494 for (final DomNode node : getSimpleRange().containedNodes()) {
495 final HtmlUnitScriptable scriptable = node.getScriptableObject();
496 if (scriptable instanceof HTMLElement element) {
497 final DOMRect childRect = element.getBoundingClientRect();
498 rect.setX(Math.min(rect.getX(), childRect.getX()));
499 rect.setY(Math.min(rect.getY(), childRect.getY()));
500 rect.setWidth(Math.max(rect.getWidth(), childRect.getWidth()));
501 rect.setHeight(Math.max(rect.getHeight(), childRect.getHeight()));
502 }
503 else if (node instanceof DomText) {
504
505 final DomNode parent = node.getParentNode();
506 if (parent != null) {
507 final HtmlUnitScriptable parentScriptable = parent.getScriptableObject();
508 if (parentScriptable instanceof HTMLElement parentElement) {
509 final DOMRect childRect = parentElement.getBoundingClientRect();
510 rect.setX(Math.min(rect.getX(), childRect.getX()));
511 rect.setY(Math.min(rect.getY(), childRect.getY()));
512 rect.setWidth(Math.max(rect.getWidth(), childRect.getWidth()));
513 rect.setHeight(Math.max(rect.getHeight(), childRect.getHeight()));
514 }
515 }
516 }
517 }
518
519 return rect;
520 }
521 catch (final IllegalStateException e) {
522 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
523 }
524 }
525 }