org.fit.cssbox.scriptbox.dom.Html5DocumentImpl.java Source code

Java tutorial

Introduction

Here is the source code for org.fit.cssbox.scriptbox.dom.Html5DocumentImpl.java

Source

/**
 * Html5DocumentImpl.java
 * (c) Radim Loskot and Radek Burget, 2013-2014
 *
 * ScriptBox is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *  
 * ScriptBox is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 * GNU Lesser General Public License for more details.
 *  
 * You should have received a copy of the GNU Lesser General Public License
 * along with ScriptBox. If not, see <http://www.gnu.org/licenses/>.
 * 
 */

package org.fit.cssbox.scriptbox.dom;

import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;

import javax.script.ScriptException;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.apache.html.dom.HTMLDocumentImpl;
import org.apache.xerces.dom.NodeImpl;
import org.apache.xerces.dom.events.EventImpl;
import org.apache.xerces.dom.events.MutationEventImpl;
import org.fit.cssbox.scriptbox.browser.BrowsingContext;
import org.fit.cssbox.scriptbox.browser.IFrameBrowsingContext;
import org.fit.cssbox.scriptbox.browser.WindowBrowsingContext;
import org.fit.cssbox.scriptbox.dom.Html5DocumentEvent.EventType;
import org.fit.cssbox.scriptbox.dom.events.EventTarget;
import org.fit.cssbox.scriptbox.dom.events.script.ErrorEvent;
import org.fit.cssbox.scriptbox.events.EventLoop;
import org.fit.cssbox.scriptbox.events.Task;
import org.fit.cssbox.scriptbox.history.History;
import org.fit.cssbox.scriptbox.history.SessionHistoryEntry;
import org.fit.cssbox.scriptbox.navigation.Location;
import org.fit.cssbox.scriptbox.parser.Html5DocumentParser;
import org.fit.cssbox.scriptbox.script.Script;
import org.fit.cssbox.scriptbox.script.annotation.ScriptFunction;
import org.fit.cssbox.scriptbox.script.annotation.ScriptGetter;
import org.fit.cssbox.scriptbox.security.SandboxingFlag;
import org.fit.cssbox.scriptbox.security.origins.DocumentOrigin;
import org.fit.cssbox.scriptbox.security.origins.Origin;
import org.fit.cssbox.scriptbox.security.origins.OriginContainer;
import org.fit.cssbox.scriptbox.security.origins.UrlOrigin;
import org.fit.cssbox.scriptbox.url.URLUtilsHelper;
import org.fit.cssbox.scriptbox.url.URLUtilsHelper.UrlComponent;
import org.fit.cssbox.scriptbox.window.Window;
import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Text;
import org.w3c.dom.events.Event;
import org.w3c.dom.events.EventListener;
import org.w3c.dom.html.HTMLBaseElement;
import org.w3c.dom.html.HTMLElement;
import org.w3c.dom.views.AbstractView;
import org.w3c.dom.views.DocumentView;

/**
 * Extends DOM4 HTMLDocument about features of the HTML5 and associated browsing
 * context features which are necessary for browsing and to be stored within document.
 * 
 * @author Radim Loskot
 * @version 0.9
 * @since 0.9 - 21.4.2014
 * 
 * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#document">HTML5 document</a>
 * @see <a href="http://dom.spec.whatwg.org/#html-document">HTML document accoring to DOM</a>
 */
public class Html5DocumentImpl extends HTMLDocumentImpl implements EventTarget, DocumentView {
    /**
     * Enumeration which signals whether is document complete, 
     * or is currently being loaded or manipulated.
     * 
     * @author Radim Loskot
     * @version 0.9
     * @since 0.9 - 21.4.2014
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#current-document-readiness">Document readiness</a>
     */
    public enum DocumentReadyState {
        LOADING, INTERACTIVE, COMPLETE
    }

    /**
     * Default address which has every document set.
     */
    final public static String DEFAULT_URL_ADDRESS = org.fit.cssbox.scriptbox.url.about.Handler.DEFAULT_URL_ADDRESS;

    /**
     * Default URL address which has every document set by default.
     */
    final public static URL DEFAULT_URL = org.fit.cssbox.scriptbox.url.about.Handler.DEFAULT_URL;
    final public static String JAVASCRIPT_SCHEME_NAME = "javascript";
    final public static String DATA_SCHEME_NAME = "data";

    private static final long serialVersionUID = -352261593104316623L;
    private static final String SCRIPT_TAG_NAME = "script";
    private static final String IFRAME_TAG_NAME = "iframe";

    private static List<String> SERVER_BASED_SCHEMES;
    static {
        SERVER_BASED_SCHEMES = new ArrayList<String>(3);
        SERVER_BASED_SCHEMES.add("http");
        SERVER_BASED_SCHEMES.add("https");
        SERVER_BASED_SCHEMES.add("file");
    }

    private BrowsingContext _browsingContext;

    @SuppressWarnings("unused")
    private int _ignoreDestructiveWritesCounter;

    // Every Document has an active sandboxing flag set
    private Set<SandboxingFlag> _activeSandboxingFlagSet;

    /*
     * FIXME: This should be stored inside SessionHistory. For simplification it is here.
     * Each Document object in a browsing context's session history is associated 
     * with a unique History object which must all model the same underlying session history.
     */
    protected History _history;
    protected Location _location;

    private OriginContainer<DocumentOrigin> _originContainer;
    private URL _address;
    private String _referrer;
    private Window _window;
    private SessionHistoryEntry _latestEntry;
    private boolean _fullscreenEnabledFlag;
    private String _contentType;
    private DocumentReadyState _documentReadiness;
    private Html5DocumentParser _parser;
    private Task unloadTask;

    private boolean _salvageableFlag;
    private boolean _firedUnloadFlag;
    private boolean _pageShowingFlag;

    private Set<Html5DocumentEventListener> listeners;

    private boolean errorReportingMode;

    private boolean unloaded;

    /*
     * Document event listener which captures events and runs prepare script
     * algorithm if experiences one of the events listed bellow:
     * a) The script element gets inserted into a document
     * b) The script element is in a Document and a node or document fragment is inserted into the script element
     * c) The script element is in a Document and has a src attribute set where previously the element had no such attribute 
     * 
     * - See paragraph after: http://www.w3.org/html/wg/drafts/html/master/scripting-1.html#the-script-block%27s-fallback-character-encoding
     */
    private EventListener documentEventListener = new EventListener() {

        @Override
        public void handleEvent(Event evt) {
            String eventType = evt.getType();
            org.w3c.dom.events.EventTarget target = evt.getTarget();

            if (!(evt instanceof MutationEventImpl)) {
                return;
            }

            Html5ScriptElementImpl script = null;

            MutationEventImpl mutationEvent = (MutationEventImpl) evt;
            if (eventType.equals(MutationEventImpl.DOM_ATTR_MODIFIED)) {
                String prevValue = mutationEvent.getPrevValue();
                String attrName = mutationEvent.getAttrName();

                if (target instanceof Html5ScriptElementImpl && attrName.equalsIgnoreCase("src")
                        && prevValue == null) {
                    // Fulfills c) point
                    script = (Html5ScriptElementImpl) target;
                }
            } else if (eventType.equals(MutationEventImpl.DOM_NODE_INSERTED)) {
                Node relatedNode = mutationEvent.getRelatedNode();

                if (target instanceof Html5ScriptElementImpl) {
                    // Fulfills a) point
                    script = (Html5ScriptElementImpl) target;
                } else if (relatedNode instanceof Html5ScriptElementImpl) {
                    // Fulfills b) point
                    script = (Html5ScriptElementImpl) relatedNode;
                }
            }

            if (script != null && !script.isParserInserted()) {
                script.prepareScript();
            }
        }
    };

    /**
     * Constructs new instance of the HTML Document according to Html5 specification.
     * 
     * @param browsingContext Associated browsing context to which belongs this instance of the Document.
     * @param address Address of the Document.
     * @param sandboxingFlagSet Sandboxing flag set associated with the document.
     * @param referrer Referrer address.
     * @param createWindow Window of which instance is re-used for this Document.
     * @param contentType Content-Type of which is this Document.
     * @param parser Associated parser, which will parse, or parsed, or is parsing this Document.
     * 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#document">HTML5 document</a>
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#the-document%27s-address">The document's address</a>
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#the-document%27s-referrer">The document's referrer</a>
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#sandboxing-flag-set">A sandboxing flag set </a>
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/syntax.html#html-parser">HTML parser</a>
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#concept-document-content-type">Content type</a>
     */
    protected Html5DocumentImpl(BrowsingContext browsingContext, URL address, Set<SandboxingFlag> sandboxingFlagSet,
            String referrer, boolean createWindow, String contentType, Html5DocumentParser parser) {
        listeners = new HashSet<Html5DocumentEventListener>();

        _salvageableFlag = true;
        _firedUnloadFlag = false;
        _pageShowingFlag = false;
        _ignoreDestructiveWritesCounter = 0;

        _browsingContext = browsingContext;
        _address = address;
        _referrer = referrer;
        _contentType = contentType;
        _documentReadiness = (parser == null) ? DocumentReadyState.COMPLETE : DocumentReadyState.LOADING;
        _activeSandboxingFlagSet = new HashSet<SandboxingFlag>();
        _parser = parser;

        if (sandboxingFlagSet != null) {
            _activeSandboxingFlagSet.addAll(sandboxingFlagSet);
        }

        DocumentOrigin documentOrigin = null;
        DocumentOrigin effectiveScriptOrigin = null;

        if (_activeSandboxingFlagSet.contains(SandboxingFlag.ORIGIN_BROWSING_CONTEXT_FLAG)) {
            documentOrigin = DocumentOrigin.createUnique(this);
            effectiveScriptOrigin = DocumentOrigin.create(this, documentOrigin);
        } else if (address != null && (SERVER_BASED_SCHEMES.contains(address.getProtocol())
                || address.getProtocol().equals(JAVASCRIPT_SCHEME_NAME))) {
            UrlOrigin addressOrigin = new UrlOrigin(address);
            documentOrigin = DocumentOrigin.create(this, addressOrigin);
            effectiveScriptOrigin = DocumentOrigin.create(this, documentOrigin);
        } else if (address != null && address.getProtocol().equals(DATA_SCHEME_NAME)) {
            // TODO: If a Document was generated from a data: URL found in another Document or in a script
        } else if (address != null && address.equals(DEFAULT_URL)) {
            Html5DocumentImpl creatorDocument = browsingContext.getCreatorDocument();
            if (creatorDocument != null) {
                OriginContainer<?> originContainer = creatorDocument.getOriginContainer();
                documentOrigin = DocumentOrigin.create(this, originContainer.getOrigin());
                effectiveScriptOrigin = DocumentOrigin.create(this, originContainer.getEffectiveScriptOrigin());
            } else {
                documentOrigin = DocumentOrigin.createUnique(this);
                effectiveScriptOrigin = DocumentOrigin.create(this, documentOrigin);
            }
        }
        /*
         * TODO:
         * else if a Document is an iframe srcdoc document
         * else if a Document was obtained in some other manner
         */

        _originContainer = new OriginContainer<DocumentOrigin>(documentOrigin, effectiveScriptOrigin);

        if (_browsingContext != null) {
            if (createWindow) {
                _window = new Window(this);
            }

            _history = new History(this);
            _location = new Location(this);
        }

        addEventListener(MutationEventImpl.DOM_NODE_INSERTED, documentEventListener);
        addEventListener(MutationEventImpl.DOM_ATTR_MODIFIED, documentEventListener);
    }

