/* * GNU LESSER GENERAL PUBLIC LICENSE Copyright (C) 2006 The Lobo Project * * This library 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 2.1 of * the License, or (at your option) any later version. * * This library 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 this library; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA * * Contact info: xamjadmin@users.sourceforge.net */ /* * Created on Sep 3, 2005 */ package org.cobra_grendel.html.domimpl; import java.io.IOException; import java.io.LineNumberReader; import java.io.Reader; import java.io.StringReader; import java.io.UnsupportedEncodingException; import java.net.MalformedURLException; import java.net.URL; import java.security.AccessController; import java.security.PrivilegedAction; import java.util.ArrayList; import java.util.HashMap; import java.util.Iterator; import java.util.Map; import java.util.logging.Level; import java.util.logging.Logger; import org.cobra_grendel.html.HtmlRendererContext; import org.cobra_grendel.html.HttpRequest; import org.cobra_grendel.html.ReadyStateChangeListener; import org.cobra_grendel.html.UserAgentContext; import org.cobra_grendel.html.io.WritableLineReader; import org.cobra_grendel.html.js.Executor; import org.cobra_grendel.html.js.Location; import org.cobra_grendel.html.js.Window; import org.cobra_grendel.html.parser.HtmlParser; import org.cobra_grendel.util.Domains; import org.cobra_grendel.util.Urls; import org.cobra_grendel.util.WeakValueHashMap; import org.cobra_grendel.util.io.EmptyReader; import org.mozilla.javascript.Function; import org.w3c.dom.Attr; import org.w3c.dom.CDATASection; import org.w3c.dom.Comment; import org.w3c.dom.DOMConfiguration; import org.w3c.dom.DOMException; import org.w3c.dom.DOMImplementation; import org.w3c.dom.DocumentFragment; import org.w3c.dom.DocumentType; import org.w3c.dom.Element; import org.w3c.dom.EntityReference; import org.w3c.dom.Node; import org.w3c.dom.NodeList; import org.w3c.dom.ProcessingInstruction; import org.w3c.dom.Text; import org.w3c.dom.UserDataHandler; import org.w3c.dom.html2.HTMLCollection; import org.w3c.dom.html2.HTMLDocument; import org.w3c.dom.html2.HTMLElement; import org.xml.sax.ErrorHandler; import org.xml.sax.SAXException; public class HTMLDocumentImpl extends NodeImpl implements HTMLDocument { private class AnchorFilter implements NodeFilter { @Override public boolean accept(final Node node) { String nodeName = node.getNodeName(); return "A".equalsIgnoreCase(nodeName) || "ANCHOR".equalsIgnoreCase(nodeName); } } private class AppletFilter implements NodeFilter { @Override public boolean accept(final Node node) { // TODO: "OBJECT" elements that are applets too. return "APPLET".equalsIgnoreCase(node.getNodeName()); } } private class ElementFilter implements NodeFilter { public ElementFilter() { } @Override public boolean accept(final Node node) { return node instanceof Element; } } private class ElementNameFilter implements NodeFilter { private final String name; public ElementNameFilter(final String name) { this.name = name; } @Override public boolean accept(final Node node) { // TODO: Case sensitive? return node instanceof Element && name.equals(((Element) node).getAttribute("name")); } } private class FormFilter implements NodeFilter { @Override public boolean accept(final Node node) { String nodeName = node.getNodeName(); return "FORM".equalsIgnoreCase(nodeName); } } private class FrameFilter implements NodeFilter { @Override public boolean accept(final Node node) { return node instanceof org.w3c.dom.html2.HTMLFrameElement || node instanceof org.w3c.dom.html2.HTMLIFrameElement; } } private class ImageFilter implements NodeFilter { @Override public boolean accept(final Node node) { return "IMG".equalsIgnoreCase(node.getNodeName()); } } private static class ImageInfo { // Access to this class is synchronized on imageInfos. public ImageEvent imageEvent; public boolean loaded; private final ArrayList listeners = new ArrayList(1); void addListener(final ImageListener listener) { listeners.add(listener); } ImageListener[] getListeners() { return (ImageListener[]) listeners.toArray(ImageListener.EMPTY_ARRAY); } } private class LinkFilter implements NodeFilter { @Override public boolean accept(final Node node) { String nodeName = node.getNodeName(); return "LINK".equalsIgnoreCase(nodeName); } } /** * Tag class that also notifies document when text is written to an open buffer. * * @author J. H. S. */ private class LocalWritableLineReader extends WritableLineReader { /** * @param reader */ public LocalWritableLineReader(final LineNumberReader reader) { super(reader); } /** * @param reader */ public LocalWritableLineReader(final Reader reader) { super(reader); } @Override public void write(final String text) throws IOException { super.write(text); if ("".equals(text)) { openBufferChanged(text); } } } private class TagNameFilter implements NodeFilter { private final String name; public TagNameFilter(final String name) { this.name = name; } @Override public boolean accept(final Node node) { if (!(node instanceof Element)) { return false; } String n = name; return n.equalsIgnoreCase(((Element) node).getTagName()); } } /** * */ private static final long serialVersionUID = 1L; private static final Logger logger = Logger.getLogger(HTMLDocumentImpl.class.getName()); private HTMLCollection anchors; private HTMLCollection applets; private volatile String baseURI; private final ImageEvent BLANK_IMAGE_EVENT = new ImageEvent(this, null); private HTMLElement body; private String defaultTarget; private DocumentType doctype; // // private final Collection styleSheets = new LinkedList(); // // final void addStyleSheet(CSSStyleSheet ss) { // synchronized(this.treeLock) { // this.styleSheets.add(ss); // // this.styleSheetAggregator = null; // // Need to invalidate all children up to // // this point. // // this.forgetRenderState(); // //TODO: this might be ineffcient. // ArrayList nl = this.nodeList; // if(nl != null) { // Iterator i = nl.iterator(); // while(i.hasNext()) { // Object node = i.next(); // if(node instanceof HTMLElementImpl) { // // ((HTMLElementImpl) node).forgetStyle(true); // } // } // } // } // this.allInvalidated(); // } // /* * private StyleSheetAggregator styleSheetAggregator = null; * * final StyleSheetAggregator getStyleSheetAggregator() { synchronized(this.treeLock) { StyleSheetAggregator ssa = this.styleSheetAggregator; if(ssa == null) { ssa = new * StyleSheetAggregator(this); try { ssa.addStyleSheets(this.styleSheets); } catch(MalformedURLException mfu) { logger.log(Level.WARNING,"getStyleSheetAggregator()", mfu); } * this.styleSheetAggregator = ssa; } return ssa; } } */ private final ArrayList documentNotificationListeners = new ArrayList(1); private String documentURI; private java.net.URL documentURL; private String domain; private DOMConfiguration domConfig; private DOMImplementation domImplementation; private final Map elementsById = new WeakValueHashMap(); private final Map elementsByName = new HashMap(0); private final ElementFactory factory; private HTMLCollection forms; private HTMLCollection frames; private final Map imageInfos = new HashMap(4); private HTMLCollection images; private String inputEncoding; private HTMLCollection links; private Function onloadHandler; private final HtmlRendererContext rcontext; private WritableLineReader reader; private String referrer; private boolean strictErrorChecking = true; private String title; private final UserAgentContext ucontext; private final Window window; private String xmlEncoding; private boolean xmlStandalone; private String xmlVersion = null; private String xssToken; public HTMLDocumentImpl(final HtmlRendererContext rcontext, final int transactionId) { this(rcontext.getUserAgentContext(), rcontext, null, null, transactionId); } public HTMLDocumentImpl(final UserAgentContext ucontext, final HtmlRendererContext rcontext, final WritableLineReader reader, final String documentURI, final int transactionId) { super(transactionId); factory = ElementFactory.getInstance(); this.rcontext = rcontext; this.ucontext = ucontext; this.reader = reader; this.documentURI = documentURI; try { java.net.URL docURL = new java.net.URL(documentURI); SecurityManager sm = System.getSecurityManager(); if (sm != null) { // Do not allow creation of HTMLDocumentImpl if there's // no permission to connect to the host of the URL. // This is so that cookies cannot be written arbitrarily // with setCookie() method. sm.checkPermission(new java.net.SocketPermission(docURL.getHost(), "connect")); } documentURL = docURL; domain = docURL.getHost(); } catch (java.net.MalformedURLException mfu) { logger.warning("HTMLDocumentImpl(): Document URI [" + documentURI + "] is malformed."); } document = this; // Get Window object Window window; if (rcontext != null) { window = Window.getWindow(rcontext); } else { // Plain parsers may use Javascript too. window = new Window(null, ucontext); } // Window must be retained or it will be garbage collected. this.window = window; window.setDocument(this); // Set up Javascript scope setUserData(Executor.SCOPE_KEY, window.getWindowScope(), null); } public HTMLDocumentImpl(final UserAgentContext ucontext, final int transactionId) { this(ucontext, null, null, null, transactionId); } public void addDocumentNotificationListener(final DocumentNotificationListener listener) { ArrayList listenersList = documentNotificationListeners; synchronized (listenersList) { listenersList.add(listener); } } @Override public Node adoptNode(final Node source) throws DOMException { if (source instanceof NodeImpl) { NodeImpl node = (NodeImpl) source; node.setOwnerDocument(this, true); return node; } else { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Invalid Node implementation"); } } public void allInvalidated() { ArrayList listenersList = documentNotificationListeners; int size; synchronized (listenersList) { size = listenersList.size(); } // Traverse list outside synchronized block. // (Shouldn't call listener methods in synchronized block. // Deadlock is possible). But assume list could have // been changed. for (int i = 0; i < size; i++) { try { DocumentNotificationListener dnl = (DocumentNotificationListener) listenersList.get(i); dnl.allInvalidated(); } catch (IndexOutOfBoundsException iob) { // ignore } } } @Override public void close() { synchronized (treeLock) { if (reader instanceof LocalWritableLineReader) { try { reader.close(); } catch (java.io.IOException ioe) { // ignore } reader = null; } else { // do nothing - could be parsing document off the web. } // TODO: cause it to render } } @Override public Attr createAttribute(final String name) throws DOMException { return new AttrImpl(name, transactionId); } @Override public Attr createAttributeNS(final String namespaceURI, final String qualifiedName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "HTML document"); } @Override public CDATASection createCDATASection(final String data) throws DOMException { CDataSectionImpl node = new CDataSectionImpl(data, transactionId); node.setOwnerDocument(this); return node; } @Override public Comment createComment(final String data) { CommentImpl node = new CommentImpl(data, transactionId); node.setOwnerDocument(this); return node; } /* * (non-Javadoc) * * @see org.w3c.dom.Document#createDocumentFragment() */ @Override public DocumentFragment createDocumentFragment() { // TODO: According to documentation, when a document // fragment is added to a node, its children are added, // not itself. DocumentFragmentImpl node = new DocumentFragmentImpl(transactionId); node.setOwnerDocument(this); return node; } @Override public Element createElement(final String tagName) throws DOMException { return factory.createElement(this, tagName, transactionId); } @Override public Element createElementNS(final String namespaceURI, final String qualifiedName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "HTML document"); } @Override public EntityReference createEntityReference(final String name) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "HTML document"); } @Override public ProcessingInstruction createProcessingInstruction(final String target, final String data) throws DOMException { HTMLProcessingInstruction node = new HTMLProcessingInstruction(target, data, transactionId); node.setOwnerDocument(this); return node; } @Override protected Node createSimilarNode() { return new HTMLDocumentImpl(ucontext, rcontext, reader, documentURI, transactionId); } @Override public Text createTextNode(final String data) { TextImpl node = new TextImpl(data, transactionId); node.setOwnerDocument(this); return node; } public void externalScriptLoading(final NodeImpl node) { ArrayList listenersList = documentNotificationListeners; int size; synchronized (listenersList) { size = listenersList.size(); } // Traverse list outside synchronized block. // (Shouldn't call listener methods in synchronized block. // Deadlock is possible). But assume list could have // been changed. for (int i = 0; i < size; i++) { try { DocumentNotificationListener dnl = (DocumentNotificationListener) listenersList.get(i); dnl.externalScriptLoading(node); } catch (IndexOutOfBoundsException iob) { // ignore } } } @Override public HTMLCollection getAnchors() { synchronized (this) { if (anchors == null) { anchors = new FilteredHTMLCollectionImpl(this, elementsById, new AnchorFilter(), treeLock, transactionId); } return anchors; } } @Override public HTMLCollection getApplets() { synchronized (this) { if (applets == null) { // TODO: Should include OBJECTs that are applets? applets = new FilteredHTMLCollectionImpl(this, elementsById, new AppletFilter(), treeLock, transactionId); } return applets; } } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getbaseURI() */ @Override public String getBaseURI() { String buri = baseURI; return buri == null ? documentURI : buri; } @Override public HTMLElement getBody() { synchronized (this) { return body; } } @Override public String getCookie() { SecurityManager sm = System.getSecurityManager(); if (sm != null) { return (String) AccessController.doPrivileged(new PrivilegedAction() { // Justification: A caller (e.g. Google Analytics script) // might want to get cookies from the parent document. // If the caller has access to the document, it appears // they should be able to get cookies on that document. // Note that this Document instance cannot be created // with an arbitrary URL. @Override public Object run() { return ucontext.getCookie(documentURL); } }); } else { return ucontext.getCookie(documentURL); } } public String getDefaultTarget() { return defaultTarget; } @Override public DocumentType getDoctype() { return doctype; } @Override public Element getDocumentElement() { synchronized (treeLock) { ArrayList nl = nodeList; if (nl != null) { Iterator i = nl.iterator(); while (i.hasNext()) { Object node = i.next(); if (node instanceof Element) { return (Element) node; } } } return null; } } String getDocumentHost() { URL docUrl = documentURL; return docUrl == null ? null : docUrl.getHost(); } @Override public String getDocumentURI() { return documentURI; } @Override public URL getDocumentURL() { // TODO: Security considerations? return documentURL; } @Override public String getDomain() { return domain; } @Override public DOMConfiguration getDomConfig() { synchronized (this) { if (domConfig == null) { domConfig = new DOMConfigurationImpl(); } return domConfig; } } @Override public Element getElementById(final String elementId) { Element element; synchronized (this) { element = (Element) elementsById.get(elementId); } return element; } /** * Gets the collection of elements whose <code>name</code> attribute is <code>elementName</code>. */ @Override public NodeList getElementsByName(final String elementName) { return getNodeList(new ElementNameFilter(elementName)); } /** * Gets all elements that match the given tag name. * * @param tagname * The element tag name or an asterisk character (*) to match all elements. */ @Override public NodeList getElementsByTagName(final String tagname) { if ("*".equals(tagname)) { return getNodeList(new ElementFilter()); } else { return getNodeList(new TagNameFilter(tagname)); } } @Override public NodeList getElementsByTagNameNS(final String namespaceURI, final String localName) { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "HTML document"); } @Override public HTMLCollection getForms() { synchronized (this) { if (forms == null) { forms = new FilteredHTMLCollectionImpl(this, elementsById, new FormFilter(), treeLock, transactionId); } return forms; } } public HTMLCollection getFrames() { synchronized (this) { if (frames == null) { frames = new FilteredHTMLCollectionImpl(this, elementsById, new FrameFilter(), treeLock, transactionId); } return frames; } } @Override public final URL getFullURL(final String uri) { try { String baseURI = getBaseURI(); URL documentURL = baseURI == null ? null : new URL(baseURI); return Urls.createURL(documentURL, uri); } catch (MalformedURLException mfu) { // Try agan, without the baseURI. try { return new URL(uri); } catch (MalformedURLException mfu2) { logger.log(Level.WARNING, "Unable to create URL for URI=[" + uri + "], with base=[" + getBaseURI() + "].", mfu); return null; } } } @Override public final HtmlRendererContext getHtmlRendererContext() { return rcontext; } @Override public HTMLCollection getImages() { synchronized (this) { if (images == null) { images = new FilteredHTMLCollectionImpl(this, elementsById, new ImageFilter(), treeLock, transactionId); } return images; } } /* * (non-Javadoc) * * @see org.w3c.dom.Document#getImplementation() */ @Override public DOMImplementation getImplementation() { synchronized (this) { if (domImplementation == null) { domImplementation = new DOMImplementationImpl(ucontext, transactionId); } return domImplementation; } } @Override public String getInputEncoding() { return inputEncoding; } @Override public HTMLCollection getLinks() { synchronized (this) { if (links == null) { links = new FilteredHTMLCollectionImpl(this, elementsById, new LinkFilter(), treeLock, transactionId); } return links; } } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getLocalName() */ @Override public String getLocalName() { // Always null for document return null; } public final Location getLocation() { return window.getLocation(); } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeName() */ @Override public String getNodeName() { return "#document"; } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeType() */ @Override public short getNodeType() { return Node.DOCUMENT_NODE; } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#getNodeValue() */ @Override public String getNodeValue() throws DOMException { // Always null for document return null; } public Function getOnloadHandler() { return onloadHandler; } @Override public String getReferrer() { return referrer; } @Override public boolean getStrictErrorChecking() { return strictErrorChecking; } @Override public String getTextContent() throws DOMException { return null; } @Override public String getTitle() { return title; } public final int getTransactionId() { return transactionId; } @Override public String getURL() { return documentURI; } @Override public UserAgentContext getUserAgentContext() { return ucontext; } @Override public String getXmlEncoding() { return xmlEncoding; } @Override public boolean getXmlStandalone() { return xmlStandalone; } @Override public String getXmlVersion() { return xmlVersion; } public String getXssToken() { return xssToken; } @Override public Node importNode(final Node importedNode, final boolean deep) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "Not implemented"); } /** * This is called when the node has changed, but it is unclear if it's a size change or a look change. * * @param node */ public void invalidated(final NodeImpl node) { ArrayList listenersList = documentNotificationListeners; int size; synchronized (listenersList) { size = listenersList.size(); } // Traverse list outside synchronized block. // (Shouldn't call listener methods in synchronized block. // Deadlock is possible). But assume list could have // been changed. for (int i = 0; i < size; i++) { try { DocumentNotificationListener dnl = (DocumentNotificationListener) listenersList.get(i); dnl.invalidated(node); } catch (IndexOutOfBoundsException iob) { // ignore } } } /** * Loads the document from the reader provided when it was constructed. It then closes the reader. * * @throws IOException * @throws SAXException * @throws UnsupportedEncodingException */ public void load() throws IOException, SAXException, UnsupportedEncodingException { this.load(true); } public void load(final boolean closeReader) throws IOException, SAXException, UnsupportedEncodingException { WritableLineReader reader; synchronized (treeLock) { removeAllChildrenImpl(); setTitle(null); setBaseURI(null); setDefaultTarget(null); // this.styleSheets.clear(); // this.styleSheetAggregator = null; reader = this.reader; } if (reader != null) { try { ErrorHandler errorHandler = new LocalErrorHandler(); String systemId = documentURI; String publicId = systemId; HtmlParser parser = new HtmlParser(ucontext, this, errorHandler, publicId, systemId); parser.parse(reader); } finally { if (closeReader) { try { reader.close(); } catch (Exception err) { logger.log(Level.WARNING, "load(): Unable to close stream", err); } synchronized (treeLock) { this.reader = null; } } } } } /** * Loads images such that they are shared if from the same URI. Informs listener immediately if an image is already known. * * @param relativeUri * @param imageListener */ void loadImage(final String relativeUri, final ImageListener imageListener) { HtmlRendererContext rcontext = getHtmlRendererContext(); if (rcontext == null) { // Ignore image loading when there's no renderer context. imageListener.imageLoaded(BLANK_IMAGE_EVENT); return; } final URL url = getFullURL(relativeUri); if (url == null) { imageListener.imageLoaded(BLANK_IMAGE_EVENT); return; } final String urlText = url.toExternalForm(); final Map map = imageInfos; ImageEvent event = null; synchronized (map) { ImageInfo info = (ImageInfo) map.get(urlText); if (info != null) { if (info.loaded) { // TODO: This can't really happen because ImageInfo // is removed right after image is loaded. event = info.imageEvent; } else { info.addListener(imageListener); } } else { UserAgentContext uac = rcontext.getUserAgentContext(); final HttpRequest httpRequest = uac.createHttpRequest(transactionId); final ImageInfo newInfo = new ImageInfo(); map.put(urlText, newInfo); newInfo.addListener(imageListener); httpRequest.addReadyStateChangeListener(new ReadyStateChangeListener() { @Override public void readyStateChanged() { if (httpRequest.getReadyState() == HttpRequest.STATE_COMPLETE) { ImageEvent newEvent = new ImageEvent(HTMLDocumentImpl.this, httpRequest.getResponseImage()); ImageListener[] listeners; synchronized (map) { newInfo.imageEvent = newEvent; newInfo.loaded = true; listeners = newInfo.getListeners(); // Must remove from map in the locked block // that got the listeners. Otherwise a new // listener might miss the event?? map.remove(urlText); } int llength = listeners.length; for (int i = 0; i < llength; i++) { // Call holding no locks listeners[i].imageLoaded(newEvent); } } } }); SecurityManager sm = System.getSecurityManager(); if (sm == null) { httpRequest.open("GET", url, true); } else { AccessController.doPrivileged(new PrivilegedAction() { @Override public Object run() { // Code might have restrictions on accessing // items from elsewhere. httpRequest.open("GET", url, true); return null; } }); } } } if (event != null) { // Call holding no locks. imageListener.imageLoaded(event); } } /** * Called if something such as a color or decoration has changed. This would be something which does not affect the rendered size. * * @param node */ public void lookInvalidated(final NodeImpl node) { ArrayList listenersList = documentNotificationListeners; int size; synchronized (listenersList) { size = listenersList.size(); } // Traverse list outside synchronized block. // (Shouldn't call listener methods in synchronized block. // Deadlock is possible). But assume list could have // been changed. for (int i = 0; i < size; i++) { try { DocumentNotificationListener dnl = (DocumentNotificationListener) listenersList.get(i); dnl.lookInvalidated(node); } catch (IndexOutOfBoundsException iob) { // ignore } } } public Element namedItem(final String name) { Element element; synchronized (this) { element = (Element) elementsByName.get(name); } return element; } public void nodeLoaded(final NodeImpl node) { ArrayList listenersList = documentNotificationListeners; int size; synchronized (listenersList) { size = listenersList.size(); } // Traverse list outside synchronized block. // (Shouldn't call listener methods in synchronized block. // Deadlock is possible). But assume list could have // been changed. for (int i = 0; i < size; i++) { try { DocumentNotificationListener dnl = (DocumentNotificationListener) listenersList.get(i); dnl.nodeLoaded(node); } catch (IndexOutOfBoundsException iob) { // ignore } } } @Override public void normalizeDocument() { // TODO: Normalization options from domConfig synchronized (treeLock) { visitImpl(new NodeVisitor() { @Override public void visit(final Node node) { node.normalize(); } }); } } @Override public void open() { synchronized (treeLock) { if (reader != null) { if (reader instanceof LocalWritableLineReader) { try { reader.close(); } catch (IOException ioe) { // ignore } reader = null; } else { // Already open, return. // Do not close http/file documents in progress. return; } } removeAllChildrenImpl(); reader = new LocalWritableLineReader(new EmptyReader()); } } private void openBufferChanged(final String text) { // Assumed to execute in a lock // Assumed that text is not broken up HTML. ErrorHandler errorHandler = new LocalErrorHandler(); String systemId = documentURI; String publicId = systemId; HtmlParser parser = new HtmlParser(ucontext, this, errorHandler, publicId, systemId); StringReader strReader = new StringReader(text); try { // This sets up another Javascript scope Window. Does it matter? parser.parse(strReader); } catch (Exception err) { this.warn("Unable to parse written HTML text. BaseURI=[" + getBaseURI() + "].", err); } } /** * Changed if the position of the node in a parent has changed. * * @param node */ public void positionInParentInvalidated(final NodeImpl node) { ArrayList listenersList = documentNotificationListeners; int size; synchronized (listenersList) { size = listenersList.size(); } // Traverse list outside synchronized block. // (Shouldn't call listener methods in synchronized block. // Deadlock is possible). But assume list could have // been changed. for (int i = 0; i < size; i++) { try { DocumentNotificationListener dnl = (DocumentNotificationListener) listenersList.get(i); dnl.positionInvalidated(node); } catch (IndexOutOfBoundsException iob) { // ignore } } } public void removeDocumentNotificationListener(final DocumentNotificationListener listener) { ArrayList listenersList = documentNotificationListeners; synchronized (listenersList) { listenersList.remove(listener); } } void removeElementById(final String id) { synchronized (this) { elementsById.remove(id); } } void removeNamedItem(final String name) { synchronized (this) { elementsByName.remove(name); } } @Override public Node renameNode(final Node n, final String namespaceURI, final String qualifiedName) throws DOMException { throw new DOMException(DOMException.NOT_SUPPORTED_ERR, "No renaming"); } public void setBaseURI(final String value) { baseURI = value; } // protected RenderState createRenderState(RenderState prevRenderState) { // return new StyleSheetRenderState(null, null); // } @Override public void setBody(final HTMLElement body) { synchronized (this) { this.body = body; } } @Override public void setCookie(final String cookie) throws DOMException { SecurityManager sm = System.getSecurityManager(); if (sm != null) { AccessController.doPrivileged(new PrivilegedAction() { // Justification: A caller (e.g. Google Analytics script) // might want to set cookies on the parent document. // If the caller has access to the document, it appears // they should be able to set cookies on that document. // Note that this Document instance cannot be created // with an arbitrary URL. @Override public Object run() { ucontext.setCookie(documentURL, cookie); return null; } }); } else { ucontext.setCookie(documentURL, cookie); } } public void setDefaultTarget(final String value) { defaultTarget = value; } public void setDoctype(final DocumentType doctype) { this.doctype = doctype; } @Override public void setDocumentURI(final String documentURI) { // TODO: Security considerations? Chaging documentURL? this.documentURI = documentURI; } public void setDomain(final String domain) { String oldDomain = this.domain; if (oldDomain != null && Domains.isValidCookieDomain(domain, oldDomain)) { this.domain = domain; } else { throw new SecurityException("Cannot set domain to '" + domain + "' when current domain is '" + oldDomain + "'"); } } /** * Caller should synchronize on document. */ void setElementById(final String id, final Element element) { synchronized (this) { elementsById.put(id, element); } } public void setLocation(final String location) { getLocation().setHref(location); } void setNamedItem(final String name, final Element element) { synchronized (this) { elementsByName.put(name, element); } } /* * (non-Javadoc) * * @see org.xamjwg.html.domimpl.NodeImpl#setNodeValue(java.lang.String) */ @Override public void setNodeValue(final String nodeValue) throws DOMException { throw new DOMException(DOMException.INVALID_MODIFICATION_ERR, "Cannot set node value of document"); } public void setOnloadHandler(final Function onloadHandler) { this.onloadHandler = onloadHandler; } public void setReferrer(final String value) { referrer = value; } @Override public void setStrictErrorChecking(final boolean strictErrorChecking) { this.strictErrorChecking = strictErrorChecking; } @Override public void setTextContent(final String textContent) throws DOMException { // NOP, per spec } @Override public void setTitle(final String title) { this.title = title; } // private class BodyFilter implements NodeFilter { // public boolean accept(Node node) { // return node instanceof org.w3c.dom.html2.HTMLBodyElement; // } // } @Override public Object setUserData(final String key, final Object data, final UserDataHandler handler) { Function onloadHandler = this.onloadHandler; if (onloadHandler != null) { if (org.cobra_grendel.html.parser.HtmlParser.MODIFYING_KEY.equals(key) && data == Boolean.FALSE) { // TODO: onload event object? Executor.executeFunction(this, onloadHandler, null); } } return super.setUserData(key, data, handler); } @Override public void setXmlStandalone(final boolean xmlStandalone) throws DOMException { this.xmlStandalone = xmlStandalone; } @Override public void setXmlVersion(final String xmlVersion) throws DOMException { this.xmlVersion = xmlVersion; } public void setXssToken(final String xssToken) { this.xssToken = xssToken; } public void sizeInvalidated(final NodeImpl node) { ArrayList listenersList = documentNotificationListeners; int size; synchronized (listenersList) { size = listenersList.size(); } // Traverse list outside synchronized block. // (Shouldn't call listener methods in synchronized block. // Deadlock is possible). But assume list could have // been changed. for (int i = 0; i < size; i++) { try { DocumentNotificationListener dnl = (DocumentNotificationListener) listenersList.get(i); dnl.sizeInvalidated(node); } catch (IndexOutOfBoundsException iob) { // ignore } } } @Override public void write(final String text) { synchronized (treeLock) { if (reader != null) { try { // This can end up in openBufferChanged reader.write(text); } catch (IOException ioe) { // ignore } } } } @Override public void writeln(final String text) { synchronized (treeLock) { if (reader != null) { try { // This can end up in openBufferChanged reader.write(text + "\r\n"); } catch (IOException ioe) { // ignore } } } } }