@@ -35,7 +35,22 @@ static void dom_lxb_str_wrapper_release(dom_lxb_str_wrapper *wrapper)
3535 }
3636}
3737
38- static zend_always_inline bool lxb_selectors_adapted_cmp_local_name_literal (const xmlNode * node , const char * name )
38+ static bool lxb_selectors_str_cmp_loright (const char * lhs , const char * rhs )
39+ {
40+ while (true) {
41+ if (* rhs != zend_tolower_ascii (* lhs )) {
42+ return false;
43+ }
44+ if (!* lhs ) {
45+ return true;
46+ }
47+ ++ rhs ;
48+ ++ lhs ;
49+ }
50+ }
51+
52+ /* `name` is lowercase */
53+ static zend_always_inline bool lxb_selectors_cmp_html_name_lit (const xmlNode * node , const char * name )
3954{
4055 return strcmp ((const char * ) node -> name , name ) == 0 ;
4156}
@@ -48,14 +63,15 @@ static zend_always_inline bool lxb_selectors_adapted_cmp_ns(const xmlNode *a, co
4863
4964static zend_always_inline bool lxb_selectors_adapted_cmp_local_name_id (const xmlNode * node , const lxb_selectors_adapted_id * id )
5065{
51- uintptr_t ptr = (uintptr_t ) node -> name ;
52- if (id -> interned && (ptr & (ZEND_MM_ALIGNMENT - 1 )) != 0 ) {
53- /* It cannot be a heap-allocated string because the pointer is not properly aligned for a heap allocation.
54- * Therefore, it must be interned into the dictionary pool. */
55- return node -> name == id -> name ;
66+ ZEND_ASSERT (node -> doc != NULL );
67+ if (php_dom_ns_is_html_and_document_is_html (node )) {
68+ /* From https://html.spec.whatwg.org/#case-sensitivity-of-selectors:
69+ * The element name must be compared case sensitively _after_ converting the selector to lowercase.
70+ * E.g. selector "DIV" must match element "div" but not "Div". */
71+ return lxb_selectors_str_cmp_loright ((const char * ) id -> name , (const char * ) node -> name );
72+ } else {
73+ return strcmp ((const char * ) node -> name , (const char * ) id -> name ) == 0 ;
5674 }
57-
58- return strcmp ((const char * ) node -> name , (const char * ) id -> name ) == 0 ;
5975}
6076
6177static zend_always_inline const xmlAttr * lxb_selectors_adapted_attr (const xmlNode * node , const lxb_char_t * name )
@@ -64,9 +80,8 @@ static zend_always_inline const xmlAttr *lxb_selectors_adapted_attr(const xmlNod
6480 ZEND_ASSERT (node -> doc != NULL );
6581 if (php_dom_ns_is_html_and_document_is_html (node )) {
6682 /* No need to handle DTD entities as we're in HTML. */
67- size_t name_bound = strlen ((const char * ) name ) + 1 ;
6883 for (const xmlAttr * cur = node -> properties ; cur != NULL ; cur = cur -> next ) {
69- if (lexbor_str_data_nlocmp_right ( cur -> name , name , name_bound )) {
84+ if (lxb_selectors_str_cmp_loright (( const char * ) name , ( const char * ) cur -> name )) {
7085 attr = cur ;
7186 break ;
7287 }
@@ -154,18 +169,7 @@ static bool lxb_selectors_is_lowercased_html_attrib_name(const lxb_css_selector_
154169static void lxb_selectors_adapted_set_entry_id_ex (lxb_selectors_entry_t * entry , const lxb_css_selector_t * selector , const xmlNode * node )
155170{
156171 entry -> id .attr_case_insensitive = lxb_selectors_is_lowercased_html_attrib_name (selector );
157-
158- if (node -> doc != NULL && node -> doc -> dict != NULL ) {
159- const xmlChar * interned = xmlDictExists (node -> doc -> dict , selector -> name .data , selector -> name .length );
160- if (interned != NULL ) {
161- entry -> id .name = interned ;
162- entry -> id .interned = true;
163- return ;
164- }
165- }
166-
167172 entry -> id .name = selector -> name .data ;
168- entry -> id .interned = false;
169173}
170174
171175static zend_always_inline void lxb_selectors_adapted_set_entry_id (lxb_selectors_entry_t * entry , const lxb_css_selector_t * selector , const xmlNode * node )
@@ -1686,8 +1690,8 @@ lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
16861690 case LXB_CSS_SELECTOR_PSEUDO_CLASS_ANY_LINK :
16871691 /* https://drafts.csswg.org/selectors/#the-any-link-pseudo */
16881692 if (php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )
1689- && (lxb_selectors_adapted_cmp_local_name_literal (node , "a" )
1690- || lxb_selectors_adapted_cmp_local_name_literal (node , "area" )))
1693+ && (lxb_selectors_cmp_html_name_lit (node , "a" )
1694+ || lxb_selectors_cmp_html_name_lit (node , "area" )))
16911695 {
16921696 return lxb_selectors_adapted_has_attr (node , "href" );
16931697 }
@@ -1705,7 +1709,7 @@ lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
17051709 if (!php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )) {
17061710 return false;
17071711 }
1708- if (lxb_selectors_adapted_cmp_local_name_literal (node , "input" )) {
1712+ if (lxb_selectors_cmp_html_name_lit (node , "input" )) {
17091713 const xmlAttr * dom_attr = lxb_selectors_adapted_attr (node , (const lxb_char_t * ) "type" );
17101714 if (dom_attr == NULL ) {
17111715 return false;
@@ -1729,7 +1733,7 @@ lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
17291733
17301734 return res ;
17311735 }
1732- else if (lxb_selectors_adapted_cmp_local_name_literal (node , "option" )) {
1736+ else if (lxb_selectors_cmp_html_name_lit (node , "option" )) {
17331737 return lxb_selectors_adapted_has_attr (node , "selected" );
17341738 }
17351739
@@ -1802,8 +1806,8 @@ lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
18021806 case LXB_CSS_SELECTOR_PSEUDO_CLASS_LINK :
18031807 /* https://html.spec.whatwg.org/multipage/semantics-other.html#selector-link */
18041808 if (php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )
1805- && (lxb_selectors_adapted_cmp_local_name_literal (node , "a" )
1806- || lxb_selectors_adapted_cmp_local_name_literal (node , "area" )))
1809+ && (lxb_selectors_cmp_html_name_lit (node , "a" )
1810+ || lxb_selectors_cmp_html_name_lit (node , "area" )))
18071811 {
18081812 return lxb_selectors_adapted_has_attr (node , "href" );
18091813 }
@@ -1823,9 +1827,9 @@ lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
18231827
18241828 case LXB_CSS_SELECTOR_PSEUDO_CLASS_OPTIONAL :
18251829 if (php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )
1826- && (lxb_selectors_adapted_cmp_local_name_literal (node , "input" )
1827- || lxb_selectors_adapted_cmp_local_name_literal (node , "select" )
1828- || lxb_selectors_adapted_cmp_local_name_literal (node , "textarea" )))
1830+ && (lxb_selectors_cmp_html_name_lit (node , "input" )
1831+ || lxb_selectors_cmp_html_name_lit (node , "select" )
1832+ || lxb_selectors_cmp_html_name_lit (node , "textarea" )))
18291833 {
18301834 return !lxb_selectors_adapted_has_attr (node , "required" );
18311835 }
@@ -1840,8 +1844,8 @@ lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
18401844
18411845 case LXB_CSS_SELECTOR_PSEUDO_CLASS_PLACEHOLDER_SHOWN :
18421846 if (php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )
1843- && (lxb_selectors_adapted_cmp_local_name_literal (node , "input" )
1844- || lxb_selectors_adapted_cmp_local_name_literal (node , "textarea" )))
1847+ && (lxb_selectors_cmp_html_name_lit (node , "input" )
1848+ || lxb_selectors_cmp_html_name_lit (node , "textarea" )))
18451849 {
18461850 return lxb_selectors_adapted_has_attr (node , "placeholder" );
18471851 }
@@ -1856,9 +1860,9 @@ lxb_selectors_pseudo_class(const lxb_css_selector_t *selector,
18561860
18571861 case LXB_CSS_SELECTOR_PSEUDO_CLASS_REQUIRED :
18581862 if (php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )
1859- && (lxb_selectors_adapted_cmp_local_name_literal (node , "input" )
1860- || lxb_selectors_adapted_cmp_local_name_literal (node , "select" )
1861- || lxb_selectors_adapted_cmp_local_name_literal (node , "textarea" )))
1863+ && (lxb_selectors_cmp_html_name_lit (node , "input" )
1864+ || lxb_selectors_cmp_html_name_lit (node , "select" )
1865+ || lxb_selectors_cmp_html_name_lit (node , "textarea" )))
18621866 {
18631867 return lxb_selectors_adapted_has_attr (node , "required" );
18641868 }
@@ -2104,32 +2108,32 @@ lxb_selectors_pseudo_class_disabled(const xmlNode *node)
21042108 }
21052109
21062110 if (lxb_selectors_adapted_has_attr (node , "disabled" )
2107- && (lxb_selectors_adapted_cmp_local_name_literal (node , "button" )
2108- || lxb_selectors_adapted_cmp_local_name_literal (node , "input" )
2109- || lxb_selectors_adapted_cmp_local_name_literal (node , "select" )
2110- || lxb_selectors_adapted_cmp_local_name_literal (node , "textarea" )
2111- || lxb_selectors_adapted_cmp_local_name_literal (node , "optgroup" )
2112- || lxb_selectors_adapted_cmp_local_name_literal (node , "fieldset" )))
2111+ && (lxb_selectors_cmp_html_name_lit (node , "button" )
2112+ || lxb_selectors_cmp_html_name_lit (node , "input" )
2113+ || lxb_selectors_cmp_html_name_lit (node , "select" )
2114+ || lxb_selectors_cmp_html_name_lit (node , "textarea" )
2115+ || lxb_selectors_cmp_html_name_lit (node , "optgroup" )
2116+ || lxb_selectors_cmp_html_name_lit (node , "fieldset" )))
21132117 {
21142118 return true;
21152119 }
21162120
2117- if (lxb_selectors_adapted_cmp_local_name_literal (node , "fieldset" )) {
2121+ if (lxb_selectors_cmp_html_name_lit (node , "fieldset" )) {
21182122 const xmlNode * fieldset = node ;
21192123 node = node -> parent ;
21202124
21212125 while (node != NULL && CMP_NODE_TYPE (node , XML_ELEMENT_NODE )) {
21222126 /* node is a disabled fieldset that is an ancestor of fieldset */
21232127 if (php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )
2124- && lxb_selectors_adapted_cmp_local_name_literal (node , "fieldset" )
2128+ && lxb_selectors_cmp_html_name_lit (node , "fieldset" )
21252129 && lxb_selectors_adapted_has_attr (node , "disabled" ))
21262130 {
21272131 /* Search first legend child and figure out if fieldset is a descendent from that. */
21282132 const xmlNode * search_current = node -> children ;
21292133 do {
21302134 if (search_current -> type == XML_ELEMENT_NODE
21312135 && php_dom_ns_is_fast (search_current , php_dom_ns_is_html_magic_token )
2132- && lxb_selectors_adapted_cmp_local_name_literal (search_current , "legend" )) {
2136+ && lxb_selectors_cmp_html_name_lit (search_current , "legend" )) {
21332137 /* search_current is a legend element. */
21342138 const xmlNode * inner_search_current = fieldset ;
21352139
@@ -2235,8 +2239,8 @@ static bool
22352239lxb_selectors_pseudo_class_read_write (const xmlNode * node )
22362240{
22372241 if (php_dom_ns_is_fast (node , php_dom_ns_is_html_magic_token )) {
2238- if (lxb_selectors_adapted_cmp_local_name_literal (node , "input" )
2239- || lxb_selectors_adapted_cmp_local_name_literal (node , "textarea" )) {
2242+ if (lxb_selectors_cmp_html_name_lit (node , "input" )
2243+ || lxb_selectors_cmp_html_name_lit (node , "textarea" )) {
22402244 return !lxb_selectors_adapted_has_attr (node , "readonly" ) && !lxb_selectors_adapted_has_attr (node , "disabled" );
22412245 } else {
22422246 const xmlAttr * attr = lxb_selectors_adapted_attr (node , (const lxb_char_t * ) "contenteditable" );
0 commit comments