On Mon Jun 16 08:28:23 2025 +0000, Gabriel Ivăncescu wrote:
New version handles tag-specific element props, and uses a blacklist instead (of what's not an attribute), which should avoid regressions if we missed anything (as we used to treat every builtin as an attr before), plus it's shorter lists since most are attributes. Note that we have to use some element specific lists, because some of them shared DISPIDs and would conflict otherwise, and some of those aren't attributes, while on other elements sharing same DISPID, they are. Also, here's a diff to apply on top of second patch to test it on IE9 as well but it only works on Windows, as Wine is too broken right now so will have to wait.
diff --git a/dlls/mshtml/tests/dom.c b/dlls/mshtml/tests/dom.c index 1a0a829..56eb87a 100644 --- a/dlls/mshtml/tests/dom.c +++ b/dlls/mshtml/tests/dom.c @@ -3941,6 +3941,14 @@ static void test_attr_collection_builtins(IHTMLDocument2 *doc) L"onmsanimationiteration", L"onmsanimationstart", L"onmsmanipulationstatechanged", L"onmstransitionend", L"onmstransitionstart", L"role", L"spellcheck", L"x-ms-acceleratorkey", L"x-ms-aria-flowfrom" }; + static const WCHAR *generic_builtins_ie9[] = { + L"draggable", L"onabort", L"oncanplay", L"oncanplaythrough", L"onchange", L"ondurationchange", L"onemptied", L"onended", L"onerror", L"oninput", L"onload", + L"onloadeddata", L"onloadedmetadata", L"onloadstart", L"onlostpointercapture", L"onmscontentzoom", L"onmsgesturechange", L"onmsgesturedoubletap", + L"onmsgestureend", L"onmsgesturehold", L"onmsgesturestart", L"onmsgesturetap", L"onmsgotpointercapture", L"onmsinertiastart", L"onmspointerdown", + L"onmspointerenter", L"onmspointerhover", L"onmspointerleave", L"onmspointermove", L"onmspointerout", L"onmsregionupdate", L"onpause", L"onplay", L"onplaying", + L"onpointercancel", L"onpointerover", L"onpointerup", L"onprogress", L"onratechange", L"onreset", L"onseeked", L"onseeking", L"onselect", L"onstalled", + L"onsubmit", L"onsuspend", L"ontimeupdate", L"ontouchcancel", L"ontouchend", L"ontouchmove", L"ontouchstart", L"onvolumechange", L"onwaiting" + }; static const WCHAR *tags[] = { L"audio", NULL, L"b", L"cite", L"dateTime", NULL, @@ -4058,10 +4066,36 @@ static void test_attr_collection_builtins(IHTMLDocument2 *doc) L"ul", L"compact", L"type", NULL, NULL }; + static const WCHAR *tags_ie9[] = { + L"audio", L"autobuffer", L"autoplay", L"controls", L"loop", L"muted", L"onmsneedkey", L"preload", L"src", L"x-ms-playtodisabled", L"x-ms-playtopreferredsourceuri", L"x-ms-playtoprimary", NULL, + L"body", L"autofocus", L"onmessage", L"onorientationchange", L"onpagehide", L"onpageshow", L"onpopstate", L"onstorage", L"required", NULL, + L"button", L"autofocus", L"required", NULL, + L"caption", L"autofocus", L"required", NULL, + L"embed", L"autofocus", L"required", L"x-ms-playtodisabled", L"x-ms-playtopreferredsourceuri", L"x-ms-playtoprimary", NULL, + L"fieldset", L"autofocus", L"required", NULL, + L"form", L"novalidate", NULL, + L"frameset", L"autofocus", L"onhashchange", L"onmessage", L"onoffline", L"ononline", L"onorientationchange", L"onpagehide", L"onpageshow", L"onstorage", L"required", NULL, + L"iframe", L"allowfullscreen", NULL, + L"img", L"autofocus", L"crossOrigin", L"required", L"x-ms-playtodisabled", L"x-ms-playtopreferredsourceuri", L"x-ms-playtoprimary", NULL, + L"input", L"autofocus", L"list", L"max", L"min", L"multiple", L"pattern", L"placeholder", L"required", L"step", NULL, + L"legend", L"autofocus", L"required", NULL, + L"object", L"autofocus", L"required", L"x-ms-playtodisabled", L"x-ms-playtopreferredsourceuri", L"x-ms-playtoprimary", NULL, + L"script", L"async", NULL, + L"select", L"autofocus", L"required", NULL, + L"table", L"autofocus", L"required", NULL, + L"td", L"autofocus", L"required", NULL, + L"textarea", L"autofocus", L"maxLength", L"placeholder", L"required", NULL, + L"tr", L"autofocus", L"required", NULL, + L"video", L"autobuffer", L"autoplay", L"controls", L"height", L"loop", L"msStereo3DPackingMode", L"msStereo3DRenderModeatype", L"muted", L"onmsneedkey", L"onMSVideoFormatChange", + L"onMSVideoFrameStepCompleted", L"onMSVideoOptimalLayoutChanged", L"poster", L"preload", L"src", L"width", L"x-ms-playtodisabled", L"x-ms-playtopreferredsourceuri", L"x-ms-playtoprimary", NULL, + NULL + }; BOOLEAN found[ARRAY_SIZE(generic_builtins)]; + BOOLEAN found_ie9[ARRAY_SIZE(generic_builtins_ie9)]; BOOLEAN found_tag_specific[36]; + BOOLEAN found_tag_specific_ie9[20]; - const WCHAR **iter = tags, **iter_todo = tags_todo; + const WCHAR **iter = tags, **iter_todo = tags_todo, **iter_ie9 = tags_ie9; IHTMLAttributeCollection *attr_col; IHTMLDOMAttribute *attr; IHTMLElement *elem; @@ -4073,13 +4107,18 @@ static void test_attr_collection_builtins(IHTMLDocument2 *doc) BSTR bstr; while(*iter) { - const WCHAR *tag = *iter++, **todos = NULL; + const WCHAR *tag = *iter++, **todos = NULL, **attrs_ie9 = NULL; if(*iter_todo && !wcscmp(tag, *iter_todo)) { todos = ++iter_todo; while(*iter_todo++) {} } + if(compat_mode >= COMPAT_IE9 && *iter_ie9 && !wcscmp(tag, *iter_ie9)) { + attrs_ie9 = ++iter_ie9; + while(*iter_ie9++) {} + } + bstr = SysAllocString(tag); hres = IHTMLDocument2_createElement(doc, bstr, &elem); ok(hres == S_OK, "[%s] createElement failed: %08lx\n", wine_dbgstr_w(tag), hres); @@ -4101,7 +4140,9 @@ static void test_attr_collection_builtins(IHTMLDocument2 *doc) ok(hres == S_OK, "[%s] get_length failed: %08lx\n", wine_dbgstr_w(tag), hres); memset(found, 0, sizeof(found)); + memset(found_ie9, 0, sizeof(found_ie9)); memset(found_tag_specific, 0, sizeof(found_tag_specific)); + memset(found_tag_specific_ie9, 0, sizeof(found_tag_specific_ie9)); for(i = 0; i < len; i++) { BOOL expected = FALSE; @@ -4133,6 +4174,24 @@ static void test_attr_collection_builtins(IHTMLDocument2 *doc) break; } } + + if(compat_mode >= COMPAT_IE9) { + for(j = 0; j < ARRAY_SIZE(generic_builtins_ie9); j++) { + if(!wcscmp(bstr, generic_builtins_ie9[j])) { + found_ie9[j] = TRUE; + expected = TRUE; + break; + } + } + + for(j = 0; !expected && attrs_ie9 && attrs_ie9[j]; j++) { + if(!wcsicmp(bstr, attrs_ie9[j])) { + found_tag_specific_ie9[j] = TRUE; + expected = TRUE; + break; + } + } + } } ok(expected, "[%s] %s is in collection but not in expected list\n", wine_dbgstr_w(tag), wine_dbgstr_w(bstr)); SysFreeString(bstr); @@ -4155,6 +4214,13 @@ static void test_attr_collection_builtins(IHTMLDocument2 *doc) ok(found_tag_specific[i], "[%s] %s not in collection\n", wine_dbgstr_w(tag), wine_dbgstr_w(iter[i])); } iter += i + 1; + + if(compat_mode >= COMPAT_IE9) { + for(i = 0; i < ARRAY_SIZE(generic_builtins_ie9); i++) + ok(found_ie9[i], "[%s] %s not in collection\n", wine_dbgstr_w(tag), wine_dbgstr_w(generic_builtins_ie9[i])); + for(i = 0; attrs_ie9 && attrs_ie9[i]; i++) + ok(found_tag_specific_ie9[i], "[%s] %s not in collection\n", wine_dbgstr_w(tag), wine_dbgstr_w(attrs_ie9[i])); + } } } @@ -12862,6 +12928,7 @@ START_TEST(dom) compat_mode = COMPAT_IE9; run_domtest(doc_blank_ie9, test_dom_elements); run_domtest(doc_blank_ie9, test_about_blank_storage); + run_domtest(doc_blank_ie9, test_attr_collection_builtins); compat_mode = COMPAT_NONE; } run_domtest(noscript_str, test_noscript);
Why is this such a focus? Honestly. While this MR improves compatibility with older compatibility modes, I think it could be skipped entirely if the goal is to implement IE9+ attribute behavior.
This isn’t observable from scripts, right? A quick test suggests these attributes haven’t been exposed through the collection since IE8. My guess (though I haven’t tested it) is that we should be using `IHTMLElement5::ie8_attributes` in those compat modes. Your test suggests we likely need two separate collections: one that exposes these built-in attributes and one that doesn’t. The latter is what matters for IE9+ (and IE8), yet your changes seem to be focused on the former.
The current attribute collection is a frustrating piece of legacy code. It's essentially a workaround that we need for old compatibility modes that don’t treat HTML attributes like proper DOM attributes. If we can avoid taking those code paths in standards-compliant modes, that would be ideal. In the best case, the collection should be a thin wrapper around `nsIDOMMozNamedAttrMap`, and the attribute object a thin wrapper around `nsIDOMAttr`. Why can’t we structure it that way?