This get us pass the "Update your browser" blocker in adobe's sign-in page. The page itself doesn't make use of `window.MutationObserver`.
However the sign-in page is still broken.
-- v5: mshtml: implement window.MutationObserver with MutationObserver stub
From: Yuxuan Shui yshui@codeweavers.com
--- dlls/mshtml/htmlwindow.c | 29 +++++- dlls/mshtml/mshtml_private.h | 6 +- dlls/mshtml/mshtml_private_iface.idl | 14 +++ dlls/mshtml/mutation.c | 144 +++++++++++++++++++++++++++ dlls/mshtml/tests/documentmode.js | 1 + 5 files changed, 192 insertions(+), 2 deletions(-)
diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index c6072c7e9fa..a381869a526 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -3330,6 +3330,25 @@ static HRESULT WINAPI window_private_get_console(IWineHTMLWindowPrivate *iface, return S_OK; }
+static HRESULT WINAPI window_private_MutationObserver(IWineHTMLWindowPrivate *iface, + IDispatch *callback, + IDispatch **mutation_observer) +{ + HTMLWindow *This = impl_from_IWineHTMLWindowPrivateVtbl(iface); + IWineMSHTMLMutationObserver *ret = NULL; + HRESULT hres; + + FIXME("iface %p, mutation_observer %p, stub.\n", iface, mutation_observer); + + hres = create_mutation_observer(dispex_compat_mode(&This->inner_window->event_target.dispex), + callback, &ret); + if (FAILED(hres)) + return hres; + + *mutation_observer = (IDispatch *)ret; + return S_OK; +} + static const IWineHTMLWindowPrivateVtbl WineHTMLWindowPrivateVtbl = { window_private_QueryInterface, window_private_AddRef, @@ -3343,6 +3362,7 @@ static const IWineHTMLWindowPrivateVtbl WineHTMLWindowPrivateVtbl = { window_private_get_console, window_private_matchMedia, window_private_postMessage, + window_private_MutationObserver };
static inline HTMLWindow *impl_from_IWineHTMLWindowCompatPrivateVtbl(IWineHTMLWindowCompatPrivate *iface) @@ -3975,12 +3995,19 @@ static void HTMLWindow_init_dispex_info(dispex_data_t *info, compat_mode_t compa {DISPID_UNKNOWN} };
+ // Hide MutationObserver from IE10 and older + static const dispex_hook_t private_ie10_hooks[] = { + {DISPID_IWINEHTMLWINDOWPRIVATE_MUTATIONOBS}, + {DISPID_UNKNOWN} + }; + if(compat_mode >= COMPAT_MODE_IE9) dispex_info_add_interface(info, IHTMLWindow7_tid, NULL); else dispex_info_add_interface(info, IWineHTMLWindowCompatPrivate_tid, NULL); if(compat_mode >= COMPAT_MODE_IE10) - dispex_info_add_interface(info, IWineHTMLWindowPrivate_tid, NULL); + dispex_info_add_interface(info, IWineHTMLWindowPrivate_tid, + compat_mode >= COMPAT_MODE_IE11 ? NULL: private_ie10_hooks);
dispex_info_add_interface(info, IHTMLWindow5_tid, NULL); dispex_info_add_interface(info, IHTMLWindow4_tid, compat_mode >= COMPAT_MODE_IE11 ? window4_ie11_hooks : NULL); diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index 59d652a828b..4283f8d5586 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -294,7 +294,8 @@ typedef struct EventTarget EventTarget; XIID(IWinePageTransitionEvent) \ XIID(IWineXMLHttpRequestPrivate) \ XIID(IWineMSHTMLConsole) \ - XIID(IWineMSHTMLMediaQueryList) + XIID(IWineMSHTMLMediaQueryList) \ + XIID(IWineMSHTMLMutationObserver)
typedef enum { #define XIID(iface) iface ## _tid, @@ -1479,3 +1480,6 @@ IInternetSecurityManager *get_security_manager(void); extern HINSTANCE hInst; void create_console(compat_mode_t compat_mode, IWineMSHTMLConsole **ret); HRESULT create_media_query_list(HTMLWindow *window, BSTR media_query, IDispatch **ret); + +HRESULT create_mutation_observer(compat_mode_t compat_mode, IDispatch *callback, + IWineMSHTMLMutationObserver **ret); diff --git a/dlls/mshtml/mshtml_private_iface.idl b/dlls/mshtml/mshtml_private_iface.idl index c0bb30fbbc8..2e7f6dd2dfd 100644 --- a/dlls/mshtml/mshtml_private_iface.idl +++ b/dlls/mshtml/mshtml_private_iface.idl @@ -76,6 +76,17 @@ interface IWineMSHTMLConsole : IDispatch HRESULT warn([in, optional] VARIANT *varargStart); }
+[ + odl, + oleautomation, + dual, + hidden, + uuid(6ac5491e-1758-4b82-98a2-83e31a7c8871) +] +interface IWineMSHTMLMutationObserver : IDispatch +{ +} + [ odl, oleautomation, @@ -95,6 +106,7 @@ interface IWineMSHTMLMediaQueryList : IDispatch HRESULT removeListener([in] VARIANT *listener); }
+const long DISPID_IWINEHTMLWINDOWPRIVATE_MUTATIONOBS = 55; [ odl, oleautomation, @@ -114,6 +126,8 @@ interface IWineHTMLWindowPrivate : IDispatch HRESULT matchMedia([in] BSTR media_query, [retval, out] IDispatch **media_query_list); [id(54)] HRESULT postMessage([in] VARIANT msg, [in] BSTR targetOrigin, [in, optional] VARIANT transfer); + [id(DISPID_IWINEHTMLWINDOWPRIVATE_MUTATIONOBS)] + HRESULT MutationObserver([in] IDispatch *callback, [retval, out] IDispatch **observer); }
[ diff --git a/dlls/mshtml/mutation.c b/dlls/mshtml/mutation.c index 83ab623aea8..f38cc6d2d80 100644 --- a/dlls/mshtml/mutation.c +++ b/dlls/mshtml/mutation.c @@ -1076,3 +1076,147 @@ void init_mutation(nsIComponentManager *component_manager) if(NS_FAILED(nsres)) ERR("Could not create nsIContentUtils instance: %08lx\n", nsres); } + +struct mutation_observer { + IWineMSHTMLMutationObserver IWineMSHTMLMutationObserver_iface; + + LONG ref; + DispatchEx dispex; + IDispatch *callback; + + HTMLDOMNode *node; +}; + +static inline struct mutation_observer *impl_from_IWineMSHTMLMutationObserver(IWineMSHTMLMutationObserver *iface) +{ + return CONTAINING_RECORD(iface, struct mutation_observer, IWineMSHTMLMutationObserver_iface); +} + +static HRESULT WINAPI MutationObserver_QueryInterface(IWineMSHTMLMutationObserver *iface, REFIID riid, void **ppv) +{ + struct mutation_observer *This = impl_from_IWineMSHTMLMutationObserver(iface); + + TRACE("(%p)->(%s %p)\n", This, debugstr_mshtml_guid(riid), ppv); + + if(IsEqualGUID(&IID_IUnknown, riid) || IsEqualGUID(&IID_IWineMSHTMLMutationObserver, riid)) { + *ppv = &This->IWineMSHTMLMutationObserver_iface; + }else { + WARN("(%p)->(%s %p)\n", This, debugstr_mshtml_guid(riid), ppv); + *ppv = NULL; + return E_NOINTERFACE; + } + + IUnknown_AddRef((IUnknown*)*ppv); + return S_OK; +} + +static ULONG WINAPI MutationObserver_AddRef(IWineMSHTMLMutationObserver *iface) +{ + struct mutation_observer *This = impl_from_IWineMSHTMLMutationObserver(iface); + LONG ref = InterlockedIncrement(&This->ref); + + TRACE("(%p) ref=%ld\n", This, ref); + + return ref; +} + +static ULONG WINAPI MutationObserver_Release(IWineMSHTMLMutationObserver *iface) +{ + struct mutation_observer *This = impl_from_IWineMSHTMLMutationObserver(iface); + LONG ref = InterlockedDecrement(&This->ref); + + TRACE("(%p) ref=%ld\n", This, ref); + + if(!ref) { + if(This->node) { + IHTMLDOMNode_Release(&This->node->IHTMLDOMNode_iface); + This->node = NULL; + } + release_dispex(&This->dispex); + IDispatch_Release(This->callback); + This->callback = NULL; + free(This); + } + + return ref; +} + +static HRESULT WINAPI MutationObserver_GetTypeInfoCount(IWineMSHTMLMutationObserver *iface, UINT *pctinfo) +{ + struct mutation_observer *This = impl_from_IWineMSHTMLMutationObserver(iface); + FIXME("(%p)->(%p)\n", This, pctinfo); + return E_NOTIMPL; +} + +static HRESULT WINAPI MutationObserver_GetTypeInfo(IWineMSHTMLMutationObserver *iface, UINT iTInfo, + LCID lcid, ITypeInfo **ppTInfo) +{ + struct mutation_observer *This = impl_from_IWineMSHTMLMutationObserver(iface); + + return IDispatchEx_GetTypeInfo(&This->dispex.IDispatchEx_iface, iTInfo, lcid, ppTInfo); +} + +static HRESULT WINAPI MutationObserver_GetIDsOfNames(IWineMSHTMLMutationObserver *iface, REFIID riid, + LPOLESTR *rgszNames, UINT cNames, LCID lcid, + DISPID *rgDispId) +{ + struct mutation_observer *This = impl_from_IWineMSHTMLMutationObserver(iface); + + return IDispatchEx_GetIDsOfNames(&This->dispex.IDispatchEx_iface, riid, rgszNames, cNames, lcid, + rgDispId); +} + +static HRESULT WINAPI MutationObserver_Invoke(IWineMSHTMLMutationObserver *iface, DISPID dispIdMember, + REFIID riid, LCID lcid, WORD wFlags, DISPPARAMS *pDispParams, + VARIANT *pVarResult, EXCEPINFO *pExcepInfo, UINT *puArgErr) +{ + struct mutation_observer *This = impl_from_IWineMSHTMLMutationObserver(iface); + + return IDispatchEx_Invoke(&This->dispex.IDispatchEx_iface, dispIdMember, riid, lcid, wFlags, + pDispParams, pVarResult, pExcepInfo, puArgErr); +} + +static const IWineMSHTMLMutationObserverVtbl WineMSHTMLMutationObserverVtbl = { + MutationObserver_QueryInterface, + MutationObserver_AddRef, + MutationObserver_Release, + MutationObserver_GetTypeInfoCount, + MutationObserver_GetTypeInfo, + MutationObserver_GetIDsOfNames, + MutationObserver_Invoke, +}; + +static const tid_t mutation_observer_iface_tids[] = { + IWineMSHTMLMutationObserver_tid, + 0 +}; +static dispex_static_data_t mutation_observer_dispex = { + L"MutationObserver", + NULL, + IWineMSHTMLMutationObserver_tid, + mutation_observer_iface_tids +}; + +HRESULT create_mutation_observer(compat_mode_t compat_mode, IDispatch *callback, + IWineMSHTMLMutationObserver **ret) { + struct mutation_observer *obj; + + TRACE("(compat_mode = %d, callback = %p, ret = %p)\n", compat_mode, callback, ret); + + obj = calloc(1, sizeof(*obj)); + if(!obj) + { + ERR("No memory.\n"); + return E_OUTOFMEMORY; + } + + obj->IWineMSHTMLMutationObserver_iface.lpVtbl = &WineMSHTMLMutationObserverVtbl; + obj->ref = 1; + init_dispatch(&obj->dispex, (IUnknown*)&obj->IWineMSHTMLMutationObserver_iface.lpVtbl, + &mutation_observer_dispex, compat_mode); + + IDispatch_AddRef(callback); + obj->callback = callback; + *ret = &obj->IWineMSHTMLMutationObserver_iface; + return S_OK; +} diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 00a6eded3fe..9f8955d87d3 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -475,6 +475,7 @@ sync_test("window_props", function() { test_exposed("performance", true); test_exposed("console", v >= 10); test_exposed("matchMedia", v >= 10); + test_exposed("MutationObserver", v >= 11); });
sync_test("domimpl_props", function() {
On Mon Jul 24 15:05:40 2023 +0000, Yuxuan Shui wrote:
I think there is some misunderstanding of my intention. I didn't want to just return an empty constructor, I did implement the constructor. (hence the `callback` parameter, and the lack of `propget`). It's the constructed `MutationObserver` that is a stub. This should resolve the stub construction comment below as well.
In that case you need **two** objects: the constructor and the instance created from it. The constructor would be as I described above, and then in `mutation_observer_ctor_value` you'd create the instance when flags are DISPATCH_CONSTRUCT (but I'd also add tests without the "new" keyword, it probably works, so you can use same path with DISPATCH_METHOD flags).
window.MutationObserver is a constructor. If you write in jscript code: ```jscript var x = window.MutationObserver; ``` this invokes the getter and places the **constructor** into x. If you write: ```jscript var y = new window.MutationObserver(); ``` this first invokes the getter and retrieves the (cached) constructor. Then it calls the constructor's DISPID_VALUE (`mutation_observer_ctor_value` callback from the vtbl) with DISPATCH_CONSTRUCT, which creates the instance of the mutation observer and places it into y. If you omit the `new` keyword, it uses DISPATCH_METHOD instead, which probably works as well (but really, you need to add tests for this). So a switch(...) statement here with DISPATCH_METHOD falling through DISPATCH_CONSTRUCT is probably ideal.
Additionally, if you're going to add a stub instance implementation, I suggest you add some basic tests for the instance, such as what props it exposes (look on MDN and test them on mshtml, it might not expose all of them), and then you add these props to `IWineMSHTMLMutationObserver`, but of course you'll stub them out.
You can then add some basic tests for the instance (in IE11, so either in es5.js, or in documentmode.js but return early if not available), and what props it exposes, maybe something like:
```javascript sync_test("MutationObserver", function() { var m;
m = MutationObserver(); ok(m !== new MutationObserver(), "MutationObserver() == new MutationObserver()"); ok(new MutationObserver() !== new MutationObserver(), "new MutationObserver() == new MutationObserver()"); }); ``` (this is very basic but you get the idea, this test will be expanded when more gets implemented)
I don't know if this test will succeed, though. Note that first I create one without `new` keyword. That's why you need to test it first.
On Mon Jul 24 14:52:37 2023 +0000, Yuxuan Shui wrote:
How do I make sure `MutationObserver` is only exposed for compat mode IE11?
You can either write a new interface or use the hook method I mentioned previously. What it does is basically it "hooks" MutationObserver so that it's NULL/unavailable in IE10, since the interface is added for IE10+. I think it's simpler than adding an entirely new interface (with all the forwarding boilerplate), but that's up to how Jacek feels in the end.
On Mon Jul 24 15:35:16 2023 +0000, Gabriel Ivăncescu wrote:
In that case you need **two** objects: the constructor and the instance created from it. The constructor would be as I described above, and then in `mutation_observer_ctor_value` you'd create the instance when flags are DISPATCH_CONSTRUCT (but I'd also add tests without the "new" keyword, it probably works, so you can use same path with DISPATCH_METHOD flags). window.MutationObserver is a constructor. If you write in jscript code:
var x = window.MutationObserver;
this invokes the getter and places the **constructor** into x. If you write:
var y = new window.MutationObserver();
this first invokes the getter and retrieves the (cached) constructor. Then it calls the constructor's DISPID_VALUE (`mutation_observer_ctor_value` callback from the vtbl) with DISPATCH_CONSTRUCT, which creates the instance of the mutation observer and places it into y. If you omit the `new` keyword, it uses DISPATCH_METHOD instead, which probably works as well (but really, you need to add tests for this). So a switch(...) statement here with DISPATCH_METHOD falling through DISPATCH_CONSTRUCT is probably ideal. Additionally, if you're going to add a stub instance implementation, I suggest you add some basic tests for the instance, such as what props it exposes (look on MDN and test them on mshtml, it might not expose all of them), and then you add these props to `IWineMSHTMLMutationObserver`, but of course you'll stub them out. You can then add some basic tests for the instance (in IE11, so either in es5.js, or in documentmode.js but return early if not available), and what props it exposes, maybe something like:
sync_test("MutationObserver", function() { var m; m = MutationObserver(); ok(m !== new MutationObserver(), "MutationObserver() == new MutationObserver()"); ok(new MutationObserver() !== new MutationObserver(), "new MutationObserver() == new MutationObserver()"); });
(this is very basic but you get the idea, this test will be expanded when more gets implemented) I don't know if this test will succeed, though. Note that first I create one without `new` keyword. That's why you need to test it first.
~~isn't `matchMedia` the same? why doesn't it need a separate constructor object, and make `matchMedia` a `propget`?~~
On Mon Jul 24 15:39:49 2023 +0000, Yuxuan Shui wrote:
~~isn't `matchMedia` the same? why doesn't it need a separate constructor object, and make `matchMedia` a `propget`?~~
ah, wait, don't answer that. I see what I missed.
On Mon Jul 24 15:40:11 2023 +0000, Yuxuan Shui wrote:
ah, wait, don't answer that. I see what I missed.
Well it's a good question, but `matchMedia` is a method/function so it doesn't need construction. `HTMLXMLHttpRequestFactory` is a constructor though, probably best to look at it for construction perspective.
On Mon Jul 24 15:42:17 2023 +0000, Gabriel Ivăncescu wrote:
Well it's a good question, but `matchMedia` is a method/function so it doesn't need construction. `HTMLXMLHttpRequestFactory` is a constructor though, probably best to look at it for construction perspective.
in current firefox calling `MutationObserver` without `new` raises an exception, so I think we are ok here.