[PATCH v6 0/7] MR10025: mshtml/msxml3: Add XMLSerializer, embedded XML declaration handling
This patchset adds XMLSerializer support and fixes several compatibility issues for Adobe Creative Cloud installer and similar applications. XMLSerializer implementation (mshtml) - Implements the `IXMLSerializer` interface with `serializeToString()` method - Allows JavaScript to serialize DOM elements to XML strings Embedded XML declaration handling (msxml3) - Adds CDATA wrapping for embedded `<?xml?>` declarations inside elements - Windows MSXML tolerates these nested declarations but libxml2 rejects them - The preprocessing step wraps problematic content in CDATA sections before parsing ~~DISPATCH_METHOD|DISPATCH_PROPERTYGET fixes (jscript/mshtml)~~ This was already done !10004 Also added tests for XMLSerializer (empty elements, text nodes, special characters, nested elements, multiple children), embedded XML declaration handling (multiple declarations, deeply nested, encoding attributes, self-closing elements) and DISPATCH_METHOD|DISPATCH_PROPERTYGET behavior in both quirks and standards modes - Filip Bakreski -- v6: libs/xml2: Tolerate embedded XML declarations inside elements. mshtml/tests: Remove XMLSerializer from todo list and add prototype props check. mshtml/tests: Fix method_reference_call test to use .call() for proper this binding. mshtml/msxml3: Add tests for XMLSerializer and embedded XML declaration handling. mshtml: Return E_ACCESSDENIED for METHOD|PROPERTYGET on function objects in quirks mode mshtml/tests: Re-add test_method_vs_getter for DISPATCH_METHOD|DISPATCH_PROPERTYGET handling mshtml/msxml3: Add IXMLSerializer implementation and CDATA wrapping for embedded XML declarations https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
From: Phiality <bakreski03@gmail.com> - Revert incorrect DISPATCH_PROPERTYGET | DISPATCH_METHOD behavior that broke compatibility - Add IXMLSerializer interface implementation for mshtml - Add CDATA wrapping for embedded XML declarations in msxml3 to handle cases where Windows MSXML tolerates embedded <?xml?> declarations but libxml2 rejects them --- dlls/jscript/dispex.c | 8 +- dlls/mshtml/dispex.c | 1 - dlls/mshtml/mshtml_private.h | 5 +- dlls/mshtml/omnavigator.c | 150 +++++++++++++ dlls/mshtml/tests/dom.c | 88 -------- dlls/msxml3/domdoc.c | 418 ++++++++++++++++++++++++++++++++++- include/mshtmdid.h | 4 + include/mshtml.idl | 40 ++++ 8 files changed, 619 insertions(+), 95 deletions(-) diff --git a/dlls/jscript/dispex.c b/dlls/jscript/dispex.c index 14eb30b03c5..f39a8cd4b12 100644 --- a/dlls/jscript/dispex.c +++ b/dlls/jscript/dispex.c @@ -2136,10 +2136,10 @@ static HRESULT WINAPI DispatchEx_InvokeEx(IWineJSDispatch *iface, DISPID id, LCI if(pspCaller) IServiceProvider_AddRef(pspCaller); - if(wFlags == (DISPATCH_METHOD | DISPATCH_PROPERTYGET)) - wFlags = (This->ctx->version < SCRIPTLANGUAGEVERSION_ES5 || pdp->cArgs) ? DISPATCH_METHOD : DISPATCH_PROPERTYGET; - switch(wFlags) { + case DISPATCH_METHOD|DISPATCH_PROPERTYGET: + wFlags = DISPATCH_METHOD; + /* fall through */ case DISPATCH_METHOD: case DISPATCH_CONSTRUCT: { jsval_t *argv, buf[6], r; @@ -2712,7 +2712,7 @@ HRESULT disp_call(script_ctx_t *ctx, IDispatch *disp, DISPID id, WORD flags, uns jsdisp_release(jsdisp); flags &= ~DISPATCH_JSCRIPT_INTERNAL_MASK; - if(ret && argc && (!jsdisp || ctx->version < SCRIPTLANGUAGEVERSION_ES5)) + if(ret && argc) flags |= DISPATCH_PROPERTYGET; dp.cArgs = argc; diff --git a/dlls/mshtml/dispex.c b/dlls/mshtml/dispex.c index a6b8afb0543..0af89aca630 100644 --- a/dlls/mshtml/dispex.c +++ b/dlls/mshtml/dispex.c @@ -868,7 +868,6 @@ static HRESULT dispex_value(DispatchEx *This, LCID lcid, WORD flags, DISPPARAMS return This->info->vtbl->value(This, lcid, flags, params, res, ei, caller); switch(flags) { - case DISPATCH_PROPERTYGET | DISPATCH_METHOD: case DISPATCH_PROPERTYGET: V_VT(res) = VT_BSTR; hres = dispex_to_string(This, &V_BSTR(res)); diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index ce872249e62..536d9ccbff7 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -160,6 +160,7 @@ struct constructor; XDIID(DispHTMLWindow2) \ XDIID(DispHTMLXMLHttpRequest) \ XDIID(DispXDomainRequest) \ + XDIID(DispXMLSerializer) \ XDIID(DispSVGCircleElement) \ XDIID(DispSVGSVGElement) \ XDIID(DispSVGTSpanElement) \ @@ -298,6 +299,7 @@ struct constructor; XIID(IHTMLXMLHttpRequestFactory) \ XIID(IHTMLXDomainRequest) \ XIID(IHTMLXDomainRequestFactory) \ + XIID(IXMLSerializer) \ XIID(IOmHistory) \ XIID(IOmNavigator) \ XIID(ISVGCircleElement) \ @@ -530,7 +532,8 @@ typedef struct { X(Window) \ X(XDomainRequest) \ X(XMLDocument) \ - X(XMLHttpRequest) + X(XMLHttpRequest) \ + X(XMLSerializer) typedef enum { OBJID_NONE, diff --git a/dlls/mshtml/omnavigator.c b/dlls/mshtml/omnavigator.c index fc4d2f5b408..6d26d13636d 100644 --- a/dlls/mshtml/omnavigator.c +++ b/dlls/mshtml/omnavigator.c @@ -451,6 +451,156 @@ static HRESULT init_dom_parser_ctor(struct constructor *constr) return S_OK; } +struct xml_serializer { + DispatchEx dispex; + IXMLSerializer IXMLSerializer_iface; +}; + +static inline struct xml_serializer *impl_from_IXMLSerializer(IXMLSerializer *iface) +{ + return CONTAINING_RECORD(iface, struct xml_serializer, IXMLSerializer_iface); +} + +DISPEX_IDISPATCH_IMPL(xml_serializer, IXMLSerializer, impl_from_IXMLSerializer(iface)->dispex) + +static HRESULT WINAPI xml_serializer_serializeToString(IXMLSerializer *iface, IHTMLDOMNode *node, BSTR *pString) +{ + struct xml_serializer *This = impl_from_IXMLSerializer(iface); + HTMLDOMNode *dom_node; + nsAString nsstr; + HRESULT hres; + + TRACE("(%p)->(%p %p)\n", This, node, pString); + + if(!node || !pString) + return E_INVALIDARG; + + *pString = NULL; + + dom_node = unsafe_impl_from_IHTMLDOMNode(node); + if(!dom_node) { + WARN("not an HTMLDOMNode\n"); + return E_INVALIDARG; + } + + nsAString_Init(&nsstr, NULL); + hres = nsnode_to_nsstring(dom_node->nsnode, &nsstr); + if(SUCCEEDED(hres)) { + const WCHAR *str; + nsAString_GetData(&nsstr, &str); + *pString = SysAllocString(str); + if(!*pString) + hres = E_OUTOFMEMORY; + } + nsAString_Finish(&nsstr); + + return hres; +} + +static const IXMLSerializerVtbl xml_serializer_vtbl = { + xml_serializer_QueryInterface, + xml_serializer_AddRef, + xml_serializer_Release, + xml_serializer_GetTypeInfoCount, + xml_serializer_GetTypeInfo, + xml_serializer_GetIDsOfNames, + xml_serializer_Invoke, + xml_serializer_serializeToString +}; + +static inline struct xml_serializer *xml_serializer_from_DispatchEx(DispatchEx *iface) +{ + return CONTAINING_RECORD(iface, struct xml_serializer, dispex); +} + +static void *xml_serializer_query_interface(DispatchEx *dispex, REFIID riid) +{ + struct xml_serializer *This = xml_serializer_from_DispatchEx(dispex); + + if(IsEqualGUID(&IID_IXMLSerializer, riid)) + return &This->IXMLSerializer_iface; + + return NULL; +} + +static void xml_serializer_destructor(DispatchEx *dispex) +{ + struct xml_serializer *This = xml_serializer_from_DispatchEx(dispex); + free(This); +} + +static HRESULT init_xml_serializer_ctor(struct constructor*); + +static const dispex_static_data_vtbl_t xml_serializer_dispex_vtbl = { + .query_interface = xml_serializer_query_interface, + .destructor = xml_serializer_destructor, +}; + +static const tid_t xml_serializer_iface_tids[] = { + IXMLSerializer_tid, + 0 +}; + +dispex_static_data_t XMLSerializer_dispex = { + .id = OBJID_XMLSerializer, + .init_constructor = &init_xml_serializer_ctor, + .vtbl = &xml_serializer_dispex_vtbl, + .disp_tid = DispXMLSerializer_tid, + .iface_tids = xml_serializer_iface_tids, +}; + +static HRESULT xml_serializer_ctor_value(DispatchEx *dispex, LCID lcid, WORD flags, DISPPARAMS *params, + VARIANT *res, EXCEPINFO *ei, IServiceProvider *caller) +{ + struct constructor *This = constructor_from_DispatchEx(dispex); + struct xml_serializer *ret; + + TRACE("\n"); + + switch(flags) { + case DISPATCH_METHOD|DISPATCH_PROPERTYGET: + if(!res) + return E_INVALIDARG; + /* fall through */ + case DISPATCH_METHOD: + case DISPATCH_CONSTRUCT: + break; + default: + FIXME("flags %x not supported\n", flags); + return E_NOTIMPL; + } + + if(!(ret = calloc(1, sizeof(*ret)))) + return E_OUTOFMEMORY; + + ret->IXMLSerializer_iface.lpVtbl = &xml_serializer_vtbl; + init_dispatch(&ret->dispex, &XMLSerializer_dispex, This->window, dispex_compat_mode(&This->dispex)); + + V_VT(res) = VT_DISPATCH; + V_DISPATCH(res) = (IDispatch*)&ret->IXMLSerializer_iface; + return S_OK; +} + +static const dispex_static_data_vtbl_t xml_serializer_ctor_dispex_vtbl = { + .destructor = constructor_destructor, + .traverse = constructor_traverse, + .unlink = constructor_unlink, + .value = xml_serializer_ctor_value, +}; + +static dispex_static_data_t xml_serializer_ctor_dispex = { + .name = "XMLSerializer", + .constructor_id = OBJID_XMLSerializer, + .vtbl = &xml_serializer_ctor_dispex_vtbl, +}; + +static HRESULT init_xml_serializer_ctor(struct constructor *constr) +{ + init_dispatch(&constr->dispex, &xml_serializer_ctor_dispex, constr->window, + dispex_compat_mode(&constr->window->event_target.dispex)); + return S_OK; +} + typedef struct { DispatchEx dispex; IHTMLScreen IHTMLScreen_iface; diff --git a/dlls/mshtml/tests/dom.c b/dlls/mshtml/tests/dom.c index 0a8398f935d..3235cbbaca5 100644 --- a/dlls/mshtml/tests/dom.c +++ b/dlls/mshtml/tests/dom.c @@ -11731,92 +11731,6 @@ static void test_case_insens(IHTMLDocument2 *doc) IDispatchEx_Release(dispex); } -static void test_method_vs_getter(IHTMLDocument2 *doc) -{ - DISPPARAMS dp = { 0 }; - IDispatchEx *dispex; - VARIANT v, arg; - DISPID dispid; - HRESULT hres; - BSTR bstr; - - hres = IHTMLDocument2_QueryInterface(doc, &IID_IDispatchEx, (void**)&dispex); - ok(hres == S_OK, "Could not get IDispatchEx: %08lx\n", hres); - - V_VT(&v) = VT_EMPTY; - hres = IDispatchEx_InvokeEx(dispex, DISPID_VALUE, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - ok(V_VT(&v) == VT_BSTR, "V_VT = %d\n", V_VT(&v)); - VariantClear(&v); - - bstr = SysAllocString(L"body"); - hres = IDispatchEx_GetDispID(dispex, bstr, 0, &dispid); - ok(hres == S_OK, "GetDispID returned: %08lx\n", hres); - SysFreeString(bstr); - - hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - ok(V_VT(&v) == VT_DISPATCH, "V_VT = %d\n", V_VT(&v)); - ok(V_DISPATCH(&v) != NULL, "V_DISPATCH == NULL\n"); - VariantClear(&v); - - bstr = SysAllocString(L"title"); - hres = IDispatchEx_GetDispID(dispex, bstr, 0, &dispid); - ok(hres == S_OK, "GetDispID returned: %08lx\n", hres); - SysFreeString(bstr); - - hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - ok(V_VT(&v) == VT_BSTR, "V_VT = %d\n", V_VT(&v)); - VariantClear(&v); - - bstr = SysAllocString(L"close"); - hres = IDispatchEx_GetDispID(dispex, bstr, 0, &dispid); - ok(hres == S_OK, "GetDispID returned: %08lx\n", hres); - SysFreeString(bstr); - - hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - if(compat_mode < COMPAT_IE9) - ok(V_VT(&v) == VT_EMPTY, "V_VT = %d\n", V_VT(&v)); - else { - ok(V_VT(&v) == VT_DISPATCH, "V_VT = %d\n", V_VT(&v)); - ok(V_DISPATCH(&v) != NULL, "V_DISPATCH == NULL\n"); - } - VariantClear(&v); - - dp.cArgs = 1; - dp.rgvarg = &arg; - V_VT(&arg) = VT_I4; - V_I4(&arg) = 42; - hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - ok(V_VT(&v) == VT_EMPTY, "V_VT = %d\n", V_VT(&v)); - - dp.cArgs = 0; - hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - ok(V_VT(&v) == VT_DISPATCH, "V_VT = %d\n", V_VT(&v)); - ok(V_DISPATCH(&v) != NULL, "V_DISPATCH == NULL\n"); - IDispatchEx_Release(dispex); - - hres = IDispatch_QueryInterface(V_DISPATCH(&v), &IID_IDispatchEx, (void**)&dispex); - ok(hres == S_OK, "Could not get IDispatchEx: %08lx\n", hres); - VariantClear(&v); - - hres = IDispatchEx_InvokeEx(dispex, DISPID_VALUE, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - if(compat_mode < COMPAT_IE9) - todo_wine - ok(hres == E_ACCESSDENIED, "InvokeEx returned: %08lx\n", hres); - else { - ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - ok(V_VT(&v) == VT_BSTR, "V_VT = %d\n", V_VT(&v)); - } - VariantClear(&v); - - IDispatchEx_Release(dispex); -} - static void test_null_write(IHTMLDocument2 *doc) { HRESULT hres; @@ -13928,11 +13842,9 @@ START_TEST(dom) run_domtest(doc_blank_ie8, test_quirks_mode_perf_toJSON); run_domtest(doctype_str, test_doctype); run_domtest(case_insens_str, test_case_insens); - run_domtest(doc_blank, test_method_vs_getter); if(is_ie9plus) { compat_mode = COMPAT_IE9; run_domtest(emptydiv_ie9_str, test_docfrag); - run_domtest(doc_blank_ie9, test_method_vs_getter); compat_mode = COMPAT_NONE; } diff --git a/dlls/msxml3/domdoc.c b/dlls/msxml3/domdoc.c index f35daff5f6d..17eff897110 100644 --- a/dlls/msxml3/domdoc.c +++ b/dlls/msxml3/domdoc.c @@ -486,11 +486,415 @@ static void sax_serror(void* ctx, const xmlError* err) LIBXML2_CALLBACK_SERROR(doparse, err); } +/* Check if ptr points to "<?xml" in UTF-8 or UTF-16LE format */ +static int is_xml_decl(const char *ptr, int len, int is_utf16) +{ + if (is_utf16) + { + /* UTF-16LE: each char is 2 bytes, second byte is 0 for ASCII */ + if (len < 10) return 0; + return ptr[0] == '<' && ptr[1] == 0 && + ptr[2] == '?' && ptr[3] == 0 && + ptr[4] == 'x' && ptr[5] == 0 && + ptr[6] == 'm' && ptr[7] == 0 && + ptr[8] == 'l' && ptr[9] == 0; + } + else + { + if (len < 5) return 0; + return !strncmp(ptr, "<?xml", 5); + } +} + +/* Check if char is whitespace (handles UTF-16LE) */ +static int is_ws(const char *ptr, int is_utf16) +{ + char c = ptr[0]; + if (is_utf16 && ptr[1] != 0) return 0; + return c == ' ' || c == '\t' || c == '\r' || c == '\n'; +} + +/* Check if ptr points to "</" in UTF-8 or UTF-16LE format */ +static int is_close_tag(const char *ptr, int len, int is_utf16) +{ + if (is_utf16) + { + if (len < 4) return 0; + return ptr[0] == '<' && ptr[1] == 0 && ptr[2] == '/' && ptr[3] == 0; + } + else + { + if (len < 2) return 0; + return ptr[0] == '<' && ptr[1] == '/'; + } +} + +/* Check if ptr points to "<" followed by a letter (start tag) */ +static int is_start_tag(const char *ptr, int len, int is_utf16) +{ + char c; + if (is_utf16) + { + if (len < 4) return 0; + if (ptr[0] != '<' || ptr[1] != 0) return 0; + c = ptr[2]; + if (ptr[3] != 0) return 0; + } + else + { + if (len < 2) return 0; + if (ptr[0] != '<') return 0; + c = ptr[1]; + } + return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z'); +} + +/* Check if ptr points to ">" */ +static int is_gt(const char *ptr, int is_utf16) +{ + if (is_utf16) + return ptr[0] == '>' && ptr[1] == 0; + return ptr[0] == '>'; +} + +/* Check if ptr points to "/>" (self-closing tag end) */ +static int is_self_close(const char *ptr, int len, int is_utf16) +{ + if (is_utf16) + { + if (len < 4) return 0; + return ptr[0] == '/' && ptr[1] == 0 && ptr[2] == '>' && ptr[3] == 0; + } + if (len < 2) return 0; + return ptr[0] == '/' && ptr[1] == '>'; +} + +/* Check if ptr points to "<!" (comment, CDATA, DOCTYPE, etc.) */ +static int is_markup_decl(const char *ptr, int len, int is_utf16) +{ + if (is_utf16) + { + if (len < 4) return 0; + return ptr[0] == '<' && ptr[1] == 0 && ptr[2] == '!' && ptr[3] == 0; + } + if (len < 2) return 0; + return ptr[0] == '<' && ptr[1] == '!'; +} + +/* Check if ptr points to "<?" (PI like <?xml) */ +static int is_pi(const char *ptr, int len, int is_utf16) +{ + if (is_utf16) + { + if (len < 4) return 0; + return ptr[0] == '<' && ptr[1] == 0 && ptr[2] == '?' && ptr[3] == 0; + } + if (len < 2) return 0; + return ptr[0] == '<' && ptr[1] == '?'; +} + +/* Check if element name ends with "XMLData" (case-sensitive). + * ptr points to first char after '<', len is remaining buffer length. + * Returns 1 if element name ends with XMLData, 0 otherwise. */ +static int is_xmldata_element(const char *ptr, int len, int is_utf16) +{ + const char *p = ptr; + const char *end = ptr + len; + const char *name_end = NULL; + int char_size = is_utf16 ? 2 : 1; + int name_len; + const char *suffix_check; + + /* Find end of element name (whitespace, >, or /) */ + while (p + char_size <= end) + { + char c = p[0]; + if (is_utf16 && p[1] != 0) { p += char_size; continue; } + if (c == ' ' || c == '\t' || c == '\r' || c == '\n' || c == '>' || c == '/') + { + name_end = p; + break; + } + p += char_size; + } + if (!name_end) return 0; + + name_len = (name_end - ptr) / char_size; + if (name_len < 7) return 0; /* "XMLData" is 7 chars */ + + /* Check if name ends with "XMLData" */ + suffix_check = name_end - (7 * char_size); + if (is_utf16) + { + return suffix_check[0] == 'X' && suffix_check[1] == 0 && + suffix_check[2] == 'M' && suffix_check[3] == 0 && + suffix_check[4] == 'L' && suffix_check[5] == 0 && + suffix_check[6] == 'D' && suffix_check[7] == 0 && + suffix_check[8] == 'a' && suffix_check[9] == 0 && + suffix_check[10] == 't' && suffix_check[11] == 0 && + suffix_check[12] == 'a' && suffix_check[13] == 0; + } + else + { + return !strncmp(suffix_check, "XMLData", 7); + } +} + +/* Wrap embedded XML content in CDATA so it becomes text, not parsed elements. + * Windows MSXML tolerates embedded <?xml?> declarations but libxml2 does not. + * Returns a newly allocated buffer that must be freed, or NULL if no changes needed. */ +static char *wrap_embedded_xml_in_cdata(const char *ptr, int len, int *new_len, xmlCharEncoding encoding) +{ + const char *p, *decl_start, *content_start, *content_end = NULL, *end; + char *result, *dst; + int skip_first = 0; + int is_utf16 = (encoding == XML_CHAR_ENCODING_UTF16LE); /* BE not handled - Windows uses LE */ + int char_size = is_utf16 ? 2 : 1; + int decl_size = is_utf16 ? 10 : 5; /* "<?xml" */ + int nesting; + + TRACE("len=%d encoding=%d is_utf16=%d\n", len, encoding, is_utf16); + + end = ptr + len; + + /* Check if document starts with XML declaration - if so, skip it for search */ + p = ptr; + while (p + char_size <= end && is_ws(p, is_utf16)) + p += char_size; + if (p + decl_size <= end && is_xml_decl(p, end - p, is_utf16)) + skip_first = 1; + + /* Search for embedded <?xml declarations */ + decl_start = NULL; + for (p = ptr; p + decl_size <= end; p += char_size) + { + if (is_xml_decl(p, end - p, is_utf16)) + { + if (skip_first) + { + skip_first = 0; + continue; + } + decl_start = p; + break; + } + } + + if (!decl_start) + { + /* No <?xml found - also check for *XMLData elements whose content should be wrapped. + * Pattern: <*XMLData><Element>... where Element content should remain as text. */ + const char *xmldata_start = NULL; + const char *xmldata_content = NULL; + + for (p = ptr; p + char_size <= end; p += char_size) + { + if (is_start_tag(p, end - p, is_utf16)) + { + /* Check if this element name ends with "XMLData" */ + if (is_xmldata_element(p + char_size, end - p - char_size, is_utf16)) + { + /* Found *XMLData element - find end of its start tag */ + const char *tag_end; + for (tag_end = p + char_size; tag_end + char_size <= end; tag_end += char_size) + { + if (is_gt(tag_end, is_utf16)) + { + xmldata_content = tag_end + char_size; + break; + } + if (is_self_close(tag_end, end - tag_end, is_utf16)) + break; /* Self-closing, no content */ + } + if (xmldata_content) + { + /* Check if content starts with an element (needs CDATA wrapping) */ + const char *content_check = xmldata_content; + /* Skip whitespace */ + while (content_check + char_size <= end && is_ws(content_check, is_utf16)) + content_check += char_size; + /* Check for element start that's not <? or <! */ + if (is_start_tag(content_check, end - content_check, is_utf16) && + !is_pi(content_check, end - content_check, is_utf16) && + !is_markup_decl(content_check, end - content_check, is_utf16)) + { + xmldata_start = p; + content_start = xmldata_content; + TRACE("found *XMLData element with element content\n"); + break; + } + } + xmldata_content = NULL; + } + } + } + + if (!xmldata_start) + { + TRACE("no embedded declarations found\n"); + return NULL; + } + + /* Find the matching close tag for the *XMLData element */ + nesting = 0; + for (p = content_start; p + char_size <= end; p += char_size) + { + if (is_start_tag(p, end - p, is_utf16)) + { + const char *tag_end; + int is_selfclose = 0; + for (tag_end = p + char_size; tag_end + char_size <= end; tag_end += char_size) + { + if (is_self_close(tag_end, end - tag_end, is_utf16)) + { + is_selfclose = 1; + break; + } + if (is_gt(tag_end, is_utf16)) + break; + } + if (!is_selfclose) + nesting++; + } + else if (is_close_tag(p, end - p, is_utf16)) + { + if (nesting == 0) + { + content_end = p; + break; + } + nesting--; + } + } + if (!content_end) + { + TRACE("could not find *XMLData element end\n"); + return NULL; + } + goto do_wrap; + } + + /* Find the > before the embedded declaration (end of parent start tag) */ + content_start = NULL; + for (p = decl_start - char_size; p >= ptr; p -= char_size) + { + if (is_gt(p, is_utf16)) + { + content_start = p + char_size; + break; + } + } + if (!content_start) + { + TRACE("could not find parent element start\n"); + return NULL; + } + + /* Find the matching closing tag by tracking nesting level */ + nesting = 0; /* Start at 0 - we're inside parent, looking for its close tag */ + content_end = NULL; + for (p = decl_start; p + char_size <= end; p += char_size) + { + if (is_start_tag(p, end - p, is_utf16)) + { + /* Check if this is a self-closing tag by scanning for /> or > */ + const char *tag_end; + int is_selfclose = 0; + for (tag_end = p + char_size; tag_end + char_size <= end; tag_end += char_size) + { + if (is_self_close(tag_end, end - tag_end, is_utf16)) + { + is_selfclose = 1; + break; + } + if (is_gt(tag_end, is_utf16)) + break; + } + if (!is_selfclose) + nesting++; + } + else if (is_close_tag(p, end - p, is_utf16)) + { + if (nesting == 0) + { + /* This close tag is for our parent element */ + content_end = p; + break; + } + nesting--; + } + } + if (!content_end) + { + TRACE("could not find parent element end\n"); + return NULL; + } + +do_wrap: + TRACE("wrapping content in CDATA: start=%d end=%d\n", + (int)(content_start - ptr), (int)(content_end - ptr)); + + /* Create result with CDATA wrapper: <![CDATA[ ... ]]> */ + /* Extra space: 9 chars for <![CDATA[ and 3 for ]]> = 12, doubled for UTF-16 */ + result = malloc(len + 24 * char_size + char_size); + if (!result) + return NULL; + + dst = result; + /* Copy everything up to content_start */ + for (p = ptr; p < content_start; p++) + *dst++ = *p; + /* Insert <![CDATA[ */ + if (is_utf16) + { + *dst++ = '<'; *dst++ = 0; + *dst++ = '!'; *dst++ = 0; + *dst++ = '['; *dst++ = 0; + *dst++ = 'C'; *dst++ = 0; + *dst++ = 'D'; *dst++ = 0; + *dst++ = 'A'; *dst++ = 0; + *dst++ = 'T'; *dst++ = 0; + *dst++ = 'A'; *dst++ = 0; + *dst++ = '['; *dst++ = 0; + } + else + { + memcpy(dst, "<![CDATA[", 9); + dst += 9; + } + /* Copy the content */ + for (p = content_start; p < content_end; p++) + *dst++ = *p; + /* Insert ]]> */ + if (is_utf16) + { + *dst++ = ']'; *dst++ = 0; + *dst++ = ']'; *dst++ = 0; + *dst++ = '>'; *dst++ = 0; + } + else + { + memcpy(dst, "]]>", 3); + dst += 3; + } + /* Copy the rest */ + for (p = content_end; p < end; p++) + *dst++ = *p; + + if (is_utf16) + *dst++ = 0; + *dst = '\0'; + *new_len = dst - result - (is_utf16 ? 1 : 0); + return result; +} + static xmlDocPtr doparse(domdoc* This, char const* ptr, int len, xmlCharEncoding encoding) { char *ctx_encoding; xmlDocPtr doc = NULL; xmlParserCtxtPtr pctx; + char *modified_ptr = NULL; + int modified_len; static xmlSAXHandler sax_handler = { xmlSAX2InternalSubset, /* internalSubset */ xmlSAX2IsStandalone, /* isStandalone */ @@ -526,10 +930,19 @@ static xmlDocPtr doparse(domdoc* This, char const* ptr, int len, xmlCharEncoding sax_serror /* serror */ }; + /* Wrap embedded XML declarations in CDATA - Windows MSXML tolerates these but libxml2 rejects them */ + modified_ptr = wrap_embedded_xml_in_cdata(ptr, len, &modified_len, encoding); + if (modified_ptr) + { + ptr = modified_ptr; + len = modified_len; + } + pctx = xmlCreateMemoryParserCtxt(ptr, len); if (!pctx) { ERR("Failed to create parser context\n"); + free(modified_ptr); return NULL; } @@ -556,6 +969,7 @@ static xmlDocPtr doparse(domdoc* This, char const* ptr, int len, xmlCharEncoding ctx_encoding = (char *)pctx->encoding; pctx->encoding = NULL; xmlFreeParserCtxt(pctx); + free(modified_ptr); /* TODO: put this in one of the SAX callbacks */ /* create first child as a <?xml...?> */ @@ -2467,7 +2881,9 @@ static HRESULT WINAPI domdoc_loadXML( if (This->properties->version == MSXML_DEFAULT || This->properties->version == MSXML26) while (*ptr && iswspace(*ptr)) ptr++; - xmldoc = doparse(This, (char*)ptr, lstrlenW(ptr)*sizeof(WCHAR), XML_CHAR_ENCODING_UTF16LE); + /* Handle empty string gracefully - Windows MSXML returns VARIANT_FALSE without error */ + if (*ptr) + xmldoc = doparse(This, (char*)ptr, lstrlenW(ptr)*sizeof(WCHAR), XML_CHAR_ENCODING_UTF16LE); if ( !xmldoc ) { This->error = E_FAIL; diff --git a/include/mshtmdid.h b/include/mshtmdid.h index 45b22f6349f..204e5e403f6 100644 --- a/include/mshtmdid.h +++ b/include/mshtmdid.h @@ -107,6 +107,7 @@ #define DISPID_XMLHTTPREQUEST DISPID_NORMAL_FIRST #define DISPID_XDOMAINREQUEST DISPID_NORMAL_FIRST #define DISPID_DOMPARSER DISPID_NORMAL_FIRST +#define DISPID_XMLSERIALIZER DISPID_NORMAL_FIRST #define DISPID_DOCUMENTCOMPATIBLEINFO_COLLECTION DISPID_NORMAL_FIRST #define DISPID_DOCUMENTCOMPATIBLEINFO DISPID_NORMAL_FIRST #define DISPID_XDOMAINREQUEST DISPID_NORMAL_FIRST @@ -4676,6 +4677,9 @@ /* IDOMParser */ #define DISPID_IDOMPARSER_PARSEFROMSTRING DISPID_DOMPARSER +/* IXMLSerializer */ +#define DISPID_IXMLSERIALIZER_SERIALIZETOSTRING DISPID_XMLSERIALIZER + /* IEventTarget */ #define DISPID_IEVENTTARGET_ADDEVENTLISTENER DISPID_HTMLOBJECT+10 #define DISPID_IEVENTTARGET_REMOVEEVENTLISTENER DISPID_HTMLOBJECT+11 diff --git a/include/mshtml.idl b/include/mshtml.idl index 1d2896f88b8..11f3fa5c59d 100644 --- a/include/mshtml.idl +++ b/include/mshtml.idl @@ -30286,6 +30286,46 @@ coclass DOMParser interface IDOMParser; } +/***************************************************************************** + * IXMLSerializer interface + */ +[ + object, + oleautomation, + dual, + uuid(30510783-98b5-11cf-bb82-00aa00bdce0b) +] +interface IXMLSerializer : IDispatch +{ + [id(DISPID_IXMLSERIALIZER_SERIALIZETOSTRING)] + HRESULT serializeToString([in] IHTMLDOMNode *node, [retval, out] BSTR *pString); +} + +/***************************************************************************** + * DispXMLSerializer dispinterface + */ +[ + hidden, + uuid(305900af-98b5-11cf-bb82-00aa00bdce0b) +] +dispinterface DispXMLSerializer +{ +properties: +methods: + [id(DISPID_IXMLSERIALIZER_SERIALIZETOSTRING)] + BSTR serializeToString([in] IHTMLDOMNode *node); +} + +[ + noncreatable, + uuid(30510784-98b5-11cf-bb82-00aa00bdce0b) +] +coclass XMLSerializer +{ + [default] dispinterface DispXMLSerializer; + interface IXMLSerializer; +} + /***************************************************************************** * IXMLGenericParse interface */ -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
From: Phiality <bakreski03@gmail.com> --- dlls/mshtml/tests/dom.c | 81 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) diff --git a/dlls/mshtml/tests/dom.c b/dlls/mshtml/tests/dom.c index 3235cbbaca5..861042bc6eb 100644 --- a/dlls/mshtml/tests/dom.c +++ b/dlls/mshtml/tests/dom.c @@ -11731,6 +11731,85 @@ static void test_case_insens(IHTMLDocument2 *doc) IDispatchEx_Release(dispex); } +static void test_method_vs_getter(IHTMLDocument2 *doc) +{ + DISPPARAMS dp = { 0 }; + IDispatchEx *dispex; + DISPID dispid; + HRESULT hres; + VARIANT v; + BSTR bstr; + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IDispatchEx, (void**)&dispex); + ok(hres == S_OK, "Could not get IDispatchEx: %08lx\n", hres); + + V_VT(&v) = VT_EMPTY; + hres = IDispatchEx_InvokeEx(dispex, DISPID_VALUE, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); + todo_wine_if(compat_mode < COMPAT_IE9) + ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); + todo_wine_if(compat_mode < COMPAT_IE9) + ok(V_VT(&v) == VT_BSTR, "V_VT = %d\n", V_VT(&v)); + VariantClear(&v); + + bstr = SysAllocString(L"body"); + hres = IDispatchEx_GetDispID(dispex, bstr, 0, &dispid); + ok(hres == S_OK, "GetDispID returned: %08lx\n", hres); + SysFreeString(bstr); + + hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); + ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); + ok(V_VT(&v) == VT_DISPATCH, "V_VT = %d\n", V_VT(&v)); + ok(V_DISPATCH(&v) != NULL, "V_DISPATCH == NULL\n"); + VariantClear(&v); + + bstr = SysAllocString(L"title"); + hres = IDispatchEx_GetDispID(dispex, bstr, 0, &dispid); + ok(hres == S_OK, "GetDispID returned: %08lx\n", hres); + SysFreeString(bstr); + + hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); + ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); + ok(V_VT(&v) == VT_BSTR, "V_VT = %d\n", V_VT(&v)); + VariantClear(&v); + + bstr = SysAllocString(L"close"); + hres = IDispatchEx_GetDispID(dispex, bstr, 0, &dispid); + ok(hres == S_OK, "GetDispID returned: %08lx\n", hres); + SysFreeString(bstr); + + hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); + ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); + if(compat_mode < COMPAT_IE9) + ok(V_VT(&v) == VT_EMPTY, "V_VT = %d\n", V_VT(&v)); + else { + ok(V_VT(&v) == VT_DISPATCH, "V_VT = %d\n", V_VT(&v)); + ok(V_DISPATCH(&v) != NULL, "V_DISPATCH == NULL\n"); + } + VariantClear(&v); + + hres = IDispatchEx_InvokeEx(dispex, dispid, LOCALE_NEUTRAL, DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); + ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); + ok(V_VT(&v) == VT_DISPATCH, "V_VT = %d\n", V_VT(&v)); + ok(V_DISPATCH(&v) != NULL, "V_DISPATCH == NULL\n"); + IDispatchEx_Release(dispex); + + hres = IDispatch_QueryInterface(V_DISPATCH(&v), &IID_IDispatchEx, (void**)&dispex); + ok(hres == S_OK, "Could not get IDispatchEx: %08lx\n", hres); + VariantClear(&v); + + hres = IDispatchEx_InvokeEx(dispex, DISPID_VALUE, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); + if(compat_mode < COMPAT_IE9) + todo_wine + ok(hres == E_ACCESSDENIED, "InvokeEx returned: %08lx\n", hres); + else { + ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); + ok(V_VT(&v) == VT_BSTR, "V_VT = %d\n", V_VT(&v)); + } + VariantClear(&v); + + IDispatchEx_Release(dispex); +} + static void test_null_write(IHTMLDocument2 *doc) { HRESULT hres; @@ -13842,9 +13921,11 @@ START_TEST(dom) run_domtest(doc_blank_ie8, test_quirks_mode_perf_toJSON); run_domtest(doctype_str, test_doctype); run_domtest(case_insens_str, test_case_insens); + run_domtest(doc_blank, test_method_vs_getter); if(is_ie9plus) { compat_mode = COMPAT_IE9; run_domtest(emptydiv_ie9_str, test_docfrag); + run_domtest(doc_blank_ie9, test_method_vs_getter); compat_mode = COMPAT_NONE; } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
From: Phiality <bakreski03@gmail.com> In quirks mode (pre-IE9), calling DISPID_VALUE with DISPATCH_METHOD | DISPATCH_PROPERTYGET on a function object should return E_ACCESSDENIED, matching Windows behavior. Also removes todo_wine markers from test_method_vs_getter since Wine now correctly matches Windows behavior for all cases. --- dlls/mshtml/dispex.c | 2 ++ dlls/mshtml/tests/dom.c | 3 --- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/dlls/mshtml/dispex.c b/dlls/mshtml/dispex.c index 0af89aca630..3bdf3455770 100644 --- a/dlls/mshtml/dispex.c +++ b/dlls/mshtml/dispex.c @@ -1150,6 +1150,8 @@ static HRESULT function_value(DispatchEx *dispex, LCID lcid, WORD flags, DISPPAR case DISPATCH_METHOD|DISPATCH_PROPERTYGET: if(!res) return E_INVALIDARG; + if(This->obj && dispex_compat_mode(This->obj) < COMPAT_MODE_IE9) + return E_ACCESSDENIED; /* fall through */ case DISPATCH_METHOD: if(!This->obj) diff --git a/dlls/mshtml/tests/dom.c b/dlls/mshtml/tests/dom.c index 861042bc6eb..b08e7363b8e 100644 --- a/dlls/mshtml/tests/dom.c +++ b/dlls/mshtml/tests/dom.c @@ -11745,9 +11745,7 @@ static void test_method_vs_getter(IHTMLDocument2 *doc) V_VT(&v) = VT_EMPTY; hres = IDispatchEx_InvokeEx(dispex, DISPID_VALUE, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); - todo_wine_if(compat_mode < COMPAT_IE9) ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); - todo_wine_if(compat_mode < COMPAT_IE9) ok(V_VT(&v) == VT_BSTR, "V_VT = %d\n", V_VT(&v)); VariantClear(&v); @@ -11799,7 +11797,6 @@ static void test_method_vs_getter(IHTMLDocument2 *doc) hres = IDispatchEx_InvokeEx(dispex, DISPID_VALUE, LOCALE_NEUTRAL, DISPATCH_METHOD | DISPATCH_PROPERTYGET, &dp, &v, NULL, NULL); if(compat_mode < COMPAT_IE9) - todo_wine ok(hres == E_ACCESSDENIED, "InvokeEx returned: %08lx\n", hres); else { ok(hres == S_OK, "InvokeEx failed: %08lx\n", hres); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
From: Phiality <bakreski03@gmail.com> --- dlls/mshtml/tests/dom.js | 101 +++++++++++++++++++ dlls/msxml3/tests/domdoc.c | 200 +++++++++++++++++++++++++++++++++++++ 2 files changed, 301 insertions(+) diff --git a/dlls/mshtml/tests/dom.js b/dlls/mshtml/tests/dom.js index 92c1deb64d3..6d464eede89 100644 --- a/dlls/mshtml/tests/dom.js +++ b/dlls/mshtml/tests/dom.js @@ -1170,3 +1170,104 @@ sync_test("document.open", function() { doc.close(); ok(doc.onclick === f, "doc.onclick != f"); }); + +sync_test("XMLSerializer", function() { + var serializer = new XMLSerializer(); + ok(serializer !== null, "XMLSerializer constructor returned null"); + ok(typeof serializer === "object", "XMLSerializer is not an object"); + + /* Test serializeToString with a simple element */ + var div = document.createElement("div"); + div.id = "testdiv"; + div.innerHTML = "test content"; + + var result = serializer.serializeToString(div); + ok(typeof result === "string", "serializeToString did not return a string"); + ok(result.length > 0, "serializeToString returned empty string"); + ok(result.indexOf("testdiv") !== -1, "serialized string does not contain id: " + result); + ok(result.indexOf("test content") !== -1, "serialized string does not contain content: " + result); + + /* Test with nested elements */ + var container = document.createElement("div"); + var child = document.createElement("span"); + child.textContent = "nested"; + container.appendChild(child); + + result = serializer.serializeToString(container); + ok(result.indexOf("span") !== -1, "serialized string does not contain span tag: " + result); + ok(result.indexOf("nested") !== -1, "serialized string does not contain nested text: " + result); + + /* Test with attributes */ + var elem = document.createElement("input"); + elem.type = "text"; + elem.value = "test value"; + + result = serializer.serializeToString(elem); + ok(result.indexOf("input") !== -1, "serialized string does not contain input tag: " + result); + + /* Test with empty element */ + var empty = document.createElement("br"); + result = serializer.serializeToString(empty); + ok(typeof result === "string", "serializeToString on empty element did not return string"); + ok(result.indexOf("br") !== -1, "serialized empty element does not contain tag name: " + result); + + /* Test with text node */ + var textContainer = document.createElement("p"); + textContainer.appendChild(document.createTextNode("plain text content")); + result = serializer.serializeToString(textContainer); + ok(result.indexOf("plain text content") !== -1, "serialized text node missing content: " + result); + + /* Test with special characters that need escaping */ + var specialChars = document.createElement("div"); + specialChars.textContent = "<test> & \"quotes\""; + result = serializer.serializeToString(specialChars); + ok(result.indexOf("<") !== -1 || result.indexOf("<test>") === -1, + "special characters should be escaped or not appear literally: " + result); + + /* Test with deeply nested elements */ + var outer = document.createElement("div"); + var middle = document.createElement("span"); + var inner = document.createElement("em"); + inner.textContent = "deep"; + middle.appendChild(inner); + outer.appendChild(middle); + result = serializer.serializeToString(outer); + ok(result.indexOf("div") !== -1, "missing outer div: " + result); + ok(result.indexOf("span") !== -1, "missing middle span: " + result); + ok(result.indexOf("em") !== -1, "missing inner em: " + result); + ok(result.indexOf("deep") !== -1, "missing deep text: " + result); + + /* Test with multiple children */ + var parent = document.createElement("ul"); + for (var i = 0; i < 3; i++) { + var li = document.createElement("li"); + li.textContent = "item" + i; + parent.appendChild(li); + } + result = serializer.serializeToString(parent); + ok(result.indexOf("item0") !== -1, "missing item0: " + result); + ok(result.indexOf("item1") !== -1, "missing item1: " + result); + ok(result.indexOf("item2") !== -1, "missing item2: " + result); + + /* Test that multiple serializers work independently */ + var serializer2 = new XMLSerializer(); + ok(serializer2 !== null, "second XMLSerializer constructor returned null"); + ok(serializer !== serializer2, "serializers should be different instances"); + + var div1 = document.createElement("div"); + div1.id = "first"; + var div2 = document.createElement("div"); + div2.id = "second"; + + var result1 = serializer.serializeToString(div1); + var result2 = serializer2.serializeToString(div2); + ok(result1.indexOf("first") !== -1, "first serializer wrong result: " + result1); + ok(result2.indexOf("second") !== -1, "second serializer wrong result: " + result2); +}); + +sync_test("method_reference_call", function() { + /* Test calling a method stored in a variable (uses METHOD|PROPERTYGET internally) */ + var f = document.body.getElementsByTagName; + var r = f("test"); + ok(r.length === 0, "r.length = " + r.length); +}); diff --git a/dlls/msxml3/tests/domdoc.c b/dlls/msxml3/tests/domdoc.c index a08506fad9b..57f7a510c70 100644 --- a/dlls/msxml3/tests/domdoc.c +++ b/dlls/msxml3/tests/domdoc.c @@ -14357,6 +14357,205 @@ static void test_indent(void) SysFreeString(str); } +static void test_embedded_xml_declaration(void) +{ + IXMLDOMDocument *doc; + IXMLDOMElement *elem; + IXMLDOMNode *node; + IXMLDOMNodeList *nodes; + BSTR str; + VARIANT_BOOL b; + HRESULT hr; + LONG len; + + /* Test XML with embedded <?xml?> declaration inside an element. + * Windows MSXML tolerates this but libxml2 rejects it. + * The implementation wraps such content in CDATA to make it parse. */ + static const char embedded_xml_str[] = + "<?xml version=\"1.0\"?>" + "<root>" + " <xmldata><?xml version=\"1.0\"?><nested>content</nested></xmldata>" + "</root>"; + + /* Test with xml:space preserved content containing XML declaration */ + static const char embedded_xml_space_str[] = + "<?xml version=\"1.0\"?>" + "<root xml:space=\"preserve\">" + " <?xml version=\"1.0\"?><data>test</data>" + "</root>"; + + /* Test normal XML without embedded declarations (should still work) */ + static const char normal_xml_str[] = + "<?xml version=\"1.0\"?>" + "<root><child>text</child></root>"; + + /* Test *XMLData element pattern - element content that should be wrapped */ + static const char xmldata_element_str[] = + "<?xml version=\"1.0\"?>" + "<root>" + " <CustomXMLData><item>value</item></CustomXMLData>" + "</root>"; + + /* Test multiple embedded declarations */ + static const char multi_embedded_str[] = + "<?xml version=\"1.0\"?>" + "<root>" + " <first><?xml version=\"1.0\"?><a>1</a></first>" + " <second><b>2</b></second>" + "</root>"; + + /* Test deeply nested embedded declaration */ + static const char deep_embedded_str[] = + "<?xml version=\"1.0\"?>" + "<root><level1><level2><data><?xml version=\"1.0\"?><deep>nested</deep></data></level2></level1></root>"; + + /* Test with encoding in embedded declaration */ + static const char embedded_with_encoding_str[] = + "<?xml version=\"1.0\"?>" + "<root>" + " <xmldata><?xml version=\"1.0\" encoding=\"UTF-8\"?><test>encoded</test></xmldata>" + "</root>"; + + /* Test self-closing XMLData element (should not need wrapping) */ + static const char selfclose_xmldata_str[] = + "<?xml version=\"1.0\"?>" + "<root><EmptyXMLData/></root>"; + + doc = NULL; + hr = CoCreateInstance(&CLSID_DOMDocument30, NULL, CLSCTX_INPROC_SERVER, + &IID_IXMLDOMDocument, (void**)&doc); + if (hr != S_OK) + { + win_skip("DOMDocument30 not available, skipping embedded XML tests\n"); + return; + } + + /* Test 1: Normal XML should parse fine */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(normal_xml_str), &b); + ok(hr == S_OK, "loadXML failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load normal XML\n"); + + hr = IXMLDOMDocument_get_documentElement(doc, &elem); + ok(hr == S_OK, "get_documentElement failed: %#lx\n", hr); + if (elem) + IXMLDOMElement_Release(elem); + + /* Test 2: XML with embedded declaration in element content */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(embedded_xml_str), &b); + ok(hr == S_OK, "loadXML with embedded XML declaration failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load XML with embedded declaration\n"); + + if (b == VARIANT_TRUE) + { + hr = IXMLDOMDocument_get_documentElement(doc, &elem); + ok(hr == S_OK, "get_documentElement failed: %#lx\n", hr); + if (elem) + IXMLDOMElement_Release(elem); + } + + /* Test 3: XML with embedded declaration and xml:space */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(embedded_xml_space_str), &b); + ok(hr == S_OK, "loadXML with embedded XML and xml:space failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load XML with embedded declaration and xml:space\n"); + + /* Test 4: *XMLData element with element content */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(xmldata_element_str), &b); + ok(hr == S_OK, "loadXML with *XMLData element failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load XML with *XMLData element\n"); + + if (b == VARIANT_TRUE) + { + hr = IXMLDOMDocument_get_documentElement(doc, &elem); + ok(hr == S_OK, "get_documentElement failed: %#lx\n", hr); + if (elem) + { + /* Verify we can access child elements */ + hr = IXMLDOMElement_get_childNodes(elem, &nodes); + ok(hr == S_OK, "get_childNodes failed: %#lx\n", hr); + if (nodes) + { + hr = IXMLDOMNodeList_get_length(nodes, &len); + ok(hr == S_OK, "get_length failed: %#lx\n", hr); + ok(len > 0, "expected child nodes, got %ld\n", len); + IXMLDOMNodeList_Release(nodes); + } + IXMLDOMElement_Release(elem); + } + } + + /* Test 5: Multiple embedded declarations in different elements */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(multi_embedded_str), &b); + ok(hr == S_OK, "loadXML with multiple embedded declarations failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load XML with multiple embedded declarations\n"); + + if (b == VARIANT_TRUE) + { + hr = IXMLDOMDocument_get_documentElement(doc, &elem); + ok(hr == S_OK, "get_documentElement failed: %#lx\n", hr); + if (elem) + { + hr = IXMLDOMElement_get_childNodes(elem, &nodes); + ok(hr == S_OK, "get_childNodes failed: %#lx\n", hr); + if (nodes) + { + hr = IXMLDOMNodeList_get_length(nodes, &len); + ok(hr == S_OK, "get_length failed: %#lx\n", hr); + /* Should have at least 2 child elements (first and second) */ + ok(len >= 2, "expected at least 2 child nodes, got %ld\n", len); + IXMLDOMNodeList_Release(nodes); + } + IXMLDOMElement_Release(elem); + } + } + + /* Test 6: Deeply nested embedded declaration */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(deep_embedded_str), &b); + ok(hr == S_OK, "loadXML with deeply nested embedded declaration failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load XML with deeply nested embedded declaration\n"); + + /* Test 7: Embedded declaration with encoding attribute */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(embedded_with_encoding_str), &b); + ok(hr == S_OK, "loadXML with embedded encoding declaration failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load XML with embedded encoding declaration\n"); + + /* Test 8: Self-closing XMLData element (no content to wrap) */ + b = VARIANT_FALSE; + hr = IXMLDOMDocument_loadXML(doc, _bstr_(selfclose_xmldata_str), &b); + ok(hr == S_OK, "loadXML with self-closing XMLData failed: %#lx\n", hr); + ok(b == VARIANT_TRUE, "failed to load XML with self-closing XMLData\n"); + + if (b == VARIANT_TRUE) + { + hr = IXMLDOMDocument_get_documentElement(doc, &elem); + ok(hr == S_OK, "get_documentElement failed: %#lx\n", hr); + if (elem) + { + hr = IXMLDOMElement_get_tagName(elem, &str); + ok(hr == S_OK, "get_tagName failed: %#lx\n", hr); + ok(!lstrcmpW(str, L"root"), "unexpected tag name: %s\n", wine_dbgstr_w(str)); + SysFreeString(str); + + /* Find the EmptyXMLData element */ + hr = IXMLDOMElement_selectSingleNode(elem, _bstr_("EmptyXMLData"), &node); + ok(hr == S_OK, "selectSingleNode failed: %#lx\n", hr); + if (node) + IXMLDOMNode_Release(node); + + IXMLDOMElement_Release(elem); + } + } + + IXMLDOMDocument_Release(doc); + free_bstrs(); +} + static DWORD WINAPI new_thread(void *arg) { HRESULT hr = CoInitialize(NULL); @@ -14460,6 +14659,7 @@ START_TEST(domdoc) test_xsltemplate(); test_xsltext(); test_max_element_depth_values(); + test_embedded_xml_declaration(); if (is_clsid_supported(&CLSID_MXNamespaceManager40, &IID_IMXNamespaceManager)) { -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
From: Phiality <bakreski03@gmail.com> --- dlls/mshtml/tests/dom.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/dlls/mshtml/tests/dom.js b/dlls/mshtml/tests/dom.js index 6d464eede89..dcf75b1324c 100644 --- a/dlls/mshtml/tests/dom.js +++ b/dlls/mshtml/tests/dom.js @@ -1268,6 +1268,6 @@ sync_test("XMLSerializer", function() { sync_test("method_reference_call", function() { /* Test calling a method stored in a variable (uses METHOD|PROPERTYGET internally) */ var f = document.body.getElementsByTagName; - var r = f("test"); + var r = f.call(document.body, "test"); ok(r.length === 0, "r.length = " + r.length); }); -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
From: Phiality <bakreski03@gmail.com> --- dlls/mshtml/tests/documentmode.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index dadd7ba6b04..71a14a7d322 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -4453,6 +4453,7 @@ sync_test("prototype props", function() { check(DocumentFragment, [ ["attachEvent",9,10], ["detachEvent",9,10], "querySelector", "querySelectorAll", "removeNode", "replaceNode", "swapNode" ]); check(DocumentType, [ "entities", "internalSubset", "name", "notations", "publicId", "systemId" ]); check(DOMParser, [ "parseFromString" ]); + check(XMLSerializer, [ "serializeToString" ]); check(Element, [ "childElementCount", "clientHeight", "clientLeft", "clientTop", "clientWidth", ["fireEvent",9,10], "firstElementChild", "getAttribute", "getAttributeNS", "getAttributeNode", "getAttributeNodeNS", "getBoundingClientRect", "getClientRects", @@ -4895,7 +4896,7 @@ async_test("window own props", function() { ["URL",10], ["ValidityState",10], ["VideoPlaybackQuality",11], ["WebGLActiveInfo",11], ["WebGLBuffer",11], ["WebGLContextEvent",11], ["WebGLFramebuffer",11], ["WebGLObject",11], ["WebGLProgram",11], ["WebGLRenderbuffer",11], ["WebGLRenderingContext",11], ["WebGLShader",11], ["WebGLShaderPrecisionFormat",11], ["WebGLTexture",11], ["WebGLUniformLocation",11], ["WEBGL_compressed_texture_s3tc",11], ["WEBGL_debug_renderer_info",11], ["WebSocket",10], "WheelEvent", ["Worker",10], - ["XMLHttpRequestEventTarget",10], "XMLSerializer" + ["XMLHttpRequestEventTarget",10] ]); next_test(); } -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
From: Phiality <bakreski03@gmail.com> --- libs/xml2/parser.c | 79 ++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 77 insertions(+), 2 deletions(-) diff --git a/libs/xml2/parser.c b/libs/xml2/parser.c index 3e8a588f536..6c0d7997ce2 100644 --- a/libs/xml2/parser.c +++ b/libs/xml2/parser.c @@ -5220,8 +5220,7 @@ xmlParsePITarget(xmlParserCtxtPtr ctxt) { int i; if ((name[0] == 'x') && (name[1] == 'm') && (name[2] == 'l') && (name[3] == 0)) { - xmlFatalErrMsg(ctxt, XML_ERR_RESERVED_XML_NAME, - "XML declaration allowed only at the start of the document\n"); + /* Wine: Windows MSXML tolerates embedded XML declarations, handled in xmlParsePI */ return(name); } else if (name[3] == 0) { xmlFatalErr(ctxt, XML_ERR_RESERVED_XML_NAME, NULL); @@ -5345,6 +5344,82 @@ xmlParsePI(xmlParserCtxtPtr ctxt) { */ target = xmlParsePITarget(ctxt); if (target != NULL) { + /* Wine: Windows MSXML tolerates embedded XML declarations inside elements. */ + if ((target[0] == 'x') && (target[1] == 'm') && + (target[2] == 'l') && (target[3] == 0)) { + xmlChar *text; + size_t textlen = 0; + size_t textsize = 1024; + int nesting = 0; + + text = (xmlChar *) xmlMallocAtomic(textsize); + if (text == NULL) { + xmlErrMemory(ctxt, NULL); + ctxt->instate = state; + return; + } + + /* Start with "<?xml" */ + memcpy(text, "<?xml", 5); + textlen = 5; + + /* Consume everything until parent's close tag, tracking nesting */ + while (RAW != 0) { + /* Check for close tag </ */ + if (RAW == '<' && NXT(1) == '/') { + if (nesting == 0) { + /* This is the parent's close tag - stop here */ + break; + } + nesting--; + } + /* Check for start tag < followed by letter (not <? or <! or </) */ + else if (RAW == '<' && NXT(1) != '?' && NXT(1) != '!' && NXT(1) != '/') { + xmlChar c = NXT(1); + if ((c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z')) { + /* Could be start tag - check if self-closing */ + const xmlChar *p = ctxt->input->cur + 1; + int is_selfclose = 0; + while (*p && *p != '>') { + if (*p == '/' && *(p+1) == '>') { + is_selfclose = 1; + break; + } + p++; + } + if (!is_selfclose) + nesting++; + } + } + + /* Grow buffer if needed */ + if (textlen + 2 >= textsize) { + xmlChar *tmp; + textsize *= 2; + tmp = (xmlChar *) xmlRealloc(text, textsize); + if (tmp == NULL) { + xmlErrMemory(ctxt, NULL); + xmlFree(text); + ctxt->instate = state; + return; + } + text = tmp; + } + text[textlen++] = RAW; + NEXT; + } + text[textlen] = 0; + + /* Emit as text content (like CDATA) */ + if ((ctxt->sax) && (!ctxt->disableSAX) && + (ctxt->sax->characters != NULL)) + ctxt->sax->characters(ctxt->userData, text, textlen); + + xmlFree(text); + if (ctxt->instate != XML_PARSER_EOF) + ctxt->instate = state; + return; + } if ((RAW == '?') && (NXT(1) == '>')) { if (inputid != ctxt->input->id) { xmlFatalErrMsg(ctxt, XML_ERR_ENTITY_BOUNDARY, -- GitLab https://gitlab.winehq.org/wine/wine/-/merge_requests/10025
On Wed Feb 4 22:31:15 2026 +0000, Phiality wrote:
You're right on the jscript fix, I missed the `|| pdp->cArgs` logic is equivalent to what I had. I'll drop that part since !10004 covers it. On the msxml3 side, @nsivov, thanks for clarifying. I'm happy to patch libs/xml2 directly instead of the CDATA approach @nsivov i applied a patch to libs/xml2 that does this, 58a8fd59 was this what you had in mind?
-- https://gitlab.winehq.org/wine/wine/-/merge_requests/10025#note_128825
participants (2)
-
Phiality -
Phiality (@PhialsBasement)