From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/mshtml_private.h | 1 + dlls/mshtml/oleobj.c | 61 ++++++++++++++ dlls/mshtml/tests/events.c | 154 +++++++++++++++++++++++++++++++++-- 3 files changed, 210 insertions(+), 6 deletions(-)
diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index 7df5c5e8b30..e7f5481b886 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -677,6 +677,7 @@ struct HTMLDocumentObj { IObjectSafety IObjectSafety_iface; IServiceProvider IServiceProvider_iface; ITargetContainer ITargetContainer_iface; + IEventTarget IEventTarget_iface;
IWindowForBindingUI IWindowForBindingUI_iface;
diff --git a/dlls/mshtml/oleobj.c b/dlls/mshtml/oleobj.c index de6fd028218..5c7673b1649 100644 --- a/dlls/mshtml/oleobj.c +++ b/dlls/mshtml/oleobj.c @@ -3238,6 +3238,58 @@ static const IDocumentRangeVtbl DocObjDocumentRangeVtbl = { DocObjDocumentRange_createRange };
+/********************************************************** + * IEventTarget implementation + */ +static inline HTMLDocumentObj *impl_from_IEventTarget(IEventTarget *iface) +{ + return CONTAINING_RECORD(iface, HTMLDocumentObj, IEventTarget_iface); +} + +HTMLDOCUMENTOBJ_IDISPATCH_METHODS(EventTarget) + +static HRESULT WINAPI DocObjEventTarget_addEventListener(IEventTarget *iface, BSTR type, IDispatch *listener, + VARIANT_BOOL capture) +{ + HTMLDocumentObj *This = impl_from_IEventTarget(iface); + + if(!This->doc_node) + return E_UNEXPECTED; + return IEventTarget_addEventListener(&This->doc_node->node.event_target.IEventTarget_iface, type, listener, capture); +} + +static HRESULT WINAPI DocObjEventTarget_removeEventListener(IEventTarget *iface, BSTR type, IDispatch *listener, + VARIANT_BOOL capture) +{ + HTMLDocumentObj *This = impl_from_IEventTarget(iface); + + if(!This->doc_node) + return E_UNEXPECTED; + return IEventTarget_removeEventListener(&This->doc_node->node.event_target.IEventTarget_iface, type, listener, capture); +} + +static HRESULT WINAPI DocObjEventTarget_dispatchEvent(IEventTarget *iface, IDOMEvent *event_iface, VARIANT_BOOL *result) +{ + HTMLDocumentObj *This = impl_from_IEventTarget(iface); + + if(!This->doc_node) + return E_UNEXPECTED; + return IEventTarget_dispatchEvent(&This->doc_node->node.event_target.IEventTarget_iface, event_iface, result); +} + +static const IEventTargetVtbl DocObjEventTargetVtbl = { + DocObjEventTarget_QueryInterface, + DocObjEventTarget_AddRef, + DocObjEventTarget_Release, + DocObjEventTarget_GetTypeInfoCount, + DocObjEventTarget_GetTypeInfo, + DocObjEventTarget_GetIDsOfNames, + DocObjEventTarget_Invoke, + DocObjEventTarget_addEventListener, + DocObjEventTarget_removeEventListener, + DocObjEventTarget_dispatchEvent +}; + static inline HTMLDocumentObj *impl_from_IUnknown(IUnknown *iface) { return CONTAINING_RECORD(iface, HTMLDocumentObj, IUnknown_inner); @@ -3339,6 +3391,14 @@ static HRESULT WINAPI HTMLDocumentObj_QueryInterface(IUnknown *iface, REFIID rii *ppv = &This->ITargetContainer_iface; }else if(IsEqualGUID(&IID_IConnectionPointContainer, riid)) { *ppv = &This->cp_container.IConnectionPointContainer_iface; + }else if(IsEqualGUID(&IID_IEventTarget, riid)) { + /* IEventTarget is conditionally exposed. This breaks COM rules when + it changes its compat mode, but it is how native works (see tests). */ + if(!This->doc_node || dispex_compat_mode(&This->doc_node->node.event_target.dispex) < COMPAT_MODE_IE9) { + *ppv = NULL; + return E_NOINTERFACE; + } + *ppv = &This->IEventTarget_iface; }else if(IsEqualGUID(&CLSID_CMarkup, riid)) { FIXME("(%p)->(CLSID_CMarkup %p)\n", This, ppv); *ppv = NULL; @@ -3734,6 +3794,7 @@ static HRESULT create_document_object(BOOL is_mhtml, IUnknown *outer, REFIID rii doc->IMarkupContainer_iface.lpVtbl = &DocObjMarkupContainerVtbl; doc->IDisplayServices_iface.lpVtbl = &DocObjDisplayServicesVtbl; doc->IDocumentRange_iface.lpVtbl = &DocObjDocumentRangeVtbl; + doc->IEventTarget_iface.lpVtbl = &DocObjEventTargetVtbl;
doc->outer_unk = outer ? outer : &doc->IUnknown_inner;
diff --git a/dlls/mshtml/tests/events.c b/dlls/mshtml/tests/events.c index 8175205b830..2c72773909c 100644 --- a/dlls/mshtml/tests/events.c +++ b/dlls/mshtml/tests/events.c @@ -60,6 +60,7 @@ #define CLEAR_CALLED(func) \ expect_ ## func = called_ ## func = FALSE
+DEFINE_EXPECT(docobj_onclick); DEFINE_EXPECT(document_onclick); DEFINE_EXPECT(body_onclick); DEFINE_EXPECT(doc_onclick_attached); @@ -965,6 +966,15 @@ static HRESULT WINAPI DispatchEx_GetNameSpaceParent(IDispatchEx *iface, IUnknown }; \ static IDispatchEx event ## _obj = { &event ## FuncVtbl };
+static HRESULT WINAPI docobj_onclick(IDispatchEx *iface, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, + VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller) +{ + CHECK_EXPECT(docobj_onclick); + return S_OK; +} + +EVENT_HANDLER_FUNC_OBJ(docobj_onclick); + static HRESULT WINAPI document_onclick(IDispatchEx *iface, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller) { @@ -3321,9 +3331,11 @@ static HRESULT WINAPI DocumentSite_ActivateMe(IOleDocumentSite *iface, IOleDocum hres = IOleDocumentView_QueryInterface(pViewToActivate, &IID_IOleDocument, (void**)&document); ok(hres == S_OK, "could not get IOleDocument: %08lx\n", hres);
- hres = IOleDocument_CreateView(document, &InPlaceSite, NULL, 0, &view); + if(!view) { + hres = IOleDocument_CreateView(document, &InPlaceSite, NULL, 0, &view); + ok(hres == S_OK, "CreateView failed: %08lx\n", hres); + } IOleDocument_Release(document); - ok(hres == S_OK, "CreateView failed: %08lx\n", hres);
hres = IOleDocumentView_SetInPlaceSite(view, &InPlaceSite); ok(hres == S_OK, "SetInPlaceSite failed: %08lx\n", hres); @@ -3812,6 +3824,44 @@ static void set_client_site(IHTMLDocument2 *doc, BOOL set)
IOleObject_Release(oleobj); } + +static void navigate(IHTMLDocument2 *doc, const WCHAR *url) +{ + IPersistMoniker *persist; + IHlinkTarget *hlink; + IMoniker *mon; + HRESULT hres; + BSTR bstr; + MSG msg; + + /* We can't use normal navigation, because Windows ignores the temporary + pluggable namespace handlers on top-level window navigation. */ + doc_complete = FALSE; + bstr = SysAllocString(url); + hres = CreateURLMoniker(NULL, bstr, &mon); + SysFreeString(bstr); + ok(hres == S_OK, "CreateUrlMoniker failed: %08lx\n", hres); + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IPersistMoniker, (void**)&persist); + ok(hres == S_OK, "Could not get IPersistMoniker iface: %08lx\n", hres); + + hres = IPersistMoniker_Load(persist, FALSE, mon, NULL, 0); + ok(hres == S_OK, "Load failed: %08lx\n", hres); + IPersistMoniker_Release(persist); + IMoniker_Release(mon); + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IHlinkTarget, (void**)&hlink); + ok(hres == S_OK, "Could not get IHlinkTarget iface: %08lx\n", hres); + hres = IHlinkTarget_Navigate(hlink, 0, NULL); + ok(hres == S_OK, "Navigate failed: %08lx\n", hres); + IHlinkTarget_Release(hlink); + + while(!doc_complete && GetMessageW(&msg, NULL, 0, 0)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } +} + static IHTMLDocument2 *create_document(void) { IHTMLDocument2 *doc; @@ -3826,7 +3876,7 @@ static IHTMLDocument2 *create_document(void) return SUCCEEDED(hres) ? doc : NULL; }
-static IHTMLDocument2 *create_document_with_origin(const char *str) +static IHTMLDocument2 *create_document_with_origin(const WCHAR *origin_url, const char *str) { IInternetSession *internet_session; IPersistMoniker *persist; @@ -3856,7 +3906,7 @@ static IHTMLDocument2 *create_document_with_origin(const char *str) set_client_site(doc, TRUE); do_advise((IUnknown*)doc, &IID_IPropertyNotifySink, (IUnknown*)&PropertyNotifySink);
- url = SysAllocString(L"http://winetest.example.org"); + url = SysAllocString(origin_url); hres = CreateURLMoniker(NULL, url, &mon); SysFreeString(url); ok(hres == S_OK, "CreateUrlMoniker failed: %08lx\n", hres); @@ -4008,7 +4058,7 @@ static void test_storage_events(const char *doc_str) unsigned i;
for(i = 0; i < ARRAY_SIZE(doc); i++) - doc[i] = create_document_with_origin(doc_str); + doc[i] = create_document_with_origin(L"http://winetest.example.org", doc_str);
document_mode = 0; for(i = 0; i < ARRAY_SIZE(doc); i++) { @@ -4204,6 +4254,95 @@ done: IHTMLDocument2_Release(doc[1]); }
+static void test_doc_obj(const char *doc_str) +{ + IHTMLDocument2 *doc = create_document_with_origin(L"http://winetest.example.org", doc_str); + IEventTarget *event_target; + IHTMLDocument6 *doc6; + IHTMLElement *body; + HRESULT hres; + VARIANT res; + BSTR bstr; + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IHTMLDocument6, (void**)&doc6); + ok(hres == S_OK, "Could not get IHTMLDocument6: %08lx\n", hres); + + hres = IHTMLDocument6_get_documentMode(doc6, &res); + ok(hres == S_OK, "get_documentMode failed: %08lx\n", hres); + ok(V_VT(&res) == VT_R4, "V_VT(documentMode) = %u\n", V_VT(&res)); + IHTMLDocument6_Release(doc6); + document_mode = V_R4(&res); + + event_target = (void*)0xdeadbeef; + hres = IHTMLDocument2_QueryInterface(doc, &IID_IEventTarget, (void**)&event_target); + if(document_mode < 9) { + ok(hres == E_NOINTERFACE, "hres = %08lx, expected E_NOINTERFACE\n", hres); + ok(!event_target, "event_target != NULL\n"); + }else { + IHTMLDocument2 *tmp; + + ok(hres == S_OK, "hres = %08lx, expected S_OK\n", hres); + ok(!!event_target, "event_target = NULL\n"); + + bstr = SysAllocString(L"click"); + IEventTarget_addEventListener(event_target, bstr, (IDispatch*)&docobj_onclick_obj, TRUE); + ok(hres == S_OK, "addEventListener failed: %08lx\n", hres); + SysFreeString(bstr); + + hres = IEventTarget_QueryInterface(event_target, &IID_IHTMLDocument2, (void**)&tmp); + ok(hres == S_OK, "Could not get IHTMLDocument2: %08lx\n", hres); + IEventTarget_Release(event_target); + + ok(doc == tmp, "IHTMLDocument2 from IEventTarget not same as original\n"); + IHTMLDocument2_Release(tmp); + + body = doc_get_body(doc); + SET_EXPECT(docobj_onclick); + hres = IHTMLElement_click(body); + ok(hres == S_OK, "click failed: %08lx\n", hres); + IHTMLElement_Release(body); + + pump_msgs(&called_docobj_onclick); + CHECK_CALLED(docobj_onclick); + } + + /* Navigate to a different document mode page, checking using the same doc obj. + Test that it breaks COM rules, since IEventTarget is conditionally exposed. + All the events registered on the old doc node are also removed. */ + protocol_doc_str = document_mode < 9 ? empty_doc_ie9_str : empty_doc_str; + navigate(doc, L"http://winetest.example.org/foobar"); + + hres = IHTMLDocument2_QueryInterface(doc, &IID_IHTMLDocument6, (void**)&doc6); + ok(hres == S_OK, "Could not get IHTMLDocument6: %08lx\n", hres); + + hres = IHTMLDocument6_get_documentMode(doc6, &res); + ok(hres == S_OK, "get_documentMode failed: %08lx\n", hres); + ok(V_VT(&res) == VT_R4, "V_VT(documentMode) = %u\n", V_VT(&res)); + ok(V_R4(&res) == (document_mode < 9 ? 9.0f : 5.0f), "V_R4(documentMode) = %f\n", V_R4(&res)); + IHTMLDocument6_Release(doc6); + document_mode = V_R4(&res); + + event_target = (void*)0xdeadbeef; + hres = IHTMLDocument2_QueryInterface(doc, &IID_IEventTarget, (void**)&event_target); + if(document_mode < 9) { + ok(hres == E_NOINTERFACE, "hres = %08lx, expected E_NOINTERFACE\n", hres); + ok(!event_target, "event_target != NULL\n"); + + body = doc_get_body(doc); + hres = IHTMLElement_click(body); + ok(hres == S_OK, "click failed: %08lx\n", hres); + IHTMLElement_Release(body); + pump_msgs(NULL); + }else { + ok(hres == S_OK, "hres = %08lx, expected S_OK\n", hres); + ok(!!event_target, "event_target = NULL\n"); + IEventTarget_Release(event_target); + } + + set_client_site(doc, FALSE); + IHTMLDocument2_Release(doc); +} + static BOOL check_ie(void) { IHTMLDocument2 *doc; @@ -4257,8 +4396,11 @@ START_TEST(events)
test_empty_document(); test_storage_events(empty_doc_str); - if(is_ie9plus) + if(is_ie9plus) { test_storage_events(empty_doc_ie9_str); + test_doc_obj(empty_doc_str); + test_doc_obj(empty_doc_ie9_str); + }
DestroyWindow(container_hwnd); }else {