
org.htmlunit.javascript.host.dom.Range Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of xlt Show documentation
Show all versions of xlt Show documentation
XLT (Xceptance LoadTest) is an extensive load and performance test tool developed and maintained by Xceptance.
/*
* Copyright (c) 2002-2023 Gargoyle Software Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.htmlunit.javascript.host.dom;
import static org.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static org.htmlunit.javascript.configuration.SupportedBrowser.EDGE;
import static org.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static org.htmlunit.javascript.configuration.SupportedBrowser.FF_ESR;
import java.util.HashSet;
import org.apache.commons.logging.LogFactory;
import org.htmlunit.SgmlPage;
import org.htmlunit.html.DomDocumentFragment;
import org.htmlunit.html.DomNode;
import org.htmlunit.html.impl.SimpleRange;
import org.htmlunit.javascript.HtmlUnitScriptable;
import org.htmlunit.javascript.configuration.JsxClass;
import org.htmlunit.javascript.configuration.JsxConstant;
import org.htmlunit.javascript.configuration.JsxConstructor;
import org.htmlunit.javascript.configuration.JsxFunction;
import org.htmlunit.javascript.configuration.JsxGetter;
import org.htmlunit.javascript.host.ClientRect;
import org.htmlunit.javascript.host.ClientRectList;
import org.htmlunit.javascript.host.Window;
import org.htmlunit.javascript.host.html.HTMLElement;
import org.htmlunit.corejs.javascript.Context;
import org.htmlunit.corejs.javascript.ScriptableObject;
import org.htmlunit.corejs.javascript.Undefined;
/**
* The JavaScript object that represents a Range.
*
* @see XULPlanet
* @see DOM-Level-2-Traversal-Range
* @author Marc Guillemot
* @author Ahmed Ashour
* @author Daniel Gredler
* @author James Phillpotts
* @author Ronald Brill
*/
@JsxClass
public class Range extends HtmlUnitScriptable {
/** Comparison mode for compareBoundaryPoints. */
@JsxConstant
public static final short START_TO_START = 0;
/** Comparison mode for compareBoundaryPoints. */
@JsxConstant
public static final short START_TO_END = 1;
/** Comparison mode for compareBoundaryPoints. */
@JsxConstant
public static final short END_TO_END = 2;
/** Comparison mode for compareBoundaryPoints. */
@JsxConstant
public static final short END_TO_START = 3;
private Node startContainer_;
private Node endContainer_;
private int startOffset_;
private int endOffset_;
/**
* Creates an instance.
*/
@JsxConstructor({CHROME, EDGE, FF, FF_ESR})
public Range() {
}
/**
* Creates a new instance.
* @param document the HTML document creating the range
*/
public Range(final Document document) {
startContainer_ = document;
endContainer_ = document;
}
Range(final org.w3c.dom.ranges.Range w3cRange) {
final DomNode domNodeStartContainer = (DomNode) w3cRange.getStartContainer();
startContainer_ = domNodeStartContainer.getScriptableObject();
startOffset_ = w3cRange.getStartOffset();
final DomNode domNodeEndContainer = (DomNode) w3cRange.getEndContainer();
endContainer_ = domNodeEndContainer.getScriptableObject();
endOffset_ = w3cRange.getEndOffset();
}
/**
* {@inheritDoc}
*/
@Override
public Object getDefaultValue(final Class> hint) {
if (getPrototype() == null
|| startContainer_ == null
|| endContainer_ == null) {
return super.getDefaultValue(hint);
}
return toW3C().toString();
}
/**
* Gets the node within which the Range begins.
* @return undefined
if not initialized
*/
@JsxGetter
public Object getStartContainer() {
if (startContainer_ == null) {
return Undefined.instance;
}
return startContainer_;
}
/**
* Gets the node within which the Range ends.
* @return undefined
if not initialized
*/
@JsxGetter
public Object getEndContainer() {
if (endContainer_ == null) {
return Undefined.instance;
}
return endContainer_;
}
/**
* Gets the offset within the starting node of the Range.
* @return 0
if not initialized
*/
@JsxGetter
public int getStartOffset() {
return startOffset_;
}
/**
* Gets the offset within the end node of the Range.
* @return 0
if not initialized
*/
@JsxGetter
public int getEndOffset() {
return endOffset_;
}
/**
* Sets the attributes describing the start of a Range.
* @param refNode the reference node
* @param offset the offset value within the node
*/
@JsxFunction
public void setStart(final Node refNode, final int offset) {
if (refNode == null) {
throw Context.reportRuntimeError("It is illegal to call Range.setStart() with a null node.");
}
startContainer_ = refNode;
startOffset_ = offset;
}
/**
* Sets the start of the range to be after the node.
* @param refNode the reference node
*/
@JsxFunction
public void setStartAfter(final Node refNode) {
if (refNode == null) {
throw Context.reportRuntimeError("It is illegal to call Range.setStartAfter() with a null node.");
}
startContainer_ = refNode.getParent();
startOffset_ = getPositionInContainer(refNode) + 1;
}
/**
* Sets the start of the range to be before the node.
* @param refNode the reference node
*/
@JsxFunction
public void setStartBefore(final Node refNode) {
if (refNode == null) {
throw Context.reportRuntimeError("It is illegal to call Range.setStartBefore() with a null node.");
}
startContainer_ = refNode.getParent();
startOffset_ = getPositionInContainer(refNode);
}
private static int getPositionInContainer(final Node refNode) {
int i = 0;
Node node = refNode;
while (node.getPreviousSibling() != null) {
node = node.getPreviousSibling();
i++;
}
return i;
}
/**
* Indicates if the range is collapsed.
* @return {@code true} if the range is collapsed
*/
@JsxGetter
public boolean isCollapsed() {
return startContainer_ == endContainer_ && startOffset_ == endOffset_;
}
/**
* Sets the attributes describing the end of a Range.
* @param refNode the reference node
* @param offset the offset value within the node
*/
@JsxFunction
public void setEnd(final Node refNode, final int offset) {
if (refNode == null) {
throw Context.reportRuntimeError("It is illegal to call Range.setEnd() with a null node.");
}
endContainer_ = refNode;
endOffset_ = offset;
}
/**
* Sets the end of the range to be after the node.
* @param refNode the reference node
*/
@JsxFunction
public void setEndAfter(final Node refNode) {
if (refNode == null) {
throw Context.reportRuntimeError("It is illegal to call Range.setEndAfter() with a null node.");
}
endContainer_ = refNode.getParent();
endOffset_ = getPositionInContainer(refNode) + 1;
}
/**
* Sets the end of the range to be before the node.
* @param refNode the reference node
*/
@JsxFunction
public void setEndBefore(final Node refNode) {
if (refNode == null) {
throw Context.reportRuntimeError("It is illegal to call Range.setEndBefore() with a null node.");
}
startContainer_ = refNode.getParent();
startOffset_ = getPositionInContainer(refNode);
}
/**
* Select the contents within a node.
* @param refNode Node to select from
*/
@JsxFunction
public void selectNodeContents(final Node refNode) {
startContainer_ = refNode;
startOffset_ = 0;
endContainer_ = refNode;
endOffset_ = refNode.getChildNodes().getLength();
}
/**
* Selects a node and its contents.
* @param refNode the node to select
*/
@JsxFunction
public void selectNode(final Node refNode) {
setStartBefore(refNode);
setEndAfter(refNode);
}
/**
* Collapse a Range onto one of its boundaries.
* @param toStart if {@code true}, collapses the Range onto its start; else collapses it onto its end
*/
@JsxFunction
public void collapse(final boolean toStart) {
if (toStart) {
endContainer_ = startContainer_;
endOffset_ = startOffset_;
}
else {
startContainer_ = endContainer_;
startOffset_ = endOffset_;
}
}
/**
* Returns the deepest common ancestor container of the Range's two boundary points.
* @return the deepest common ancestor container of the Range's two boundary points
*/
@JsxGetter
public Object getCommonAncestorContainer() {
final HashSet startAncestors = new HashSet<>();
Node ancestor = startContainer_;
while (ancestor != null) {
startAncestors.add(ancestor);
ancestor = ancestor.getParent();
}
ancestor = endContainer_;
while (ancestor != null) {
if (startAncestors.contains(ancestor)) {
return ancestor;
}
ancestor = ancestor.getParent();
}
return Undefined.instance;
}
/**
* Parses an HTML snippet.
* @param valueAsString text that contains text and tags to be converted to a document fragment
* @return a document fragment
* @see Mozilla
* documentation
*/
@JsxFunction
public Object createContextualFragment(final String valueAsString) {
final SgmlPage page = startContainer_.getDomNodeOrDie().getPage();
final DomDocumentFragment fragment = new DomDocumentFragment(page);
try {
page.getWebClient().getPageCreator().getHtmlParser()
.parseFragment(fragment, startContainer_.getDomNodeOrDie(), valueAsString, false);
}
catch (final Exception e) {
LogFactory.getLog(Range.class).error("Unexpected exception occurred in createContextualFragment", e);
throw Context.reportRuntimeError("Unexpected exception occurred in createContextualFragment: "
+ e.getMessage());
}
return fragment.getScriptableObject();
}
/**
* Moves this range's contents from the document tree into a document fragment.
* @return the new document fragment containing the range contents
*/
@JsxFunction
public Object extractContents() {
return toW3C().extractContents().getScriptableObject();
}
/**
* Returns a W3C {@link org.w3c.dom.ranges.Range} version of this object.
* @return a W3C {@link org.w3c.dom.ranges.Range} version of this object
*/
public SimpleRange toW3C() {
return new SimpleRange(startContainer_.getDomNodeOrNull(), startOffset_,
endContainer_.getDomNodeOrDie(), endOffset_);
}
/**
* Compares the boundary points of two Ranges.
* @param how a constant describing the comparison method
* @param sourceRange the Range to compare boundary points with this range
* @return -1, 0, or 1, indicating whether the corresponding boundary-point of range is respectively before,
* equal to, or after the corresponding boundary-point of sourceRange.
*/
@JsxFunction
public Object compareBoundaryPoints(final int how, final Range sourceRange) {
final Node nodeForThis;
final int offsetForThis;
final int containingMoficator;
if (START_TO_START == how || END_TO_START == how) {
nodeForThis = startContainer_;
offsetForThis = startOffset_;
containingMoficator = 1;
}
else {
nodeForThis = endContainer_;
offsetForThis = endOffset_;
containingMoficator = -1;
}
final Node nodeForOther;
final int offsetForOther;
if (START_TO_END == how || START_TO_START == how) {
nodeForOther = sourceRange.startContainer_;
offsetForOther = sourceRange.startOffset_;
}
else {
nodeForOther = sourceRange.endContainer_;
offsetForOther = sourceRange.endOffset_;
}
if (nodeForThis == nodeForOther) {
if (offsetForThis < offsetForOther) {
return Integer.valueOf(-1);
}
else if (offsetForThis > offsetForOther) {
return Integer.valueOf(1);
}
return Integer.valueOf(0);
}
final byte nodeComparision = (byte) nodeForThis.compareDocumentPosition(nodeForOther);
if ((nodeComparision & Node.DOCUMENT_POSITION_CONTAINED_BY) != 0) {
return Integer.valueOf(-1 * containingMoficator);
}
else if ((nodeComparision & Node.DOCUMENT_POSITION_PRECEDING) != 0) {
return Integer.valueOf(-1);
}
// TODO: handle other cases!
return Integer.valueOf(1);
}
/**
* Returns a clone of the range in a document fragment.
* @return a clone
*/
@JsxFunction
public Object cloneContents() {
return toW3C().cloneContents().getScriptableObject();
}
/**
* Deletes the contents of the range.
*/
@JsxFunction
public void deleteContents() {
toW3C().deleteContents();
}
/**
* Inserts a new node at the beginning of the range. If the range begins at an offset, the node is split.
* @param newNode The node to insert
* @see https://developer.mozilla.org/en/DOM/range
*/
@JsxFunction
public void insertNode(final Node newNode) {
toW3C().insertNode(newNode.getDomNodeOrDie());
}
/**
* Surrounds the contents of the range in a new node.
* @param newNode The node to surround the range in
*/
@JsxFunction
public void surroundContents(final Node newNode) {
toW3C().surroundContents(newNode.getDomNodeOrDie());
}
/**
* Returns a clone of the range.
* @return a clone of the range
*/
@JsxFunction
public Object cloneRange() {
return new Range(toW3C().cloneRange());
}
/**
* Releases Range from use to improve performance.
*/
@JsxFunction
public void detach() {
// Java garbage collection should take care of this for us
}
/**
* Returns the text of the Range.
* @return the text
*/
@JsxFunction(functionName = "toString")
public String jsToString() {
return toW3C().toString();
}
@Override
protected Object equivalentValues(final Object value) {
if (!(value instanceof Range)) {
return false;
}
final Range other = (Range) value;
return startContainer_ == other.startContainer_
&& endContainer_ == other.endContainer_
&& startOffset_ == other.startOffset_
&& endOffset_ == other.endOffset_;
}
/**
* Retrieves a collection of rectangles that describes the layout of the contents of an object
* or range within the client. Each rectangle describes a single line.
* @return a collection of rectangles that describes the layout of the contents
*/
@JsxFunction
public ClientRectList getClientRects() {
final Window w = getWindow();
final ClientRectList rectList = new ClientRectList();
rectList.setParentScope(w);
rectList.setPrototype(getPrototype(rectList.getClass()));
// simple impl for now
for (final DomNode node : toW3C().containedNodes()) {
final ScriptableObject scriptable = node.getScriptableObject();
if (scriptable instanceof HTMLElement) {
final ClientRect rect = new ClientRect(0, 0, 1, 1);
rect.setParentScope(w);
rect.setPrototype(getPrototype(rect.getClass()));
rectList.add(rect);
}
}
return rectList;
}
/**
* Returns an object that bounds the contents of the range.
* this a rectangle enclosing the union of the bounding rectangles for all the elements in the range.
* @return an object the bounds the contents of the range
*/
@JsxFunction
public ClientRect getBoundingClientRect() {
final ClientRect rect = new ClientRect();
rect.setParentScope(getWindow());
rect.setPrototype(getPrototype(rect.getClass()));
// simple impl for now
for (final DomNode node : toW3C().containedNodes()) {
final ScriptableObject scriptable = node.getScriptableObject();
if (scriptable instanceof HTMLElement) {
final ClientRect childRect = ((HTMLElement) scriptable).getBoundingClientRect();
rect.setTop(Math.min(rect.getTop(), childRect.getTop()));
rect.setLeft(Math.min(rect.getLeft(), childRect.getLeft()));
rect.setRight(Math.max(rect.getRight(), childRect.getRight()));
rect.setBottom(Math.max(rect.getBottom(), childRect.getBottom()));
}
}
return rect;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy