From: <asa...@us...> - 2012-12-31 06:54:55
|
Revision: 7927 http://sourceforge.net/p/htmlunit/code/7927 Author: asashour Date: 2012-12-31 06:54:50 +0000 (Mon, 31 Dec 2012) Log Message: ----------- - IE handles CSS3 only when the corresponding documentMode allows - More CSS3 selectors Modified Paths: -------------- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomNode.java trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSStyleSheet.java trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSSelectorTest.java Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomNode.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomNode.java 2012-12-31 05:39:24 UTC (rev 7926) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/html/DomNode.java 2012-12-31 06:54:50 UTC (rev 7927) @@ -17,6 +17,7 @@ import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.DISPLAYED_COLLAPSE; import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.DOM_NORMALIZE_REMOVE_CHILDREN; import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.NODE_APPEND_CHILD_SELF_IGNORE; +import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.QUERYSELECTORALL_NOT_IN_QUIRKS; import java.io.IOException; import java.io.PrintWriter; @@ -55,8 +56,10 @@ import com.gargoylesoftware.htmlunit.WebClient; import com.gargoylesoftware.htmlunit.html.xpath.XPathUtils; import com.gargoylesoftware.htmlunit.javascript.SimpleScriptable; +import com.gargoylesoftware.htmlunit.javascript.host.Window; import com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleDeclaration; import com.gargoylesoftware.htmlunit.javascript.host.css.CSSStyleSheet; +import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLDocument; import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement; import com.steadystate.css.parser.CSSOMParser; import com.steadystate.css.parser.SACParserCSS3; @@ -1566,9 +1569,17 @@ throw new CSSException("Invalid selectors: " + selectors); } if (null != selectorList) { - CSSStyleSheet.validateSelectors(selectorList); + final BrowserVersion browserVersion = webClient.getBrowserVersion(); + final int documentMode; + if (browserVersion.hasFeature(QUERYSELECTORALL_NOT_IN_QUIRKS)) { + documentMode = ((HTMLDocument) ((Window) getScriptObject().getParentScope()).getDocument()) + .getDocumentMode(); + } + else { + documentMode = 9; + } + CSSStyleSheet.validateSelectors(selectorList, documentMode); - final BrowserVersion browserVersion = webClient.getBrowserVersion(); for (final HtmlElement child : getHtmlElementDescendants()) { for (int i = 0; i < selectorList.getLength(); i++) { final Selector selector = selectorList.item(i); Modified: trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSStyleSheet.java =================================================================== --- trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSStyleSheet.java 2012-12-31 05:39:24 UTC (rev 7926) +++ trunk/htmlunit/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSStyleSheet.java 2012-12-31 06:54:50 UTC (rev 7927) @@ -29,6 +29,7 @@ import java.io.StringReader; import java.net.MalformedURLException; import java.net.URL; +import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.HashMap; @@ -131,9 +132,18 @@ /** This stylesheet's URI (used to resolved contained @import rules). */ private String uri_; - private static final Collection<String> PSEUDO_CLASSES = Arrays.asList("link", "visited", "hover", "active", - "focus", "target", "lang", "disabled", "checked", "indeterminated", "root", "nth-child()"); + private static final Collection<String> CSS2_PSEUDO_CLASSES = Arrays.asList("link", "visited", "hover", "active", + "focus", "lang", "first-child"); + private static final Collection<String> CSS3_PSEUDO_CLASSES = new ArrayList<String>(Arrays.asList( + "checked", "disabled", "indeterminated", "root", "target", + "nth-child()", "nth-last-child()", "nth-of-type()", "nth-last-of-type()", + "last-child", "first-of-type", "last-of-type", "only-child", "only-of-type", "empty")); + + static { + CSS3_PSEUDO_CLASSES.addAll(CSS2_PSEUDO_CLASSES); + } + /** * Creates a new empty stylesheet. */ @@ -531,7 +541,7 @@ final AttributeCondition condition, final DomElement element) { if (browserVersion.hasFeature(QUERYSELECTORALL_NOT_IN_QUIRKS) && ((HTMLDocument) ((Window) element.getScriptObject().getParentScope()).getDocument()) - .getDocumentMode() < 9) { + .getDocumentMode() < 8) { return false; } @@ -549,6 +559,40 @@ return (element instanceof HtmlCheckBoxInput && ((HtmlCheckBoxInput) element).isChecked()) || (element instanceof HtmlRadioButtonInput && ((HtmlRadioButtonInput) element).isChecked()); } + else if ("first-child".equals(value)) { + for (DomNode n = element.getPreviousSibling(); n != null; n = n.getPreviousSibling()) { + if (n instanceof DomElement) { + return false; + } + } + return true; + } + else if ("last-child".equals(value)) { + for (DomNode n = element.getNextSibling(); n != null; n = n.getNextSibling()) { + if (n instanceof DomElement) { + return false; + } + } + return true; + } + else if ("first-of-type".equals(value)) { + final String type = element.getNodeName(); + for (DomNode n = element.getPreviousSibling(); n != null; n = n.getPreviousSibling()) { + if (n instanceof DomElement && n.getNodeName().equals(type)) { + return false; + } + } + return true; + } + else if ("last-of-type".equals(value)) { + final String type = element.getNodeName(); + for (DomNode n = element.getNextSibling(); n != null; n = n.getNextSibling()) { + if (n instanceof DomElement && n.getNodeName().equals(type)) { + return false; + } + } + return true; + } else if (value.startsWith("nth-child(")) { final String nth = value.substring(value.indexOf('(') + 1, value.length() - 1); int index = 0; @@ -559,6 +603,65 @@ } return getNth(nth, index); } + else if (value.startsWith("nth-last-child(")) { + final String nth = value.substring(value.indexOf('(') + 1, value.length() - 1); + int index = 0; + for (DomNode n = element; n != null; n = n.getNextSibling()) { + if (n instanceof DomElement) { + index++; + } + } + return getNth(nth, index); + } + else if (value.startsWith("nth-of-type(")) { + final String type = element.getNodeName(); + final String nth = value.substring(value.indexOf('(') + 1, value.length() - 1); + int index = 0; + for (DomNode n = element; n != null; n = n.getPreviousSibling()) { + if (n instanceof DomElement && n.getNodeName().equals(type)) { + index++; + } + } + return getNth(nth, index); + } + else if (value.startsWith("nth-last-of-type(")) { + final String type = element.getNodeName(); + final String nth = value.substring(value.indexOf('(') + 1, value.length() - 1); + int index = 0; + for (DomNode n = element; n != null; n = n.getNextSibling()) { + if (n instanceof DomElement && n.getNodeName().equals(type)) { + index++; + } + } + return getNth(nth, index); + } + else if ("only-child".equals(value)) { + for (DomNode n = element.getPreviousSibling(); n != null; n = n.getPreviousSibling()) { + if (n instanceof DomElement) { + return false; + } + } + for (DomNode n = element.getNextSibling(); n != null; n = n.getNextSibling()) { + if (n instanceof DomElement) { + return false; + } + } + return true; + } + else if ("only-of-type".equals(value)) { + final String type = element.getNodeName(); + for (DomNode n = element.getPreviousSibling(); n != null; n = n.getPreviousSibling()) { + if (n instanceof DomElement && n.getNodeName().equals(type)) { + return false; + } + } + for (DomNode n = element.getNextSibling(); n != null; n = n.getNextSibling()) { + if (n instanceof DomElement && n.getNodeName().equals(type)) { + return false; + } + } + return true; + } return false; } @@ -883,42 +986,53 @@ /** * Validates the list of selectors. * @param selectorList the selectors + * @param documentMode see {@link HTMLDocument#getDocumentMode()} * @throws CSSException if a selector is invalid */ - public static void validateSelectors(final SelectorList selectorList) throws CSSException { + public static void validateSelectors(final SelectorList selectorList, final int documentMode) throws CSSException { for (int i = 0; i < selectorList.getLength(); ++i) { final Selector item = selectorList.item(i); - if (!isValidSelector(item)) { + if (!isValidSelector(item, documentMode)) { throw new CSSException("Invalid selector: " + item); } } } - private static boolean isValidSelector(final Selector selector) { + /** + * @param documentMode see {@link HTMLDocument#getDocumentMode()} + */ + private static boolean isValidSelector(final Selector selector, final int documentMode) { switch (selector.getSelectorType()) { case Selector.SAC_ELEMENT_NODE_SELECTOR: return true; case Selector.SAC_CONDITIONAL_SELECTOR: final ConditionalSelector conditional = (ConditionalSelector) selector; - return isValidSelector(conditional.getSimpleSelector()) && isValidSelector(conditional.getCondition()); + return isValidSelector(conditional.getSimpleSelector(), documentMode) + && isValidSelector(conditional.getCondition(), documentMode); case Selector.SAC_DESCENDANT_SELECTOR: case Selector.SAC_CHILD_SELECTOR: final DescendantSelector ds = (DescendantSelector) selector; - return isValidSelector(ds.getAncestorSelector()) && isValidSelector(ds.getSimpleSelector()); + return isValidSelector(ds.getAncestorSelector(), documentMode) + && isValidSelector(ds.getSimpleSelector(), documentMode); case Selector.SAC_DIRECT_ADJACENT_SELECTOR: final SiblingSelector ss = (SiblingSelector) selector; - return isValidSelector(ss.getSelector()) && isValidSelector(ss.getSiblingSelector()); + return isValidSelector(ss.getSelector(), documentMode) + && isValidSelector(ss.getSiblingSelector(), documentMode); default: LOG.warn("Unhandled CSS selector type '" + selector.getSelectorType() + "'. Accepting it silently."); return true; // at least in a first time to break less stuff } } - private static boolean isValidSelector(final Condition condition) { + /** + * @param documentMode see {@link HTMLDocument#getDocumentMode()} + */ + private static boolean isValidSelector(final Condition condition, final int documentMode) { switch (condition.getConditionType()) { case Condition.SAC_AND_CONDITION: final CombinatorCondition cc1 = (CombinatorCondition) condition; - return isValidSelector(cc1.getFirstCondition()) && isValidSelector(cc1.getSecondCondition()); + return isValidSelector(cc1.getFirstCondition(), documentMode) + && isValidSelector(cc1.getSecondCondition(), documentMode); case Condition.SAC_ATTRIBUTE_CONDITION: case Condition.SAC_ID_CONDITION: case Condition.SAC_CLASS_CONDITION: @@ -932,7 +1046,10 @@ } value = value.substring(0, value.indexOf('(') + 1) + ')'; } - return PSEUDO_CLASSES.contains(value); + if (documentMode < 9) { + return CSS2_PSEUDO_CLASSES.contains(value); + } + return CSS3_PSEUDO_CLASSES.contains(value); default: LOG.warn("Unhandled CSS condition type '" + condition.getConditionType() + "'. Accepting it silently."); return true; Modified: trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSSelectorTest.java =================================================================== --- trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSSelectorTest.java 2012-12-31 05:39:24 UTC (rev 7926) +++ trunk/htmlunit/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/CSSSelectorTest.java 2012-12-31 06:54:50 UTC (rev 7927) @@ -19,7 +19,6 @@ import com.gargoylesoftware.htmlunit.BrowserRunner; import com.gargoylesoftware.htmlunit.BrowserRunner.Alerts; -import com.gargoylesoftware.htmlunit.BrowserRunner.NotYetImplemented; import com.gargoylesoftware.htmlunit.WebDriverTestCase; import com.gargoylesoftware.htmlunit.html.HtmlPageTest; @@ -40,10 +39,10 @@ * @throws Exception if an error occurs */ @Test - @Alerts(DEFAULT = "li2", IE = "exception") + @Alerts(DEFAULT = "li2", IE8 = "exception") public void nth_child() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" @@ -285,15 +284,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("li3") - @NotYetImplemented + @Alerts(DEFAULT = "li3", IE8 = "exception") public void nth_last_child() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('li:nth-last-child(1)')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('li:nth-last-child(1)')[0].id);\n" + + " } catch(e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" @@ -312,15 +312,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("id3") - @NotYetImplemented + @Alerts(DEFAULT = "id3", IE8 = "exception") public void nth_of_type() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('p:nth-of-type(2)')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('p:nth-of-type(2)')[0].id);\n" + + " } catch(e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" @@ -339,15 +340,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("id3") - @NotYetImplemented + @Alerts(DEFAULT = "id3", IE8 = "exception") public void nth_last_of_type() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('p:nth-last-of-type(1)')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('p:nth-last-of-type(1)')[0].id);\n" + + " } catch(e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" @@ -367,7 +369,6 @@ */ @Test @Alerts("li1") - @NotYetImplemented public void first_child() throws Exception { final String html = "<html><head><title>First</title>\n" + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" @@ -393,15 +394,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("li3") - @NotYetImplemented + @Alerts(DEFAULT = "li3", IE8 = "exception") public void last_child() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('li:last-child')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('li:last-child')[0].id);\n" + + " } catch (e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" @@ -420,15 +422,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("id2") - @NotYetImplemented + @Alerts(DEFAULT = "id2", IE8 = "exception") public void first_of_type() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('p:first-of-type')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('p:first-of-type')[0].id);\n" + + " } catch(e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" @@ -449,15 +452,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("id4") - @NotYetImplemented + @Alerts(DEFAULT = "id4", IE8 = "exception") public void last_of_type() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('p:last-of-type')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('p:last-of-type')[0].id);\n" + + " } catch(e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" @@ -478,15 +482,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("id3") - @NotYetImplemented + @Alerts(DEFAULT = "id3", IE8 = "exception") public void only_child() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('h1:only-child')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('h1:only-child')[0].id);\n" + + " } catch(e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" @@ -507,15 +512,16 @@ * @throws Exception if an error occurs */ @Test - @Alerts("id3") - @NotYetImplemented + @Alerts(DEFAULT = "id3", IE8 = "exception") public void only_of_type() throws Exception { final String html = "<html><head><title>First</title>\n" - + "<meta http-equiv='X-UA-Compatible' content='IE=8'>\n" + + "<meta http-equiv='X-UA-Compatible' content='IE=9'>\n" + "<script>\n" + "function test() {\n" + " if (document.querySelectorAll) {\n" - + " alert(document.querySelectorAll('p:only-of-type')[0].id);\n" + + " try {\n" + + " alert(document.querySelectorAll('p:only-of-type')[0].id);\n" + + " } catch(e) {alert('exception')}\n" + " }\n" + "}\n" + "</script></head>\n" |