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.Window;
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 internSetStartContainer(refNode.getParent());
185 internSetStartOffset(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 containingMoficator;
302 if (START_TO_START == how || END_TO_START == how) {
303 nodeForThis = internGetStartContainer();
304 offsetForThis = internGetStartOffset();
305 containingMoficator = 1;
306 }
307 else {
308 nodeForThis = internGetEndContainer();
309 offsetForThis = internGetEndOffset();
310 containingMoficator = -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 * containingMoficator;
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 Window w = getWindow();
446 final DOMRectList rectList = new DOMRectList();
447 rectList.setParentScope(w);
448 rectList.setPrototype(getPrototype(rectList.getClass()));
449
450 try {
451
452 for (final DomNode node : getSimpleRange().containedNodes()) {
453 final HtmlUnitScriptable scriptable = node.getScriptableObject();
454 if (scriptable instanceof HTMLElement) {
455 final DOMRect rect = new DOMRect(0, 0, 1, 1);
456 rect.setParentScope(w);
457 rect.setPrototype(getPrototype(rect.getClass()));
458 rectList.add(rect);
459 }
460 }
461
462 return rectList;
463 }
464 catch (final IllegalStateException e) {
465 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
466 }
467 }
468
469
470
471
472
473
474 @JsxFunction
475 public DOMRect getBoundingClientRect() {
476 final DOMRect rect = new DOMRect();
477 rect.setParentScope(getWindow());
478 rect.setPrototype(getPrototype(rect.getClass()));
479
480 try {
481
482 for (final DomNode node : getSimpleRange().containedNodes()) {
483 final HtmlUnitScriptable scriptable = node.getScriptableObject();
484 if (scriptable instanceof HTMLElement) {
485 final DOMRect childRect = ((HTMLElement) scriptable).getBoundingClientRect();
486 rect.setTop(Math.min(rect.getTop(), childRect.getTop()));
487 rect.setLeft(Math.min(rect.getLeft(), childRect.getLeft()));
488 rect.setRight(Math.max(rect.getRight(), childRect.getRight()));
489 rect.setBottom(Math.max(rect.getBottom(), childRect.getBottom()));
490 }
491 }
492
493 return rect;
494 }
495 catch (final IllegalStateException e) {
496 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
497 }
498 }
499 }