diff --git a/ext/dom/element.c b/ext/dom/element.c index 64c53e4a2d8a1..e25805df53eb6 100644 --- a/ext/dom/element.c +++ b/ext/dom/element.c @@ -177,10 +177,7 @@ zend_result dom_element_class_name_write(dom_object *obj, zval *newval) } /* }}} */ -/* {{{ classList TokenList -URL: https://dom.spec.whatwg.org/#dom-element-classlist -*/ -zend_result dom_element_class_list_read(dom_object *obj, zval *retval) +zval *dom_element_class_list_zval(dom_object *obj) { const uint32_t PROP_INDEX = 0; @@ -191,7 +188,15 @@ zend_result dom_element_class_list_read(dom_object *obj, zval *retval) ZEND_ASSERT(OBJ_PROP_TO_NUM(prop_info->offset) == PROP_INDEX); #endif - zval *cached_token_list = OBJ_PROP_NUM(&obj->std, PROP_INDEX); + return OBJ_PROP_NUM(&obj->std, PROP_INDEX); +} + +/* {{{ classList TokenList +URL: https://dom.spec.whatwg.org/#dom-element-classlist +*/ +zend_result dom_element_class_list_read(dom_object *obj, zval *retval) +{ + zval *cached_token_list = dom_element_class_list_zval(obj); if (Z_ISUNDEF_P(cached_token_list)) { object_init_ex(cached_token_list, dom_token_list_class_entry); dom_token_list_object *intern = php_dom_token_list_from_obj(Z_OBJ_P(cached_token_list)); diff --git a/ext/dom/php_dom.c b/ext/dom/php_dom.c index 3c8d6e15cea20..3242529d8842c 100644 --- a/ext/dom/php_dom.c +++ b/ext/dom/php_dom.c @@ -101,6 +101,7 @@ static zend_object_handlers dom_modern_nodelist_object_handlers; static zend_object_handlers dom_html_collection_object_handlers; static zend_object_handlers dom_object_namespace_node_handlers; static zend_object_handlers dom_modern_domimplementation_object_handlers; +static zend_object_handlers dom_modern_element_object_handlers; static zend_object_handlers dom_token_list_object_handlers; #ifdef LIBXML_XPATH_ENABLED zend_object_handlers dom_xpath_object_handlers; @@ -669,6 +670,21 @@ static zend_object *dom_objects_store_clone_obj(zend_object *zobject) /* {{{ */ } /* }}} */ +static zend_object *dom_modern_element_clone_obj(zend_object *zobject) +{ + zend_object *clone = dom_objects_store_clone_obj(zobject); + + /* The $classList property is unique per element, and cached due to its [[SameObject]] requirement. + * Remove it from the clone so the clone will get a fresh instance upon demand. */ + zval *class_list = dom_element_class_list_zval(php_dom_obj_from_obj(clone)); + if (!Z_ISUNDEF_P(class_list)) { + zval_ptr_dtor(class_list); + ZVAL_UNDEF(class_list); + } + + return clone; +} + static zend_object *dom_object_namespace_node_clone_obj(zend_object *zobject) { dom_object_namespace_node *intern = php_dom_namespace_node_obj_from_obj(zobject); @@ -778,6 +794,9 @@ PHP_MINIT_FUNCTION(dom) * one instance per parent object. */ dom_modern_domimplementation_object_handlers.clone_obj = NULL; + memcpy(&dom_modern_element_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); + dom_modern_element_object_handlers.clone_obj = dom_modern_element_clone_obj; + memcpy(&dom_nnodemap_object_handlers, &dom_object_handlers, sizeof(zend_object_handlers)); dom_nnodemap_object_handlers.free_obj = dom_nnodemap_objects_free_storage; dom_nnodemap_object_handlers.read_dimension = dom_nodemap_read_dimension; @@ -1108,7 +1127,7 @@ PHP_MINIT_FUNCTION(dom) dom_modern_element_class_entry = register_class_Dom_Element(dom_modern_node_class_entry, dom_modern_parentnode_class_entry, dom_modern_childnode_class_entry); dom_modern_element_class_entry->create_object = dom_objects_new; - dom_modern_element_class_entry->default_object_handlers = &dom_object_handlers; + dom_modern_element_class_entry->default_object_handlers = &dom_modern_element_object_handlers; zend_hash_init(&dom_modern_element_prop_handlers, 0, NULL, NULL, true); DOM_REGISTER_PROP_HANDLER(&dom_modern_element_prop_handlers, "namespaceURI", dom_node_namespace_uri_read, NULL); @@ -1132,7 +1151,7 @@ PHP_MINIT_FUNCTION(dom) dom_html_element_class_entry = register_class_Dom_HTMLElement(dom_modern_element_class_entry); dom_html_element_class_entry->create_object = dom_objects_new; - dom_html_element_class_entry->default_object_handlers = &dom_object_handlers; + dom_html_element_class_entry->default_object_handlers = &dom_modern_element_object_handlers; zend_hash_add_new_ptr(&classes, dom_html_element_class_entry->name, &dom_modern_element_prop_handlers); dom_text_class_entry = register_class_DOMText(dom_characterdata_class_entry); diff --git a/ext/dom/php_dom.h b/ext/dom/php_dom.h index bb0d83676dca1..22c738b20e0f6 100644 --- a/ext/dom/php_dom.h +++ b/ext/dom/php_dom.h @@ -187,6 +187,7 @@ bool php_dom_create_nullable_object(xmlNodePtr obj, zval *return_value, dom_obje xmlNodePtr dom_clone_node(php_dom_libxml_ns_mapper *ns_mapper, xmlNodePtr node, xmlDocPtr doc, bool recursive); void dom_set_document_ref_pointers(xmlNodePtr node, php_libxml_ref_obj *document); void dom_set_document_ref_pointers_attr(xmlAttrPtr attr, php_libxml_ref_obj *document); +zval *dom_element_class_list_zval(dom_object *obj); typedef enum { DOM_LOAD_STRING = 0, diff --git a/ext/dom/tests/modern/token_list/gh18744.phpt b/ext/dom/tests/modern/token_list/gh18744.phpt new file mode 100644 index 0000000000000..a9109df789d8f --- /dev/null +++ b/ext/dom/tests/modern/token_list/gh18744.phpt @@ -0,0 +1,30 @@ +--TEST-- +GH-18744 (classList works not correctly if copy HTMLElement by clone keyword.) +--EXTENSIONS-- +dom +--FILE-- +createElement('div'); +$ele1->classList->add('foo'); +$ele2 = clone $ele1; +$ele2->classList->add('bar'); + +echo "Element1 class: " . $ele1->getAttribute('class'); +echo "\n"; +echo "Element2 class: " . $ele2->getAttribute('class'); +echo "\n"; + +var_dump($ele1->classList !== $ele2->classList); +// These comparisons are not pointless: they're getters and should not create new objects +var_dump($ele1->classList === $ele1->classList); +var_dump($ele2->classList === $ele2->classList); + +?> +--EXPECT-- +Element1 class: foo +Element2 class: foo bar +bool(true) +bool(true) +bool(true)