false if no standard attribute exists with this name
*/
protected boolean isAttributeName(final String name) {
// can name be an attribute of current element?
// first approximation: attribute are all lowercase
// this should be improved because it's wrong. For instance: tabIndex, hideFocus, acceptCharset
return name.toLowerCase().equals(name);
}
/**
* Returns a collection of the attributes of this element.
* @return a collection of the attributes of this element
* @see Gecko DOM Reference
*/
public NamedNodeMap jsxGet_attributes() {
return new NamedNodeMap(getHtmlElementOrDie());
}
/**
* Gets the specified attribute.
* @param attributeName attribute name
* @return the value of the specified attribute, null if the attribute is not defined
*/
public String jsxFunction_getAttribute(final String attributeName) {
final String value = getHtmlElementOrDie().getAttributeValue(attributeName);
if (value == HtmlElement.ATTRIBUTE_NOT_DEFINED) {
return null;
}
return value;
}
/**
* Gets the specified attribute.
* @param namespaceURI the namespace URI
* @param localName the local name of the attribute to look for
* @return the value of the specified attribute, null if the attribute is not defined
*/
public String jsxFunction_getAttributeNS(final String namespaceURI, final String localName) {
return getHtmlElementOrDie().getAttributeNS(namespaceURI, localName);
}
/**
* Test for attribute.
* See also
* the DOM reference
*
* @param name Name of the attribute to test
* @return true if the node has this attribute
*/
public boolean jsxFunction_hasAttribute(final String name) {
return getHtmlElementOrDie().getAttribute(name) != HtmlElement.ATTRIBUTE_NOT_DEFINED;
}
/**
* Test for attribute.
* See also
* the DOM reference
*
* @param namespaceURI the namespace URI
* @param localName the local name of the attribute to look for
* @return true if the node has this attribute
*/
public boolean jsxFunction_hasAttributeNS(final String namespaceURI, final String localName) {
return getHtmlElementOrDie().getAttributeNS(namespaceURI, localName) != HtmlElement.ATTRIBUTE_NOT_DEFINED;
}
/**
* Sets an attribute.
* See also
* the DOM reference
*
* @param name Name of the attribute to set
* @param value Value to set the attribute to
*/
public void jsxFunction_setAttribute(final String name, final String value) {
getHtmlElementOrDie().setAttributeValue(name, value);
//FF: call corresponding event handler jsxSet_onxxx if found
if (getBrowserVersion().isNetscape()) {
try {
final Method method = getClass().getMethod("jsxSet_" + name, new Class[] {Object.class});
final String source = "function(){" + value + "}";
method.invoke(this, new Object[] {
Context.getCurrentContext().compileFunction(getWindow(), source, "", 0, null)});
}
catch (final NoSuchMethodException e) {
//silently ignore
}
catch (final IllegalAccessException e) {
//silently ignore
}
catch (final InvocationTargetException e) {
throw new RuntimeException(e.getCause());
}
}
}
/**
* Sets the specified attribute.
* @param namespaceURI the namespace URI
* @param qualifiedName the local name of the attribute to look for
* @param value the new attribute value
*/
public void jsxFunction_setAttributeNS(final String namespaceURI, final String qualifiedName, final String value) {
getHtmlElementOrDie().setAttributeValue(namespaceURI, qualifiedName, value);
}
/**
* Remove an attribute.
*
* @param name Name of the attribute to remove
*/
public void jsxFunction_removeAttribute(final String name) {
getHtmlElementOrDie().removeAttribute(name);
}
/**
* Gets the attribute node for the specified attribute.
* @param attributeName the name of the attribute to retrieve
* @return the attribute node for the specified attribute
*/
public Object jsxFunction_getAttributeNode(final String attributeName) {
final Attr att = new Attr();
att.setPrototype(getPrototype(Attr.class));
att.setParentScope(getWindow());
att.init(attributeName, getHtmlElementOrDie());
return att;
}
/**
* Sets the attribute node for the specified attribute.
* @param newAtt the attribute to set
* @return the replaced attribute node, if any
*/
public Attr jsxFunction_setAttributeNode(final Attr newAtt) {
final String name = newAtt.jsxGet_name();
final String value = newAtt.jsxGet_value();
final Attr replacedAtt = (Attr) jsxFunction_getAttributeNode(name);
replacedAtt.detachFromParent();
getHtmlElementOrDie().setAttributeValue(name, value);
return replacedAtt;
}
/**
* Returns all the descendant elements with the specified tag name.
* @param tagName the name to search for
* @return all the descendant elements with the specified tag name
*/
public Object jsxFunction_getElementsByTagName(final String tagName) {
final DomNode node = getDomNodeOrDie();
final HTMLCollection collection = new HTMLCollection(this);
final String xpath;
if ("*".equals(tagName)) {
xpath = ".//*";
}
else {
xpath = ".//node()[name() = '" + tagName.toLowerCase() + "']";
}
collection.init(node, xpath);
return collection;
}
/**
* Returns the class defined for this element.
* @return the class name
*/
public Object jsxGet_className() {
return getHtmlElementOrDie().getAttributeValue("class");
}
/**
* Returns "clientHeight" attribute.
* @return the clientHeight attribute
*/
public int jsxGet_clientHeight() {
final boolean includePadding = !getBrowserVersion().isIE();
final ComputedCSSStyleDeclaration style = getWindow().jsxFunction_getComputedStyle(this, null);
return style.getCalculatedHeight(false, includePadding);
}
/**
* Returns "clientWidth" attribute.
* @return the clientWidth attribute
*/
public int jsxGet_clientWidth() {
final boolean includePadding = !getBrowserVersion().isIE();
final ComputedCSSStyleDeclaration style = getWindow().jsxFunction_getComputedStyle(this, null);
return style.getCalculatedWidth(false, includePadding);
}
/**
* Sets the class attribute for this element.
* @param className - the new class name
*/
public void jsxSet_className(final String className) {
getHtmlElementOrDie().setAttributeValue("class", className);
}
/**
* Gets the innerHTML attribute.
* @return the contents of this node as HTML
*/
public String jsxGet_innerHTML() {
final StringBuilder buf = new StringBuilder();
// we can't rely on DomNode.asXml because it adds indentation and new lines
printChildren(buf, getDomNodeOrDie(), !"SCRIPT".equals(jsxGet_tagName()));
return buf.toString();
}
/**
* Gets the innerText attribute.
* @return the contents of this node as text
*/
public String jsxGet_innerText() {
final StringBuilder buf = new StringBuilder();
// we can't rely on DomNode.asXml because it adds indentation and new lines
printChildren(buf, getDomNodeOrDie(), false);
return buf.toString();
}
/**
* Gets the textContent attribute.
* @return the contents of this node as text
*/
public String jsxGet_textContent() {
return jsxGet_innerText();
}
/**
* Gets the outerHTML of the node.
* @see
* MSDN documentation
* @return the contents of this node as HTML
*/
public String jsxGet_outerHTML() {
final StringBuilder buf = new StringBuilder();
// we can't rely on DomNode.asXml because it adds indentation and new lines
printNode(buf, getDomNodeOrDie(), true);
return buf.toString();
}
private void printChildren(final StringBuilder buffer, final DomNode node, final boolean html) {
for (final DomNode child : node.getChildren()) {
printNode(buffer, child, html);
}
}
private void printNode(final StringBuilder buffer, final DomNode node, final boolean html) {
if (node instanceof DomComment) {
// Remove whitespace sequences.
final String s = node.getNodeValue().replaceAll(" ", " ");
buffer.append("");
}
else if (node instanceof DomCharacterData) {
// Remove whitespace sequences, possibly escape XML characters.
String s = node.getNodeValue().replaceAll(" ", " ");
if (html) {
s = com.gargoylesoftware.htmlunit.util.StringUtils.escapeXmlChars(s);
}
buffer.append(s);
}
else if (html) {
// Start the tag name. IE does it in uppercase, FF in lowercase.
final HtmlElement element = (HtmlElement) node;
final boolean ie = getBrowserVersion().isIE();
String tag = element.getTagName();
if (ie) {
tag = tag.toUpperCase();
}
buffer.append("<").append(tag);
// Add the attributes. IE does not use quotes, FF does.
for (final DomAttr attr : element.getAttributesCollection()) {
final String name = attr.getName();
final String value = attr.getValue().replaceAll("\"", """);
final boolean quote = !ie || com.gargoylesoftware.htmlunit.util.StringUtils.containsWhitespace(value);
buffer.append(' ').append(name).append("=");
if (quote) {
buffer.append("\"");
}
buffer.append(value);
if (quote) {
buffer.append("\"");
}
}
buffer.append(">");
// Add the children.
printChildren(buffer, node, html);
// Close the tag. IE does it in uppercase, FF in lowercase.
buffer.append("").append(tag).append(">");
}
else {
final HtmlElement element = (HtmlElement) node;
if (element.getTagName().equals("p")) {
buffer.append("\r\n"); // \r\n because it's to implement something IE specific
}
if (!element.getTagName().equals("script")) {
printChildren(buffer, node, html);
}
}
}
/**
* Replace all children elements of this element with the supplied value.
* @param value - the new value for the contents of this node
*/
public void jsxSet_innerHTML(final Object value) {
final DomNode domNode = getDomNodeOrDie();
domNode.removeAllChildren();
final BrowserVersion browserVersion = getBrowserVersion();
// null && IE -> add child
// null && non-IE -> Don't add
// '' -> Don't add
if ((value == null && browserVersion.isIE())
|| (value != null && !"".equals(value))) {
final String valueAsString = Context.toString(value);
parseHtmlSnippet(domNode, true, valueAsString);
//if the parentNode has null parentNode in IE,
//create a DocumentFragment to be the parentNode's parentNode.
if (domNode.getParentNode() == null
&& getWindow().getWebWindow().getWebClient().getBrowserVersion().isIE()) {
final DomDocumentFragment fragment = ((HtmlPage) domNode.getPage()).createDomDocumentFragment();
fragment.appendChild(domNode);
}
}
}
/**
* Replace all children elements of this element with the supplied value.
* @param value - the new value for the contents of this node
*/
public void jsxSet_innerText(final String value) {
final DomNode domNode = getDomNodeOrDie();
domNode.removeAllChildren();
final DomNode node = new DomText(getDomNodeOrDie().getPage(), value);
domNode.appendChild(node);
//if the parentNode has null parentNode in IE,
//create a DocumentFragment to be the parentNode's parentNode.
if (domNode.getParentNode() == null && getBrowserVersion().isIE()) {
final DomDocumentFragment fragment = ((HtmlPage) domNode.getPage()).createDomDocumentFragment();
fragment.appendChild(domNode);
}
}
/**
* Replace all children elements of this element with the supplied value.
* @param value - the new value for the contents of this node
*/
public void jsxSet_textContent(final String value) {
jsxSet_innerText(value);
}
/**
* Replace all children elements of this element with the supplied value.
* Sets the outerHTML of the node.
* @see
* MSDN documentation
* @param value - the new value for replacing this node
*/
public void jsxSet_outerHTML(final String value) {
final DomNode domNode = getDomNodeOrDie();
if (OUTER_HTML_READONLY.contains(domNode.getNodeName())) {
throw Context.reportRuntimeError("outerHTML is read-only for tag " + domNode.getNodeName());
}
parseHtmlSnippet(domNode, false, value);
domNode.remove();
}
/**
* Parses the specified HTML source code, appending the resultant content at the specified target location.
* @param target the node indicating the position at which the parsed content should be placed
* @param append if true, append the parsed content as a child of the specified target;
* if false, append the parsed content as the previous sibling of the specified target
* @param source the HTML code extract to parse
*/
static void parseHtmlSnippet(final DomNode target, final boolean append, final String source) {
final HtmlPage page = (HtmlPage) target.getPage();
final DomNode proxyNode = new HtmlDivision(null, HtmlDivision.TAG_NAME, page, null) {
private static final long serialVersionUID = 2108037256628269797L;
@Override
public DomNode appendChild(final org.w3c.dom.Node node) {
final DomNode domNode = (DomNode) node;
if (append) {
return target.appendChild(domNode);
}
target.insertBefore(domNode);
return domNode;
}
};
try {
HTMLParser.parseFragment(proxyNode, source);
}
catch (final IOException e) {
LogFactory.getLog(HtmlElement.class).error("Unexpected exception occurred while parsing HTML snippet", e);
throw Context.reportRuntimeError("Unexpected exception occurred while parsing HTML snippet: "
+ e.getMessage());
}
catch (final SAXException e) {
LogFactory.getLog(HtmlElement.class).error("Unexpected exception occurred while parsing HTML snippet", e);
throw Context.reportRuntimeError("Unexpected exception occurred while parsing HTML snippet: "
+ e.getMessage());
}
}
/**
* Gets the attributes of the element in the form of a {@link org.xml.sax.Attributes}.
* @param element the element to read the attributes from
* @return the attributes
*/
protected AttributesImpl readAttributes(final HtmlElement element) {
final AttributesImpl attributes = new AttributesImpl();
for (final DomAttr entry : element.getAttributesCollection()) {
final String name = entry.getName();
final String value = entry.getValue();
attributes.addAttribute(null, name, name, null, value);
}
return attributes;
}
/**
* Inserts the given HTML text into the element at the location.
* @see
* MSDN documentation
* @param where specifies where to insert the HTML text, using one of the following value:
* beforeBegin, afterBegin, beforeEnd, afterEnd
* @param text the HTML text to insert
*/
public void jsxFunction_insertAdjacentHTML(final String where, final String text) {
final Object[] values = getInsertAdjacentLocation(where);
final DomNode node = (DomNode) values[0];
final boolean append = ((Boolean) values[1]).booleanValue();
// add the new nodes
parseHtmlSnippet(node, append, text);
}
/**
* Inserts the given element into the element at the location.
* @see
* MSDN documentation
* @param where specifies where to insert the element, using one of the following value:
* beforeBegin, afterBegin, beforeEnd, afterEnd
* @param object the element to insert
* @return an element object
*/
public Object jsxFunction_insertAdjacentElement(final String where, final Object object) {
if (object instanceof Node) {
final DomNode childNode = ((Node) object).getDomNodeOrDie();
final Object[] values = getInsertAdjacentLocation(where);
final DomNode node = (DomNode) values[0];
final boolean append = ((Boolean) values[1]).booleanValue();
if (append) {
node.appendChild(childNode);
}
else {
node.insertBefore(childNode);
}
return object;
}
throw Context.reportRuntimeError("Passed object is not an element: " + object);
}
/**
* Returns where and how to add the new node.
* Used by {@link #jsxFunction_insertAdjacentHTML(String, String)} and
* {@link #jsxFunction_insertAdjacentElement(String, Object)}.
*
* @param where specifies where to insert the element, using one of the following value:
* beforeBegin, afterBegin, beforeEnd, afterEnd
*
* @return an array of 1-DomNode:parentNode and 2-Boolean:append
*/
private Object[] getInsertAdjacentLocation(final String where) {
final DomNode currentNode = getDomNodeOrDie();
final DomNode node;
final boolean append;
// compute the where and how the new nodes should be added
if (POSITION_AFTER_BEGIN.equalsIgnoreCase(where)) {
if (currentNode.getFirstChild() == null) {
// new nodes should appended to the children of current node
node = currentNode;
append = true;
}
else {
// new nodes should be inserted before first child
node = currentNode.getFirstChild();
append = false;
}
}
else if (POSITION_BEFORE_BEGIN.equalsIgnoreCase(where)) {
// new nodes should be inserted before current node
node = currentNode;
append = false;
}
else if (POSITION_BEFORE_END.equalsIgnoreCase(where)) {
// new nodes should appended to the children of current node
node = currentNode;
append = true;
}
else if (POSITION_AFTER_END.equalsIgnoreCase(where)) {
if (currentNode.getNextSibling() == null) {
// new nodes should appended to the children of parent node
node = currentNode.getParentNode();
append = true;
}
else {
// new nodes should be inserted before current node's next sibling
node = currentNode.getNextSibling();
append = false;
}
}
else {
throw Context.reportRuntimeError("Illegal position value: \"" + where + "\"");
}
if (append) {
return new Object[] {node, Boolean.TRUE};
}
return new Object[] {node, Boolean.FALSE};
}
/**
* Adds the specified behavior to this HTML element. Currently only supports
* the following default IE behaviors:
* false
*/
public boolean doComponentRequest() {
return false;
}
/**
* Returns the version of the specified component.
* @param id the identifier for the component whose version is to be returned
* @param idType the type of identifier specified
* @return the version of the specified component
*/
public String getComponentVersion(final String id, final String idType) {
if ("{E5D12C4E-7B4F-11D3-B5C9-0050045C3C96}".equals(id)) {
// Yahoo Messenger.
return "";
}
// Everything else.
return "1.0";
}
/**
* Returns true if the specified component is installed.
* @param id the identifier for the component to check for
* @param idType the type of id specified
* @param minVersion the minimum version to check for
* @return true if the specified component is installed
*/
public boolean isComponentInstalled(final String id, final String idType, final String minVersion) {
return false;
}
//----------------------- START #default#download BEHAVIOR -----------------------
/**
* Implementation of the IE behavior #default#download.
* @param uri the URI of the download source
* @param callback the method which should be called when the download is finished
* @see
* MSDN documentation
* @throws MalformedURLException if the URL cannot be created
*/
public void startDownload(final String uri, final Function callback) throws MalformedURLException {
final HtmlPage page = (HtmlPage) getWindow().getWebWindow().getEnclosedPage();
final URL url = page.getFullyQualifiedUrl(uri);
if (!page.getWebResponse().getUrl().getHost().equals(url.getHost())) {
throw Context.reportRuntimeError("Not authorized url: " + url);
}
final Thread t = new DownloadBehaviorDownloader(url, callback);
getLog().debug("Starting download thread for " + url);
t.start();
}
/**
* A helper class for the IE behavior #default#download
* This represents a download action. The download is handled
* asynchronously, when the download is finished, the method specified
* by callback is called with one argument - the content of the response as string.
* @see #startDownload(String, Function)
* @author Stefan Anzinger
*/
private final class DownloadBehaviorDownloader extends Thread {
private final URL url_;
private final Function callback_;
/**
* @param url the URL to download
* @param callback the function to callback
*/
private DownloadBehaviorDownloader(final URL url, final Function callback) {
super("Downloader for behavior #default#download '" + url + "'");
url_ = url;
callback_ = callback;
}
/**
* Performs the download and calls the callback method.
*/
@Override
public void run() {
final WebClient wc = getWindow().getWebWindow().getWebClient();
final Scriptable scope = callback_.getParentScope();
final WebRequestSettings settings = new WebRequestSettings(url_);
try {
final WebResponse webResponse = wc.loadWebResponse(settings);
final String content = webResponse.getContentAsString();
getLog().debug("Downloaded content: " + StringUtils.abbreviate(content, 512));
final Object[] args = new Object[] {content};
final ContextAction action = new ContextAction() {
public Object run(final Context cx) {
callback_.call(cx, scope, scope, args);
return null;
}
};
ContextFactory.getGlobal().call(action);
}
catch (final IOException e) {
getLog().error("Behavior #default#download: Cannot download " + url_, e);
}
}
}
//----------------------- END #default#download BEHAVIOR -----------------------
//----------------------- START #default#homePage BEHAVIOR -----------------------
/**
* Returns true if the specified URL is the web client's current
* homepage and the document calling the method is on the same domain as the
* user's homepage. Part of the #default#homePage default IE behavior
* implementation.
* @param url the URL to check
* @return true if the specified URL is the current homepage
*/
public boolean isHomePage(final String url) {
try {
final URL newUrl = new URL(url);
final URL currentUrl = getDomNodeOrDie().getPage().getWebResponse().getUrl();
final String home = getDomNodeOrDie().getPage().getEnclosingWindow().getWebClient().getHomePage();
final boolean sameDomains = newUrl.getHost().equalsIgnoreCase(currentUrl.getHost());
final boolean isHomePage = (home != null && home.equals(url));
return (sameDomains && isHomePage);
}
catch (final MalformedURLException e) {
return false;
}
}
/**
* Sets the web client's current homepage. Part of the #default#homePage
* default IE behavior implementation.
* @param url the new homepage URL
*/
public void setHomePage(final String url) {
getDomNodeOrDie().getPage().getEnclosingWindow().getWebClient().setHomePage(url);
}
/**
* Causes the web client to navigate to the current home page. Part of the
* #default#homePage default IE behavior implementation.
* @throws IOException if loading home page fails
*/
public void navigateHomePage() throws IOException {
final WebClient webClient = getDomNodeOrDie().getPage().getEnclosingWindow().getWebClient();
webClient.getPage(webClient.getHomePage());
}
//----------------------- END #default#homePage BEHAVIOR -----------------------
/**
* Gets the children of the current node.
* @see
* MSDN documentation
* @return the child at the given position
*/
public Object jsxGet_children() {
final HTMLCollection children = new HTMLCollection(this);
children.init(getDomNodeOrDie(), "./*");
return children;
}
/**
* Returns this element's offsetHeight, which is the element height plus the element's padding
* plus the element's border. This method returns a dummy value compatible with mouse event coordinates
* during mouse events.
* @return this element's offsetHeight
* @see MSDN Documentation
* @see Element Dimensions
*/
public int jsxGet_offsetHeight() {
final MouseEvent event = MouseEvent.getCurrentMouseEvent();
if (isAncestorOfEventTarget(event)) {
// compute appropriate offsetHeight to make as if mouse event produced within this element
return event.jsxGet_clientY() - getPosY() + 50;
}
return jsxGet_currentStyle().getCalculatedHeight(true, true);
}
/**
* Returns this element's offsetWidth, which is the element width plus the element's padding
* plus the element's border. This method returns a dummy value compatible with mouse event coordinates
* during mouse events.
* @return this element's offsetWidth
* @see MSDN Documentation
* @see Element Dimensions
*/
public int jsxGet_offsetWidth() {
final MouseEvent event = MouseEvent.getCurrentMouseEvent();
if (isAncestorOfEventTarget(event)) {
// compute appropriate offsetwidth to make as if mouse event produced within this element
return event.jsxGet_clientX() - getPosX() + 50;
}
return jsxGet_currentStyle().getCalculatedWidth(true, true);
}
/**
* Returns true if this element's node is an ancestor of the specified event's target node.
* @param event the event whose target node is to be checked
* @return true if this element's node is an ancestor of the specified event's target node
*/
private boolean isAncestorOfEventTarget(final MouseEvent event) {
if (event == null) {
return false;
}
final HTMLElement target = (HTMLElement) event.jsxGet_target();
return getHtmlElementOrDie().isAncestorOf(target.getHtmlElementOrDie());
}
/**
* Returns this element's X position.
* @return this element's X position
*/
int getPosX() {
int cumulativeOffset = 0;
HTMLElement element = this;
while (element != null) {
cumulativeOffset += element.jsxGet_offsetLeft();
element = element.jsxGet_offsetParent();
}
return cumulativeOffset;
}
/**
* Returns this element's Y position.
* @return this element's Y position
*/
int getPosY() {
int cumulativeOffset = 0;
HTMLElement element = this;
while (element != null) {
cumulativeOffset += element.jsxGet_offsetTop();
element = element.jsxGet_offsetParent();
}
return cumulativeOffset;
}
/**
* Returns this element's offsetLeft, which is the calculated left position of this
* element relative to the offsetParent.
*
* @return this element's offsetLeft
* @see MSDN Documentation
* @see Element Dimensions
* @see Reverse Engineering by Anne van Kesteren
*/
public int jsxGet_offsetLeft() {
if (this instanceof HTMLBodyElement) {
return 0;
}
int left = 0;
final HTMLElement offsetParent = jsxGet_offsetParent();
// Add the offset for this node.
DomNode node = getDomNodeOrDie();
HTMLElement element = (HTMLElement) node.getScriptObject();
left += element.jsxGet_currentStyle().getLeft(true, false, false);
// If this node is absolutely positioned, we're done.
final String position = element.jsxGet_currentStyle().jsxGet_position();
if ("absolute".equals(position)) {
return left;
}
// Add the offset for the ancestor nodes.
node = node.getParentNode();
while (node != null && node.getScriptObject() != offsetParent) {
if (node.getScriptObject() instanceof HTMLElement) {
element = (HTMLElement) node.getScriptObject();
left += element.jsxGet_currentStyle().getLeft(true, true, true);
}
node = node.getParentNode();
}
// Add the offset for the final ancestor node (the offset parent).
if (node != null && node.getScriptObject() instanceof HTMLElement) {
left += offsetParent.jsxGet_currentStyle().getLeft(true, false, true);
}
return left;
}
/**
* Returns this element's offsetTop, which is the calculated top position of this
* element relative to the offsetParent.
*
* @return this element's offsetTop
* @see MSDN Documentation
* @see Element Dimensions
* @see Reverse Engineering by Anne van Kesteren
*/
public int jsxGet_offsetTop() {
if (this instanceof HTMLBodyElement) {
return 0;
}
int top = 0;
final HTMLElement offsetParent = jsxGet_offsetParent();
// Add the offset for this node.
DomNode node = getDomNodeOrDie();
HTMLElement element = (HTMLElement) node.getScriptObject();
top += element.jsxGet_currentStyle().getTop(true, false, false);
// If this node is absolutely positioned, we're done.
final String position = element.jsxGet_currentStyle().jsxGet_position();
if ("absolute".equals(position)) {
return top;
}
// Add the offset for the ancestor nodes.
node = node.getParentNode();
while (node != null && node.getScriptObject() != offsetParent) {
if (node.getScriptObject() instanceof HTMLElement) {
element = (HTMLElement) node.getScriptObject();
top += element.jsxGet_currentStyle().getTop(false, true, true);
}
node = node.getParentNode();
}
// Add the offset for the final ancestor node (the offset parent).
if (node != null && node.getScriptObject() instanceof HTMLElement) {
top += offsetParent.jsxGet_currentStyle().getTop(false, false, true);
}
return top;
}
/**
* Returns this element's offsetParent. The offsetLeft and
* offsetTop attributes are relative to the offsetParent.
*
* @return this element's offsetParent
* @see MSDN Documentation
* @see Gecko DOM Reference
* @see Element Dimensions
* @see Box Model
* @see Reverse Engineering by Anne van Kesteren
*/
public HTMLElement jsxGet_offsetParent() {
HTMLElement offsetParent = null;
DomNode currentElement = getHtmlElementOrDie();
final HTMLElement htmlElement = (HTMLElement) currentElement.getScriptObject();
final ComputedCSSStyleDeclaration style = htmlElement.jsxGet_currentStyle();
final String position = style.jsxGet_position();
final boolean ie = getBrowserVersion().isIE();
final boolean staticPos = "static".equals(position);
final boolean fixedPos = "fixed".equals(position);
final boolean useTables = ((ie && (staticPos || fixedPos)) || (!ie && staticPos));
while (currentElement != null) {
final DomNode parentNode = currentElement.getParentNode();
if (parentNode instanceof HtmlBody
|| (useTables && parentNode instanceof HtmlTableDataCell)
|| (useTables && parentNode instanceof HtmlTable)) {
offsetParent = (HTMLElement) parentNode.getScriptObject();
break;
}
if (parentNode != null && parentNode.getScriptObject() instanceof HTMLElement) {
final HTMLElement parentElement = (HTMLElement) parentNode.getScriptObject();
final ComputedCSSStyleDeclaration parentStyle = parentElement.jsxGet_currentStyle();
final String parentPosition = parentStyle.jsxGet_position();
final boolean parentIsStatic = "static".equals(parentPosition);
final boolean parentIsFixed = "fixed".equals(parentPosition);
if ((ie && !parentIsStatic && !parentIsFixed) || (!ie && !parentIsStatic)) {
offsetParent = (HTMLElement) parentNode.getScriptObject();
break;
}
}
currentElement = currentElement.getParentNode();
}
return offsetParent;
}
/**
* {@inheritDoc}
*/
@Override
public String toString() {
return "HTMLElement for " + getHtmlElementOrNull();
}
/**
* Gets the scrollTop for this element.
* @return a dummy value (default is 0)
* @see
* MSDN documentation
*/
public int jsxGet_scrollTop() {
return scrollTop_;
}
/**
* Sets the scrollTop for this element.
* @param scroll the new value
*/
public void jsxSet_scrollTop(final int scroll) {
scrollTop_ = scroll;
}
/**
* Gets the scrollLeft for this element.
* @return a dummy value (default is 0)
* @see
* MSDN documentation
*/
public int jsxGet_scrollLeft() {
return scrollLeft_;
}
/**
* Sets the scrollLeft for this element.
* @param scroll the new value
*/
public void jsxSet_scrollLeft(final int scroll) {
scrollLeft_ = scroll;
}
/**
* Gets the scrollHeight for this element.
* @return a dummy value of 10
* @see
* MSDN documentation
*/
public int jsxGet_scrollHeight() {
return 10;
}
/**
* Gets the scrollWidth for this element.
* @return a dummy value of 10
* @see
* MSDN documentation
*/
public int jsxGet_scrollWidth() {
return 10;
}
/**
* Gets the JavaScript property "parentElement".
* It is identical to {@link #jsxGet_parentNode()} * with the exception of HTML, which has a null parent element. * @return the parent element * @see #jsxGet_parentNode() */ public Object jsxGet_parentElement() { if ("html".equalsIgnoreCase(getDomNodeOrDie().getNodeName())) { return null; } return jsxGet_parentNode(); } /** * Implement the scrollIntoView() JavaScript function but don't actually do * anything. The requirement * is just to prevent scripts that call that method from failing */ public void jsxFunction_scrollIntoView() { } /** * Retrieves an object that specifies the bounds of a collection of TextRectangle objects. * @return an object that specifies the bounds of a collection of TextRectangle objects */ public TextRectangle jsxFunction_getBoundingClientRect() { final TextRectangle textRectangle = new TextRectangle(); textRectangle.setParentScope(getWindow()); textRectangle.setPrototype(getPrototype(textRectangle.getClass())); return textRectangle; } /** * 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 */ public Object jsxFunction_getClientRects() { return new NativeArray(0); } /** * Sets an expression for the specified HTMLElement. * * @param propertyName Specifies the name of the property to which expression is added * @param expression specifies any valid script statement without quotations or semicolons * This string can include references to other properties on the current page. * Array references are not allowed on object properties included in this script. * @param language specified the language used */ public void jsxFunction_setExpression(final String propertyName, final String expression, final String language) { // Empty. } /** * Removes the expression from the specified property. * * @param propertyName Specifies the name of the property from which to remove an expression * @return true if the expression was successfully removed */ public boolean jsxFunction_removeExpression(final String propertyName) { return true; } /** * Retrieves an auto-generated, unique identifier for the object. * Note The unique ID generated is not guaranteed to be the same every time the page is loaded. * @return an auto-generated, unique identifier for the object */ public String jsxGet_uniqueID() { if (uniqueID_ == null) { uniqueID_ = "ms__id" + UniqueID_Counter_++; } return uniqueID_; } /** * Dispatches an event into the event system (standards-conformant browsers only). See * the Gecko * DOM reference for more information. * * @param event the event to be dispatched * @return false if at least one of the event handlers which handled the event * called preventDefault; true otherwise */ public boolean jsxFunction_dispatchEvent(final Event event) { event.setTarget(this); final HtmlElement element = getHtmlElementOrDie(); if (event instanceof MouseEvent && element instanceof ClickableElement) { if (event.jsxGet_type().equals(MouseEvent.TYPE_CLICK)) { try { ((ClickableElement) element).click(event); } catch (final IOException e) { throw Context.reportRuntimeError("Error calling click(): " + e.getMessage()); } } else if (event.jsxGet_type().equals(MouseEvent.TYPE_DBL_CLICK)) { try { ((ClickableElement) element).dblClick(event.jsxGet_shiftKey(), event.jsxGet_ctrlKey(), event.jsxGet_altKey()); } catch (final IOException e) { throw Context.reportRuntimeError("Error calling dblClick(): " + e.getMessage()); } } else { fireEvent(event); } } else { fireEvent(event); } return !event.isPreventDefault(); } /** * Returns the HTML element that corresponds to this JavaScript object or throw an exception * if one cannot be found. * @return the HTML element * @exception IllegalStateException If the HTML element could not be found. */ public final HtmlElement getHtmlElementOrDie() throws IllegalStateException { return (HtmlElement) getDomNodeOrDie(); } /** * Returns the HTML element that corresponds to this JavaScript object * or null if an element hasn't been set. * @return the HTML element or null */ public final HtmlElement getHtmlElementOrNull() { return (HtmlElement) getDomNodeOrNull(); } /** * Remove focus from this element. */ public void jsxFunction_blur() { final HtmlElement element = (HtmlElement) getDomNodeOrDie(); element.blur(); } /** * Sets the focus to this element. */ public void jsxFunction_focus() { final HtmlElement element = (HtmlElement) getDomNodeOrDie(); element.focus(); } /** * Sets the object as active without setting focus to the object. * @see MSDN documentation */ public void jsxFunction_setActive() { final Window window = getWindow(); final HTMLDocument document = window.jsxGet_document(); document.setActiveElement(this); if (window.getWebWindow() == window.getWebWindow().getWebClient().getCurrentWindow()) { final HtmlElement element = (HtmlElement) getDomNodeOrDie(); ((HtmlPage) element.getPage()).setFocusedElement(element); } } }