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