Since `EVENT_SYSTEM_MINIMIZESTART` and `EVENT_SYSTEM_MINIMIZEEND` are implemented now (thanks to Esme), this is implementing visibility change events using them. We have to keep track of the browsers to iterate all the documents from the hook.
Note that I tried to refcount the per-thread hook and add it only when necessary (on a visiblity change listener), since it can slow down the entire prefix.
-- v3: mshtml: Implement visibilitychange event.
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/htmlevent.c | 57 +++++++++++++++++++++++++++++++++++- dlls/mshtml/mshtml_private.h | 5 ++++ dlls/mshtml/nsembed.c | 13 ++++++-- dlls/mshtml/task.c | 1 + dlls/mshtml/tests/events.c | 37 ++++++++++++++++++----- 5 files changed, 102 insertions(+), 11 deletions(-)
diff --git a/dlls/mshtml/htmlevent.c b/dlls/mshtml/htmlevent.c index f83153fc084..130b1668d1e 100644 --- a/dlls/mshtml/htmlevent.c +++ b/dlls/mshtml/htmlevent.c @@ -218,7 +218,7 @@ static const event_info_t event_info[] = { {L"unload", EVENT_TYPE_UIEVENT, DISPID_EVMETH_ONUNLOAD, EVENT_BIND_TO_TARGET}, {L"visibilitychange", EVENT_TYPE_EVENT, DISPID_EVPROP_VISIBILITYCHANGE, - EVENT_FIXME | EVENT_BUBBLES}, + EVENT_BUBBLES},
/* EVENTID_LAST special entry */ {NULL, EVENT_TYPE_EVENT, 0, 0} @@ -266,6 +266,49 @@ const WCHAR *get_event_name(eventid_t eid) return event_info[eid].name; }
+static void CALLBACK minimize_event_hook(HWINEVENTHOOK hWinEventHook, DWORD event, HWND hwnd, LONG idObject, LONG idChild, DWORD idEventThread, DWORD dwmsEventTime) +{ + thread_data_t *thread_data = get_thread_data(FALSE); + HTMLDocumentNode *doc; + GeckoBrowser *browser; + DOMEvent *dom_event; + HRESULT hres; + + assert(thread_data != NULL); + if(thread_data->minimize_hook != hWinEventHook) + return; + + LIST_FOR_EACH_ENTRY(browser, &thread_data->browsers, GeckoBrowser, entry) { + if(hwnd == GetAncestor(browser->doc->hwnd, GA_ROOT)) { + LIST_FOR_EACH_ENTRY(doc, &browser->document_nodes, HTMLDocumentNode, browser_entry) { + if(doc->document_mode >= COMPAT_MODE_IE10) { + hres = create_document_event(doc, EVENTID_VISIBILITYCHANGE, &dom_event); + if(SUCCEEDED(hres)) { + dispatch_event(&doc->node.event_target, dom_event); + IDOMEvent_Release(&dom_event->IDOMEvent_iface); + } + } + } + } + } +} + +static BOOL install_minimize_event_hook(void) +{ + thread_data_t *thread_data = get_thread_data(TRUE); + + if(!thread_data) + return FALSE; + + if(!thread_data->minimize_hook) { + thread_data->minimize_hook = SetWinEventHook(EVENT_SYSTEM_MINIMIZESTART, EVENT_SYSTEM_MINIMIZEEND, NULL, minimize_event_hook, 0, 0, WINEVENT_OUTOFCONTEXT); + if(!thread_data->minimize_hook) + return FALSE; + } + thread_data->minimize_hook_ref++; + return TRUE; +} + static listener_container_t *get_listener_container(EventTarget *event_target, const WCHAR *type, BOOL alloc) { const event_target_vtbl_t *vtbl; @@ -290,6 +333,12 @@ static listener_container_t *get_listener_container(EventTarget *event_target, c return NULL; memcpy(container->type, type, (type_len + 1) * sizeof(WCHAR)); list_init(&container->listeners); + + if(eid == EVENTID_VISIBILITYCHANGE && !install_minimize_event_hook()) { + free(container); + return NULL; + } + vtbl = dispex_get_vtbl(&event_target->dispex); if (!vtbl->bind_event) FIXME("Unsupported event binding on target %p\n", event_target); @@ -5136,9 +5185,15 @@ void traverse_event_target(EventTarget *event_target, nsCycleCollectionTraversal
void release_event_target(EventTarget *event_target) { + thread_data_t *thread_data = get_thread_data(FALSE); listener_container_t *iter, *iter2;
WINE_RB_FOR_EACH_ENTRY_DESTRUCTOR(iter, iter2, &event_target->handler_map, listener_container_t, entry) { + if(thread_data && !wcscmp(iter->type, L"visibilitychange") && !--thread_data->minimize_hook_ref) { + UnhookWinEvent(thread_data->minimize_hook); + thread_data->minimize_hook = NULL; + thread_data = NULL; + } while(!list_empty(&iter->listeners)) { event_listener_t *listener = LIST_ENTRY(list_head(&iter->listeners), event_listener_t, entry); list_remove(&listener->entry); diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index 4b2cffd0f54..c1b51c687fb 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -858,6 +858,8 @@ struct GeckoBrowser {
struct list document_nodes; struct list outer_windows; + + struct list entry; };
typedef struct { @@ -1352,12 +1354,15 @@ typedef struct {
typedef struct { HWND thread_hwnd; + struct list browsers; struct list task_list; struct list event_task_list; struct list timer_list; struct list *pending_xhr_events_tail; struct wine_rb_tree session_storage_map; void *blocking_xhr; + HWINEVENTHOOK minimize_hook; + ULONG minimize_hook_ref; } thread_data_t;
thread_data_t *get_thread_data(BOOL); diff --git a/dlls/mshtml/nsembed.c b/dlls/mshtml/nsembed.c index 1b31e4f7159..53e02bf3eda 100644 --- a/dlls/mshtml/nsembed.c +++ b/dlls/mshtml/nsembed.c @@ -2292,12 +2292,16 @@ static HRESULT init_browser(GeckoBrowser *browser)
HRESULT create_gecko_browser(HTMLDocumentObj *doc, GeckoBrowser **_ret) { + thread_data_t *thread_data; GeckoBrowser *ret; HRESULT hres;
if(!load_gecko()) return CLASS_E_CLASSNOTAVAILABLE;
+ if(!(thread_data = get_thread_data(TRUE))) + return E_OUTOFMEMORY; + ret = calloc(1, sizeof(GeckoBrowser)); if(!ret) return E_OUTOFMEMORY; @@ -2318,10 +2322,12 @@ HRESULT create_gecko_browser(HTMLDocumentObj *doc, GeckoBrowser **_ret) list_init(&ret->outer_windows);
hres = init_browser(ret); - if(SUCCEEDED(hres)) - *_ret = ret; - else + if(FAILED(hres)) nsIWebBrowserChrome_Release(&ret->nsIWebBrowserChrome_iface); + else { + *_ret = ret; + list_add_tail(&thread_data->browsers, &ret->entry); + } return hres; }
@@ -2331,6 +2337,7 @@ void detach_gecko_browser(GeckoBrowser *This)
TRACE("(%p)\n", This);
+ list_remove(&This->entry); This->doc = NULL;
if(This->content_window) { diff --git a/dlls/mshtml/task.c b/dlls/mshtml/task.c index 49f76d101a9..067f2efd119 100644 --- a/dlls/mshtml/task.c +++ b/dlls/mshtml/task.c @@ -458,6 +458,7 @@ thread_data_t *get_thread_data(BOOL create) return NULL;
TlsSetValue(mshtml_tls, thread_data); + list_init(&thread_data->browsers); list_init(&thread_data->task_list); list_init(&thread_data->event_task_list); list_init(&thread_data->timer_list); diff --git a/dlls/mshtml/tests/events.c b/dlls/mshtml/tests/events.c index 9760d6bcb9a..65d76c11d89 100644 --- a/dlls/mshtml/tests/events.c +++ b/dlls/mshtml/tests/events.c @@ -2002,6 +2002,29 @@ static void pump_msgs(BOOL *b) } }
+#define pump_msgs_timeout(a,b) pump_msgs_timeout_(a,b,__LINE__) +static void pump_msgs_timeout_(BOOL *b, DWORD timeout, unsigned line) +{ + DWORD end = GetTickCount() + timeout, t; + MSG msg; + + while(!*b && end - (t = GetTickCount()) <= timeout) { + UINT_PTR timer = SetTimer(NULL, 0, end - t, NULL); + BOOL ret = GetMessageW(&msg, NULL, 0, 0); + + KillTimer(NULL, timer); + if(ret <= 0 || (msg.message == WM_TIMER && msg.wParam == timer)) + break; + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + while(PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE)) { + TranslateMessage(&msg); + DispatchMessageW(&msg); + } + ok_(__FILE__,line)(*b, "timed out\n"); +} + static IOleCommandTarget cmdtarget, cmdtarget_stub;
static HRESULT WINAPI cmdtarget_QueryInterface(IOleCommandTarget *iface, REFIID riid, void **ppv) @@ -3498,19 +3521,19 @@ static void test_visibilitychange(IHTMLDocument2 *doc) ShowWindow(container_hwnd, SW_RESTORE); pump_msgs(NULL); }else { - /* FIXME: currently not implemented in Wine, so we can't wait for it */ - BOOL *expect = broken(1) ? &called_visibilitychange : NULL; - + /* Wine can be flaky here depending on driver and compositor (on X11 wait_for_withdrawn_state can time out randomly) */ SET_EXPECT(visibilitychange); ShowWindow(container_hwnd, SW_MINIMIZE); - pump_msgs(expect); - todo_wine + flaky + pump_msgs_timeout(&called_visibilitychange, 3000); + flaky CHECK_CALLED(visibilitychange);
SET_EXPECT(visibilitychange); ShowWindow(container_hwnd, SW_RESTORE); - pump_msgs(expect); - todo_wine + flaky + pump_msgs_timeout(&called_visibilitychange, 3000); + flaky CHECK_CALLED(visibilitychange); }
Nevermind, moving them around didn't seem to help. After much hair pulling I decided to mark them as flaky with a timeout… since it's a Wine WM integration quirk.