Reviewed by darin.
authoreric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 6 Jan 2008 20:35:04 +0000 (20:35 +0000)
committereric@webkit.org <eric@webkit.org@268f45cc-cd09-0410-ab3c-d52691b4dbfc>
Sun, 6 Jan 2008 20:35:04 +0000 (20:35 +0000)
        Make attr selectors case-insensitive for certain HTML attributes
        http://bugs.webkit.org/show_bug.cgi?id=15470

        Test: fast/css/html-attr-case-sensitivity.html

        * css/CSSStyleSelector.cpp:
        (WebCore::addLocalNameToSet):
        (WebCore::createHtmlCaseInsensitiveAttributesSet):
        (WebCore::htmlAttributeHasCaseInsensitiveValue):
        (WebCore::CSSStyleSelector::checkOneSelector):

git-svn-id: https://svn.webkit.org/repository/webkit/trunk@29212 268f45cc-cd09-0410-ab3c-d52691b4dbfc

LayoutTests/ChangeLog
LayoutTests/fast/css/html-attr-case-sensitivity-expected.txt [new file with mode: 0644]
LayoutTests/fast/css/html-attr-case-sensitivity.html [new file with mode: 0644]
LayoutTests/fast/css/resources/html-attr-case-sensitivity.js [new file with mode: 0644]
WebCore/ChangeLog
WebCore/css/CSSStyleSelector.cpp

index a1661bda23d25d5c2aa35a832e0fb44aabf343ad..2819bb64bdee31d062f04bf9658532ca4d59635a 100644 (file)
         * fast/dom/Range/range-clone-empty.html: Added.
         * fast/dom/Range/resources/range-clone-empty.js: Added.
 
+2008-01-06  Eric Seidel  <eric@webkit.org>
+
+        Reviewed by darin.
+        
+        Make attr selectors case-insensitive for certain HTML attributes
+        http://bugs.webkit.org/show_bug.cgi?id=15470
+
+        * fast/css/html-attr-case-sensitivity-expected.txt: Added.
+        * fast/css/html-attr-case-sensitivity.html: Added.
+        * fast/css/resources/html-attr-case-sensitivity.js: Added.
+
 2008-01-06  Eric Seidel  <eric@webkit.org>
 
         Reviewed by Sam.
diff --git a/LayoutTests/fast/css/html-attr-case-sensitivity-expected.txt b/LayoutTests/fast/css/html-attr-case-sensitivity-expected.txt
new file mode 100644 (file)
index 0000000..3d59366
--- /dev/null
@@ -0,0 +1,129 @@
+This tests that [attr=value] CSS selectors are case sensitive depending on attr
+
+On success, you will see a series of "PASS" messages, followed by "TEST COMPLETE".
+
+
+PASS isCaseSensitive("abbr") is true
+PASS isCaseSensitive("accept-charset") is false
+PASS isCaseSensitive("accept") is false
+PASS isCaseSensitive("accesskey") is true
+PASS isCaseSensitive("action") is true
+PASS isCaseSensitive("align") is false
+PASS isCaseSensitive("alink") is false
+PASS isCaseSensitive("alt") is true
+PASS isCaseSensitive("archive") is true
+PASS isCaseSensitive("axis") is false
+PASS isCaseSensitive("background") is true
+PASS isCaseSensitive("bgcolor") is false
+PASS isCaseSensitive("border") is true
+PASS isCaseSensitive("cellpadding") is true
+PASS isCaseSensitive("cellspacing") is true
+PASS isCaseSensitive("char") is true
+PASS isCaseSensitive("charoff") is true
+PASS isCaseSensitive("charset") is false
+PASS isCaseSensitive("checked") is false
+PASS isCaseSensitive("cite") is true
+PASS isCaseSensitive("class") is true
+PASS isCaseSensitive("classid") is true
+PASS isCaseSensitive("clear") is false
+PASS isCaseSensitive("code") is true
+PASS isCaseSensitive("codebase") is true
+PASS isCaseSensitive("codetype") is false
+PASS isCaseSensitive("color") is false
+PASS isCaseSensitive("cols") is true
+PASS isCaseSensitive("colspan") is true
+PASS isCaseSensitive("compact") is false
+PASS isCaseSensitive("content") is true
+PASS isCaseSensitive("coords") is true
+PASS isCaseSensitive("data") is true
+PASS isCaseSensitive("datetime") is true
+PASS isCaseSensitive("declare") is false
+PASS isCaseSensitive("defer") is false
+PASS isCaseSensitive("dir") is false
+PASS isCaseSensitive("disabled") is false
+PASS isCaseSensitive("enctype") is false
+PASS isCaseSensitive("face") is false
+PASS isCaseSensitive("for") is true
+PASS isCaseSensitive("frame") is false
+PASS isCaseSensitive("frameborder") is true
+PASS isCaseSensitive("headers") is true
+PASS isCaseSensitive("height") is true
+PASS isCaseSensitive("href") is true
+PASS isCaseSensitive("hreflang") is false
+PASS isCaseSensitive("hspace") is true
+PASS isCaseSensitive("http-equiv") is false
+PASS isCaseSensitive("id") is true
+PASS isCaseSensitive("ismap") is true
+PASS isCaseSensitive("label") is true
+PASS isCaseSensitive("lang") is false
+PASS isCaseSensitive("language") is false
+PASS isCaseSensitive("link") is false
+PASS isCaseSensitive("longdesc") is true
+PASS isCaseSensitive("marginheight") is true
+PASS isCaseSensitive("marginwidth") is true
+PASS isCaseSensitive("maxlength") is true
+PASS isCaseSensitive("media") is false
+PASS isCaseSensitive("method") is false
+PASS isCaseSensitive("multiple") is false
+PASS isCaseSensitive("name") is true
+PASS isCaseSensitive("nohref") is false
+PASS isCaseSensitive("noresize") is false
+PASS isCaseSensitive("noshade") is false
+PASS isCaseSensitive("nowrap") is false
+PASS isCaseSensitive("object") is true
+PASS isCaseSensitive("onblur") is true
+PASS isCaseSensitive("onchange") is true
+PASS isCaseSensitive("onclick") is true
+PASS isCaseSensitive("ondblclick") is true
+PASS isCaseSensitive("onfocus") is true
+PASS isCaseSensitive("onkeydown") is true
+PASS isCaseSensitive("onkeypress") is true
+PASS isCaseSensitive("onkeyup") is true
+PASS isCaseSensitive("onload") is true
+PASS isCaseSensitive("onmousedown") is true
+PASS isCaseSensitive("onmousemove") is true
+PASS isCaseSensitive("onmouseout") is true
+PASS isCaseSensitive("onmouseover") is true
+PASS isCaseSensitive("onmouseup") is true
+PASS isCaseSensitive("onreset") is true
+PASS isCaseSensitive("onselect") is true
+PASS isCaseSensitive("onsubmit") is true
+PASS isCaseSensitive("onunload") is true
+PASS isCaseSensitive("profile") is true
+PASS isCaseSensitive("prompt") is true
+PASS isCaseSensitive("readonly") is false
+PASS isCaseSensitive("rel") is false
+PASS isCaseSensitive("rev") is false
+PASS isCaseSensitive("rows") is true
+PASS isCaseSensitive("rowspan") is true
+PASS isCaseSensitive("rules") is false
+PASS isCaseSensitive("scheme") is true
+PASS isCaseSensitive("scope") is false
+PASS isCaseSensitive("scrolling") is false
+PASS isCaseSensitive("selected") is false
+PASS isCaseSensitive("shape") is false
+PASS isCaseSensitive("size") is true
+PASS isCaseSensitive("span") is true
+PASS isCaseSensitive("src") is true
+PASS isCaseSensitive("standby") is true
+PASS isCaseSensitive("start") is true
+PASS isCaseSensitive("style") is true
+PASS isCaseSensitive("summary") is true
+PASS isCaseSensitive("tabindex") is true
+PASS isCaseSensitive("target") is false
+PASS isCaseSensitive("text") is false
+PASS isCaseSensitive("title") is true
+PASS isCaseSensitive("type") is false
+PASS isCaseSensitive("usemap") is true
+PASS isCaseSensitive("valign") is false
+PASS isCaseSensitive("value") is true
+PASS isCaseSensitive("valuetype") is false
+PASS isCaseSensitive("version") is true
+PASS isCaseSensitive("vlink") is false
+PASS isCaseSensitive("vspace") is true
+PASS isCaseSensitive("width") is true
+PASS isCaseSensitive("foobar") is true
+PASS successfullyParsed is true
+
+TEST COMPLETE
+
diff --git a/LayoutTests/fast/css/html-attr-case-sensitivity.html b/LayoutTests/fast/css/html-attr-case-sensitivity.html
new file mode 100644 (file)
index 0000000..2a62a65
--- /dev/null
@@ -0,0 +1,13 @@
+<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
+<html>
+<head>
+<link rel="stylesheet" href="../js/resources/js-test-style.css">
+<script src="../js/resources/js-test-pre.js"></script>
+</head>
+<body>
+<p id="description"></p>
+<div id="console"></div>
+<script src="resources/html-attr-case-sensitivity.js"></script>
+<script src="../js/resources/js-test-post.js"></script>
+</body>
+</html>
diff --git a/LayoutTests/fast/css/resources/html-attr-case-sensitivity.js b/LayoutTests/fast/css/resources/html-attr-case-sensitivity.js
new file mode 100644 (file)
index 0000000..edcf1d7
--- /dev/null
@@ -0,0 +1,176 @@
+description("This tests that [attr=value] CSS selectors are case sensitive depending on attr");
+
+// List of all HTML4 attrs
+// true = case sensitive
+var htmlAttrs = {
+    "abbr" : true,
+    "accept-charset" : false,
+    "accept" : false,
+    "accesskey" : true,
+    "action" : true,
+    "align" : false,
+    "alink" : false,
+    "alt" : true,
+    "archive" : true,
+    "axis" : false,
+    "background" : true,
+    "bgcolor" : false,
+    "border" : true,
+    "cellpadding" : true,
+    "cellspacing" : true,
+    "char" : true,
+    "charoff" : true,
+    "charset" : false,
+    "checked" : false,
+    "cite" : true,
+    "class" : true,
+    "classid" : true,
+    "clear" : false,
+    "code" : true,
+    "codebase" : true,
+    "codetype" : false,
+    "color" : false,
+    "cols" : true,
+    "colspan" : true,
+    "compact" : false,
+    "content" : true,
+    "coords" : true,
+    "data" : true,
+    "datetime" : true,
+    "declare" : false,
+    "defer" : false,
+    "dir" : false,
+    "disabled" : false,
+    "enctype" : false,
+    "face" : false,
+    "for" : true,
+    "frame" : false,
+    "frameborder" : true,
+    "headers" : true,
+    "height" : true,
+    "href" : true,
+    "hreflang" : false,
+    "hspace" : true,
+    "http-equiv" : false,
+    "id" : true,
+    "ismap" : true,
+    "label" : true,
+    "lang" : false,
+    "language" : false,
+    "link" : false,
+    "longdesc" : true,
+    "marginheight" : true,
+    "marginwidth" : true,
+    "maxlength" : true,
+    "media" : false,
+    "method" : false,
+    "multiple" : false,
+    "name" : true,
+    "nohref" : false,
+    "noresize" : false,
+    "noshade" : false,
+    "nowrap" : false,
+    "object" : true,
+    "onblur" : true,
+    "onchange" : true,
+    "onclick" : true,
+    "ondblclick" : true,
+    "onfocus" : true,
+    "onkeydown" : true,
+    "onkeypress" : true,
+    "onkeyup" : true,
+    "onload" : true,
+    "onmousedown" : true,
+    "onmousemove" : true,
+    "onmouseout" : true,
+    "onmouseover" : true,
+    "onmouseup" : true,
+    "onreset" : true,
+    "onselect" : true,
+    "onsubmit" : true,
+    "onunload" : true,
+    "profile" : true,
+    "prompt" : true,
+    "readonly" : false,
+    "rel" : false,
+    "rev" : false,
+    "rows" : true,
+    "rowspan" : true,
+    "rules" : false,
+    "scheme" : true,
+    "scope" : false,
+    "scrolling" : false,
+    "selected" : false,
+    "shape" : false,
+    "size" : true,
+    "span" : true,
+    "src" : true,
+    "standby" : true,
+    "start" : true,
+    "style" : true,
+    "summary" : true,
+    "tabindex" : true,
+    "target" : false,
+    "text" : false,
+    "title" : true,
+    "type" : false,
+    "usemap" : true,
+    "valign" : false,
+    "value" : true,
+    "valuetype" : false,
+    "version" : true,
+    "vlink" : false,
+    "vspace" : true,
+    "width" : true
+};
+
+function isCaseSensitive(attrName) {
+    var style = document.createElement('style');
+    style.appendChild(document.createTextNode("br { color: red; float: right; border-color: red; }\n"));
+    style.appendChild(document.createTextNode("br[" + attrName + "=foo] { color: blue; }\n"));
+    style.appendChild(document.createTextNode("br[" + attrName + "~=foo] { float: left; }\n"));
+    style.appendChild(document.createTextNode("br[" + attrName + "|=foo] { border-left-color: green; }\n"));
+    style.appendChild(document.createTextNode("br[" + attrName + "^=foo] { border-right-color: pink; }\n"));
+    style.appendChild(document.createTextNode("br[" + attrName + "$=foo] { border-top-color: orange; }\n"));
+    style.appendChild(document.createTextNode("br[" + attrName + "*=foo] { border-bottom-color: black; }\n"));
+    
+    document.documentElement.firstChild.appendChild(style);
+    
+    var testElement = document.createElement("br");
+    testElement.setAttribute(attrName, "FOO");
+    document.body.appendChild(testElement);
+    
+    var computedStyle = window.getComputedStyle(testElement, '');
+    
+    var isCaseInsensitive = (computedStyle.getPropertyValue("color") == "rgb(0, 0, 255)");
+    
+    if ((computedStyle.getPropertyValue("float") == "left") != isCaseInsensitive)
+        testFailed("~=foo and =foo for " + attrName + " did not match with same case sensitivity");
+    
+    if ((computedStyle.getPropertyValue("border-left-color") == "rgb(0, 128, 0)") != isCaseInsensitive)
+        testFailed("|=foo and =foo for " + attrName + " did not match with same case sensitivity");
+
+    if ((computedStyle.getPropertyValue("border-right-color") == "rgb(255, 192, 203)") != isCaseInsensitive)
+        testFailed("^=foo and =foo for " + attrName + " did not match with same case sensitivity");
+
+    if ((computedStyle.getPropertyValue("border-top-color") == "rgb(255, 165, 0)") != isCaseInsensitive)
+        testFailed("$=foo and =foo for " + attrName + " did not match with same case sensitivity");
+
+    if ((computedStyle.getPropertyValue("border-bottom-color") == "rgb(0, 0, 0)") != isCaseInsensitive)
+        testFailed("*=foo and =foo for " + attrName + " did not match with same case sensitivity");
+    
+    // clean up
+    testElement.parentNode.removeChild(testElement);
+    style.parentNode.removeChild(style);
+    
+    return !isCaseInsensitive;
+}
+
+for (var attr in htmlAttrs) {
+    shouldBe('isCaseSensitive("' + attr + '")', "" + htmlAttrs[attr]);
+}
+
+// test a nonexistent attr
+shouldBe('isCaseSensitive("foobar")', "true");
+
+var successfullyParsed = true;
index 3a7199b42f7630b29be3901e2ef63efb4ba297da..7391eb2764a2a4d03c935bd2a5a23ebcb8e9d584 100644 (file)
         * html/CanvasPattern.cpp:
         (WebCore::CanvasPattern::CanvasPattern):
 
+2008-01-06  Eric Seidel  <eric@webkit.org>
+
+        Reviewed by darin.
+
+        Make attr selectors case-insensitive for certain HTML attributes
+        http://bugs.webkit.org/show_bug.cgi?id=15470
+
+        Test: fast/css/html-attr-case-sensitivity.html
+
+        * css/CSSStyleSelector.cpp:
+        (WebCore::addLocalNameToSet):
+        (WebCore::createHtmlCaseInsensitiveAttributesSet):
+        (WebCore::htmlAttributeHasCaseInsensitiveValue):
+        (WebCore::CSSStyleSelector::checkOneSelector):
+
 2008-01-06  Eric Seidel  <eric@webkit.org>
 
         Reviewed by Sam.
index 08e62451c13180b3393e077cde13618c4a13ece7..f2276154818aecff0c412fc76e4e50dfa156f0c4 100644 (file)
@@ -1409,6 +1409,73 @@ CSSStyleSelector::SelectorMatch CSSStyleSelector::checkSelector(CSSSelector* sel
     return SelectorFailsCompletely;
 }
 
+static void addLocalNameToSet(HashSet<AtomicStringImpl*>* set, const QualifiedName& qName)
+{
+    set->add(qName.localName().impl());
+}
+
+static HashSet<AtomicStringImpl*>* createHtmlCaseInsensitiveAttributesSet()
+{
+    // This is the list of attributes in HTML 4.01 with values marked as "[CI]" or case-insensitive
+    // Mozilla treats all other values as case-sensitive, thus so do we.
+    HashSet<AtomicStringImpl*>* attrSet = new HashSet<AtomicStringImpl*>;
+
+    addLocalNameToSet(attrSet, accept_charsetAttr);
+    addLocalNameToSet(attrSet, acceptAttr);
+    addLocalNameToSet(attrSet, alignAttr);
+    addLocalNameToSet(attrSet, alinkAttr);
+    addLocalNameToSet(attrSet, axisAttr);
+    addLocalNameToSet(attrSet, bgcolorAttr);
+    addLocalNameToSet(attrSet, charsetAttr);
+    addLocalNameToSet(attrSet, checkedAttr);
+    addLocalNameToSet(attrSet, clearAttr);
+    addLocalNameToSet(attrSet, codetypeAttr);
+    addLocalNameToSet(attrSet, colorAttr);
+    addLocalNameToSet(attrSet, compactAttr);
+    addLocalNameToSet(attrSet, declareAttr);
+    addLocalNameToSet(attrSet, deferAttr);
+    addLocalNameToSet(attrSet, dirAttr);
+    addLocalNameToSet(attrSet, disabledAttr);
+    addLocalNameToSet(attrSet, enctypeAttr);
+    addLocalNameToSet(attrSet, faceAttr);
+    addLocalNameToSet(attrSet, frameAttr);
+    addLocalNameToSet(attrSet, hreflangAttr);
+    addLocalNameToSet(attrSet, http_equivAttr);
+    addLocalNameToSet(attrSet, langAttr);
+    addLocalNameToSet(attrSet, languageAttr);
+    addLocalNameToSet(attrSet, linkAttr);
+    addLocalNameToSet(attrSet, mediaAttr);
+    addLocalNameToSet(attrSet, methodAttr);
+    addLocalNameToSet(attrSet, multipleAttr);
+    addLocalNameToSet(attrSet, nohrefAttr);
+    addLocalNameToSet(attrSet, noresizeAttr);
+    addLocalNameToSet(attrSet, noshadeAttr);
+    addLocalNameToSet(attrSet, nowrapAttr);
+    addLocalNameToSet(attrSet, readonlyAttr);
+    addLocalNameToSet(attrSet, relAttr);
+    addLocalNameToSet(attrSet, revAttr);
+    addLocalNameToSet(attrSet, rulesAttr);
+    addLocalNameToSet(attrSet, scopeAttr);
+    addLocalNameToSet(attrSet, scrollingAttr);
+    addLocalNameToSet(attrSet, selectedAttr);
+    addLocalNameToSet(attrSet, shapeAttr);
+    addLocalNameToSet(attrSet, targetAttr);
+    addLocalNameToSet(attrSet, textAttr);
+    addLocalNameToSet(attrSet, typeAttr);
+    addLocalNameToSet(attrSet, valignAttr);
+    addLocalNameToSet(attrSet, valuetypeAttr);
+    addLocalNameToSet(attrSet, vlinkAttr);
+
+    return attrSet;
+}
+
+static bool htmlAttributeHasCaseInsensitiveValue(const QualifiedName& attr)
+{
+    static HashSet<AtomicStringImpl*>* htmlCaseInsensitiveAttributesSet = createHtmlCaseInsensitiveAttributesSet();
+    bool isPossibleHTMLAttr = !attr.hasPrefix() && (attr.namespaceURI() == nullAtom);
+    return isPossibleHTMLAttr && htmlCaseInsensitiveAttributesSet->contains(attr.localName().impl());
+}
+
 bool CSSStyleSelector::checkOneSelector(CSSSelector* sel, Element* e, bool isAncestor, bool isSubSelector)
 {
     if (!e)
@@ -1446,9 +1513,11 @@ bool CSSStyleSelector::checkOneSelector(CSSSelector* sel, Element* e, bool isAnc
         if (value.isNull())
             return false; // attribute is not set
 
+        bool caseSensitive = isXMLDoc || !htmlAttributeHasCaseInsensitiveValue(sel->m_attr);
+
         switch (sel->m_match) {
         case CSSSelector::Exact:
-            if ((isXMLDoc && sel->m_value != value) || (!isXMLDoc && !equalIgnoringCase(sel->m_value, value)))
+            if (caseSensitive ? sel->m_value != value : !equalIgnoringCase(sel->m_value, value))
                 return false;
             break;
         case CSSSelector::List:
@@ -1459,7 +1528,7 @@ bool CSSStyleSelector::checkOneSelector(CSSSelector* sel, Element* e, bool isAnc
 
             int startSearchAt = 0;
             while (true) {
-                int foundPos = value.find(sel->m_value, startSearchAt, isXMLDoc);
+                int foundPos = value.find(sel->m_value, startSearchAt, caseSensitive);
                 if (foundPos == -1)
                     return false;
                 if (foundPos == 0 || value[foundPos-1] == ' ') {
@@ -1474,21 +1543,21 @@ bool CSSStyleSelector::checkOneSelector(CSSSelector* sel, Element* e, bool isAnc
             break;
         }
         case CSSSelector::Contain:
-            if (!value.contains(sel->m_value, isXMLDoc))
+            if (!value.contains(sel->m_value, caseSensitive))
                 return false;
             break;
         case CSSSelector::Begin:
-            if (!value.startsWith(sel->m_value, isXMLDoc))
+            if (!value.startsWith(sel->m_value, caseSensitive))
                 return false;
             break;
         case CSSSelector::End:
-            if (!value.endsWith(sel->m_value, isXMLDoc))
+            if (!value.endsWith(sel->m_value, caseSensitive))
                 return false;
             break;
         case CSSSelector::Hyphen:
             if (value.length() < sel->m_value.length())
                 return false;
-            if (!value.startsWith(sel->m_value, isXMLDoc))
+            if (!value.startsWith(sel->m_value, caseSensitive))
                 return false;
             // It they start the same, check for exact match or following '-':
             if (value.length() != sel->m_value.length() && value[sel->m_value.length()] != '-')
@@ -1500,8 +1569,7 @@ bool CSSStyleSelector::checkOneSelector(CSSSelector* sel, Element* e, bool isAnc
             break;
         }
     }
-    if (sel->m_match == CSSSelector::PseudoClass) 
-    {
+    if (sel->m_match == CSSSelector::PseudoClass) {
         switch (sel->pseudoType()) {
             // Pseudo classes:
             case CSSSelector::PseudoEmpty: