All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument Maven / Gradle / Ivy

There is a newer version: 2.70.0
Show newest version
/*
 * Copyright (c) 2002-2020 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.HTMLDOCUMENT_COLOR;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLDOCUMENT_ELEMENTS_BY_NAME_EMPTY;
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_PREFERS_STANDARD_FUNCTIONS;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTML_COLOR_EXPAND_ZERO;
import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_DOCUMENT_CREATE_ATTRUBUTE_LOWER_CASE;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.CHROME;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF60;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.FF68;
import static com.gargoylesoftware.htmlunit.javascript.configuration.SupportedBrowser.IE;

import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.gargoylesoftware.htmlunit.ScriptResult;
import com.gargoylesoftware.htmlunit.StringWebResponse;
import com.gargoylesoftware.htmlunit.WebClient;
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.HtmlApplet;
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.HtmlInlineFrame;
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.SimpleScriptable;
import com.gargoylesoftware.htmlunit.javascript.configuration.JsxClass;
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.host.Element;
import com.gargoylesoftware.htmlunit.javascript.host.Window;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Attr;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Document;
import com.gargoylesoftware.htmlunit.javascript.host.dom.Selection;
import com.gargoylesoftware.htmlunit.javascript.host.event.Event;
import com.gargoylesoftware.htmlunit.util.Cookie;

import net.sourceforge.htmlunit.corejs.javascript.Context;
import net.sourceforge.htmlunit.corejs.javascript.Function;
import net.sourceforge.htmlunit.corejs.javascript.Scriptable;
import net.sourceforge.htmlunit.corejs.javascript.ScriptableObject;

/**
 * A JavaScript object for {@code HTMLDocument}.
 *
 * @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
 */
@JsxClass
public class HTMLDocument extends Document {

    private static final Log LOG = LogFactory.getLog(HTMLDocument.class);

    private enum ParsingStatus { OUTSIDE, START, IN_NAME, INSIDE, IN_STRING }

    private HTMLElement activeElement_;

    /** The buffer that will be used for calls to document.write(). */
    private final StringBuilder writeBuilder_ = new StringBuilder();
    private boolean writeInCurrentDocument_ = true;

    private boolean closePostponedAction_;
    private boolean executionExternalPostponed_;

