/* * Copyright (c) 2007 Henri Sivonen * Copyright (c) 2008-2009 Mozilla Foundation * * Permission is hereby granted, free of charge, to any person obtaining a * copy of this software and associated documentation files (the "Software"), * to deal in the Software without restriction, including without limitation * the rights to use, copy, modify, merge, publish, distribute, sublicense, * and/or sell copies of the Software, and to permit persons to whom the * Software is furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ package nu.validator.htmlparser.gwt; import java.util.LinkedList; import nu.validator.htmlparser.common.DocumentMode; import nu.validator.htmlparser.impl.CoalescingTreeBuilder; import nu.validator.htmlparser.impl.HtmlAttributes; import org.xml.sax.SAXException; import com.google.gwt.core.client.JavaScriptException; import com.google.gwt.core.client.JavaScriptObject; class BrowserTreeBuilder extends CoalescingTreeBuilder<JavaScriptObject> { private JavaScriptObject document; private JavaScriptObject script; private JavaScriptObject placeholder; private boolean readyToRun; private final LinkedList<ScriptHolder> scriptStack = new LinkedList<ScriptHolder>(); private class ScriptHolder { private final JavaScriptObject script; private final JavaScriptObject placeholder; /** * @param script * @param placeholder */ public ScriptHolder(JavaScriptObject script, JavaScriptObject placeholder) { this.script = script; this.placeholder = placeholder; } /** * Returns the script. * * @return the script */ public JavaScriptObject getScript() { return script; } /** * Returns the placeholder. * * @return the placeholder */ public JavaScriptObject getPlaceholder() { return placeholder; } } protected BrowserTreeBuilder(JavaScriptObject document) { super(); this.document = document; installExplorerCreateElementNS(document); } private static native boolean installExplorerCreateElementNS( JavaScriptObject doc) /*-{ if (!doc.createElementNS) { doc.createElementNS = function (uri, local) { if ("http://www.w3.org/1999/xhtml" == uri) { return doc.createElement(local); } else if ("http://www.w3.org/1998/Math/MathML" == uri) { if (!doc.mathplayerinitialized) { var obj = document.createElement("object"); obj.setAttribute("id", "mathplayer"); obj.setAttribute("classid", "clsid:32F66A20-7614-11D4-BD11-00104BD3F987"); document.getElementsByTagName("head")[0].appendChild(obj); document.namespaces.add("m", "http://www.w3.org/1998/Math/MathML", "#mathplayer"); doc.mathplayerinitialized = true; } return doc.createElement("m:" + local); } else if ("http://www.w3.org/2000/svg" == uri) { if (!doc.renesisinitialized) { var obj = document.createElement("object"); obj.setAttribute("id", "renesis"); obj.setAttribute("classid", "clsid:AC159093-1683-4BA2-9DCF-0C350141D7F2"); document.getElementsByTagName("head")[0].appendChild(obj); document.namespaces.add("s", "http://www.w3.org/2000/svg", "#renesis"); doc.renesisinitialized = true; } return doc.createElement("s:" + local); } else { // throw } } } }-*/; private static native boolean hasAttributeNS(JavaScriptObject element, String uri, String localName) /*-{ return element.hasAttributeNS(uri, localName); }-*/; private static native void setAttributeNS(JavaScriptObject element, String uri, String localName, String value) /*-{ element.setAttributeNS(uri, localName, value); }-*/; @Override protected void addAttributesToElement(JavaScriptObject element, HtmlAttributes attributes) throws SAXException { try { for (int i = 0; i < attributes.getLength(); i++) { String localName = attributes.getLocalNameNoBoundsCheck(i); String uri = attributes.getURINoBoundsCheck(i); if (!hasAttributeNS(element, uri, localName)) { setAttributeNS(element, uri, localName, attributes.getValueNoBoundsCheck(i)); } } } catch (JavaScriptException e) { fatal(e); } } private static native void appendChild(JavaScriptObject parent, JavaScriptObject child) /*-{ parent.appendChild(child); }-*/; private static native JavaScriptObject createTextNode(JavaScriptObject doc, String text) /*-{ return doc.createTextNode(text); }-*/; private static native JavaScriptObject getLastChild(JavaScriptObject node) /*-{ return node.lastChild; }-*/; private static native void extendTextNode(JavaScriptObject node, String text) /*-{ node.data += text; }-*/; @Override protected void appendCharacters(JavaScriptObject parent, String text) throws SAXException { try { if (parent == placeholder) { appendChild(script, createTextNode(document, text)); } JavaScriptObject lastChild = getLastChild(parent); if (lastChild != null && getNodeType(lastChild) == 3) { extendTextNode(lastChild, text); return; } appendChild(parent, createTextNode(document, text)); } catch (JavaScriptException e) { fatal(e); } } private static native boolean hasChildNodes(JavaScriptObject element) /*-{ return element.hasChildNodes(); }-*/; private static native JavaScriptObject getFirstChild( JavaScriptObject element) /*-{ return element.firstChild; }-*/; @Override protected void appendChildrenToNewParent( JavaScriptObject oldParent, JavaScriptObject newParent) throws SAXException { try { while (hasChildNodes(oldParent)) { appendChild(newParent, getFirstChild(oldParent)); } } catch (JavaScriptException e) { fatal(e); } } private static native JavaScriptObject createComment(JavaScriptObject doc, String text) /*-{ return doc.createComment(text); }-*/; @Override protected void appendComment(JavaScriptObject parent, String comment) throws SAXException { try { if (parent == placeholder) { appendChild(script, createComment(document, comment)); } appendChild(parent, createComment(document, comment)); } catch (JavaScriptException e) { fatal(e); } } @Override protected void appendCommentToDocument(String comment) throws SAXException { try { appendChild(document, createComment(document, comment)); } catch (JavaScriptException e) { fatal(e); } } private static native JavaScriptObject createElementNS( JavaScriptObject doc, String ns, String local) /*-{ return doc.createElementNS(ns, local); }-*/; @Override protected JavaScriptObject createElement(String ns, String name, HtmlAttributes attributes) throws SAXException { try { JavaScriptObject rv = createElementNS(document, ns, name); for (int i = 0; i < attributes.getLength(); i++) { setAttributeNS(rv, attributes.getURINoBoundsCheck(i), attributes.getLocalNameNoBoundsCheck(i), attributes.getValueNoBoundsCheck(i)); } if ("script" == name) { if (placeholder != null) { scriptStack.addLast(new ScriptHolder(script, placeholder)); } script = rv; placeholder = createElementNS(document, "http://n.validator.nu/placeholder/", "script"); rv = placeholder; for (int i = 0; i < attributes.getLength(); i++) { setAttributeNS(rv, attributes.getURINoBoundsCheck(i), attributes.getLocalNameNoBoundsCheck(i), attributes.getValueNoBoundsCheck(i)); } } return rv; } catch (JavaScriptException e) { fatal(e); throw new RuntimeException("Unreachable"); } } @Override protected JavaScriptObject createHtmlElementSetAsRoot( HtmlAttributes attributes) throws SAXException { try { JavaScriptObject rv = createElementNS(document, "http://www.w3.org/1999/xhtml", "html"); for (int i = 0; i < attributes.getLength(); i++) { setAttributeNS(rv, attributes.getURINoBoundsCheck(i), attributes.getLocalNameNoBoundsCheck(i), attributes.getValueNoBoundsCheck(i)); } appendChild(document, rv); return rv; } catch (JavaScriptException e) { fatal(e); throw new RuntimeException("Unreachable"); } } private static native JavaScriptObject getParentNode( JavaScriptObject element) /*-{ return element.parentNode; }-*/; @Override protected void appendElement(JavaScriptObject child, JavaScriptObject newParent) throws SAXException { try { if (newParent == placeholder) { appendChild(script, cloneNodeDeep(child)); } appendChild(newParent, child); } catch (JavaScriptException e) { fatal(e); } } @Override protected boolean hasChildren(JavaScriptObject element) throws SAXException { try { return hasChildNodes(element); } catch (JavaScriptException e) { fatal(e); throw new RuntimeException("Unreachable"); } } private static native void insertBeforeNative(JavaScriptObject parent, JavaScriptObject child, JavaScriptObject sibling) /*-{ parent.insertBefore(child, sibling); }-*/; private static native int getNodeType(JavaScriptObject node) /*-{ return node.nodeType; }-*/; private static native JavaScriptObject cloneNodeDeep(JavaScriptObject node) /*-{ return node.cloneNode(true); }-*/; /** * Returns the document. * * @return the document */ JavaScriptObject getDocument() { JavaScriptObject rv = document; document = null; return rv; } private static native JavaScriptObject createDocumentFragment( JavaScriptObject doc) /*-{ return doc.createDocumentFragment(); }-*/; JavaScriptObject getDocumentFragment() { JavaScriptObject rv = createDocumentFragment(document); JavaScriptObject rootElt = getFirstChild(document); while (hasChildNodes(rootElt)) { appendChild(rv, getFirstChild(rootElt)); } document = null; return rv; } /** * @see nu.validator.htmlparser.impl.TreeBuilder#createJavaScriptObject(String, * java.lang.String, org.xml.sax.Attributes, java.lang.Object) */ @Override protected JavaScriptObject createElement(String ns, String name, HtmlAttributes attributes, JavaScriptObject form) throws SAXException { try { JavaScriptObject rv = createElement(ns, name, attributes); // rv.setUserData("nu.validator.form-pointer", form, null); return rv; } catch (JavaScriptException e) { fatal(e); return null; } } /** * @see nu.validator.htmlparser.impl.TreeBuilder#start() */ @Override protected void start(boolean fragment) throws SAXException { script = null; placeholder = null; readyToRun = false; } protected void documentMode(DocumentMode mode, String publicIdentifier, String systemIdentifier, boolean html4SpecificAdditionalErrorChecks) throws SAXException { // document.setUserData("nu.validator.document-mode", mode, null); } /** * @see nu.validator.htmlparser.impl.TreeBuilder#elementPopped(java.lang.String, * java.lang.String, java.lang.Object) */ @Override protected void elementPopped(String ns, String name, JavaScriptObject node) throws SAXException { if (node == placeholder) { readyToRun = true; requestSuspension(); } } private static native void replace(JavaScriptObject oldNode, JavaScriptObject newNode) /*-{ oldNode.parentNode.replaceChild(newNode, oldNode); }-*/; private static native JavaScriptObject getPreviousSibling(JavaScriptObject node) /*-{ return node.previousSibling; }-*/; void maybeRunScript() { if (readyToRun) { readyToRun = false; replace(placeholder, script); if (scriptStack.isEmpty()) { script = null; placeholder = null; } else { ScriptHolder scriptHolder = scriptStack.removeLast(); script = scriptHolder.getScript(); placeholder = scriptHolder.getPlaceholder(); } } } @Override protected void insertFosterParentedCharacters(String text, JavaScriptObject table, JavaScriptObject stackParent) throws SAXException { try { JavaScriptObject parent = getParentNode(table); if (parent != null) { // always an element if not null JavaScriptObject previousSibling = getPreviousSibling(table); if (previousSibling != null && getNodeType(previousSibling) == 3) { extendTextNode(previousSibling, text); return; } insertBeforeNative(parent, createTextNode(document, text), table); return; } JavaScriptObject lastChild = getLastChild(stackParent); if (lastChild != null && getNodeType(lastChild) == 3) { extendTextNode(lastChild, text); return; } appendChild(stackParent, createTextNode(document, text)); } catch (JavaScriptException e) { fatal(e); } } @Override protected void insertFosterParentedChild(JavaScriptObject child, JavaScriptObject table, JavaScriptObject stackParent) throws SAXException { JavaScriptObject parent = getParentNode(table); try { if (parent != null && getNodeType(parent) == 1) { insertBeforeNative(parent, child, table); } else { appendChild(stackParent, child); } } catch (JavaScriptException e) { fatal(e); } } private static native void removeChild(JavaScriptObject parent, JavaScriptObject child) /*-{ parent.removeChild(child); }-*/; @Override protected void detachFromParent(JavaScriptObject element) throws SAXException { try { JavaScriptObject parent = getParentNode(element); if (parent != null) { removeChild(parent, element); } } catch (JavaScriptException e) { fatal(e); } } }