    /**
     * Constructs new instance of the HTML Document with no associated parser.
     * 
     * @param browsingContext Associated browsing context to which belongs this instance of the Document.
     * @param address Address of the Document.
     * @param recycleWindowDocument Window of which instance is re-used for this Document.
     * @param contentType Content-Type of which is this Document.
     * @return New Document object.
     * 
     * @see #Html5DocumentImpl(BrowsingContext, URL, Set, String, boolean, String, Html5DocumentParser)
     */
    public static Html5DocumentImpl createDocument(BrowsingContext browsingContext, URL address,
            Html5DocumentImpl recycleWindowDocument, String contentType) {
        return createDocument(browsingContext, address, recycleWindowDocument, contentType, null);
    }

    /**
     * Constructs new instance of the HTML Document.
     * 
     * @param browsingContext Associated browsing context to which belongs this instance of the Document.
     * @param address Address of the Document.
     * @param recycleDocument Window of which instance is re-used for this Document.
     * @param contentType Content-Type of which is this Document.
     * @param parser Associated parser, which will parse, or parsed, or is parsing this Document.
     * @return New Document object.
     * 
     * @see #Html5DocumentImpl(BrowsingContext, URL, Set, String, boolean, String, Html5DocumentParser)
     */
    public static Html5DocumentImpl createDocument(BrowsingContext browsingContext, URL address,
            Html5DocumentImpl recycleDocument, String contentType, Html5DocumentParser parser) {
        Html5DocumentImpl document = null;

        if (recycleDocument != null) {
            document = new Html5DocumentImpl(browsingContext, address, null, null, false, contentType, parser);
            document._window = recycleDocument._window;
            document._window.setDocumentImpl(document);
        } else {
            document = new Html5DocumentImpl(browsingContext, address, null, null, true, contentType, parser);
        }

        return document;
    }

    /**
     * Constructs new instance of the blank HTML Document for specified browsing context
     * 
     * @param browsingContext Associated browsing context to which belongs this instance of the Document.
     * @return New blank Document object.
     * 
     * @see #Html5DocumentImpl(BrowsingContext, URL, Set, String, boolean, String, Html5DocumentParser)
     */
    public static Html5DocumentImpl createBlankDocument(BrowsingContext browsingContext) {
        String refferer = null;

        if (browsingContext.hasCreatorDocument()) {
            refferer = browsingContext.getCreatorDocument().getURL();
        }

        Html5DocumentImpl document = new Html5DocumentImpl(browsingContext, DEFAULT_URL, null, refferer, true,
                "text/html", null);

        document.setInputEncoding("UTF-8");

        Element htmlElement = document.createElement("html");
        Element headElement = document.createElement("head");
        Element bodyElement = document.createElement("body");

        htmlElement.appendChild(headElement);
        htmlElement.appendChild(bodyElement);
        document.appendChild(htmlElement);

        return document;
    }

    /**
     * Constructs new instance of the sandboxed HTML Document for specified browsing context
     * 
     * @param browsingContext Associated browsing context to which belongs this instance of the Document.
     * @return New sandboxed Document object.
     * 
     * @see #Html5DocumentImpl(BrowsingContext, URL, Set, String, boolean, String, Html5DocumentParser)
     */
    public static Html5DocumentImpl createSandboxedDocument(BrowsingContext browsingContext) {
        Set<SandboxingFlag> sandboxingFlagSet = new HashSet<SandboxingFlag>();
        Html5DocumentImpl document = new Html5DocumentImpl(browsingContext, DEFAULT_URL, sandboxingFlagSet, null,
                true, "text/html", null);

        return document;
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    @Override
    public Text createTextNode(String data) {
        return super.createTextNode(data);
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptGetter
    @Override
    public synchronized HTMLElement getBody() {
        return super.getBody();
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    @Override
    public synchronized Element getElementById(String elementId) {
        return super.getElementById(elementId);
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    @Override
    public Event createEvent(String type) throws DOMException {
        return super.createEvent(type);
    }

    /*
     * FIXME: Remove @ScriptFunction, use Document adapter instead for exposing into the script.
     */
    @ScriptFunction
    @Override
    public Element createElement(String tagName) throws DOMException {
        tagName = tagName.toLowerCase(Locale.ENGLISH);

        if (tagName.equals(SCRIPT_TAG_NAME)) {
            return new Html5ScriptElementImpl(this, tagName);
        } else if (tagName.equals(IFRAME_TAG_NAME)) {
            return new Html5IFrameElementImpl(this, tagName);
        }

        return super.createElement(tagName);
    }

    @ScriptGetter
    @Override
    public synchronized String getTitle() {
        return super.getTitle();
    }

    /**
     * Returns associated browsing context.
     * 
     * @return Associated browsing context.
     */
    public BrowsingContext getBrowsingContext() {
        return _browsingContext;
    }

    /**
     * Returns base element if there is any inside Document.
     * 
     * @return Base element if there is any inside Document.
     */
    public HTMLBaseElement getBaseElement() {
        // FIXME: We should not wait for completeness, but try to get base address from actual loaded elements - proper synchronized section is needed.
        if (_documentReadiness == DocumentReadyState.COMPLETE) {
            HTMLElement head = getHead();
            NodeList baseElements = head.getElementsByTagName("base");

            if (baseElements.getLength() > 0) {
                Node baseElement = baseElements.item(0);

                if (baseElement instanceof HTMLBaseElement) {
                    return (HTMLBaseElement) baseElement;
                }
            }
        }

        return null;
    }

    /**
     * Returns fallback base address.
     * 
     * @return Fallback base address.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#fallback-base-url">Fallback base URL</a>
     */
    public URL getFallbackBaseAddress() {

        /* 1) If the Document is an iframe srcdoc document, then return the document base URL of the 
         * Document's browsing context's browsing context container's Document and abort these steps.
         */

        IFrameBrowsingContext iframeContext = (_browsingContext instanceof IFrameBrowsingContext)
                ? (IFrameBrowsingContext) _browsingContext
                : null;
        Element _iframeElement = (iframeContext != null) ? iframeContext.getContainer() : null;
        Html5IFrameElementImpl iframeElement = (_iframeElement instanceof Html5IFrameElementImpl)
                ? (Html5IFrameElementImpl) _iframeElement
                : null;
        if (iframeElement != null && iframeElement.getSrcdoc() != null) {
            Document _iframeDocument = iframeElement.getOwnerDocument();
            Html5DocumentImpl iframeDocument = (_iframeDocument instanceof Html5DocumentImpl)
                    ? (Html5DocumentImpl) _iframeDocument
                    : null;

            if (iframeDocument != null) {
                URL url = iframeDocument.getBaseAddress();
                return url;
            }
        }

        /*
         * 2) If the document's address is about:blank, and the Document's browsing context has a creator 
         * browsing context, then return the document base URL of the creator Document, and abort these steps.
         */

        if (_browsingContext != null) {
            Html5DocumentImpl creatorDocument = _browsingContext.getCreatorDocument();
            if (_address != null && _address.equals(DEFAULT_URL) && creatorDocument != null) {
                URL url = creatorDocument.getBaseAddress();
                return url;
            }
        }

        /*
         * 3) Return the document's address.
         */
        return _address;
    }

    /**
     * Returns Document base address.
     * 
     * @return Document base address.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#document-base-url">Document base URL</a>
     */
    public URL getBaseAddress() {
        HTMLBaseElement baseElement = getBaseElement();
        URL baseURL = null;
        try {
            baseURL = (baseElement != null) ? new URL(baseElement.getHref()) : null;
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }

        /*
         * 1) If there is no base element that has an href attribute in the Document, 
         * then the document base URL is the Document's fallback base URL
         */
        if (baseURL == null) {
            return getFallbackBaseAddress();
        }

        /*
         * TODO?: Implement frozen URL?
         * http://www.w3.org/html/wg/drafts/html/master/document-metadata.html#frozen-base-url
         */
        /*
         * 2) Otherwise, the document base URL is the frozen base URL of the first 
         * base element in the Document that has an href attribute, in tree order.
         */
        return baseURL;

    }

    /**
     * Tests whether this Document is fully active.
     * 
     * @return True if this Document is fully active, otherwise false.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#fully-active">Fully active</a>
     */
    public boolean isFullyActive() {
        if (_browsingContext == null) {
            return false;
        }

        /*
         * A Document is said to be fully active when it is the active document of its 
         * browsing context, and either its browsing context is a top-level browsing 
         * context, or it has a parent browsing context and the Document through 
         * which it is nested is itself fully active.
         */
        boolean fullyActive = true;
        BrowsingContext parentContext = _browsingContext.getParentContext();

        fullyActive = fullyActive && _browsingContext.getActiveDocument() == this;
        fullyActive = fullyActive && _browsingContext.isTopLevelBrowsingContext();

        if (parentContext != null) {
            fullyActive = fullyActive
                    || (parentContext != null && parentContext.getActiveDocument().isFullyActive());
        }

        return fullyActive;
    }

    /**
     * Tests whether this Document is the active document of the associated browsing context.
     * 
     * @return True if this Document is the active document of the associated browsing context, otherwise false.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#active-document">Active document</a>
     */
    public boolean isActiveDocument() {
        return (_browsingContext != null) ? _browsingContext.getActiveDocument() == this : false;
    }

    /**
     * Returns document family of this document.
     * 
     * @return Document family of this document.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#document-family">The document family</a>
     */
    public Collection<Html5DocumentImpl> getDocumentFamily() {
        if (_browsingContext == null) {
            return new ArrayList<Html5DocumentImpl>();
        }

        /*
         * The document family of a Document object consists of the union of all 
         * the document families of the browsing contexts that are nested through the Document object.
         */
        Set<Html5DocumentImpl> family = new HashSet<Html5DocumentImpl>();
        Collection<BrowsingContext> nestedContexts = _browsingContext.getNestedContexts();

        for (BrowsingContext context : nestedContexts) {
            family.addAll(context.getDocumentFamily());
        }

        return family;
    }

    /**
     * Returns active sandboxing flag set.
     * 
     * @return active sandboxing flag set
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#active-sandboxing-flag-set">Active sandboxing flag set</a>
     */
    public Set<SandboxingFlag> getActiveSandboxingFlagSet() {
        return Collections.unmodifiableSet(_activeSandboxingFlagSet);
    }

    /**
     * Sets new active sandboxing flag set.
     * 
     * @param flags New active sandboxing flag set
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#active-sandboxing-flag-set">Active sandboxing flag set</a>
     */
    public void setActiveSandboxingFlags(Collection<SandboxingFlag> flags) {
        if (flags == null) {
            return;
        }

        for (SandboxingFlag flag : flags) {
            setActiveSandboxingFlag(flag);
        }
    }

    /**
     * Adds new flag into active sandboxing flag set.
     * 
     * @param flag New flag to be added into active sandboxing flag set.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#active-sandboxing-flag-set">Active sandboxing flag set</a>
     */
    public void setActiveSandboxingFlag(SandboxingFlag flag) {
        _activeSandboxingFlagSet.add(flag);

        DocumentOrigin documentOrigin = null;
        DocumentOrigin effectiveScriptOrigin = null;

        if (flag.equals(SandboxingFlag.ORIGIN_BROWSING_CONTEXT_FLAG)) {
            documentOrigin = DocumentOrigin.createUnique(this);
            effectiveScriptOrigin = DocumentOrigin.create(this, documentOrigin);
        }

        _originContainer = new OriginContainer<DocumentOrigin>(documentOrigin, effectiveScriptOrigin);
    }

    /**
     * Sets latest session history entry.
     * 
     * @param entry New latest session history entry
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#latest-entry">Latest entry</a>
     */
    public void setLatestEntry(SessionHistoryEntry entry) {
        _latestEntry = entry;
    }

    /**
     * Returns latest session history entry.
     * 
     * @return Latest session history entry
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#latest-entry">Latest entry</a>
     */
    public SessionHistoryEntry getLatestEntry() {
        return _latestEntry;
    }

    /**
     * Sets Document's address.
     * 
     * @param address New address with which should be associated this Document
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#the-document%27s-address">The document's address</a>
     */
    public void setAddress(URL address) {
        _address = address;

        _location.onAddressChanged();

        fireHtmlDocumentEvent(EventType.ADDRESS_CHANGED);
    }

    /**
     * Returns Document's address.
     * 
     * @return Address with which is associated this Document
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#the-document%27s-address">The document's address</a>
     */
    public URL getAddress() {
        return _address;
    }

    /**
     * Returns Document's address.
     * 
     * @return Address with which is associated this Document
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#the-document%27s-address">The document's address</a>
     */
    @Override
    public String getURL() {
        return _address.toExternalForm();
    }

    /**
     * Returns Document's referrer.
     * 
     * @return Refferer address
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#the-document%27s-referrer">The document's referrer</a>
     */
    @Override
    public String getReferrer() {
        return _referrer;
    }

    /**
     * Sets new fragment component inside address.
     * 
     * @param fragment New fragment component.
     */
    public void setAddressFragment(String fragment) {
        _address = URLUtilsHelper.setComponent(_address, UrlComponent.REF, fragment);
    }

    /**
     * Returns origin container.
     * 
     * @return Origin container.
     */
    public OriginContainer<DocumentOrigin> getOriginContainer() {
        return _originContainer;
    }

    /**
     * Returns origin of this document.
     * 
     * @return Origin of this document.
     */
    public Origin<?> getOrigin() {
        return _originContainer.getOrigin();
    }

    /**
     * Returns effective script origin.
     * 
     * @return Effective script origin.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#effective-script-origin">Effective script origin</a>
     */
    public Origin<?> getEffectiveScriptOrigin() {
        return _originContainer.getEffectiveScriptOrigin();
    }

    /**
     * Implements sandboxing for this Document.
     * 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#implement-the-sandboxing">Implement the sandboxing for a Document</a>
     */
    public void implementSandboxing() {
        if (_browsingContext instanceof WindowBrowsingContext) {
            setActiveSandboxingFlags(((WindowBrowsingContext) _browsingContext).getPopupSandboxingFlagSet());
        }

        if (_browsingContext instanceof IFrameBrowsingContext) {
            setActiveSandboxingFlags(((IFrameBrowsingContext) _browsingContext).getIframeSandboxingFlagSet());
        }

        if (_browsingContext.isNestedBrowsingContext()) {
            setActiveSandboxingFlags(
                    _browsingContext.getCreatorContext().getActiveDocument().getActiveSandboxingFlagSet());
        }

        //TODO: The flags set on the Document's resource's forced sandboxing flag set, if it has one.
    }

    /*
     * TODO: Implement
     */
    /**
     * Prompts to unload this Document.
     * 
     * @return True if prompt was submitted, otherwise false. 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#prompt-to-unload-a-document">Prompt to unload a document</a>
     */
    public boolean promptToUnload() {
        return true;
    }

    /*
     * TODO: Implement
     */
    /**
     * Tests whether is prompt to unload a document currently running.
     * 
     * @return True if prompt to unload a document currently running, otherwise false.
     * @see #promptToUnload()
     */
    public boolean isPromptToUnloadRunning() {
        return false;
    }

    /*
     * TODO: Implement
     */
    /**
     * Tests whether is unload of this document currently running.
     * 
     * @return True if unload of this document is currently running, otherwise false.
     * @see #unload(boolean)
     */
    public boolean isUnloadRunning() {
        return false;
    }

    /*
     * TODO: Implement
     */
    /**
     * Unloads this Document.
     * 
     * @param recycle Specifies whether recycle this document.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#unload-a-document">Unload a document</a>
     */
    public void unload(boolean recycle) {
        synchronized (this) {
            unloadTask = getEventLoop().getRunningTask();
        }

        unloaded = true;

        _pageShowingFlag = false;

        if (!_firedUnloadFlag) {
            _window.fireSimpleEvent("unload", _window);
            //_firedUnloadFlag = true;
        }

        /*
         * TODO: Implement
         */

        synchronized (this) {
            unloadTask = null;
        }
    }

    /**
     * Returns task which invoked unloading of this document.
     * 
     * @return Task which invoked unloading of this document.
     */
    public synchronized Task getUnloadTask() {
        return unloadTask;
    }

    /**
     * Returns {@link Window} to which is associated this Document object.
     * 
     * @return {@link Window} object.
     */
    public Window getWindow() {
        return _window;
    }

    /**
     * Tests whether is fullscreen enabled flag set.
     * 
     * @return True if is fullscreen enabled flag set, otherwise false.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#fullscreen-enabled-flag">Fullscreen enabled flag</a>
     */
    public boolean isFullscreenEnabledFlag() {
        return _fullscreenEnabledFlag;
    }

    /**
     * Sets fullscreen enabled flag to passed value.
     * 
     * @param value Boolean value to be fullscreen enabled flag set to.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#fullscreen-enabled-flag">Fullscreen enabled flag</a>
     */
    public void setEnableFullscreenFlag(boolean value) {
        _fullscreenEnabledFlag = value;
    }

    /**
     * Returns content type of this document.
     * 
     * @return Content type of this document.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#concept-document-content-type">Content type</a>
     */
    public String getContentType() {
        return _contentType;
    }

    /**
     * Returns document's readiness.
     * 
     * @return Document's readiness.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#current-document-readiness">Document readiness</a>
     */
    public DocumentReadyState getDocumentReadiness() {
        return _documentReadiness;
    }

    /**
     * Sets document's readiness.
     * 
     * @param readiness New value of the Document's readiness.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/dom.html#current-document-readiness">Document readiness</a>
     */
    public void setDocumentReadiness(DocumentReadyState readiness) {
        _documentReadiness = readiness;
    }

    /**
     * Tests whether has this Document the default address.
     * 
     * @return True if this document has default address, otherwise false.
     * @see #DEFAULT_URL
     */
    public boolean hasDefaultAddress() {
        return _address == null || _address.equals(DEFAULT_URL);
    }

    /**
     * Returns associated parser.
     * 
     * @return Associated parser.
     */
    public Html5DocumentParser getParser() {
        return _parser;
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    @Override
    public void addEventListener(String type, EventListener listener) {
        addEventListener(type, listener, false);
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    @Override
    public void addEventListener(String type, EventListener listener, boolean useCapture) {
        super.addEventListener(type, listener, useCapture);
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    @Override
    public void removeEventListener(String type, EventListener listener) {
        removeEventListener(type, listener, false);
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    @Override
    public void removeEventListener(String type, EventListener listener, boolean useCapture) {
        super.removeEventListener(type, listener, useCapture);
    }

    /*
     * FIXME: Only for testing purposes, use Document adapter instead 
     *        for exposing into the script and then remove this method.
     */
    @ScriptFunction
    public boolean dispatchEvent(Event event) {
        return super.dispatchEvent(event);
    }

    /* 
     * TODO: Implement discarding of the documents.
     */
    /**
     * Discards this document.
     * 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#discard-a-document">Discard a document</a>
     */
    public void discard() {
        _browsingContext = null;
    }

    /**
     * Returns associated event loop.
     * 
     * @return Associated event loop.
     */
    public EventLoop getEventLoop() {
        return (_browsingContext != null) ? _browsingContext.getEventLoop() : null;
    }

    /**
     * Returns serialized Document which was retrieved from some resource and parsed into this Document.
     * 
     * @return Serialized Document (HTML source code).
     */
    public String getParserSource() {
        return (_parser != null) ? _parser.getParserSource() : null;
    }

    /**
     * Dispatches event above Window object, which is then propagated into 
     * this document if Window was not the target.
     */
    @Override
    protected boolean dispatchEvent(NodeImpl node, Event event) {
        if (!(event instanceof EventImpl)) {
            return false;
        }
        EventImpl evt = (EventImpl) event;

        // Initialize
        evt.target = node;
        evt.stopPropagation = false;
        evt.preventDefault = false;

        // Window capture phase
        evt.currentTarget = _window;
        evt.eventPhase = Event.CAPTURING_PHASE;
        _window.dispatchEventFromDocument(evt);

        if (!evt.stopPropagation) {
            super.dispatchEvent(node, evt);
        }

        // Window bubble phase
        if (!evt.stopPropagation && evt.bubbles) {
            evt.currentTarget = _window;
            evt.eventPhase = Event.BUBBLING_PHASE;
            _window.dispatchEventFromDocument(evt);
        }

        return evt.preventDefault;
    }

    /**
     * Returns default document view of this document - returns window proxy object.
     * 
     * @return default Document view of this document.
     */
    @Override
    public AbstractView getDefaultView() {
        return (_browsingContext != null) ? _browsingContext.getWindowProxy() : null;
    }

    /*
     * TODO: Implement.
     */
    /**
     * Runs unloading document cleanup steps
     * 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#unloading-document-cleanup-steps">Unloading document cleanup steps</a>
     */
    public void runUnloadingDocumentCleanupSteps() {

    }

    /**
     * Aborts a document
     * 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#abort-a-document">Abort a document</a>
     */
    @Override
    public void abort() {
        if (_browsingContext != null) {

            if (_browsingContext.getActiveDocument() == this) {
                _window.dispatchSimpleEvent("abort", _window);
            }

            Collection<BrowsingContext> contexts = _browsingContext.getNestedContexts();

            for (BrowsingContext context : contexts) {
                Html5DocumentImpl activeDocument = context.getActiveDocument();
                activeDocument.abort();

                if (!activeDocument.isSalvageableFlag()) {
                    _salvageableFlag = false;
                }
            }

            /*
             * TODO:
             * Cancel any instances of the fetch algorithm in the context of this Document, 
             * discarding any tasks queued for them, and discarding any further data received 
             * from the network for them. If this resulted in any instances of the fetch algorithm 
             * being canceled or any queued tasks or any network data getting discarded, then set the 
             * Document's salvageable state to false.
             */

            if (_parser != null && _parser.isActive()) {
                _parser.abort();
                _salvageableFlag = false;
            }
        } else {
            _salvageableFlag = false;
        }

    }

    /**
     * Tests whether this Document has salvageable flag set.
     * 
     * @return True if this Document has salvageable flag set, otherwise false.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#concept-document-salvageable">Salvageable flag</a>
     */
    public boolean isSalvageableFlag() {
        return _salvageableFlag;
    }

    /**
     * Sets salvageable flag to a given value.
     * 
     * @param salvageableFlag New value of the flag.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#concept-document-salvageable">Salvageable flag</a>
     */
    public void setSalvageableFlag(boolean salvageableFlag) {
        this._salvageableFlag = salvageableFlag;
    }

    /**
     * Tests whether this Document has fired unload flag set.
     * 
     * @return True if this Document has fired unload flag set, otherwise false.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#fired-unload">Fired unload flag</a>
     */
    public boolean isFiredUnloadFlag() {
        return _firedUnloadFlag;
    }

    /**
     * Sets fired unload flag to a given value.
     * 
     * @param firedUnloadFlag New value of the flag.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#fired-unload">Fired unload flag</a>
     */
    public void setFiredUnloadFlag(boolean firedUnloadFlag) {
        this._firedUnloadFlag = firedUnloadFlag;
    }

    /**
     * Tests whether this Document has page showing flag set.
     * 
     * @return True if this Document has page showing flag set, otherwise false.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#page-showing">Page showing flag</a>
     */
    public boolean isPageShowingFlag() {
        return _pageShowingFlag;
    }

    /**
     * Sets page showing flag to a given value.
     * 
     * @param pageShowingFlag New value of the flag.
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/browsers.html#page-showing">Page showing flag</a>
     */
    public void setPageShowingFlag(boolean pageShowingFlag) {
        this._pageShowingFlag = pageShowingFlag;
    }

    /**
     * Increments ignore-destructive-writes counter.
     * 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/webappapis.html#ignore-destructive-writes-counter">Ignore-destructive-writes counter</a>
     */
    public synchronized void incrementIgnoreDestructiveWritesCounter() {
        _ignoreDestructiveWritesCounter++;
    }

    /**
     * Decrements ignore-destructive-writes counter.
     * 
     * @see <a href="http://www.w3.org/html/wg/drafts/html/master/webappapis.html#ignore-destructive-writes-counter">Ignore-destructive-writes counter</a>
     */
    public synchronized void decrementIgnoreDestructiveWritesCounter() {
        _ignoreDestructiveWritesCounter--;
    }

    public synchronized boolean isUnloaded() {
        return unloaded;
    }

    @ScriptFunction
    @Override
    public String toString() {
        return super.toString();
    }

    /**
     * Returns associated history.
     * 
     * @return Associated history.
     */
    public History getHistory() {
        return _history;
    }

    /**
     * Returns associated location.
     * 
     * @return Associated location.
     */
    public Location getLocation() {
        return _location;
    }

    /*
     * TODO: Implement.
     */
    /**
     * .
     * 
     * @see <a href="https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#dom-document-exitfullscreen">Exit fullscreen</a>
     */
    public void exitFullscreen() {

    }

    /*
     * TODO: Implement.
     */
    /**
     * Fully exits fullscreen.
     * 
     * @see <a href="https://dvcs.w3.org/hg/fullscreen/raw-file/tip/Overview.html#fully-exit-fullscreen">Fully exit fullscreen</a>
     */
    public void fullyExitFullscreen() {

    }

    /**
     * Reports document's script error.
     * 
     * @see <a href=http://www.w3.org/html/wg/drafts/html/CR/webappapis.html#runtime-script-errors-in-documents">Runtime script errors in documents</a>
     */
    public void reportScriptError(Script<?, ?, ?> script) {
        if (errorReportingMode) {
            return;
        }
        ScriptException scriptException = script.getException();
        Throwable rootException = ExceptionUtils.getRootCause(scriptException);

        if (scriptException == null) {
            return;
        }

        errorReportingMode = true;

        int lineno = scriptException.getLineNumber();
        int colno = scriptException.getColumnNumber();
        ;
        String message = rootException.getMessage();
        String location = _address.toExternalForm();
        Object errorObject = rootException; // TODO: Here should be an Error object

        if (script.hasMutedErros()) {
            message = "Script error.";
        }

        ErrorEvent errorEvent = new ErrorEvent();

        errorEvent.initEvent("error", false, true, message, location, lineno, colno, errorObject);

        errorReportingMode = false;

        _window.dispatchEvent(errorEvent);
    }

    /**
     * Fires HTML document event with the specified event type.
     * 
     * @param eventType Type of the event to be fired.
     */
    protected void fireHtmlDocumentEvent(EventType eventType) {
        if (listeners.isEmpty()) {
            return;
        }

        Html5DocumentEvent event = new Html5DocumentEvent(this, eventType);
        Set<Html5DocumentEventListener> listenersCopy = new HashSet<Html5DocumentEventListener>(listeners);

        for (Html5DocumentEventListener listener : listenersCopy) {
            listener.onDocumentEvent(event);
        }
    }
}