    /**
     * The constructor.
     */
    @JsxConstructor({CHROME, FF, FF68, FF60})
    public HTMLDocument() {
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public DomNode getDomNodeOrDie() {
        try {
            return super.getDomNodeOrDie();
        }
        catch (final IllegalStateException e) {
            throw Context.reportRuntimeError("No node attached to this object");
        }
    }

    /**
     * Returns the HTML page that this document is modeling.
     * @return the HTML page that this document is modeling
     */
    @Override
    public HtmlPage getPage() {
        return (HtmlPage) getDomNodeOrDie();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getForms() {
        return super.getForms();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getEmbeds() {
        return super.getEmbeds();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getPlugins() {
        return super.getPlugins();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getLinks() {
        return super.getLinks();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getAnchors() {
        return super.getAnchors();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getApplets() {
        return super.getApplets();
    }

    /**
     * JavaScript function "write" may accept a variable number of arguments.
     * @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 builder = new StringBuilder();
        for (final Object arg : args) {
            builder.append(Context.toString(arg));
        }
        return builder.toString();
    }

    /**
     * JavaScript function "writeln" may accept a variable number of arguments.
     * @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");
    }

    /**
     * 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 = (HtmlPage) getDomNodeOrDie();
        if (!page.isBeingParsed()) {
            writeInCurrentDocument_ = false;
        }

        // Add content to the content buffer.
        writeBuilder_.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 = writeBuilder_.toString();
        if (!canAlreadyBeParsed(bufferedContent)) {
            if (LOG.isDebugEnabled()) {
                LOG.debug("write: not enough content to parse it now");
            }
            return;
        }

        writeBuilder_.setLength(0);
        page.writeInParsedStream(bufferedContent);
    }

    private void scheduleImplicitClose() {
        if (!closePostponedAction_) {
            closePostponedAction_ = true;
            final HtmlPage page = (HtmlPage) getDomNodeOrDie();
            final WebWindow enclosingWindow = page.getEnclosingWindow();
            page.getWebClient().getJavaScriptEngine().addPostponedAction(new PostponedAction(page) {
                @Override
                public void execute() throws Exception {
                    if (writeBuilder_.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 {@code 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);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter
    public String getCookie() {
        final HtmlPage page = getPage();

        final URL url = page.getUrl();

        final StringBuilder builder = new StringBuilder();
        final Set cookies = page.getWebClient().getCookies(url);
        for (final Cookie cookie : cookies) {
            if (cookie.isHttpOnly()) {
                continue;
            }
            if (builder.length() != 0) {
                builder.append("; ");
            }
            if (!HtmlUnitBrowserCompatCookieSpec.EMPTY_COOKIE_NAME.equals(cookie.getName())) {
                builder.append(cookie.getName());
                builder.append('=');
            }
            builder.append(cookie.getValue());
        }

        return builder.toString();
    }

    /**
     * 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 = getPage();
        final WebClient client = page.getWebClient();

        client.addCookie(newCookie, getPage().getUrl(), this);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getImages() {
        return super.getImages();
    }

    /**
     * Returns the value of the {@code all} property.
     * @return the value of the {@code all} property
     */
    @JsxGetter
    public HTMLCollection getAll() {
        return new HTMLAllCollection(getDomNodeOrDie()) {
            @Override
            protected boolean isMatching(final DomNode node) {
                return true;
            }

            @Override
            public boolean avoidObjectDetection() {
                return true;
            }
        };
    }

    /**
     * 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.
     * @see MSDN documentation
     */
    @JsxFunction
    public Object open(final Object 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 = getPage();
        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;
        final WebWindow ww = getWindow().getWebWindow();
        if (ww instanceof FrameWindow
                && WebClient.ABOUT_BLANK.equals(getPage().getUrl().toExternalForm())) {
            final URL enclosingUrl = ((FrameWindow) ww).getEnclosingPage().getUrl();
            getPage().getWebResponse().getWebRequest().setUrl(enclosingUrl);
        }
        return this;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction({FF, FF68, FF60})
    public void close() throws IOException {
        if (writeInCurrentDocument_) {
            LOG.warn("close() called when document is not open.");
        }
        else {
            final HtmlPage page = getPage();
            final URL url = page.getUrl();
            final StringWebResponse webResponse = new StringWebResponse(writeBuilder_.toString(), url);
            webResponse.setFromJavascript(true);
            writeInCurrentDocument_ = true;
            writeBuilder_.setLength(0);

            final WebClient webClient = page.getWebClient();
            final WebWindow window = page.getEnclosingWindow();
            // reset isAttachedToPageDuringOnload_ to trigger the onload event for chrome also
            if (window instanceof FrameWindow) {
                final BaseFrameElement frame = ((FrameWindow) window).getFrameElement();
                final ScriptableObject scriptable = frame.getScriptableObject();
                if (scriptable instanceof HTMLIFrameElement) {
                    ((HTMLIFrameElement) scriptable).onRefresh();
                }
            }
            webClient.loadWebResponseInto(webResponse, window);
        }
    }

    /**
     * {@inheritDoc}
     */
    @JsxGetter
    @Override
    public Element getDocumentElement() {
        implicitCloseIfNecessary();
        return super.getDocumentElement();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction({FF, FF68, FF60})
    public boolean execCommand(final String cmd, final boolean userInterface, final Object value) {
        return super.execCommand(cmd, userInterface, value);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction({FF, FF68, FF60})
    public boolean queryCommandEnabled(final String cmd) {
        return super.queryCommandEnabled(cmd);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction({FF, FF68, FF60})
    public boolean queryCommandSupported(final String cmd) {
        return super.queryCommandSupported(cmd);
    }

    /**
     * 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(IE)
    public Object getParentWindow() {
        return getWindow();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object appendChild(final Object childObject) {
        throw Context.reportRuntimeError("Node cannot be inserted at the specified point in the hierarchy.");
    }

    /**
     * Returns the element with the specified ID, or {@code null} if that element could not be found.
     * @param id the ID to search for
     * @return the element, or {@code null} if it could not be found
     */
    @JsxFunction
    public Object getElementById(final String id) {
        implicitCloseIfNecessary();
        Object result = null;
        final DomElement domElement = getPage().getElementById(id);
        if (null == domElement) {
            // Just fall through - result is already set to null
            if (LOG.isDebugEnabled()) {
                LOG.debug("getElementById(" + id + "): no DOM node found with this id");
            }
        }
        else {
            final Object jsElement = getScriptableFor(domElement);
            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 "
                            + domElement.getClass().getName());
                }
            }
            else {
                result = jsElement;
            }
        }
        return result;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public HTMLCollection getElementsByClassName(final String className) {
        return ((HTMLElement) getDocumentElement()).getElementsByClassName(className);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction({FF, FF68, FF60})
    public HTMLCollection getElementsByName(final String elementName) {
        implicitCloseIfNecessary();
        if ("null".equals(elementName)
                || (elementName.isEmpty()
                    && getBrowserVersion().hasFeature(HTMLDOCUMENT_ELEMENTS_BY_NAME_EMPTY))) {
            return HTMLCollection.emptyCollection(getWindow().getDomNodeOrDie());
        }

        final HtmlPage page = getPage();
        return new HTMLCollection(page, true) {
            @Override
            protected List computeElements() {
                return new ArrayList<>(page.getElementsByName(elementName));
            }

            @Override
            protected EffectOnCache getEffectOnCache(final HtmlAttributeChangeEvent event) {
                if ("name".equals(event.getName())) {
                    return EffectOnCache.RESET;
                }
                return EffectOnCache.NONE;
            }
        };
    }

    /**
     * Calls to document.XYZ should first look at elements named XYZ before
     * using standard functions.
     *
     * {@inheritDoc}
     */
    @Override
    protected Object getWithPreemption(final String name) {
        final HtmlPage page = (HtmlPage) getDomNodeOrNull();
        if (page == null || getBrowserVersion().hasFeature(HTMLDOCUMENT_GET_PREFERS_STANDARD_FUNCTIONS)) {
            final Object response = getPrototype().get(name, this);
            if (response != NOT_FOUND) {
                return response;
            }
        }
        return getIt(name);
    }

    private Object getIt(final String name) {
        final HtmlPage page = (HtmlPage) getDomNodeOrNull();
        if (page == null) {
            return NOT_FOUND;
        }

        final boolean forIDAndOrName = getBrowserVersion().hasFeature(HTMLDOCUMENT_GET_FOR_ID_AND_OR_NAME);
        final boolean alsoFrames = getBrowserVersion().hasFeature(HTMLDOCUMENT_GET_ALSO_FRAMES);

        // for performance
        // we will calculate the elements to decide if we really have
        // to really create a HTMLCollection or not
        final List matchingElements = getItComputeElements(page, name, forIDAndOrName, alsoFrames);
        final int size = matchingElements.size();
        if (size == 0) {
            return NOT_FOUND;
        }
        if (size == 1) {
            final DomNode object = matchingElements.get(0);
            if (alsoFrames && object instanceof BaseFrameElement) {
                return ((BaseFrameElement) object).getEnclosedWindow().getScriptableObject();
            }
            return super.getScriptableFor(object);
        }

        return new HTMLCollection(page, matchingElements) {
            @Override
            protected List computeElements() {
                return getItComputeElements(page, name, forIDAndOrName, alsoFrames);
            }

            @Override
            protected EffectOnCache getEffectOnCache(final HtmlAttributeChangeEvent event) {
                final String attributeName = event.getName();
                if ("name".equals(attributeName) || (forIDAndOrName && "id".equals(attributeName))) {
                    return EffectOnCache.RESET;
                }

                return EffectOnCache.NONE;
            }

            @Override
            protected SimpleScriptable getScriptableFor(final Object object) {
                if (alsoFrames && object instanceof BaseFrameElement) {
                    return ((BaseFrameElement) object).getEnclosedWindow().getScriptableObject();
                }
                return super.getScriptableFor(object);
            }
        };
    }

    private static List getItComputeElements(final HtmlPage page, final String name,
            final boolean forIDAndOrName, final boolean alsoFrames) {
        final List elements;
        if (forIDAndOrName) {
            elements = page.getElementsByIdAndOrName(name);
        }
        else {
            elements = page.getElementsByName(name);
        }
        final List matchingElements = new ArrayList<>();
        for (final DomElement elt : elements) {
            if (elt instanceof HtmlForm || elt instanceof HtmlImage || elt instanceof HtmlApplet
                    || (alsoFrames && elt instanceof BaseFrameElement)) {
                matchingElements.add(elt);
            }
        }
        return matchingElements;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter
    public HTMLElement getHead() {
        final HtmlElement head = getPage().getHead();
        if (head == null) {
            return null;
        }
        return head.getScriptableObject();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public HTMLElement getBody() {
        return super.getBody();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getTitle() {
        return getPage().getTitleText();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void setTitle(final String title) {
        getPage().setTitleText(title);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({CHROME, FF, FF68, FF60})
    public String getBgColor() {
        String color = getPage().getBody().getAttribute("bgColor");
        if (color == DomElement.ATTRIBUTE_NOT_DEFINED && getBrowserVersion().hasFeature(HTMLDOCUMENT_COLOR)) {
            color = "#ffffff";
        }
        if (getBrowserVersion().hasFeature(HTML_COLOR_EXPAND_ZERO) && "#0".equals(color)) {
            color = "#000000";
        }
        return color;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxSetter({CHROME, FF, FF68, FF60})
    public void setBgColor(final String color) {
        final HTMLBodyElement body = getPage().getBody().getScriptableObject();
        body.setBgColor(color);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({CHROME, FF, FF68, FF60})
    public String getAlinkColor() {
        String color = getPage().getBody().getAttribute("aLink");
        if (color == DomElement.ATTRIBUTE_NOT_DEFINED && getBrowserVersion().hasFeature(HTMLDOCUMENT_COLOR)) {
            color = "#0000ff";
        }
        if (getBrowserVersion().hasFeature(HTML_COLOR_EXPAND_ZERO) && "#0".equals(color)) {
            color = "#000000";
        }
        return color;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxSetter({CHROME, FF, FF68, FF60})
    public void setAlinkColor(final String color) {
        final HTMLBodyElement body = getPage().getBody().getScriptableObject();
        body.setALink(color);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({CHROME, FF, FF68, FF60})
    public String getLinkColor() {
        String color = getPage().getBody().getAttributeDirect("link");
        if (color == DomElement.ATTRIBUTE_NOT_DEFINED && getBrowserVersion().hasFeature(HTMLDOCUMENT_COLOR)) {
            color = "#0000ff";
        }
        if (getBrowserVersion().hasFeature(HTML_COLOR_EXPAND_ZERO) && "#0".equals(color)) {
            color = "#000000";
        }
        return color;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxSetter({CHROME, FF, FF68, FF60})
    public void setLinkColor(final String color) {
        final HTMLBodyElement body = getPage().getBody().getScriptableObject();
        body.setLink(color);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({CHROME, FF, FF68, FF60})
    public String getVlinkColor() {
        String color = getPage().getBody().getAttribute("vLink");
        if (color == DomElement.ATTRIBUTE_NOT_DEFINED && getBrowserVersion().hasFeature(HTMLDOCUMENT_COLOR)) {
            color = "#800080";
        }
        if (getBrowserVersion().hasFeature(HTML_COLOR_EXPAND_ZERO) && "#0".equals(color)) {
            color = "#000000";
        }
        return color;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxSetter({CHROME, FF, FF68, FF60})
    public void setVlinkColor(final String color) {
        final HTMLBodyElement body = getPage().getBody().getScriptableObject();
        body.setVLink(color);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({CHROME, FF, FF68, FF60})
    public String getFgColor() {
        String color = getPage().getBody().getAttributeDirect("text");
        if (color == DomElement.ATTRIBUTE_NOT_DEFINED && getBrowserVersion().hasFeature(HTMLDOCUMENT_COLOR)) {
            color = "#000000";
        }
        if (getBrowserVersion().hasFeature(HTML_COLOR_EXPAND_ZERO) && "#0".equals(color)) {
            color = "#000000";
        }
        return color;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxSetter({CHROME, FF, FF68, FF60})
    public void setFgColor(final String color) {
        final HTMLBodyElement body = getPage().getBody().getScriptableObject();
        body.setText(color);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public String getDomain() {
        return super.getDomain();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxSetter({FF, FF68, FF60})
    public void setDomain(final String newDomain) {
        super.setDomain(newDomain);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public Object getScripts() {
        return super.getScripts();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public HTMLElement getActiveElement() {
        if (activeElement_ == null) {
            final HtmlElement body = getPage().getBody();
            if (body != null) {
                activeElement_ = (HTMLElement) getScriptableFor(body);
            }
        }

        return activeElement_;
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public boolean hasFocus() {
        return activeElement_ != null && getPage().getFocusedElement() == activeElement_.getDomNodeOrDie();
    }

    /**
     * Sets the specified element as the document's active element.
     * @see HTMLElement#setActive()
     * @param element the new active element for this document
     */
    public void setActiveElement(final HTMLElement element) {
        // TODO update page focus element also

        activeElement_ = element;

        if (element != null) {
            // if this is part of an iFrame, make the iFrame tag the
            // active element of his doc
            final WebWindow window = element.getDomNodeOrDie().getPage().getEnclosingWindow();
            if (window instanceof FrameWindow) {
                final BaseFrameElement frame = ((FrameWindow) window).getFrameElement();
                if (frame instanceof HtmlInlineFrame) {
                    final Window winWithFrame = frame.getPage().getEnclosingWindow().getScriptableObject();
                    ((HTMLDocument) winWithFrame.getDocument()).setActiveElement(
                                (HTMLElement) frame.getScriptableObject());
                }
            }
        }
    }

    /**
     * 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 {@code false} if at least one of the event handlers which handled the event
     *         called preventDefault; {@code true} otherwise
     */
    @Override
    @JsxFunction
    public boolean dispatchEvent(final Event event) {
        event.setTarget(this);
        final ScriptResult result = fireEvent(event);
        return !event.isAborted(result);
    }

    /**
     * Does... nothing.
     * @see Mozilla doc
     */
    @JsxFunction
    public void clear() {
        // nothing
    }

    /**
     * Sets the head.
     * @param head the head
     */
    @JsxSetter({FF, FF68, FF60, IE})
    public void setHead(final ScriptableObject head) {
        //ignore
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction
    public Selection getSelection() {
        return getWindow().getSelectionImpl();
    }

    /**
     * Creates a new HTML attribute with the specified name.
     *
     * @param attributeName the name of the attribute to create
     * @return an attribute with the specified name
     */
    @Override
    public Attr createAttribute(final String attributeName) {
        String name = attributeName;
        if (StringUtils.isNotEmpty(name)
                && getBrowserVersion().hasFeature(JS_DOCUMENT_CREATE_ATTRUBUTE_LOWER_CASE)) {
            name = name.toLowerCase(Locale.ROOT);
        }

        return super.createAttribute(name);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public String getBaseURI() {
        return getPage().getBaseURL().toString();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction({CHROME, FF, FF68, FF60})
    public void captureEvents(final String type) {
        // Empty.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxFunction({CHROME, FF, FF68, FF60})
    public void releaseEvents(final String type) {
        // Empty.
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxGetter({FF, FF68, FF60})
    public String getDesignMode() {
        return super.getDesignMode();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    @JsxSetter({FF, FF68, FF60})
    public void setDesignMode(final String mode) {
        super.setDesignMode(mode);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public Object elementFromPoint(final int x, final int y) {
        final HtmlElement element = getPage().getElementFromPoint(x, y);
        return element == null ? null : element.getScriptableObject();
    }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy