From 1a81c752dc0002c1866a5ed162da9e2ef493a3c6 Mon Sep 17 00:00:00 2001 From: "eric@webkit.org" Date: Sun, 6 Jan 2008 20:35:04 +0000 Subject: [PATCH] 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): git-svn-id: https://svn.webkit.org/repository/webkit/trunk@29212 268f45cc-cd09-0410-ab3c-d52691b4dbfc --- LayoutTests/ChangeLog | 11 ++ .../html-attr-case-sensitivity-expected.txt | 129 +++++++++++++ .../fast/css/html-attr-case-sensitivity.html | 13 ++ .../resources/html-attr-case-sensitivity.js | 176 ++++++++++++++++++ WebCore/ChangeLog | 15 ++ WebCore/css/CSSStyleSelector.cpp | 84 ++++++++- 6 files changed, 420 insertions(+), 8 deletions(-) create mode 100644 LayoutTests/fast/css/html-attr-case-sensitivity-expected.txt create mode 100644 LayoutTests/fast/css/html-attr-case-sensitivity.html create mode 100644 LayoutTests/fast/css/resources/html-attr-case-sensitivity.js diff --git a/LayoutTests/ChangeLog b/LayoutTests/ChangeLog index a1661bda23d2..2819bb64bdee 100644 --- a/LayoutTests/ChangeLog +++ b/LayoutTests/ChangeLog @@ -34,6 +34,17 @@ * fast/dom/Range/range-clone-empty.html: Added. * fast/dom/Range/resources/range-clone-empty.js: Added. +2008-01-06 Eric Seidel + + 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 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 index 000000000000..3d59366b0224 --- /dev/null +++ b/LayoutTests/fast/css/html-attr-case-sensitivity-expected.txt @@ -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 index 000000000000..2a62a65c972f --- /dev/null +++ b/LayoutTests/fast/css/html-attr-case-sensitivity.html @@ -0,0 +1,13 @@ + + + + + + + +

+
+ + + + 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 index 000000000000..edcf1d7df7fd --- /dev/null +++ b/LayoutTests/fast/css/resources/html-attr-case-sensitivity.js @@ -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; diff --git a/WebCore/ChangeLog b/WebCore/ChangeLog index 3a7199b42f76..7391eb2764a2 100644 --- a/WebCore/ChangeLog +++ b/WebCore/ChangeLog @@ -60,6 +60,21 @@ * html/CanvasPattern.cpp: (WebCore::CanvasPattern::CanvasPattern): +2008-01-06 Eric Seidel + + 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 Reviewed by Sam. diff --git a/WebCore/css/CSSStyleSelector.cpp b/WebCore/css/CSSStyleSelector.cpp index 08e62451c131..f2276154818a 100644 --- a/WebCore/css/CSSStyleSelector.cpp +++ b/WebCore/css/CSSStyleSelector.cpp @@ -1409,6 +1409,73 @@ CSSStyleSelector::SelectorMatch CSSStyleSelector::checkSelector(CSSSelector* sel return SelectorFailsCompletely; } +static void addLocalNameToSet(HashSet* set, const QualifiedName& qName) +{ + set->add(qName.localName().impl()); +} + +static HashSet* 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* attrSet = new HashSet; + + 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* 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: -- 2.36.0