Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/*
* Copyright (c) 2002-2015 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
* http://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 com.gargoylesoftware.htmlunit.javascript.host.html;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.DOCTYPE_4_0_TRANSITIONAL_STANDARDS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_DOM_LEVEL_2;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_DOM_LEVEL_3;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_TYPE_BEFOREUNLOADEVENT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_TYPE_EVENTS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_TYPE_HASHCHANGEEVENT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_TYPE_KEY_EVENTS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EVENT_TYPE_POINTEREVENT;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.EXECCOMMAND_THROWS_ON_WRONG_COMMAND;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLCOLLECTION_OBJECT_DETECTION;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_CHARSET_LOWERCASE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_CHARSET_NORMALIZED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_COLOR;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_FUNCTION_DETACHED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_GET_ALSO_FRAMES;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_GET_FOR_ID_AND_OR_NAME;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_GET_FOR_NAME;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_GET_PREFERS_STANDARD_FUNCTIONS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_METHOD_AS_VARIABLE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTML_COLOR_EXPAND_ZERO;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_ANCHORS_REQUIRES_NAME_OR_ID;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_APPEND_CHILD_SUPPORTED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_CREATE_ELEMENT_EXTENDED_SYNTAX;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_DOCTYPE_NULL;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_DOMAIN_IS_LOWERCASE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_ELEMENT_FROM_POINT_NULL_WHEN_OUTSIDE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_FORMS_FUNCTION_SUPPORTED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_SETTING_DOMAIN_THROWS_FOR_ABOUT_BLANK;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_FRAME_BODY_NULL_IF_NOT_LOADED;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_GET_ELEMENTS_BY_NAME_EMPTY_RETURNS_NOTHING;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_GET_ELEMENT_BY_ID_ALSO_BY_NAME_IN_QUICKS_MODE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_GET_ELEMENT_BY_ID_CASE_SENSITIVE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_TREEWALKER_EXPAND_ENTITY_REFERENCES_FALSE;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_TREEWALKER_FILTER_FUNCTION_ONLY;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.QUERYSELECTORALL_NOT_IN_QUIRKS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.QUIRKS_MODE_ALWAYS_DOC_MODE_5;
import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.BrowserName.IE;
import static com.gargoylesoftware.htmlunit.util.StringUtils.parseHttpDate;
import java.io.IOException;
import java.io.StringReader;
import java.net.URL;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import net.sourceforge.htmlunit.corejs.javascript.Callable;
import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.FunctionObject;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;
import net.sourceforge.htmlunit.corejs.javascript.Undefined;
import net.sourceforge.htmlunit.corejs.javascript.UniqueTag;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.w3c.css.sac.CSSException;
import org.w3c.css.sac.InputSource;
import org.w3c.dom.DOMException;
import org.w3c.dom.DocumentType;
import com.gargoylesoftware.htmlunit.BrowserVersion;
import com.gargoylesoftware.htmlunit.ElementNotFoundException;
import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.StringWebResponse;
import com.gargoylesoftware.htmlunit.WebClient;
import com.gargoylesoftware.htmlunit.WebResponse;
import com.gargoylesoftware.htmlunit.WebWindow;
import com.gargoylesoftware.htmlunit.html.BaseFrameElement;
import com.gargoylesoftware.htmlunit.html.DomElement;
import com.gargoylesoftware.htmlunit.html.DomNode;
import com.gargoylesoftware.htmlunit.html.FrameWindow;
import com.gargoylesoftware.htmlunit.html.HtmlAnchor;
import com.gargoylesoftware.htmlunit.html.HtmlApplet;
import com.gargoylesoftware.htmlunit.html.HtmlArea;
import com.gargoylesoftware.htmlunit.html.HtmlAttributeChangeEvent;
import com.gargoylesoftware.htmlunit.html.HtmlElement;
import com.gargoylesoftware.htmlunit.html.HtmlForm;
import com.gargoylesoftware.htmlunit.html.HtmlImage;
import com.gargoylesoftware.htmlunit.html.HtmlPage;
import com.gargoylesoftware.htmlunit.html.HtmlScript;
import com.gargoylesoftware.htmlunit.httpclient.HtmlUnitBrowserCompatCookieSpec;
import com.gargoylesoftware.htmlunit.javascript.PostponedAction;
import com.gargoylesoftware.htmlunit.javascript.ScriptableWithFallbackGetter;
import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.configuration.CanSetReadOnly;
import com.gargoylesoftware.htmlunit.javascript.configuration.CanSetReadOnlyStatus;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClasses;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxConstructor;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxFunction;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxGetter;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxSetter;
import com.gargoylesoftware.htmlunit.javascript.configuration.WebBrowser;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import com.gargoylesoftware.htmlunit.javascript.host.KeyboardEvent;
import com.gargoylesoftware.htmlunit.javascript.host.MouseEvent;
import com.gargoylesoftware.htmlunit.javascript.host.NamespaceCollection;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
import com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet;
import com.gargoylesoftware.htmlunit.javascript.host.css.StyleSheetList;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Document;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Node;
import com.gargoylesoftware.htmlunit.javascript.host.dom.NodeFilter;
import com.gargoylesoftware.htmlunit.javascript.host.dom.NodeIterator;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Range;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Selection;
import com.gargoylesoftware.htmlunit.javascript.host.dom.StaticNodeList;
import com.gargoylesoftware.htmlunit.javascript.host.dom.TreeWalker;
import com.gargoylesoftware.htmlunit.javascript.host.event.BeforeUnloadEvent;
import com.gargoylesoftware.htmlunit.javascript.host.event.CustomEvent;
import com.gargoylesoftware.htmlunit.javascript.host.event.HashChangeEvent;
import com.gargoylesoftware.htmlunit.javascript.host.event.MessageEvent;
import com.gargoylesoftware.htmlunit.javascript.host.event.MutationEvent;
import com.gargoylesoftware.htmlunit.javascript.host.event.PointerEvent;
import com.gargoylesoftware.htmlunit.javascript.host.event.UIEvent;
import com.gargoylesoftware.htmlunit.util.Cookie;
import com.gargoylesoftware.htmlunit.util.EncodingSniffer;
/**
* A JavaScript object for a Document.
*
* @version $Revision: 10589 $
* @author Mike Bowler
* @author David K. Taylor
* @author Chen Jun
* @author Christian Sell
* @author Chris Erskine
* @author Marc Guillemot
* @author Daniel Gredler
* @author Michael Ottati
* @author George Murnock
* @author Ahmed Ashour
* @author Rob Di Marco
* @author Sudhan Moghe
* @author Mike Dirolf
* @author Ronald Brill
* @author Frank Danek
* @see MSDN documentation
* @see
* W3C DOM Level 1
*/
@JsxClasses({
@JsxClass(browsers = { @WebBrowser(CHROME), @WebBrowser(FF), @WebBrowser(value = IE, minVersion = 11) }),
@JsxClass(isJSObject = false, browsers = { @WebBrowser(value = IE, maxVersion = 8) })
})
public class HTMLDocument extends Document implements ScriptableWithFallbackGetter {
private static final Log LOG = LogFactory.getLog(HTMLDocument.class);
/** The format to use for the lastModified attribute. */
private static final String LAST_MODIFIED_DATE_FORMAT = "MM/dd/yyyy HH:mm:ss";
private static final Pattern FIRST_TAG_PATTERN = Pattern.compile("<(\\w+)(\\s+[^>]*)?>");
private static final Pattern ATTRIBUTES_PATTERN = Pattern.compile("(\\w+)\\s*=\\s*['\"]([^'\"]*)['\"]");
/**
* Map which maps strings a caller may use when calling into
* {@link #createEvent(String)} to the associated event class. To support a new
* event creation type, the event type and associated class need to be added into this map in
* the static initializer. The map is unmodifiable. Any class that is a value in this map MUST
* have a no-arg constructor.
*/
/** Contains all supported DOM level 2 events. */
private static final Map> SUPPORTED_DOM2_EVENT_TYPE_MAP;
/** Contains all supported DOM level 3 events. DOM level 2 events are not included. */
private static final Map> SUPPORTED_DOM3_EVENT_TYPE_MAP;
/** Contains all supported vendor specific events. */
private static final Map> SUPPORTED_VENDOR_EVENT_TYPE_MAP;
// all as lowercase for performance
private static final Set EXECUTE_CMDS_IE = new HashSet<>();
/** https://developer.mozilla.org/en/Rich-Text_Editing_in_Mozilla#Executing_Commands */
private static final Set EXECUTE_CMDS_FF = new HashSet<>();
private static final Set EXECUTE_CMDS_CHROME = new HashSet<>();
/**
* Static counter for {@link #uniqueID_}.
*/
private static int UniqueID_Counter_ = 1;
private static enum ParsingStatus { OUTSIDE, START, IN_NAME, INSIDE, IN_STRING }
private HTMLCollection all_; // has to be a member to have equality (==) working
private HTMLCollection forms_; // has to be a member to have equality (==) working
private HTMLCollection links_; // has to be a member to have equality (==) working
private HTMLCollection images_; // has to be a member to have equality (==) working
private HTMLCollection scripts_; // has to be a member to have equality (==) working
private HTMLCollection anchors_; // has to be a member to have equality (==) working
private HTMLCollection applets_; // has to be a member to have equality (==) working
private StyleSheetList styleSheets_; // has to be a member to have equality (==) working
private NamespaceCollection namespaces_; // has to be a member to have equality (==) working
private HTMLElement activeElement_;
/** The buffer that will be used for calls to document.write(). */
private final StringBuilder writeBuffer_ = new StringBuilder();
private boolean writeInCurrentDocument_ = true;
private String domain_;
private String uniqueID_;
private String lastModified_;
private String compatMode_;
private int documentMode_ = -1;
private boolean closePostponedAction_;
/** Initializes the supported event type map. */
static {
final Map> dom2EventMap = new HashMap<>();
dom2EventMap.put("HTMLEvents", Event.class);
dom2EventMap.put("MouseEvents", MouseEvent.class);
dom2EventMap.put("MutationEvents", MutationEvent.class);
dom2EventMap.put("UIEvents", UIEvent.class);
SUPPORTED_DOM2_EVENT_TYPE_MAP = Collections.unmodifiableMap(dom2EventMap);
final Map> dom3EventMap = new HashMap<>();
dom3EventMap.put("Event", Event.class);
dom3EventMap.put("KeyboardEvent", KeyboardEvent.class);
dom3EventMap.put("MouseEvent", MouseEvent.class);
dom3EventMap.put("MessageEvent", MessageEvent.class);
dom3EventMap.put("MutationEvent", MutationEvent.class);
dom3EventMap.put("UIEvent", UIEvent.class);
dom3EventMap.put("CustomEvent", CustomEvent.class);
SUPPORTED_DOM3_EVENT_TYPE_MAP = Collections.unmodifiableMap(dom3EventMap);
final Map> additionalEventMap = new HashMap<>();
additionalEventMap.put("BeforeUnloadEvent", BeforeUnloadEvent.class);
additionalEventMap.put("Events", Event.class);
additionalEventMap.put("HashChangeEvent", HashChangeEvent.class);
additionalEventMap.put("KeyEvents", KeyboardEvent.class);
additionalEventMap.put("PointerEvent", PointerEvent.class);
SUPPORTED_VENDOR_EVENT_TYPE_MAP = Collections.unmodifiableMap(additionalEventMap);
// commands
List cmds = Arrays.asList(
"2D-Position", "AbsolutePosition",
"BlockDirLTR", "BlockDirRTL", "BrowseMode",
"ClearAuthenticationCache", "CreateBookmark", "Copy", "Cut",
"DirLTR", "DirRTL",
"EditMode",
"InlineDirLTR", "InlineDirRTL", "InsertButton", "InsertFieldset",
"InsertIFrame", "InsertInputButton", "InsertInputCheckbox",
"InsertInputFileUpload", "InsertInputHidden", "InsertInputImage", "InsertInputPassword", "InsertInputRadio",
"InsertInputReset", "InsertInputSubmit", "InsertInputText", "InsertMarquee",
"InsertSelectDropdown", "InsertSelectListbox", "InsertTextArea",
"LiveResize", "MultipleSelection", "Open",
"OverWrite", "PlayImage",
"Refresh", "RemoveParaFormat", "SaveAs",
"SizeToControl", "SizeToControlHeight", "SizeToControlWidth", "Stop", "StopImage",
"UnBookmark",
"Paste"
);
for (String cmd : cmds) {
EXECUTE_CMDS_IE.add(cmd.toLowerCase(Locale.ENGLISH));
}
cmds = Arrays.asList(
"BackColor", "BackgroundImageCache" /* Undocumented */,
"Bold",
"CreateLink", "Delete",
"FontName", "FontSize", "ForeColor", "FormatBlock",
"Indent", "InsertHorizontalRule", "InsertImage",
"InsertOrderedList", "InsertParagraph", "InsertUnorderedList",
"Italic", "JustifyCenter", "JustifyFull", "JustifyLeft", "JustifyNone",
"JustifyRight",
"Outdent",
"Print",
"Redo", "RemoveFormat",
"SelectAll", "StrikeThrough", "Subscript", "Superscript",
"Underline", "Undo", "Unlink", "Unselect"
);
for (String cmd : cmds) {
EXECUTE_CMDS_IE.add(cmd.toLowerCase(Locale.ENGLISH));
EXECUTE_CMDS_CHROME.add(cmd.toLowerCase(Locale.ENGLISH));
}
cmds = Arrays.asList(
"backColor", "bold", "contentReadOnly", "copy", "createLink", "cut", "decreaseFontSize", "delete",
"fontName", "fontSize", "foreColor", "formatBlock", "heading", "hiliteColor", "increaseFontSize",
"indent", "insertHorizontalRule", "insertHTML", "insertImage", "insertOrderedList", "insertUnorderedList",
"insertParagraph", "italic",
"justifyCenter", "JustifyFull", "justifyLeft", "justifyRight", "outdent", "paste", "redo",
"removeFormat", "selectAll", "strikeThrough", "subscript", "superscript", "underline", "undo", "unlink",
"useCSS", "styleWithCSS"
);
for (String cmd : cmds) {
EXECUTE_CMDS_FF.add(cmd.toLowerCase(Locale.ENGLISH));
}
}
/**
* The constructor.
*/
@JsxConstructor({ @WebBrowser(CHROME), @WebBrowser(FF) })
public HTMLDocument() {
}
/**
* {@inheritDoc}
*/
@Override
@SuppressWarnings("unchecked")
public N getDomNodeOrDie() throws IllegalStateException {
try {
return (N) super.getDomNodeOrDie();
}
catch (final IllegalStateException e) {
final DomNode node = getDomNodeOrNullFromRealDocument();
if (node != null) {
return (N) node;
}
throw Context.reportRuntimeError("No node attached to this object");
}
}
/**
* {@inheritDoc}
*/
@Override
public N getDomNodeOrNull() {
N node = super.getDomNodeOrNull();
if (node == null) {
node = getDomNodeOrNullFromRealDocument();
}
return node;
}
/**
* Document functions invoked on the window end up executing on the document prototype -- and
* this is supposed to work when we're emulating IE! So when {@link #getDomNodeOrDie()} or
* {@link #getDomNodeOrNull()} are invoked on the document prototype (which would usually fail),
* we need to actually return the real document's DOM node so that other functions which rely
* on these two functions work. See {@link HTMLDocumentTest#documentMethodsWithoutDocument()}
* for sample JavaScript code.
*
* @return the real document's DOM node, or null if we're not emulating IE
*/
private N getDomNodeOrNullFromRealDocument() {
N node = null;
// don't use getWindow().getBrowserVersion() here because this is called
// from getBrowserVersion() and results in endless loop
if (getWindow().getWebWindow().getWebClient().getBrowserVersion()
.hasFeature(HTMLDOCUMENT_METHOD_AS_VARIABLE)) {
final Scriptable scope = getParentScope();
if (scope instanceof Window) {
final Window w = (Window) scope;
final Document realDocument = w.getDocument();
if (realDocument != this) {
node = realDocument.getDomNodeOrDie();
}
}
}
return node;
}
/**
* Returns the HTML page that this document is modeling.
* @return the HTML page that this document is modeling
*/
public HtmlPage getHtmlPage() {
return (HtmlPage) getDomNodeOrDie();
}
/**
* Returns the HTML page that this document is modeling, or null if the page is empty.
* @return the HTML page that this document is modeling, or null if the page is empty
*/
public HtmlPage getHtmlPageOrNull() {
return (HtmlPage) getDomNodeOrNull();
}
/**
* Returns the value of the JavaScript attribute "forms".
* @return the value of the JavaScript attribute "forms"
*/
@JsxGetter
public Object getForms() {
if (forms_ == null) {
final boolean allowFunctionCall = getBrowserVersion().hasFeature(JS_DOCUMENT_FORMS_FUNCTION_SUPPORTED);
forms_ = new HTMLCollection(getDomNodeOrDie(), false, "HTMLDocument.forms") {
@Override
protected boolean isMatching(final DomNode node) {
return node instanceof HtmlForm;
}
@Override
public final Object call(final Context cx, final Scriptable scope,
final Scriptable thisObj, final Object[] args) {
if (allowFunctionCall) {
return super.call(cx, scope, thisObj, args);
}
throw Context.reportRuntimeError("TypeError: document.forms is not a function");
}
};
}
return forms_;
}
/**
* Returns the value of the JavaScript attribute "links". Refer also to the
* MSDN documentation.
* @return the value of this attribute
*/
@JsxGetter
public Object getLinks() {
if (links_ == null) {
links_ = new HTMLCollection(getDomNodeOrDie(), true, "HTMLDocument.links") {
@Override
protected boolean isMatching(final DomNode node) {
return (node instanceof HtmlAnchor || node instanceof HtmlArea)
&& ((HtmlElement) node).hasAttribute("href");
}
@Override
protected EffectOnCache getEffectOnCache(final HtmlAttributeChangeEvent event) {
final HtmlElement node = event.getHtmlElement();
if ((node instanceof HtmlAnchor || node instanceof HtmlArea) && "href".equals(event.getName())) {
return EffectOnCache.RESET;
}
return EffectOnCache.NONE;
}
};
}
return links_;
}
/**
* Returns the last modification date of the document.
* @see Mozilla documentation
* @return the date as string
*/
@JsxGetter
public String getLastModified() {
if (lastModified_ == null) {
final WebResponse webResponse = getPage().getWebResponse();
String stringDate = webResponse.getResponseHeaderValue("Last-Modified");
if (stringDate == null) {
stringDate = webResponse.getResponseHeaderValue("Date");
}
final Date lastModified = parseDateOrNow(stringDate);
lastModified_ = new SimpleDateFormat(LAST_MODIFIED_DATE_FORMAT).format(lastModified);
}
return lastModified_;
}
private static Date parseDateOrNow(final String stringDate) {
final Date date = parseHttpDate(stringDate);
if (date == null) {
return new Date();
}
return date;
}
/**
* Returns the value of the JavaScript attribute "namespaces".
* @return the value of the JavaScript attribute "namespaces"
*/
@JsxGetter(@WebBrowser(value = IE, maxVersion = 8))
public Object getNamespaces() {
if (namespaces_ == null) {
namespaces_ = new NamespaceCollection(this);
}
return namespaces_;
}
/**
* Returns the value of the JavaScript attribute "anchors".
* @see MSDN documentation
* @see
* Gecko DOM reference
* @return the value of this attribute
*/
@JsxGetter
public Object getAnchors() {
if (anchors_ == null) {
final boolean checkId = getBrowserVersion().hasFeature(JS_ANCHORS_REQUIRES_NAME_OR_ID);
anchors_ = new HTMLCollection(getDomNodeOrDie(), true, "HTMLDocument.anchors") {
@Override
protected boolean isMatching(final DomNode node) {
if (!(node instanceof HtmlAnchor)) {
return false;
}
final HtmlAnchor anchor = (HtmlAnchor) node;
if (checkId) {
return anchor.hasAttribute("name") || anchor.hasAttribute("id");
}
return anchor.hasAttribute("name");
}
@Override
protected EffectOnCache getEffectOnCache(final HtmlAttributeChangeEvent event) {
final HtmlElement node = event.getHtmlElement();
if (!(node instanceof HtmlAnchor)) {
return EffectOnCache.NONE;
}
if ("name".equals(event.getName()) || "id".equals(event.getName())) {
return EffectOnCache.RESET;
}
return EffectOnCache.NONE;
}
};
}
return anchors_;
}
/**
* Returns the value of the JavaScript attribute "applets".
* @see
* MSDN documentation
* @see
* Gecko DOM reference
* @return the value of this attribute
*/
@JsxGetter
public Object getApplets() {
if (applets_ == null) {
applets_ = new HTMLCollection(getDomNodeOrDie(), false, "HTMLDocument.applets") {
@Override
protected boolean isMatching(final DomNode node) {
return node instanceof HtmlApplet;
}
};
}
return applets_;
}
/**
* JavaScript function "write" may accept a variable number of arguments.
* It's not documented by W3C, Mozilla or MSDN but works with Mozilla and IE.
* @param context the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param function the function
* @see MSDN documentation
*/
@JsxFunction
public static void write(final Context context, final Scriptable thisObj, final Object[] args,
final Function function) {
final HTMLDocument thisAsDocument = getDocument(thisObj);
thisAsDocument.write(concatArgsAsString(args));
}
/**
* Converts the arguments to strings and concatenate them.
* @param args the JavaScript arguments
* @return the string concatenation
*/
private static String concatArgsAsString(final Object[] args) {
final StringBuilder buffer = new StringBuilder();
for (final Object arg : args) {
buffer.append(Context.toString(arg));
}
return buffer.toString();
}
/**
* JavaScript function "writeln" may accept a variable number of arguments.
* It's not documented by W3C, Mozilla or MSDN but works with Mozilla and IE.
* @param context the JavaScript context
* @param thisObj the scriptable
* @param args the arguments passed into the method
* @param function the function
* @see MSDN documentation
*/
@JsxFunction
public static void writeln(
final Context context, final Scriptable thisObj, final Object[] args, final Function function) {
final HTMLDocument thisAsDocument = getDocument(thisObj);
thisAsDocument.write(concatArgsAsString(args) + "\n");
}
/**
* Returns the current document instance, using thisObj as a hint.
* @param thisObj a hint as to the current document (may be the prototype when function is used without "this")
* @return the current document instance
*/
private static HTMLDocument getDocument(final Scriptable thisObj) {
// if function is used "detached", then thisObj is the top scope (ie Window), not the real object
// cf unit test DocumentTest#testDocumentWrite_AssignedToVar
// may be the prototype too
// cf DocumentTest#testDocumentWrite_AssignedToVar2
if (thisObj instanceof HTMLDocument && thisObj.getPrototype() instanceof HTMLDocument) {
return (HTMLDocument) thisObj;
}
if (thisObj instanceof DocumentProxy && thisObj.getPrototype() instanceof HTMLDocument) {
return (HTMLDocument) ((DocumentProxy) thisObj).getDelegee();
}
final Window window = getWindow(thisObj);
if (window.getBrowserVersion().hasFeature(HTMLDOCUMENT_FUNCTION_DETACHED)) {
return (HTMLDocument) window.getDocument();
}
throw Context.reportRuntimeError("Function can't be used detached from document");
}
private boolean executionExternalPostponed_ = false;
/**
* This a hack!!! A cleaner way is welcome.
* Handle a case where document.write is simply ignored.
* See HTMLDocumentWrite2Test.write_fromScriptAddedWithAppendChild_external.
* @param executing indicates if executing or not
*/
public void setExecutingDynamicExternalPosponed(final boolean executing) {
executionExternalPostponed_ = executing;
}
/**
* JavaScript function "write".
*
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/section-dynamic.html for
* a good description of the semantics of open(), write(), writeln() and close().
*
* @param content the content to write
*/
protected void write(final String content) {
// really strange: if called from an external script loaded as postponed action, write is ignored!!!
if (executionExternalPostponed_) {
if (LOG.isDebugEnabled()) {
LOG.debug("skipping write for external posponed: " + content);
}
return;
}
if (LOG.isDebugEnabled()) {
LOG.debug("write: " + content);
}
final HtmlPage page = getDomNodeOrDie();
if (!page.isBeingParsed()) {
writeInCurrentDocument_ = false;
}
// Add content to the content buffer.
writeBuffer_.append(content);
// If open() was called; don't write to doc yet -- wait for call to close().
if (!writeInCurrentDocument_) {
if (LOG.isDebugEnabled()) {
LOG.debug("wrote content to buffer");
}
scheduleImplicitClose();
return;
}
final String bufferedContent = writeBuffer_.toString();
if (!canAlreadyBeParsed(bufferedContent)) {
if (LOG.isDebugEnabled()) {
LOG.debug("write: not enough content to parse it now");
}
return;
}
writeBuffer_.setLength(0);
page.writeInParsedStream(bufferedContent);
}
private void scheduleImplicitClose() {
if (!closePostponedAction_) {
closePostponedAction_ = true;
final HtmlPage page = getDomNodeOrDie();
final WebWindow enclosingWindow = page.getEnclosingWindow();
page.getWebClient().getJavaScriptEngine().addPostponedAction(new PostponedAction(page) {
@Override
public void execute() throws Exception {
if (writeBuffer_.length() > 0) {
close();
}
closePostponedAction_ = false;
}
@Override
public boolean isStillAlive() {
return !enclosingWindow.isClosed();
}
});
}
}
/**
* Indicates if the content is a well formed HTML snippet that can already be parsed to be added to the DOM.
*
* @param content the HTML snippet
* @return false if it not well formed
*/
static boolean canAlreadyBeParsed(final String content) {
// all because the parser doesn't close automatically this tag
// All tags must be complete, that is from '<' to '>'.
ParsingStatus tagState = ParsingStatus.OUTSIDE;
int tagNameBeginIndex = 0;
int scriptTagCount = 0;
boolean tagIsOpen = true;
char stringBoundary = 0;
boolean stringSkipNextChar = false;
int index = 0;
char openingQuote = 0;
for (final char currentChar : content.toCharArray()) {
switch (tagState) {
case OUTSIDE:
if (currentChar == '<') {
tagState = ParsingStatus.START;
tagIsOpen = true;
}
else if (scriptTagCount > 0 && (currentChar == '\'' || currentChar == '"')) {
tagState = ParsingStatus.IN_STRING;
stringBoundary = currentChar;
stringSkipNextChar = false;
}
break;
case START:
if (currentChar == '/') {
tagIsOpen = false;
tagNameBeginIndex = index + 1;
}
else {
tagNameBeginIndex = index;
}
tagState = ParsingStatus.IN_NAME;
break;
case IN_NAME:
if (Character.isWhitespace(currentChar) || currentChar == '>') {
final String tagName = content.substring(tagNameBeginIndex, index);
if ("script".equalsIgnoreCase(tagName)) {
if (tagIsOpen) {
scriptTagCount++;
}
else if (scriptTagCount > 0) {
// Ignore extra close tags for now. Let the parser deal with them.
scriptTagCount--;
}
}
if (currentChar == '>') {
tagState = ParsingStatus.OUTSIDE;
}
else {
tagState = ParsingStatus.INSIDE;
}
}
else if (!Character.isLetter(currentChar)) {
tagState = ParsingStatus.OUTSIDE;
}
break;
case INSIDE:
if (currentChar == openingQuote) {
openingQuote = 0;
}
else if (openingQuote == 0) {
if (currentChar == '\'' || currentChar == '"') {
openingQuote = currentChar;
}
else if (currentChar == '>' && openingQuote == 0) {
tagState = ParsingStatus.OUTSIDE;
}
}
break;
case IN_STRING:
if (stringSkipNextChar) {
stringSkipNextChar = false;
}
else {
if (currentChar == stringBoundary) {
tagState = ParsingStatus.OUTSIDE;
}
else if (currentChar == '\\') {
stringSkipNextChar = true;
}
}
break;
default:
// nothing
}
index++;
}
if (scriptTagCount > 0 || tagState != ParsingStatus.OUTSIDE) {
if (LOG.isDebugEnabled()) {
final StringBuilder message = new StringBuilder();
message.append("canAlreadyBeParsed() retruns false for content: '");
message.append(StringUtils.abbreviateMiddle(content, ".", 100));
message.append("' (scriptTagCount: " + scriptTagCount);
message.append(" tagState: " + tagState);
message.append(")");
LOG.debug(message.toString());
}
return false;
}
return true;
}
/**
* Gets the node that is the last one when exploring following nodes, depth-first.
* @param node the node to search
* @return the searched node
*/
HtmlElement getLastHtmlElement(final HtmlElement node) {
final DomNode lastChild = node.getLastChild();
if (lastChild == null
|| !(lastChild instanceof HtmlElement)
|| lastChild instanceof HtmlScript) {
return node;
}
return getLastHtmlElement((HtmlElement) lastChild);
}
/**
* Returns the cookie attribute.
* @return the cookie attribute
*/
@JsxGetter
public String getCookie() {
final HtmlPage page = getHtmlPage();
final URL url = page.getUrl();
final StringBuilder buffer = new StringBuilder();
final Set cookies = page.getWebClient().getCookies(url);
for (final Cookie cookie : cookies) {
if (cookie.isHttpOnly()) {
continue;
}
if (buffer.length() != 0) {
buffer.append("; ");
}
if (!HtmlUnitBrowserCompatCookieSpec.EMPTY_COOKIE_NAME.equals(cookie.getName())) {
buffer.append(cookie.getName());
buffer.append("=");
}
buffer.append(cookie.getValue());
}
return buffer.toString();
}
/**
* Returns the "compatMode" attribute.
* Note that it is deprecated in Internet Explorer 8 in favor of the documentMode.
* @return the "compatMode" attribute
*/
@JsxGetter
public String getCompatMode() {
// initialize the modes
getDocumentMode();
return compatMode_;
}
/**
* Returns the "documentMode" attribute.
* @return the "documentMode" attribute
*/
@JsxGetter(@WebBrowser(IE))
public int getDocumentMode() {
if (documentMode_ != -1) {
return documentMode_;
}
compatMode_ = "CSS1Compat";
final BrowserVersion browserVersion = getBrowserVersion();
if (isQuirksDocType(browserVersion)) {
compatMode_ = "BackCompat";
if (browserVersion.hasFeature(QUIRKS_MODE_ALWAYS_DOC_MODE_5)) {
documentMode_ = 5;
return documentMode_;
}
}
final float version = browserVersion.getBrowserVersionNumeric();
documentMode_ = (int) Math.floor(version);
return documentMode_;
}
/**
* INTERNAL API - SUBJECT TO CHANGE AT ANY TIME - USE AT YOUR OWN RISK.
*
* Called from the HTMLParser if a 'X-UA-Compatible' meta tag found.
* @param documentMode the mode forced by the meta tag
*/
public void forceDocumentMode(final int documentMode) {
documentMode_ = documentMode;
compatMode_ = documentMode == 5 ? "BackCompat" : "CSS1Compat";
}
private boolean isQuirksDocType(final BrowserVersion browserVersion) {
final DocumentType docType = getHtmlPage().getDoctype();
if (docType != null) {
final String systemId = docType.getSystemId();
if (systemId != null) {
if ("http://www.w3.org/TR/html4/strict.dtd".equals(systemId)) {
return false;
}
if ("http://www.w3.org/TR/html4/loose.dtd".equals(systemId)) {
final String publicId = docType.getPublicId();
if ("-//W3C//DTD HTML 4.01 Transitional//EN".equals(publicId)
|| ("-//W3C//DTD HTML 4.0 Transitional//EN".equals(publicId)
&& browserVersion.hasFeature(DOCTYPE_4_0_TRANSITIONAL_STANDARDS))) {
return false;
}
}
if ("http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd".equals(systemId)
|| "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd".equals(systemId)) {
return false;
}
}
else if (docType.getPublicId() == null) {
return false;
}
}
return true;
}
/**
* Adds a cookie, as long as cookies are enabled.
* @see MSDN documentation
* @param newCookie in the format "name=value[;expires=date][;domain=domainname][;path=path][;secure]
*/
@JsxSetter
public void setCookie(final String newCookie) {
final HtmlPage page = getHtmlPage();
final WebClient client = page.getWebClient();
client.addCookie(newCookie, getHtmlPage().getUrl(), this);
}
/**
* Returns the value of the "images" property.
* @return the value of the "images" property
*/
@JsxGetter
public Object getImages() {
if (images_ == null) {
images_ = new HTMLCollection(getDomNodeOrDie(), false, "HTMLDocument.images") {
@Override
protected boolean isMatching(final DomNode node) {
return node instanceof HtmlImage;
}
};
}
return images_;
}
/**
* Returns a string representing the encoding under which the document was parsed.
* @return a string representing the encoding under which the document was parsed
*/
@JsxGetter({ @WebBrowser(FF), @WebBrowser(CHROME), @WebBrowser(value = IE, minVersion = 11) })
public String getInputEncoding() {
final String encoding = getHtmlPage().getPageEncoding();
if (encoding != null && getBrowserVersion().hasFeature(HTMLDOCUMENT_CHARSET_NORMALIZED)) {
return EncodingSniffer.translateEncodingLabel(encoding);
}
return encoding;
}
/**
* Returns the character encoding of the current document.
* @return the character encoding of the current document
*/
@JsxGetter({ @WebBrowser(FF), @WebBrowser(CHROME), @WebBrowser(value = IE, minVersion = 11) })
public String getCharacterSet() {
final String charset = getHtmlPage().getPageEncoding();
if (charset != null && getBrowserVersion().hasFeature(HTMLDOCUMENT_CHARSET_LOWERCASE)) {
return charset.toLowerCase(Locale.ENGLISH);
}
if (charset != null && getBrowserVersion().hasFeature(HTMLDOCUMENT_CHARSET_NORMALIZED)) {
return EncodingSniffer.translateEncodingLabel(charset);
}
return charset;
}
/**
* Retrieves the character set used to encode the document.
* @return the character set used to encode the document
*/
@JsxGetter({ @WebBrowser(IE), @WebBrowser(CHROME) })
public String getCharset() {
String charset = getHtmlPage().getPageEncoding();
if (charset != null && getBrowserVersion().hasFeature(HTMLDOCUMENT_CHARSET_NORMALIZED)) {
return EncodingSniffer.translateEncodingLabel(charset);
}
if (getBrowserVersion().hasFeature(HTMLDOCUMENT_CHARSET_LOWERCASE)) {
charset = charset.toLowerCase(Locale.ENGLISH);
}
return charset;
}
/**
* Gets the default character set from the current regional language settings.
* @return the default character set from the current regional language settings
*/
@JsxGetter({ @WebBrowser(IE), @WebBrowser(CHROME) })
public String getDefaultCharset() {
return "windows-1252";
}
/**
* Returns the value of the "URL" property.
* @return the value of the "URL" property
*/
@JsxGetter(propertyName = "URL")
public String getURL() {
return getHtmlPage().getUrl().toExternalForm();
}
/**
* 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
*/
@JsxGetter(@WebBrowser(IE))
public String getUniqueID() {
if (uniqueID_ == null) {
uniqueID_ = "ms__id" + UniqueID_Counter_++;
}
return uniqueID_;
}
/**
* Returns the value of the "all" property.
* @return the value of the "all" property
*/
@JsxGetter
public HTMLCollection getAll() {
if (all_ == null) {
all_ = new HTMLAllCollection(getDomNodeOrDie(), "HTMLDocument.all") {
@Override
protected boolean isMatching(final DomNode node) {
return true;
}
};
all_.setAvoidObjectDetection(!getBrowserVersion().hasFeature(HTMLCOLLECTION_OBJECT_DETECTION));
}
return all_;
}
/**
* JavaScript function "open".
*
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/section-dynamic.html for
* a good description of the semantics of open(), write(), writeln() and close().
*
* @param url when a new document is opened, url is a String that specifies a MIME type for the document.
* When a new window is opened, url is a String that specifies the URL to render in the new window
* @param name the name
* @param features the features
* @param replace whether to replace in the history list or no
* @return a reference to the new document object or the window object.
* @see MSDN documentation
*/
@JsxFunction
public Object open(final String url, final Object name, final Object features,
final Object replace) {
// Any open() invocations are ignored during the parsing stage, because write() and
// writeln() invocations will directly append content to the current insertion point.
final HtmlPage page = getHtmlPage();
if (page.isBeingParsed()) {
LOG.warn("Ignoring call to open() during the parsing stage.");
return null;
}
// We're not in the parsing stage; OK to continue.
if (!writeInCurrentDocument_) {
LOG.warn("Function open() called when document is already open.");
}
writeInCurrentDocument_ = false;
if (getWindow().getWebWindow() instanceof FrameWindow
&& WebClient.URL_ABOUT_BLANK.equals(getPage().getUrl())) {
final URL enclosingUrl = ((FrameWindow) getWindow().getWebWindow()).getEnclosingPage().getUrl();
getPage().getWebResponse().getWebRequest().setUrl(enclosingUrl);
}
return null;
}
/**
* JavaScript function "close".
*
* See http://www.whatwg.org/specs/web-apps/current-work/multipage/section-dynamic.html for
* a good description of the semantics of open(), write(), writeln() and close().
*
* @throws IOException if an IO problem occurs
*/
@JsxFunction
public void close() throws IOException {
if (writeInCurrentDocument_) {
LOG.warn("close() called when document is not open.");
}
else {
final HtmlPage page = getHtmlPage();
final URL url = page.getUrl();
final StringWebResponse webResponse = new StringWebResponse(writeBuffer_.toString(), url);
webResponse.setFromJavascript(true);
writeInCurrentDocument_ = true;
writeBuffer_.setLength(0);
final WebClient webClient = page.getWebClient();
final WebWindow window = page.getEnclosingWindow();
webClient.loadWebResponseInto(webResponse, window);
}
}
/**
* Closes the document implicitly, i.e. flushes the document.write buffer (IE only).
*/
private void implicitCloseIfNecessary() {
if (!writeInCurrentDocument_) {
try {
close();
}
catch (final IOException e) {
throw Context.throwAsScriptRuntimeEx(e);
}
}
}
/**
* Gets the window in which this document is contained.
* @return the window
*/
@JsxGetter(@WebBrowser(IE))
public Object getParentWindow() {
return getWindow();
}
/**
* {@inheritDoc}
*/
@Override
public Object appendChild(final Object childObject) {
if (getBrowserVersion().hasFeature(JS_DOCUMENT_APPEND_CHILD_SUPPORTED)) {
// We're emulating IE; we can allow insertion.
return super.appendChild(childObject);
}
// Firefox does not allow insertion at the document level.
throw Context.reportRuntimeError("Node cannot be inserted at the specified point in the hierarchy.");
}
/**
* Create a new HTML element with the given tag name.
*
* @param tagName the tag name
* @return the new HTML element, or NOT_FOUND if the tag is not supported
*/
@Override
public Object createElement(String tagName) {
Object result = NOT_FOUND;
// IE can handle HTML, but it takes only the first tag found
if (tagName.startsWith("<") && getBrowserVersion().hasFeature(JS_DOCUMENT_CREATE_ELEMENT_EXTENDED_SYNTAX)) {
final Matcher m = FIRST_TAG_PATTERN.matcher(tagName);
if (m.find()) {
tagName = m.group(1);
result = super.createElement(tagName);
if (result == NOT_FOUND || m.group(2) == null) {
return result;
}
final HTMLElement elt = (HTMLElement) result;
// handle attributes
final String attributes = m.group(2);
final Matcher mAttribute = ATTRIBUTES_PATTERN.matcher(attributes);
while (mAttribute.find()) {
final String attrName = mAttribute.group(1);
final String attrValue = mAttribute.group(2);
elt.setAttribute(attrName, attrValue);
}
}
}
else {
return super.createElement(tagName);
}
return result;
}
/**
* Creates a new Stylesheet.
* Current implementation just creates an empty {@link CSSStyleSheet} object.
* @param url the stylesheet URL
* @param index where to insert the sheet in the collection
* @return the newly created stylesheet
*/
@JsxFunction(@WebBrowser(value = IE, maxVersion = 8))
public CSSStyleSheet createStyleSheet(final String url, final Object index) {
final HTMLLinkElement link = (HTMLLinkElement) createElement("link");
link.setHref(url);
link.setRel("stylesheet");
int insertPos = Integer.MAX_VALUE;
if (Undefined.instance != index) {
try {
insertPos = ((Double) index).intValue();
}
catch (final NumberFormatException e) {
// ignore
}
}
final InputSource source = new InputSource(new StringReader(""));
final CSSStyleSheet stylesheet = new CSSStyleSheet(link, source, url);
stylesheet.setPrototype(getPrototype(CSSStyleSheet.class));
stylesheet.setParentScope(getWindow());
final HTMLCollection heads = getElementsByTagName("head");
if (heads.getLength() > 0) {
final DomNode head = ((SimpleScriptable) heads.item(0)).getDomNodeOrDie();
int stylesheetPos = -1;
for (DomNode domNode : head.getChildNodes()) {
if (StyleSheetList.isStyleSheetLink(domNode)) {
stylesheetPos++;
if (insertPos <= stylesheetPos) {
domNode.insertBefore(link.getDomNodeOrDie());
return stylesheet;
}
}
}
head.appendChild(link.getDomNodeOrDie());
}
return stylesheet;
}
/**
* Returns the element with the specified ID, or null if that element could not be found.
* @param id the ID to search for
* @return the element, or null if it could not be found
*/
@JsxFunction
public Object getElementById(final String id) {
implicitCloseIfNecessary();
Object result = null;
try {
final boolean caseSensitive = getBrowserVersion().hasFeature(JS_GET_ELEMENT_BY_ID_CASE_SENSITIVE);
final DomElement htmlElement = getHtmlPage().getElementById(id, caseSensitive);
final Object jsElement = getScriptableFor(htmlElement);
if (jsElement == NOT_FOUND) {
if (LOG.isDebugEnabled()) {
LOG.debug("getElementById(" + id
+ ") cannot return a result as there isn't a JavaScript object for the HTML element "
+ htmlElement.getClass().getName());
}
}
else {
result = jsElement;
}
}
catch (final ElementNotFoundException e) {
// Just fall through - result is already set to null
final BrowserVersion browser = getBrowserVersion();
if (browser.hasFeature(JS_GET_ELEMENT_BY_ID_ALSO_BY_NAME_IN_QUICKS_MODE)
&& getHtmlPage().isQuirksMode()) {
final HTMLCollection elements = getElementsByName(id);
result = elements.get(0, elements);
if (result instanceof UniqueTag) {
return null;
}
LOG.warn("getElementById(" + id + ") did a getElementByName for Internet Explorer");
return result;
}
if (LOG.isDebugEnabled()) {
LOG.debug("getElementById(" + id + "): no DOM node found with this id");
}
}
return result;
}
/**
* Returns all the descendant elements with the specified class name.
* @param className the name to search for
* @return all the descendant elements with the specified class name
* @see Mozilla doc
*/
@JsxFunction({ @WebBrowser(FF), @WebBrowser(value = IE, minVersion = 11), @WebBrowser(CHROME) })
public HTMLCollection getElementsByClassName(final String className) {
return ((HTMLElement) getDocumentElement()).getElementsByClassName(className);
}
/**
* Returns all HTML elements that have a "name" attribute with the specified value.
*
* Refer to
* The DOM spec for details.
*
* @param elementName - value of the "name" attribute to look for
* @return all HTML elements that have a "name" attribute with the specified value
*/
@JsxFunction
public HTMLCollection getElementsByName(final String elementName) {
implicitCloseIfNecessary();
if (getBrowserVersion().hasFeature(JS_GET_ELEMENTS_BY_NAME_EMPTY_RETURNS_NOTHING)
&& StringUtils.isEmpty(elementName)
|| "null".equals(elementName)) {
return HTMLCollection.emptyCollection(getWindow());
}
// Null must me changed to '' for proper collection initialization.
final String expElementName = "null".equals(elementName) ? "" : elementName;
final HtmlPage page = (HtmlPage) getPage();
final String description = "HTMLDocument.getElementsByName('" + elementName + "')";
final HTMLCollection collection = new HTMLCollection(page, true, description) {
@Override
protected List