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.html.DomDocumentFragment;
22 import org.htmlunit.html.DomNode;
23 import org.htmlunit.html.impl.SimpleRange;
24 import org.htmlunit.javascript.HtmlUnitScriptable;
25 import org.htmlunit.javascript.JavaScriptEngine;
26 import org.htmlunit.javascript.configuration.JsxClass;
27 import org.htmlunit.javascript.configuration.JsxConstant;
28 import org.htmlunit.javascript.configuration.JsxConstructor;
29 import org.htmlunit.javascript.configuration.JsxFunction;
30 import org.htmlunit.javascript.configuration.JsxGetter;
31 import org.htmlunit.javascript.host.ClientRect;
32 import org.htmlunit.javascript.host.ClientRectList;
33 import org.htmlunit.javascript.host.Window;
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 page.getWebClient().getPageCreator().getHtmlParser()
262 .parseFragment(fragment, internGetStartContainer().getDomNodeOrDie(), valueAsString, false);
263 }
264 catch (final Exception e) {
265 LogFactory.getLog(Range.class).error("Unexpected exception occurred in createContextualFragment", e);
266 throw JavaScriptEngine.reportRuntimeError("Unexpected exception occurred in createContextualFragment: "
267 + e.getMessage());
268 }
269
270 return fragment.getScriptableObject();
271 }
272
273
274
275
276
277 @JsxFunction
278 public HtmlUnitScriptable extractContents() {
279 try {
280 return getSimpleRange().extractContents().getScriptableObject();
281 }
282 catch (final IllegalStateException e) {
283 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
284 }
285 }
286
287
288
289
290
291
292
293
294 @JsxFunction
295 public int compareBoundaryPoints(final int how, final Range sourceRange) {
296 final Node nodeForThis;
297 final int offsetForThis;
298 final int containingMoficator;
299 if (START_TO_START == how || END_TO_START == how) {
300 nodeForThis = internGetStartContainer();
301 offsetForThis = internGetStartOffset();
302 containingMoficator = 1;
303 }
304 else {
305 nodeForThis = internGetEndContainer();
306 offsetForThis = internGetEndOffset();
307 containingMoficator = -1;
308 }
309
310 final Node nodeForOther;
311 final int offsetForOther;
312 if (START_TO_END == how || START_TO_START == how) {
313 nodeForOther = sourceRange.internGetStartContainer();
314 offsetForOther = sourceRange.internGetStartOffset();
315 }
316 else {
317 nodeForOther = sourceRange.internGetEndContainer();
318 offsetForOther = sourceRange.internGetEndOffset();
319 }
320
321 if (nodeForThis == nodeForOther) {
322 if (offsetForThis < offsetForOther) {
323 return -1;
324 }
325 else if (offsetForThis > offsetForOther) {
326 return 1;
327 }
328 return 0;
329 }
330
331 final byte nodeComparision = (byte) nodeForThis.compareDocumentPosition(nodeForOther);
332 if ((nodeComparision & Node.DOCUMENT_POSITION_CONTAINED_BY) != 0) {
333 return -1 * containingMoficator;
334 }
335 else if ((nodeComparision & Node.DOCUMENT_POSITION_PRECEDING) != 0) {
336 return -1;
337 }
338
339
340 return 1;
341 }
342
343
344
345
346
347 @JsxFunction
348 public HtmlUnitScriptable cloneContents() {
349 try {
350 return getSimpleRange().cloneContents().getScriptableObject();
351 }
352 catch (final IllegalStateException e) {
353 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
354 }
355 }
356
357
358
359
360 @JsxFunction
361 public void deleteContents() {
362 try {
363 getSimpleRange().deleteContents();
364 }
365 catch (final IllegalStateException e) {
366 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
367 }
368 }
369
370
371
372
373
374
375 @JsxFunction
376 public void insertNode(final Node newNode) {
377 try {
378 getSimpleRange().insertNode(newNode.getDomNodeOrDie());
379 }
380 catch (final IllegalStateException e) {
381 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
382 }
383 }
384
385
386
387
388
389 @JsxFunction
390 public void surroundContents(final Node newNode) {
391 try {
392 getSimpleRange().surroundContents(newNode.getDomNodeOrDie());
393 }
394 catch (final IllegalStateException e) {
395 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
396 }
397 }
398
399
400
401
402
403 @JsxFunction
404 public Range cloneRange() {
405 try {
406 return new Range(getSimpleRange().cloneRange());
407 }
408 catch (final IllegalStateException e) {
409 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
410 }
411 }
412
413
414
415
416 @JsxFunction
417 public void detach() {
418
419 }
420
421
422
423
424
425 @JsxFunction(functionName = "toString")
426 public String jsToString() {
427 try {
428 return getSimpleRange().toString();
429 }
430 catch (final IllegalStateException e) {
431 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
432 }
433 }
434
435
436
437
438
439
440 @JsxFunction
441 public ClientRectList getClientRects() {
442 final Window w = getWindow();
443 final ClientRectList rectList = new ClientRectList();
444 rectList.setParentScope(w);
445 rectList.setPrototype(getPrototype(rectList.getClass()));
446
447 try {
448
449 for (final DomNode node : getSimpleRange().containedNodes()) {
450 final HtmlUnitScriptable scriptable = node.getScriptableObject();
451 if (scriptable instanceof HTMLElement) {
452 final ClientRect rect = new ClientRect(0, 0, 1, 1);
453 rect.setParentScope(w);
454 rect.setPrototype(getPrototype(rect.getClass()));
455 rectList.add(rect);
456 }
457 }
458
459 return rectList;
460 }
461 catch (final IllegalStateException e) {
462 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
463 }
464 }
465
466
467
468
469
470
471 @JsxFunction
472 public ClientRect getBoundingClientRect() {
473 final ClientRect rect = new ClientRect();
474 rect.setParentScope(getWindow());
475 rect.setPrototype(getPrototype(rect.getClass()));
476
477 try {
478
479 for (final DomNode node : getSimpleRange().containedNodes()) {
480 final HtmlUnitScriptable scriptable = node.getScriptableObject();
481 if (scriptable instanceof HTMLElement) {
482 final ClientRect childRect = ((HTMLElement) scriptable).getBoundingClientRect();
483 rect.setTop(Math.min(rect.getTop(), childRect.getTop()));
484 rect.setLeft(Math.min(rect.getLeft(), childRect.getLeft()));
485 rect.setRight(Math.max(rect.getRight(), childRect.getRight()));
486 rect.setBottom(Math.max(rect.getBottom(), childRect.getBottom()));
487 }
488 }
489
490 return rect;
491 }
492 catch (final IllegalStateException e) {
493 throw JavaScriptEngine.reportRuntimeError(e.getMessage());
494 }
495 }
496 }