wine-gecko does actually support synchronous XMLHttpRequests, even if they are deprecated, but unfortunately it is very broken compared to IE or all the other major browsers (more details [here](https://bugzilla.mozilla.org/show_bug.cgi?id=697151)). Thankfully, it still provides us with the event message loop, which is enough to fix it on mshtml side and act like IE.
For sync XHRs, send() is supposed to block all script code, except for notifications sent to the sync XHR. This is because when send() blocks, no other piece of code in the script should execute other than the sync XHR handlers. Unfortunately, that's not the case in Gecko's broken implementation, which can execute handlers as if they were APCs while it's "blocked" in send().
Note that it doesn't actually block, though, because we still process the message loop (non-event related tasks such as navigation, paints, and other stuff), and that's normal. Gecko doesn't block everything related to script events, only some things on the document and timers, but that's far from enough and not even enough for our purposes since we use our own timer tasks. And not even message events are blocked, even though we *also* use our own message event dispatchers (but it doesn't block Gecko ones either).
So what we have to do is we need to track all the timers and events dispatched that result in script handlers being executed, while "blocking" inside of a sync XHR send(), and queue them up to defer them to be dispatched later, after send() unblocks. But this is easier said that done. There are many corner cases to consider here, for example:
* Events dispatched *synchronously* from within a sync XHR handler or other piece of code called from it. * Nested sync XHR send() calls (called during handler of another sync XHR). * Async XHRs having their events dispatched during a blocking sync XHR send() are complicated. `readyStateChange` for example needs to have the async XHR's readyState at the time it was sent, **not** at the time it was actually dispatched, since we're going to delay it and dispatch it later. It's similar with other XHR states, such as responseText (which can be partial). * Aborts of async XHRs during such handlers.
These patches hopefully should address all the issues and, on a high level, work like this:
* Track the `readyState` and `responseText` length manually, so we can override / force them to specific values. * "Snapshot" the async XHR at the time we encounter an event for it, and queue this event with such information. When later dispatching this event (after being deferred), we temporarily set the state of the async XHR to the snapshot. * To deal with nested event dispatches, keep track of a "dispatch depth" (everytime we dispatch an event we increase the depth, and when it returns we decrease it). * For sync XHRs, we note the depth when send() is called, and defer events at that depth, since they're dispatched during the "blocked" message loop and need to be delayed (except for sync XHR events, which is a special case since it must be dispatched synchronously). When it returns, we restore the previous blocking depth.
-- v3: mshtml: Send all readystatechange events for synchronous XHRs in IE9 mshtml: Implement synchronous XMLHttpRequest. mshtml: Add separate task list for tasks dispatching events. mshtml: Track responseText's length in XHRs and report it manually. mshtml: Track readyState in XHRs and report it manually. mshtml: Pass optional args to XMLHttpRequest.open() correctly. mshtml: Free the task after the destructor. mshtml: Use proper types for readystate_locked and readystate_pending.
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/mshtml_private.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index bec982a75fc..1873799dbf9 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -568,8 +568,8 @@ struct HTMLOuterWindow { struct list browser_entry;
READYSTATE readystate; - BOOL readystate_locked; - unsigned readystate_pending; + unsigned readystate_locked; + BOOL readystate_pending;
HTMLInnerWindow *pending_window; HTMLLocation location;
From: Gabriel Ivăncescu gabrielopcode@gmail.com
And get rid of default_task_destr since it's only used in one place anyway. It was already confusing and leaking in some cases.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/navigate.c | 4 ---- dlls/mshtml/nsio.c | 1 - dlls/mshtml/olecmd.c | 1 - dlls/mshtml/persist.c | 7 +++++-- dlls/mshtml/task.c | 15 +++++---------- 5 files changed, 10 insertions(+), 18 deletions(-)
diff --git a/dlls/mshtml/navigate.c b/dlls/mshtml/navigate.c index fd92483cbf5..b8d7937f071 100644 --- a/dlls/mshtml/navigate.c +++ b/dlls/mshtml/navigate.c @@ -1415,7 +1415,6 @@ static void stop_request_task_destr(task_t *_task) stop_request_task_t *task = (stop_request_task_t*)_task;
IBindStatusCallback_Release(&task->bsc->bsc.IBindStatusCallback_iface); - free(task); }
static HRESULT async_stop_request(nsChannelBSC *This) @@ -1950,7 +1949,6 @@ static void start_doc_binding_task_destr(task_t *_task) start_doc_binding_task_t *task = (start_doc_binding_task_t*)_task;
IHTMLWindow2_Release(&task->pending_window->base.IHTMLWindow2_iface); - free(task); }
HRESULT async_start_doc_binding(HTMLOuterWindow *window, HTMLInnerWindow *pending_window, DWORD flags) @@ -2107,7 +2105,6 @@ static void navigate_javascript_task_destr(task_t *_task) navigate_javascript_task_t *task = (navigate_javascript_task_t*)_task;
IUri_Release(task->uri); - free(task); }
typedef struct { @@ -2139,7 +2136,6 @@ static void navigate_task_destr(task_t *_task) IBindStatusCallback_Release(&task->bscallback->bsc.IBindStatusCallback_iface); IMoniker_Release(task->mon); IUri_Release(task->uri); - free(task); }
static HRESULT navigate_fragment(HTMLOuterWindow *window, IUri *uri) diff --git a/dlls/mshtml/nsio.c b/dlls/mshtml/nsio.c index 612d91ed386..509a83efbd3 100644 --- a/dlls/mshtml/nsio.c +++ b/dlls/mshtml/nsio.c @@ -986,7 +986,6 @@ static void start_binding_task_destr(task_t *_task) start_binding_task_t *task = (start_binding_task_t*)_task;
IBindStatusCallback_Release(&task->bscallback->bsc.IBindStatusCallback_iface); - free(task); }
static nsresult async_open(nsChannel *This, HTMLOuterWindow *window, BOOL is_doc_channel, UINT32 load_type, diff --git a/dlls/mshtml/olecmd.c b/dlls/mshtml/olecmd.c index 5767751ac14..9e59778ccb9 100644 --- a/dlls/mshtml/olecmd.c +++ b/dlls/mshtml/olecmd.c @@ -446,7 +446,6 @@ static void refresh_destr(task_t *_task) refresh_task_t *task = (refresh_task_t*)_task;
IHTMLWindow2_Release(&task->window->base.IHTMLWindow2_iface); - free(task); }
HRESULT reload_page(HTMLOuterWindow *window) diff --git a/dlls/mshtml/persist.c b/dlls/mshtml/persist.c index 581a25c4e2e..e527b3dc3c5 100644 --- a/dlls/mshtml/persist.c +++ b/dlls/mshtml/persist.c @@ -240,6 +240,10 @@ static void set_progress_proc(task_t *_task) } }
+static void set_progress_destr(task_t *_task) +{ +} + static void set_downloading_proc(task_t *_task) { download_proc_task_t *task = (download_proc_task_t*)_task; @@ -275,7 +279,6 @@ static void set_downloading_task_destr(task_t *_task) download_proc_task_t *task = (download_proc_task_t*)_task;
CoTaskMemFree(task->url); - free(task); }
void prepare_for_binding(HTMLDocumentObj *This, IMoniker *mon, DWORD flags) @@ -406,7 +409,7 @@ HRESULT set_moniker(HTMLOuterWindow *window, IMoniker *mon, IUri *nav_uri, IBind
task = malloc(sizeof(docobj_task_t)); task->doc = doc_obj; - hres = push_task(&task->header, set_progress_proc, NULL, doc_obj->task_magic); + hres = push_task(&task->header, set_progress_proc, set_progress_destr, doc_obj->task_magic); if(FAILED(hres)) { CoTaskMemFree(url); return hres; diff --git a/dlls/mshtml/task.c b/dlls/mshtml/task.c index 2987b4bf19d..34b6c711ddd 100644 --- a/dlls/mshtml/task.c +++ b/dlls/mshtml/task.c @@ -46,27 +46,20 @@ typedef struct { struct list entry; } task_timer_t;
-static void default_task_destr(task_t *task) -{ - free(task); -} - HRESULT push_task(task_t *task, task_proc_t proc, task_proc_t destr, LONG magic) { thread_data_t *thread_data;
thread_data = get_thread_data(TRUE); if(!thread_data) { - if(destr) - destr(task); - else - free(task); + destr(task); + free(task); return E_OUTOFMEMORY; }
task->target_magic = magic; task->proc = proc; - task->destr = destr ? destr : default_task_destr; + task->destr = destr;
list_add_tail(&thread_data->task_list, &task->entry);
@@ -128,6 +121,7 @@ void remove_target_tasks(LONG target) if(task->target_magic == target) { list_remove(&task->entry); task->destr(task); + free(task); } } } @@ -341,6 +335,7 @@ static LRESULT WINAPI hidden_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPa
task->proc(task); task->destr(task); + free(task); }
return 0;
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/xmlhttprequest.c | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-)
diff --git a/dlls/mshtml/xmlhttprequest.c b/dlls/mshtml/xmlhttprequest.c index b4269bbe8d1..dff54abb8e2 100644 --- a/dlls/mshtml/xmlhttprequest.c +++ b/dlls/mshtml/xmlhttprequest.c @@ -546,8 +546,9 @@ static HRESULT HTMLXMLHttpRequest_open_hook(DispatchEx *dispex, WORD flags, static HRESULT WINAPI HTMLXMLHttpRequest_open(IHTMLXMLHttpRequest *iface, BSTR bstrMethod, BSTR bstrUrl, VARIANT varAsync, VARIANT varUser, VARIANT varPassword) { HTMLXMLHttpRequest *This = impl_from_IHTMLXMLHttpRequest(iface); - nsACString method, url; nsAString user, password; + nsACString method, url; + unsigned opt_argc = 1; nsresult nsres; HRESULT hres;
@@ -594,8 +595,11 @@ static HRESULT WINAPI HTMLXMLHttpRequest_open(IHTMLXMLHttpRequest *iface, BSTR b return hres; }
- nsres = nsIXMLHttpRequest_Open(This->nsxhr, &method, &url, TRUE, - &user, &password, 0); + if(V_VT(&varPassword) != VT_EMPTY && V_VT(&varPassword) != VT_ERROR) + opt_argc += 2; + else if(V_VT(&varUser) != VT_EMPTY && V_VT(&varUser) != VT_ERROR) + opt_argc += 1; + nsres = nsIXMLHttpRequest_Open(This->nsxhr, &method, &url, !!V_BOOL(&varAsync), &user, &password, opt_argc);
nsACString_Finish(&method); nsACString_Finish(&url);
From: Gabriel Ivăncescu gabrielopcode@gmail.com
So it can be forced to specific values during synchronous XHRs.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/xmlhttprequest.c | 75 +++++++++++++++++------------------- 1 file changed, 35 insertions(+), 40 deletions(-)
diff --git a/dlls/mshtml/xmlhttprequest.c b/dlls/mshtml/xmlhttprequest.c index dff54abb8e2..18ece7b865d 100644 --- a/dlls/mshtml/xmlhttprequest.c +++ b/dlls/mshtml/xmlhttprequest.c @@ -138,6 +138,7 @@ struct HTMLXMLHttpRequest { IWineXMLHttpRequestPrivate IWineXMLHttpRequestPrivate_iface; IProvideClassInfo2 IProvideClassInfo2_iface; LONG ref; + LONG readyState; response_type_t response_type; nsIXMLHttpRequest *nsxhr; XMLHttpReqEventListener *event_listener; @@ -224,6 +225,7 @@ static nsrefcnt NSAPI XMLHttpReqEventListener_Release(nsIDOMEventListener *iface static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *iface, nsIDOMEvent *nsevent) { XMLHttpReqEventListener *This = impl_from_nsIDOMEventListener(iface); + UINT16 readyState; DOMEvent *event; HRESULT hres;
@@ -232,6 +234,9 @@ static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *i if(!This->xhr) return NS_OK;
+ if(NS_SUCCEEDED(nsIXMLHttpRequest_GetReadyState(This->xhr->nsxhr, &readyState))) + This->xhr->readyState = readyState; + hres = create_event_from_nsevent(nsevent, dispex_compat_mode(&This->xhr->event_target.dispex), &event); if(SUCCEEDED(hres) ){ dispatch_event(&This->xhr->event_target, event); @@ -344,19 +349,12 @@ static HRESULT WINAPI HTMLXMLHttpRequest_Invoke(IHTMLXMLHttpRequest *iface, DISP static HRESULT WINAPI HTMLXMLHttpRequest_get_readyState(IHTMLXMLHttpRequest *iface, LONG *p) { HTMLXMLHttpRequest *This = impl_from_IHTMLXMLHttpRequest(iface); - UINT16 val; - nsresult nsres;
TRACE("(%p)->(%p)\n", This, p);
if(!p) return E_POINTER; - nsres = nsIXMLHttpRequest_GetReadyState(This->nsxhr, &val); - if(NS_FAILED(nsres)) { - ERR("nsIXMLHttpRequest_GetReadyState failed: %08lx\n", nsres); - return E_FAIL; - } - *p = val; + *p = This->readyState; return S_OK; }
@@ -378,6 +376,11 @@ static HRESULT WINAPI HTMLXMLHttpRequest_get_responseText(IHTMLXMLHttpRequest *i if(!p) return E_POINTER;
+ if(This->readyState < 3) { + *p = NULL; + return S_OK; + } + nsAString_Init(&nsstr, NULL); nsres = nsIXMLHttpRequest_GetResponseText(This->nsxhr, &nsstr); return return_nsstr(nsres, &nsstr, p); @@ -394,6 +397,11 @@ static HRESULT WINAPI HTMLXMLHttpRequest_get_responseXML(IHTMLXMLHttpRequest *if
TRACE("(%p)->(%p)\n", This, p);
+ if(This->readyState < 4) { + *p = NULL; + return S_OK; + } + if(dispex_compat_mode(&This->event_target.dispex) >= COMPAT_MODE_IE10) { nsIDOMDocument *nsdoc; nsresult nsres; @@ -448,6 +456,11 @@ static HRESULT WINAPI HTMLXMLHttpRequest_get_status(IHTMLXMLHttpRequest *iface, if(!p) return E_POINTER;
+ if(This->readyState < 2) { + *p = 0; + return E_FAIL; + } + nsres = nsIXMLHttpRequest_GetStatus(This->nsxhr, &val); if(NS_FAILED(nsres)) { ERR("nsIXMLHttpRequest_GetStatus failed: %08lx\n", nsres); @@ -465,19 +478,13 @@ static HRESULT WINAPI HTMLXMLHttpRequest_get_statusText(IHTMLXMLHttpRequest *ifa HTMLXMLHttpRequest *This = impl_from_IHTMLXMLHttpRequest(iface); nsACString nscstr; nsresult nsres; - HRESULT hres; - LONG state;
TRACE("(%p)->(%p)\n", This, p);
if(!p) return E_POINTER;
- hres = IHTMLXMLHttpRequest_get_readyState(iface, &state); - if(FAILED(hres)) - return hres; - - if(state < 2) { + if(This->readyState < 2) { *p = NULL; return E_FAIL; } @@ -518,6 +525,8 @@ static HRESULT WINAPI HTMLXMLHttpRequest_abort(IHTMLXMLHttpRequest *iface) return E_FAIL; }
+ /* This state change is not broadcast by Gecko, so set it manually */ + This->readyState = 0; return S_OK; }
@@ -661,19 +670,13 @@ static HRESULT WINAPI HTMLXMLHttpRequest_getAllResponseHeaders(IHTMLXMLHttpReque HTMLXMLHttpRequest *This = impl_from_IHTMLXMLHttpRequest(iface); nsACString nscstr; nsresult nsres; - HRESULT hres; - LONG state;
TRACE("(%p)->(%p)\n", This, p);
if(!p) return E_POINTER;
- hres = IHTMLXMLHttpRequest_get_readyState(iface, &state); - if(FAILED(hres)) - return hres; - - if(state < 2) { + if(This->readyState < 2) { *p = NULL; return E_FAIL; } @@ -689,8 +692,6 @@ static HRESULT WINAPI HTMLXMLHttpRequest_getResponseHeader(IHTMLXMLHttpRequest * nsACString header, ret; char *cstr; nsresult nsres; - HRESULT hres; - LONG state; TRACE("(%p)->(%s %p)\n", This, debugstr_w(bstrHeader), p);
if(!p) @@ -698,11 +699,7 @@ static HRESULT WINAPI HTMLXMLHttpRequest_getResponseHeader(IHTMLXMLHttpRequest * if(!bstrHeader) return E_INVALIDARG;
- hres = IHTMLXMLHttpRequest_get_readyState(iface, &state); - if(FAILED(hres)) - return hres; - - if(state < 2) { + if(This->readyState < 2) { *p = NULL; return E_FAIL; } @@ -943,8 +940,6 @@ static HRESULT WINAPI HTMLXMLHttpRequest_private_get_response(IWineXMLHttpReques { HTMLXMLHttpRequest *This = impl_from_IWineXMLHttpRequestPrivate(iface); HRESULT hres = S_OK; - nsresult nsres; - UINT16 state;
TRACE("(%p)->(%p)\n", This, p);
@@ -962,8 +957,7 @@ static HRESULT WINAPI HTMLXMLHttpRequest_private_get_response(IWineXMLHttpReques
case response_type_arraybuf: case response_type_blob: - nsres = nsIXMLHttpRequest_GetReadyState(This->nsxhr, &state); - if(NS_FAILED(nsres) || state < 4) { + if(This->readyState < 4) { V_VT(p) = VT_EMPTY; break; } @@ -990,17 +984,11 @@ static HRESULT WINAPI HTMLXMLHttpRequest_private_put_responseType(IWineXMLHttpRe HTMLXMLHttpRequest *This = impl_from_IWineXMLHttpRequestPrivate(iface); nsAString nsstr; nsresult nsres; - HRESULT hres; unsigned i; - LONG state;
TRACE("(%p)->(%s)\n", This, debugstr_w(v));
- hres = IHTMLXMLHttpRequest_get_readyState(&This->IHTMLXMLHttpRequest_iface, &state); - if(FAILED(hres)) - return hres; - - if(state < 1 || state > 2) { + if(This->readyState < 1 || This->readyState > 2) { /* FIXME: Return InvalidStateError */ return E_FAIL; } @@ -1313,6 +1301,9 @@ static void HTMLXMLHttpRequest_bind_event(DispatchEx *dispex, eventid_t eid) This->event_listener->events_mask = 0; }
+ if(This->event_listener->events_mask & (1 << i)) + return; + nsres = nsIXMLHttpRequest_QueryInterface(This->nsxhr, &IID_nsIDOMEventTarget, (void**)&nstarget); assert(nsres == NS_OK);
@@ -1493,6 +1484,10 @@ static HRESULT WINAPI HTMLXMLHttpRequestFactory_create(IHTMLXMLHttpRequestFactor &HTMLXMLHttpRequest_dispex, This->window->doc->document_mode); ret->ref = 1;
+ /* Always register these handlers because we need them to track state */ + HTMLXMLHttpRequest_bind_event(&ret->event_target.dispex, EVENTID_READYSTATECHANGE); + HTMLXMLHttpRequest_bind_event(&ret->event_target.dispex, EVENTID_PROGRESS); + *p = &ret->IHTMLXMLHttpRequest_iface; return S_OK; }
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/xmlhttprequest.c | 31 +++++++++++++++++++++++++++++-- 1 file changed, 29 insertions(+), 2 deletions(-)
diff --git a/dlls/mshtml/xmlhttprequest.c b/dlls/mshtml/xmlhttprequest.c index 18ece7b865d..8ebf6ad2529 100644 --- a/dlls/mshtml/xmlhttprequest.c +++ b/dlls/mshtml/xmlhttprequest.c @@ -139,6 +139,7 @@ struct HTMLXMLHttpRequest { IProvideClassInfo2 IProvideClassInfo2_iface; LONG ref; LONG readyState; + size_t responseText_length; response_type_t response_type; nsIXMLHttpRequest *nsxhr; XMLHttpReqEventListener *event_listener; @@ -225,7 +226,9 @@ static nsrefcnt NSAPI XMLHttpReqEventListener_Release(nsIDOMEventListener *iface static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *iface, nsIDOMEvent *nsevent) { XMLHttpReqEventListener *This = impl_from_nsIDOMEventListener(iface); + const PRUnichar *text; UINT16 readyState; + nsAString nsstr; DOMEvent *event; HRESULT hres;
@@ -234,9 +237,21 @@ static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *i if(!This->xhr) return NS_OK;
- if(NS_SUCCEEDED(nsIXMLHttpRequest_GetReadyState(This->xhr->nsxhr, &readyState))) + if(NS_SUCCEEDED(nsIXMLHttpRequest_GetReadyState(This->xhr->nsxhr, &readyState))) { This->xhr->readyState = readyState;
+ if(This->xhr->readyState >= 3) { + nsAString_Init(&nsstr, NULL); + if(NS_SUCCEEDED(nsIXMLHttpRequest_GetResponseText(This->xhr->nsxhr, &nsstr))) { + /* Avoid recalculating from the beginning, since it can't be shorter */ + nsAString_GetData(&nsstr, &text); + if(text && text[0]) + This->xhr->responseText_length += wcslen(text + This->xhr->responseText_length); + nsAString_Finish(&nsstr); + } + } + } + hres = create_event_from_nsevent(nsevent, dispex_compat_mode(&This->xhr->event_target.dispex), &event); if(SUCCEEDED(hres) ){ dispatch_event(&This->xhr->event_target, event); @@ -368,6 +383,8 @@ static HRESULT WINAPI HTMLXMLHttpRequest_get_responseBody(IHTMLXMLHttpRequest *i static HRESULT WINAPI HTMLXMLHttpRequest_get_responseText(IHTMLXMLHttpRequest *iface, BSTR *p) { HTMLXMLHttpRequest *This = impl_from_IHTMLXMLHttpRequest(iface); + const PRUnichar *text; + HRESULT hres = S_OK; nsAString nsstr; nsresult nsres;
@@ -383,7 +400,17 @@ static HRESULT WINAPI HTMLXMLHttpRequest_get_responseText(IHTMLXMLHttpRequest *i
nsAString_Init(&nsstr, NULL); nsres = nsIXMLHttpRequest_GetResponseText(This->nsxhr, &nsstr); - return return_nsstr(nsres, &nsstr, p); + if(NS_FAILED(nsres)) + hres = map_nsresult(nsres); + else { + nsAString_GetData(&nsstr, &text); + if(!text || !text[0]) + *p = NULL; + else if(!(*p = SysAllocStringLen(text, This->responseText_length))) + hres = E_OUTOFMEMORY; + nsAString_Finish(&nsstr); + } + return hres; }
static HRESULT WINAPI HTMLXMLHttpRequest_get_responseXML(IHTMLXMLHttpRequest *iface, IDispatch **p)
Updated with suggested changes and a bunch of fixes (also to some existing leaks). Apparently, some events are still dispatched on different contexts, while others are not. Async XHRs are thread blocked, even with different DocumentObjs on same thread, but e.g. `readystatechange` events are not. Message events also seem to be thread blocked, which makes a bit of sense since they can be posted by arbitrary windows in different contexts, as well as timeouts. The test for msg events being thread blocked is the `sync_xhr(pre-send)` msg.
Tasks that dispatch events are now pushed separately from other tasks and implicitly store ref to the inner window (needed by task processing to know if the window is "blocked").
Also, when doing a sync XHR send() now, it increases blocking depth for that window, so that event tasks for it are not going to be processed, until its depth is 0. Similar for the thread block depth, for the thread blocked events and timeouts.
From: Gabriel Ivăncescu gabrielopcode@gmail.com
They need to be handled separately because they are blocked by sync XHRs for the given window.
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/htmlstorage.c | 14 +++---- dlls/mshtml/htmlwindow.c | 16 +++----- dlls/mshtml/mshtml_private.h | 13 ++++++ dlls/mshtml/persist.c | 21 +++------- dlls/mshtml/script.c | 10 ++--- dlls/mshtml/task.c | 76 +++++++++++++++++++++++++++--------- 6 files changed, 92 insertions(+), 58 deletions(-)
diff --git a/dlls/mshtml/htmlstorage.c b/dlls/mshtml/htmlstorage.c index 21e3df5c3fb..24eec4ba078 100644 --- a/dlls/mshtml/htmlstorage.c +++ b/dlls/mshtml/htmlstorage.c @@ -200,15 +200,14 @@ static inline HTMLStorage *impl_from_IHTMLStorage(IHTMLStorage *iface) static HRESULT build_session_origin(IUri*,BSTR,BSTR*);
struct storage_event_task { - task_t header; - HTMLInnerWindow *window; + event_task_t header; DOMEvent *event; };
-static void storage_event_proc(task_t *_task) +static void storage_event_proc(event_task_t *_task) { struct storage_event_task *task = (struct storage_event_task*)_task; - HTMLInnerWindow *window = task->window; + HTMLInnerWindow *window = task->header.window; DOMEvent *event = task->event; VARIANT_BOOL cancelled;
@@ -221,11 +220,10 @@ static void storage_event_proc(task_t *_task) } }
-static void storage_event_destr(task_t *_task) +static void storage_event_destr(event_task_t *_task) { struct storage_event_task *task = (struct storage_event_task*)_task; IDOMEvent_Release(&task->event->IDOMEvent_iface); - IHTMLWindow2_Release(&task->window->base.IHTMLWindow2_iface); }
struct send_storage_event_ctx { @@ -254,9 +252,7 @@ static HRESULT push_storage_event_task(struct send_storage_event_ctx *ctx, HTMLI }
task->event = event; - task->window = window; - IHTMLWindow2_AddRef(&task->window->base.IHTMLWindow2_iface); - return push_task(&task->header, storage_event_proc, storage_event_destr, window->task_magic); + return push_event_task(&task->header, window, storage_event_proc, storage_event_destr, window->task_magic); }
static HRESULT send_storage_event_impl(struct send_storage_event_ctx *ctx, HTMLInnerWindow *window) diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index ed497eacf4e..2d5db55c14d 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -2201,22 +2201,20 @@ static HRESULT WINAPI HTMLWindow6_get_maxConnectionsPerServer(IHTMLWindow6 *ifac }
struct post_message_task { - task_t header; - HTMLInnerWindow *window; + event_task_t header; DOMEvent *event; -} ; +};
-static void post_message_proc(task_t *_task) +static void post_message_proc(event_task_t *_task) { struct post_message_task *task = (struct post_message_task *)_task; - dispatch_event(&task->window->event_target, task->event); + dispatch_event(&task->header.window->event_target, task->event); }
-static void post_message_destr(task_t *_task) +static void post_message_destr(event_task_t *_task) { struct post_message_task *task = (struct post_message_task *)_task; IDOMEvent_Release(&task->event->IDOMEvent_iface); - IHTMLWindow2_Release(&task->window->base.IHTMLWindow2_iface); }
static HRESULT WINAPI HTMLWindow6_postMessage(IHTMLWindow6 *iface, BSTR msg, VARIANT targetOrigin) @@ -3297,9 +3295,7 @@ static HRESULT WINAPI window_private_postMessage(IWineHTMLWindowPrivate *iface, }
task->event = event; - task->window = window; - IHTMLWindow2_AddRef(&task->window->base.IHTMLWindow2_iface); - return push_task(&task->header, post_message_proc, post_message_destr, window->task_magic); + return push_event_task(&task->header, window, post_message_proc, post_message_destr, window->task_magic); }
dispatch_event(&window->event_target, event); diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index 1873799dbf9..6f0a4043115 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -1283,6 +1283,17 @@ struct task_t { struct list entry; };
+typedef struct event_task_t event_task_t; +typedef void (*event_task_proc_t)(event_task_t*); + +struct event_task_t { + LONG target_magic; + event_task_proc_t proc; + event_task_proc_t destr; + struct list entry; + HTMLInnerWindow *window; +}; + typedef struct { task_t header; HTMLDocumentObj *doc; @@ -1291,6 +1302,7 @@ typedef struct { typedef struct { HWND thread_hwnd; struct list task_list; + struct list event_task_list; struct list timer_list; struct wine_rb_tree session_storage_map; } thread_data_t; @@ -1302,6 +1314,7 @@ void destroy_session_storage(thread_data_t*) DECLSPEC_HIDDEN;
LONG get_task_target_magic(void) DECLSPEC_HIDDEN; HRESULT push_task(task_t*,task_proc_t,task_proc_t,LONG) DECLSPEC_HIDDEN; +HRESULT push_event_task(event_task_t*,HTMLInnerWindow*,event_task_proc_t,event_task_proc_t,LONG) DECLSPEC_HIDDEN; void remove_target_tasks(LONG) DECLSPEC_HIDDEN; ULONGLONG get_time_stamp(void) DECLSPEC_HIDDEN;
diff --git a/dlls/mshtml/persist.c b/dlls/mshtml/persist.c index e527b3dc3c5..486643d8b21 100644 --- a/dlls/mshtml/persist.c +++ b/dlls/mshtml/persist.c @@ -455,21 +455,13 @@ static void notif_readystate(HTMLOuterWindow *window) } }
-typedef struct { - task_t header; - HTMLOuterWindow *window; -} readystate_task_t; - -static void notif_readystate_proc(task_t *_task) +static void notif_readystate_proc(event_task_t *task) { - readystate_task_t *task = (readystate_task_t*)_task; - notif_readystate(task->window); + notif_readystate(task->window->base.outer_window); }
-static void notif_readystate_destr(task_t *_task) +static void notif_readystate_destr(event_task_t *task) { - readystate_task_t *task = (readystate_task_t*)_task; - IHTMLWindow2_Release(&task->window->base.IHTMLWindow2_iface); }
void set_ready_state(HTMLOuterWindow *window, READYSTATE readystate) @@ -479,7 +471,7 @@ void set_ready_state(HTMLOuterWindow *window, READYSTATE readystate) window->readystate = readystate;
if(window->readystate_locked) { - readystate_task_t *task; + event_task_t *task; HRESULT hres;
if(window->readystate_pending || prev_state == readystate) @@ -489,10 +481,7 @@ void set_ready_state(HTMLOuterWindow *window, READYSTATE readystate) if(!task) return;
- IHTMLWindow2_AddRef(&window->base.IHTMLWindow2_iface); - task->window = window; - - hres = push_task(&task->header, notif_readystate_proc, notif_readystate_destr, window->task_magic); + hres = push_event_task(task, window->base.inner_window, notif_readystate_proc, notif_readystate_destr, window->task_magic); if(SUCCEEDED(hres)) window->readystate_pending = TRUE; return; diff --git a/dlls/mshtml/script.c b/dlls/mshtml/script.c index 8b1c1f11938..0e2241663dc 100644 --- a/dlls/mshtml/script.c +++ b/dlls/mshtml/script.c @@ -773,11 +773,11 @@ static void dispatch_script_readystatechange_event(HTMLScriptElement *script) }
typedef struct { - task_t header; + event_task_t header; HTMLScriptElement *elem; } fire_readystatechange_task_t;
-static void fire_readystatechange_proc(task_t *_task) +static void fire_readystatechange_proc(event_task_t *_task) { fire_readystatechange_task_t *task = (fire_readystatechange_task_t*)_task;
@@ -788,7 +788,7 @@ static void fire_readystatechange_proc(task_t *_task) dispatch_script_readystatechange_event(task->elem); }
-static void fire_readystatechange_task_destr(task_t *_task) +static void fire_readystatechange_task_destr(event_task_t *_task) { fire_readystatechange_task_t *task = (fire_readystatechange_task_t*)_task;
@@ -818,8 +818,8 @@ static void set_script_elem_readystate(HTMLScriptElement *script_elem, READYSTAT IHTMLScriptElement_AddRef(&script_elem->IHTMLScriptElement_iface); task->elem = script_elem;
- hres = push_task(&task->header, fire_readystatechange_proc, fire_readystatechange_task_destr, - script_elem->element.node.doc->window->task_magic); + hres = push_event_task(&task->header, script_elem->element.node.doc->window, fire_readystatechange_proc, + fire_readystatechange_task_destr, script_elem->element.node.doc->window->task_magic); if(SUCCEEDED(hres)) script_elem->pending_readystatechange_event = TRUE; }else { diff --git a/dlls/mshtml/task.c b/dlls/mshtml/task.c index 34b6c711ddd..37667dcc9cc 100644 --- a/dlls/mshtml/task.c +++ b/dlls/mshtml/task.c @@ -67,21 +67,33 @@ HRESULT push_task(task_t *task, task_proc_t proc, task_proc_t destr, LONG magic) return S_OK; }
-static task_t *pop_task(void) +static void release_event_task(event_task_t *task) +{ + task->destr(task); + IHTMLWindow2_Release(&task->window->base.IHTMLWindow2_iface); + free(task); +} + +HRESULT push_event_task(event_task_t *task, HTMLInnerWindow *window, event_task_proc_t proc, event_task_proc_t destr, LONG magic) { thread_data_t *thread_data; - task_t *task;
- thread_data = get_thread_data(FALSE); - if(!thread_data) - return NULL; + task->target_magic = magic; + task->proc = proc; + task->destr = destr; + task->window = window; + IHTMLWindow2_AddRef(&window->base.IHTMLWindow2_iface);
- if(list_empty(&thread_data->task_list)) - return NULL; + thread_data = get_thread_data(TRUE); + if(!thread_data) { + release_event_task(task); + return E_OUTOFMEMORY; + } + + list_add_tail(&thread_data->event_task_list, &task->entry);
- task = LIST_ENTRY(thread_data->task_list.next, task_t, entry); - list_remove(&task->entry); - return task; + PostMessageW(thread_data->thread_hwnd, WM_PROCESSTASK, 0, 0); + return S_OK; }
static void release_task_timer(HWND thread_hwnd, task_timer_t *timer) @@ -124,6 +136,14 @@ void remove_target_tasks(LONG target) free(task); } } + + LIST_FOR_EACH_SAFE(liter, ltmp, &thread_data->event_task_list) { + event_task_t *task = LIST_ENTRY(liter, event_task_t, entry); + if(task->target_magic == target) { + list_remove(&task->entry); + release_event_task(task); + } + } }
LONG get_task_target_magic(void) @@ -326,18 +346,37 @@ static LRESULT process_timer(void)
static LRESULT WINAPI hidden_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam) { + thread_data_t *thread_data; + switch(msg) { case WM_PROCESSTASK: - while(1) { - task_t *task = pop_task(); - if(!task) - break; + thread_data = get_thread_data(FALSE); + if(!thread_data) + return 0;
- task->proc(task); - task->destr(task); - free(task); + while(1) { + struct list *head = list_head(&thread_data->task_list); + + if(head) { + task_t *task = LIST_ENTRY(head, task_t, entry); + list_remove(&task->entry); + task->proc(task); + task->destr(task); + free(task); + continue; + } + + head = list_head(&thread_data->event_task_list); + if(head) { + event_task_t *task = LIST_ENTRY(head, event_task_t, entry); + list_remove(&task->entry); + task->proc(task); + release_event_task(task); + continue; + } + + break; } - return 0; case WM_TIMER: return process_timer(); @@ -410,6 +449,7 @@ thread_data_t *get_thread_data(BOOL create)
TlsSetValue(mshtml_tls, thread_data); list_init(&thread_data->task_list); + list_init(&thread_data->event_task_list); list_init(&thread_data->timer_list); wine_rb_init(&thread_data->session_storage_map, session_storage_map_cmp); }
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com ---
The todo_wine that are added are not new failures, they were just never checked before (it bailed out early since open() failed). --- dlls/mshtml/htmlwindow.c | 2 + dlls/mshtml/mshtml_private.h | 6 + dlls/mshtml/task.c | 82 ++++++++-- dlls/mshtml/tests/events.c | 231 ++++++++++++++++++++++++++--- dlls/mshtml/tests/rsrc.rc | 3 + dlls/mshtml/tests/script.c | 26 +++- dlls/mshtml/tests/xhr.js | 134 +++++++++++++++++ dlls/mshtml/tests/xhr_iframe.html | 23 +++ dlls/mshtml/tests/xmlhttprequest.c | 18 +-- dlls/mshtml/xmlhttprequest.c | 112 +++++++++++--- 10 files changed, 578 insertions(+), 59 deletions(-) create mode 100644 dlls/mshtml/tests/xhr_iframe.html
diff --git a/dlls/mshtml/htmlwindow.c b/dlls/mshtml/htmlwindow.c index 2d5db55c14d..c19896e419e 100644 --- a/dlls/mshtml/htmlwindow.c +++ b/dlls/mshtml/htmlwindow.c @@ -3294,6 +3294,8 @@ static HRESULT WINAPI window_private_postMessage(IWineHTMLWindowPrivate *iface, return E_OUTOFMEMORY; }
+ /* Because message events can be sent to different windows, they get blocked by any context */ + task->header.thread_blocked = TRUE; task->event = event; return push_event_task(&task->header, window, post_message_proc, post_message_destr, window->task_magic); } diff --git a/dlls/mshtml/mshtml_private.h b/dlls/mshtml/mshtml_private.h index 6f0a4043115..521291c152d 100644 --- a/dlls/mshtml/mshtml_private.h +++ b/dlls/mshtml/mshtml_private.h @@ -607,6 +607,7 @@ struct HTMLInnerWindow { VARIANT performance; HTMLPerformanceTiming *performance_timing;
+ unsigned blocking_depth; unsigned parser_callback_cnt; struct list script_queue;
@@ -1288,6 +1289,7 @@ typedef void (*event_task_proc_t)(event_task_t*);
struct event_task_t { LONG target_magic; + BOOL thread_blocked; event_task_proc_t proc; event_task_proc_t destr; struct list entry; @@ -1304,13 +1306,17 @@ typedef struct { struct list task_list; struct list event_task_list; struct list timer_list; + struct list queued_xhr_events_list; struct wine_rb_tree session_storage_map; + unsigned int blocking_depth; } thread_data_t;
thread_data_t *get_thread_data(BOOL) DECLSPEC_HIDDEN; HWND get_thread_hwnd(void) DECLSPEC_HIDDEN; int session_storage_map_cmp(const void*,const struct wine_rb_entry*) DECLSPEC_HIDDEN; void destroy_session_storage(thread_data_t*) DECLSPEC_HIDDEN; +void dispatch_queued_xhr_event(struct list*) DECLSPEC_HIDDEN; +nsresult sync_xhr_send(nsIXMLHttpRequest*,HTMLInnerWindow*,nsIVariant*) DECLSPEC_HIDDEN;
LONG get_task_target_magic(void) DECLSPEC_HIDDEN; HRESULT push_task(task_t*,task_proc_t,task_proc_t,LONG) DECLSPEC_HIDDEN; diff --git a/dlls/mshtml/task.c b/dlls/mshtml/task.c index 37667dcc9cc..f1a683b9c5a 100644 --- a/dlls/mshtml/task.c +++ b/dlls/mshtml/task.c @@ -303,7 +303,7 @@ static LRESULT process_timer(void) thread_data = get_thread_data(FALSE); assert(thread_data != NULL);
- if(list_empty(&thread_data->timer_list)) { + if(list_empty(&thread_data->timer_list) || thread_data->blocking_depth) { KillTimer(thread_data->thread_hwnd, TIMER_ID); return 0; } @@ -338,7 +338,7 @@ static LRESULT process_timer(void) call_timer_disp(disp, timer_type);
IDispatch_Release(disp); - }while(!list_empty(&thread_data->timer_list)); + }while(!list_empty(&thread_data->timer_list) && !thread_data->blocking_depth);
KillTimer(thread_data->thread_hwnd, TIMER_ID); return 0; @@ -366,16 +366,25 @@ static LRESULT WINAPI hidden_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPa continue; }
- head = list_head(&thread_data->event_task_list); - if(head) { - event_task_t *task = LIST_ENTRY(head, event_task_t, entry); - list_remove(&task->entry); - task->proc(task); - release_event_task(task); + head = list_head(&thread_data->queued_xhr_events_list); + if(head && !thread_data->blocking_depth) { + dispatch_queued_xhr_event(head); continue; }
- break; + head = &thread_data->event_task_list; + while((head = list_next(&thread_data->event_task_list, head))) { + event_task_t *task = LIST_ENTRY(head, event_task_t, entry); + + if((!task->thread_blocked || !thread_data->blocking_depth) && !task->window->blocking_depth) { + list_remove(&task->entry); + task->proc(task); + release_event_task(task); + break; + } + } + if(!head) + break; } return 0; case WM_TIMER: @@ -451,6 +460,7 @@ thread_data_t *get_thread_data(BOOL create) list_init(&thread_data->task_list); list_init(&thread_data->event_task_list); list_init(&thread_data->timer_list); + list_init(&thread_data->queued_xhr_events_list); wine_rb_init(&thread_data->session_storage_map, session_storage_map_cmp); }
@@ -467,3 +477,57 @@ ULONGLONG get_time_stamp(void) GetSystemTimeAsFileTime(&time); return (((ULONGLONG)time.dwHighDateTime << 32) + time.dwLowDateTime) / 10000 - time_epoch; } + +nsresult sync_xhr_send(nsIXMLHttpRequest *nsxhr, HTMLInnerWindow *window, nsIVariant *nsbody) +{ + thread_data_t *thread_data = get_thread_data(TRUE); + nsresult nsres; + + if(!thread_data) + return NS_ERROR_OUT_OF_MEMORY; + + /* Note: Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), + * synchronous requests on the main thread have been deprecated due to the negative + * effects to the user experience. However, they still work. The larger issue is that + * it is broken because it still dispatches async XHR and some other events, while all + * other major browsers don't, including IE, so we have to filter them out during Send. + * + * They will need to be queued and dispatched later, after Send returns, otherwise it + * breaks JavaScript single-threaded expectations (js code will switch from blocking in + * Send to executing some event handler, then returning back to Send, messing its state). + * + * Of course we can't just delay dispatching the events, because the state won't match + * for each event later on to what it's supposed to be (most notably, XHR's readyState). + * So unfortunately we have to hardcode and snapshot the XHR state for async events... + * + * While queuing an event this way would not work correctly with their default behavior + * in Gecko (preventDefault() can't be called because we need to *delay* the default, + * rather than prevent it completely), Gecko does suppress events reaching the document + * during the sync XHR event loop, so all the element/node events should be safe. If we + * find an event that has defaults on Gecko's side and isn't delayed by Gecko, we need + * to figure out a way to handle it... + * + * For details (and bunch of problems to consider) see: https://bugzil.la/697151 + */ + window->base.outer_window->readystate_locked++; + window->blocking_depth++; + thread_data->blocking_depth++; + nsres = nsIXMLHttpRequest_Send(nsxhr, nsbody); + thread_data->blocking_depth--; + window->base.outer_window->readystate_locked--; + + if(--window->blocking_depth) + return nsres; + + /* Make sure blocked events and timers are processed */ + if(!list_empty(&thread_data->event_task_list) || (!thread_data->blocking_depth && !list_empty(&thread_data->queued_xhr_events_list))) + PostMessageW(thread_data->thread_hwnd, WM_PROCESSTASK, 0, 0); + + if(!thread_data->blocking_depth && !list_empty(&thread_data->timer_list)) { + task_timer_t *timer = LIST_ENTRY(list_head(&thread_data->timer_list), task_timer_t, entry); + DWORD tc = GetTickCount(); + + SetTimer(thread_data->thread_hwnd, TIMER_ID, timer->time > tc ? timer->time - tc : 0, NULL); + } + return nsres; +} diff --git a/dlls/mshtml/tests/events.c b/dlls/mshtml/tests/events.c index a410b464756..0a5397c12be 100644 --- a/dlls/mshtml/tests/events.c +++ b/dlls/mshtml/tests/events.c @@ -111,6 +111,8 @@ DEFINE_EXPECT(window1_onstorage); DEFINE_EXPECT(doc2_onstorage); DEFINE_EXPECT(doc2_onstoragecommit); DEFINE_EXPECT(window2_onstorage); +DEFINE_EXPECT(async_xhr_done); +DEFINE_EXPECT(sync_xhr_done);
static HWND container_hwnd = NULL; static IHTMLWindow2 *window; @@ -3730,6 +3732,60 @@ static HRESULT WINAPI window2_onstorage(IDispatchEx *iface, DISPID id, LCID lcid
EVENT_HANDLER_FUNC_OBJ(window2_onstorage);
+static HRESULT WINAPI async_xhr(IDispatchEx *iface, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, + VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller) +{ + IHTMLXMLHttpRequest *xhr; + LONG ready_state; + HRESULT hres; + + ok(pdp != NULL, "pdp == NULL\n"); + ok(pdp->cArgs == (document_mode < 9 ? 1 : 2), "pdp->cArgs = %d\n", pdp->cArgs); + ok(pdp->cNamedArgs == 1, "pdp->cNamedArgs = %d\n", pdp->cNamedArgs); + ok(pdp->rgdispidNamedArgs[0] == DISPID_THIS, "pdp->rgdispidNamedArgs[0] = %ld\n", pdp->rgdispidNamedArgs[0]); + ok(V_VT(pdp->rgvarg) == VT_DISPATCH, "V_VT(this) = %d\n", V_VT(pdp->rgvarg)); + ok(V_DISPATCH(pdp->rgvarg) != NULL, "V_DISPATCH(this) == NULL\n"); + + hres = IDispatch_QueryInterface(V_DISPATCH(pdp->rgvarg), &IID_IHTMLXMLHttpRequest, (void**)&xhr); + ok(hres == S_OK, "Could not get IHTMLXMLHttpRequest: %08lx\n", hres); + + hres = IHTMLXMLHttpRequest_get_readyState(xhr, &ready_state); + if(SUCCEEDED(hres) && ready_state == 4) + CHECK_EXPECT(async_xhr_done); + + IHTMLXMLHttpRequest_Release(xhr); + return S_OK; +} + +EVENT_HANDLER_FUNC_OBJ(async_xhr); + +static HRESULT WINAPI sync_xhr(IDispatchEx *iface, DISPID id, LCID lcid, WORD wFlags, DISPPARAMS *pdp, + VARIANT *pvarRes, EXCEPINFO *pei, IServiceProvider *pspCaller) +{ + IHTMLXMLHttpRequest *xhr; + LONG ready_state; + HRESULT hres; + + ok(pdp != NULL, "pdp == NULL\n"); + ok(pdp->cArgs == (document_mode < 9 ? 1 : 2), "pdp->cArgs = %d\n", pdp->cArgs); + ok(pdp->cNamedArgs == 1, "pdp->cNamedArgs = %d\n", pdp->cNamedArgs); + ok(pdp->rgdispidNamedArgs[0] == DISPID_THIS, "pdp->rgdispidNamedArgs[0] = %ld\n", pdp->rgdispidNamedArgs[0]); + ok(V_VT(pdp->rgvarg) == VT_DISPATCH, "V_VT(this) = %d\n", V_VT(pdp->rgvarg)); + ok(V_DISPATCH(pdp->rgvarg) != NULL, "V_DISPATCH(this) == NULL\n"); + + hres = IDispatch_QueryInterface(V_DISPATCH(pdp->rgvarg), &IID_IHTMLXMLHttpRequest, (void**)&xhr); + ok(hres == S_OK, "Could not get IHTMLXMLHttpRequest: %08lx\n", hres); + + hres = IHTMLXMLHttpRequest_get_readyState(xhr, &ready_state); + if(SUCCEEDED(hres) && ready_state == 4) + CHECK_EXPECT(sync_xhr_done); + + IHTMLXMLHttpRequest_Release(xhr); + return S_OK; +} + +EVENT_HANDLER_FUNC_OBJ(sync_xhr); + static HRESULT QueryInterface(REFIID,void**); static HRESULT browserservice_qi(REFIID,void**); static HRESULT wb_qi(REFIID,void**); @@ -5133,8 +5189,27 @@ typedef struct { ULONG size; const char *data; const char *ptr; + + HANDLE delay_event; + LONG delay; } ProtocolHandler;
+static ProtocolHandler *delay_with_signal_handler; + +static DWORD WINAPI delay_proc(void *arg) +{ + PROTOCOLDATA protocol_data = {PI_FORCE_ASYNC}; + ProtocolHandler *protocol_handler = arg; + + if(protocol_handler->delay_event) + WaitForSingleObject(protocol_handler->delay_event, INFINITE); + else + Sleep(protocol_handler->delay); + protocol_handler->delay = -1; + IInternetProtocolSink_Switch(protocol_handler->sink, &protocol_data); + return 0; +} + static DWORD WINAPI async_switch_proc(void *arg) { PROTOCOLDATA protocol_data = { PI_FORCE_ASYNC }; @@ -5177,6 +5252,8 @@ static ULONG WINAPI Protocol_Release(IInternetProtocolEx *iface) LONG ref = InterlockedDecrement(&This->ref);
if(!ref) { + if(This->delay_event) + CloseHandle(This->delay_event); if(This->sink) IInternetProtocolSink_Release(This->sink); if(This->uri) @@ -5203,25 +5280,34 @@ static HRESULT WINAPI Protocol_Continue(IInternetProtocolEx *iface, PROTOCOLDATA HRESULT hres; BSTR bstr;
- hres = IInternetProtocolSink_QueryInterface(This->sink, &IID_IServiceProvider, (void**)&service_provider); - ok(hres == S_OK, "Could not get IServiceProvider iface: %08lx\n", hres); + if(This->delay >= 0) { + hres = IInternetProtocolSink_QueryInterface(This->sink, &IID_IServiceProvider, (void**)&service_provider); + ok(hres == S_OK, "Could not get IServiceProvider iface: %08lx\n", hres);
- hres = IServiceProvider_QueryService(service_provider, &IID_IHttpNegotiate, &IID_IHttpNegotiate, (void**)&http_negotiate); - IServiceProvider_Release(service_provider); - ok(hres == S_OK, "Could not get IHttpNegotiate interface: %08lx\n", hres); + hres = IServiceProvider_QueryService(service_provider, &IID_IHttpNegotiate, &IID_IHttpNegotiate, (void**)&http_negotiate); + IServiceProvider_Release(service_provider); + ok(hres == S_OK, "Could not get IHttpNegotiate interface: %08lx\n", hres);
- hres = IUri_GetDisplayUri(This->uri, &bstr); - ok(hres == S_OK, "Failed to get display uri: %08lx\n", hres); - hres = IHttpNegotiate_BeginningTransaction(http_negotiate, bstr, L"", 0, &addl_headers); - ok(hres == S_OK, "BeginningTransaction failed: %08lx\n", hres); - CoTaskMemFree(addl_headers); - SysFreeString(bstr); + hres = IUri_GetDisplayUri(This->uri, &bstr); + ok(hres == S_OK, "Failed to get display uri: %08lx\n", hres); + hres = IHttpNegotiate_BeginningTransaction(http_negotiate, bstr, L"", 0, &addl_headers); + ok(hres == S_OK, "BeginningTransaction failed: %08lx\n", hres); + CoTaskMemFree(addl_headers); + SysFreeString(bstr);
- bstr = SysAllocString(L"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"); - hres = IHttpNegotiate_OnResponse(http_negotiate, 200, bstr, NULL, NULL); - ok(hres == S_OK, "OnResponse failed: %08lx\n", hres); - IHttpNegotiate_Release(http_negotiate); - SysFreeString(bstr); + bstr = SysAllocString(L"HTTP/1.1 200 OK\r\nContent-Type: text/html\r\n\r\n"); + hres = IHttpNegotiate_OnResponse(http_negotiate, 200, bstr, NULL, NULL); + ok(hres == S_OK, "OnResponse failed: %08lx\n", hres); + IHttpNegotiate_Release(http_negotiate); + SysFreeString(bstr); + + if(This->delay || This->delay_event) { + IInternetProtocolEx_AddRef(&This->IInternetProtocolEx_iface); + QueueUserWorkItem(delay_proc, This, 0); + return S_OK; + } + } + This->delay = 0;
hres = IInternetProtocolSink_ReportData(This->sink, BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION, This->size, This->size); @@ -5298,6 +5384,8 @@ static HRESULT WINAPI ProtocolEx_StartEx(IInternetProtocolEx *iface, IUri *uri, IInternetBindInfo *pOIBindInfo, DWORD grfPI, HANDLE *dwReserved) { ProtocolHandler *This = impl_from_IInternetProtocolEx(iface); + HRESULT hres; + BSTR query;
This->data = protocol_doc_str; This->size = strlen(This->data); @@ -5306,6 +5394,23 @@ static HRESULT WINAPI ProtocolEx_StartEx(IInternetProtocolEx *iface, IUri *uri, IUri_AddRef(This->uri = uri); This->ptr = This->data;
+ hres = IUri_GetQuery(uri, &query); + if(hres == S_OK) { + if(!wcscmp(query, L"?delay_with_signal")) { + if(delay_with_signal_handler) { + ProtocolHandler *delayed = delay_with_signal_handler; + delay_with_signal_handler = NULL; + SetEvent(delayed->delay_event); + This->delay = 30; + }else { + delay_with_signal_handler = This; + This->delay_event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(This->delay_event != NULL, "CreateEvent failed: %08lx\n", GetLastError()); + } + } + SysFreeString(query); + } + IInternetProtocolEx_AddRef(&This->IInternetProtocolEx_iface); QueueUserWorkItem(async_switch_proc, This, 0); return E_PENDING; @@ -6008,6 +6113,95 @@ done: IHTMLDocument2_Release(doc[1]); }
+static void test_sync_xhr_events(const char *doc_str) +{ + IHTMLXMLHttpRequest *xhr[2]; + IHTMLDocument2 *doc[2]; + IHTMLDocument6 *doc6; + VARIANT var, vempty; + HRESULT hres; + unsigned i; + + for(i = 0; i < ARRAY_SIZE(doc); i++) + doc[i] = create_document_with_origin(doc_str); + + document_mode = 0; + V_VT(&vempty) = VT_EMPTY; + + hres = IHTMLDocument2_QueryInterface(doc[0], &IID_IHTMLDocument6, (void**)&doc6); + if(SUCCEEDED(hres)) { + hres = IHTMLDocument6_get_documentMode(doc6, &var); + ok(hres == S_OK, "get_documentMode failed: %08lx\n", hres); + ok(V_VT(&var) == VT_R4, "V_VT(documentMode) = %u\n", V_VT(&var)); + document_mode = V_R4(&var); + IHTMLDocument6_Release(doc6); + } + + for(i = 0; i < ARRAY_SIZE(doc); i++) { + IHTMLXMLHttpRequestFactory *ctor; + IHTMLWindow5 *window5; + IHTMLWindow2 *window; + BSTR bstr, method; + + hres = IHTMLDocument2_get_parentWindow(doc[i], &window); + ok(hres == S_OK, "[%u] get_parentWindow failed: %08lx\n", i, hres); + ok(window != NULL, "[%u] window == NULL\n", i); + + hres = IHTMLWindow2_QueryInterface(window, &IID_IHTMLWindow5, (void**)&window5); + ok(hres == S_OK, "[%u] Could not get IHTMLWindow5: %08lx\n", i, hres); + IHTMLWindow2_Release(window); + + hres = IHTMLWindow5_get_XMLHttpRequest(window5, &var); + ok(hres == S_OK, "[%u] get_XMLHttpRequest failed: %08lx\n", i, hres); + ok(V_VT(&var) == VT_DISPATCH, "[%u] V_VT(XMLHttpRequest) == %d\n", i, V_VT(&var)); + ok(V_DISPATCH(&var) != NULL, "[%u] V_DISPATCH(XMLHttpRequest) == NULL\n", i); + IHTMLWindow5_Release(window5); + + hres = IDispatch_QueryInterface(V_DISPATCH(&var), &IID_IHTMLXMLHttpRequestFactory, (void**)&ctor); + ok(hres == S_OK, "[%u] Could not get IHTMLXMLHttpRequestFactory: %08lx\n", i, hres); + IDispatch_Release(V_DISPATCH(&var)); + hres = IHTMLXMLHttpRequestFactory_create(ctor, &xhr[i]); + ok(hres == S_OK, "[%u] create failed: %08lx\n", i, hres); + IHTMLXMLHttpRequestFactory_Release(ctor); + + V_VT(&var) = VT_BOOL; + V_BOOL(&var) = i ? VARIANT_FALSE : VARIANT_TRUE; + method = SysAllocString(L"GET"); + bstr = SysAllocString(L"blank.html?delay_with_signal"); + hres = IHTMLXMLHttpRequest_open(xhr[i], method, bstr, var, vempty, vempty); + ok(hres == S_OK, "[%u] open failed: %08lx\n", i, hres); + SysFreeString(method); + SysFreeString(bstr); + + V_VT(&var) = VT_DISPATCH; + V_DISPATCH(&var) = (IDispatch*)(i ? &sync_xhr_obj : &async_xhr_obj); + hres = IHTMLXMLHttpRequest_put_onreadystatechange(xhr[i], var); + ok(hres == S_OK, "[%u] put_onreadystatechange failed: %08lx\n", i, hres); + } + + /* async xhr */ + hres = IHTMLXMLHttpRequest_send(xhr[0], vempty); + ok(hres == S_OK, "async xhr send failed: %08lx\n", hres); + + /* sync xhr */ + SET_EXPECT(sync_xhr_done); + hres = IHTMLXMLHttpRequest_send(xhr[1], vempty); + ok(hres == S_OK, "sync xhr send failed: %08lx\n", hres); + CHECK_CALLED(sync_xhr_done); + + SET_EXPECT(async_xhr_done); + pump_msgs(&called_async_xhr_done); + CHECK_CALLED(async_xhr_done); + + for(i = 0; i < ARRAY_SIZE(xhr); i++) + IHTMLXMLHttpRequest_Release(xhr[i]); + + set_client_site(doc[0], FALSE); + set_client_site(doc[1], FALSE); + IHTMLDocument2_Release(doc[0]); + IHTMLDocument2_Release(doc[1]); +} + static BOOL check_ie(void) { IHTMLDocument2 *doc; @@ -6069,8 +6263,11 @@ START_TEST(events)
test_empty_document(); test_storage_events(empty_doc_str); - if(is_ie9plus) + test_sync_xhr_events(empty_doc_str); + if(is_ie9plus) { test_storage_events(empty_doc_ie9_str); + test_sync_xhr_events(empty_doc_str); + }
DestroyWindow(container_hwnd); }else { diff --git a/dlls/mshtml/tests/rsrc.rc b/dlls/mshtml/tests/rsrc.rc index 10b92ab78cb..61b53520781 100644 --- a/dlls/mshtml/tests/rsrc.rc +++ b/dlls/mshtml/tests/rsrc.rc @@ -88,6 +88,9 @@ doc_with_prop_ie9.html HTML "doc_with_prop_ie9.html" /* @makedep: iframe.html */ iframe.html HTML "iframe.html"
+/* @makedep: xhr_iframe.html */ +xhr_iframe.html HTML "xhr_iframe.html" + /* For res: protocol test: */
/* @makedep: jstest.html */ diff --git a/dlls/mshtml/tests/script.c b/dlls/mshtml/tests/script.c index 2bcd588f51f..cd27a05e397 100644 --- a/dlls/mshtml/tests/script.c +++ b/dlls/mshtml/tests/script.c @@ -3675,6 +3675,7 @@ typedef struct { IInternetProtocolSink *sink; BINDINFO bind_info;
+ HANDLE delay_event; BSTR content_type; IStream *stream; char *data; @@ -3685,12 +3686,17 @@ typedef struct { IUri *uri; } ProtocolHandler;
+static ProtocolHandler *delay_with_signal_handler; + static DWORD WINAPI delay_proc(void *arg) { PROTOCOLDATA protocol_data = {PI_FORCE_ASYNC}; ProtocolHandler *protocol_handler = arg;
- Sleep(protocol_handler->delay); + if(protocol_handler->delay_event) + WaitForSingleObject(protocol_handler->delay_event, INFINITE); + else + Sleep(protocol_handler->delay); protocol_handler->delay = -1; IInternetProtocolSink_Switch(protocol_handler->sink, &protocol_data); return 0; @@ -3735,7 +3741,7 @@ static void report_data(ProtocolHandler *This)
IHttpNegotiate_Release(http_negotiate);
- if(This->delay) { + if(This->delay || This->delay_event) { IInternetProtocolEx_AddRef(&This->IInternetProtocolEx_iface); QueueUserWorkItem(delay_proc, This, 0); return; @@ -3880,6 +3886,8 @@ static ULONG WINAPI Protocol_Release(IInternetProtocolEx *iface) LONG ref = InterlockedDecrement(&This->ref);
if(!ref) { + if(This->delay_event) + CloseHandle(This->delay_event); if(This->sink) IInternetProtocolSink_Release(This->sink); if(This->stream) @@ -4061,8 +4069,20 @@ static HRESULT WINAPI ProtocolEx_StartEx(IInternetProtocolEx *iface, IUri *uri,
hres = IUri_GetQuery(uri, &query); if(SUCCEEDED(hres)) { - if(!lstrcmpW(query, L"?delay")) + if(!wcscmp(query, L"?delay")) This->delay = 1000; + else if(!wcscmp(query, L"?delay_with_signal")) { + if(delay_with_signal_handler) { + ProtocolHandler *delayed = delay_with_signal_handler; + delay_with_signal_handler = NULL; + SetEvent(delayed->delay_event); + This->delay = 30; + }else { + delay_with_signal_handler = This; + This->delay_event = CreateEventW(NULL, FALSE, FALSE, NULL); + ok(This->delay_event != NULL, "CreateEvent failed: %08lx\n", GetLastError()); + } + } else if(!wcsncmp(query, L"?content-type=", sizeof("?content-type=")-1)) This->content_type = SysAllocString(query + sizeof("?content-type=")-1); SysFreeString(query); diff --git a/dlls/mshtml/tests/xhr.js b/dlls/mshtml/tests/xhr.js index 2b07ee2f188..ebe1a44168a 100644 --- a/dlls/mshtml/tests/xhr.js +++ b/dlls/mshtml/tests/xhr.js @@ -76,6 +76,139 @@ function test_xhr() { xhr.send(xml); }
+function test_sync_xhr() { + var async_xhr, async_xhr2, sync_xhr, sync_xhr_in_async, sync_xhr_nested, a = [ 0 ]; + function onmsg(e) { a.push("msg" + e.data); } + document.ondblclick = function() { a.push("dblclick"); }; + window.addEventListener("message", onmsg); + window.postMessage("1", "*"); + window.setTimeout(function() { a.push("timeout"); }, 0); + window.postMessage("2", "*"); + a.push(1); + + async_xhr = new XMLHttpRequest(); + async_xhr.open("POST", "echo.php", true); + async_xhr.onreadystatechange = function() { + if(async_xhr.readyState < 3) + return; + a.push("async_xhr(" + async_xhr.readyState + ")"); + ok(async_xhr2.readyState === 1, "async_xhr2.readyState = " + async_xhr2.readyState); + if(async_xhr.readyState == 4) { + window.postMessage("_async", "*"); + + sync_xhr_in_async = new XMLHttpRequest(); + sync_xhr_in_async.open("POST", "echo.php", false); + sync_xhr_in_async.onreadystatechange = function() { if(sync_xhr_in_async.readyState == 4) a.push("sync_xhr_in_async"); }; + sync_xhr_in_async.setRequestHeader("X-Test", "True"); + sync_xhr_in_async.send("sync_in_async"); + } + }; + async_xhr.addEventListener("click", function() { a.push("click"); }); + async_xhr.setRequestHeader("X-Test", "True"); + async_xhr.send("1234"); + a.push(2); + + async_xhr2 = new XMLHttpRequest(); + async_xhr2.open("POST", "echo.php?delay_with_signal", true); + async_xhr2.onreadystatechange = function() { + if(async_xhr2.readyState < 3) + return; + a.push("async_xhr2(" + async_xhr2.readyState + ")"); + ok(async_xhr.readyState === 4, "async_xhr.readyState = " + async_xhr.readyState); + }; + async_xhr2.setRequestHeader("X-Test", "True"); + async_xhr2.send("foobar"); + a.push(3); + + sync_xhr = new XMLHttpRequest(); + sync_xhr.open("POST", "echo.php?delay_with_signal", false); + sync_xhr.onreadystatechange = function() { + a.push("sync_xhr(" + sync_xhr.readyState + ")"); + ok(async_xhr.readyState === 1, "async_xhr.readyState in sync_xhr handler = " + async_xhr.readyState); + ok(async_xhr2.readyState === 1, "async_xhr2.readyState in sync_xhr handler = " + async_xhr2.readyState); + if(sync_xhr.readyState < 4) + return; + window.setTimeout(function() { a.push("timeout_sync"); }, 0); + window.postMessage("_sync", "*"); + + sync_xhr_nested = new XMLHttpRequest(); + sync_xhr_nested.open("POST", "echo.php", false); + sync_xhr_nested.onreadystatechange = function() { + a.push("nested(" + sync_xhr_nested.readyState + ")"); + if(sync_xhr_nested.readyState == 4) { + window.setTimeout(function() { a.push("timeout_nested"); }, 0); + window.postMessage("_nested", "*"); + + var e = document.createEvent("Event"); + e.initEvent("click", true, false); + async_xhr.dispatchEvent(e); + document.fireEvent("ondblclick", document.createEventObject()); + } + }; + sync_xhr_nested.setRequestHeader("X-Test", "True"); + sync_xhr_nested.send("nested"); + }; + sync_xhr.setRequestHeader("X-Test", "True"); + sync_xhr.send("abcd"); + a.push(4); + + window.setTimeout(function() { + var r = a.join(","); + todo_wine_if(document.documentMode < 10 || document.documentMode >= 11). + ok(r === "0,1,2,3," + (document.documentMode < 10 ? "sync_xhr(1),sync_xhr(2),sync_xhr(3)," : "") + + "sync_xhr(4)," + (document.documentMode < 10 ? "nested(1),nested(2),nested(3)," : "") + + "nested(4),click," + (document.documentMode < 11 ? "dblclick," : "") + + "4,async_xhr(3),async_xhr(4),sync_xhr_in_async,async_xhr2(3),async_xhr2(4)," + + "msg1,msg2,msg_sync,msg_nested,msg_async,timeout,timeout_sync,timeout_nested", + "unexpected order: " + r); + window.removeEventListener("message", onmsg); + document.ondblclick = null; + a = [ 0 ]; + + // Events dispatched to other iframes are not blocked by a send() in another context, + // except for async XHR events (which are a special case again), messages, and timeouts. + var iframe = document.createElement("iframe"), iframe2 = document.createElement("iframe"); + iframe.onload = function() { + iframe2.onload = function() { + function onmsg(e) { + a.push(e.data); + if(e.data === "echo") + iframe2.contentWindow.postMessage("sync_xhr", "*"); + }; + + window.setTimeout(function() { + var r = a.join(","); + ok(r === "0,1,async_xhr,echo,sync_xhr(pre-send),sync_xhr(DONE),sync_xhr,async_xhr(DONE)", + "[iframes 1] unexpected order: " + r); + a = [ 0 ]; + + window.setTimeout(function() { + var r = a.join(","); + ok(r === "0,1,echo,blank(DONE),sync_xhr(pre-send),sync_xhr(DONE),sync_xhr", + "[iframes 2] unexpected order: " + r); + window.removeEventListener("message", onmsg); + next_test(); + }, 0); + + iframe.onload = function() { a.push("blank(DONE)"); }; + iframe.src = "blank.html?delay_with_signal"; + iframe2.contentWindow.postMessage("echo", "*"); + a.push(1); + }, 0); + + window.addEventListener("message", onmsg); + iframe.contentWindow.postMessage("async_xhr", "*"); + iframe2.contentWindow.postMessage("echo", "*"); + a.push(1); + }; + iframe2.src = "xhr_iframe.html"; + document.body.appendChild(iframe2); + }; + iframe.src = "xhr_iframe.html"; + document.body.appendChild(iframe); + }, 0); +} + function test_content_types() { var xhr = new XMLHttpRequest(), types, i = 0, override = false; var v = document.documentMode; @@ -291,6 +424,7 @@ function test_response() {
var tests = [ test_xhr, + test_sync_xhr, test_content_types, test_abort, test_timeout, diff --git a/dlls/mshtml/tests/xhr_iframe.html b/dlls/mshtml/tests/xhr_iframe.html new file mode 100644 index 00000000000..1a683f700e1 --- /dev/null +++ b/dlls/mshtml/tests/xhr_iframe.html @@ -0,0 +1,23 @@ +<html><head><script type="text/javascript">window.onmessage = function(e) +{ + if(e.data === "echo") + parent.postMessage("echo", "*"); + else if(e.data === "async_xhr") { + var async_xhr = new XMLHttpRequest(); + async_xhr.open("POST", "echo.php?delay_with_signal", true); + async_xhr.onreadystatechange = function() { if(async_xhr.readyState == 4) parent.postMessage("async_xhr(DONE)", "*"); }; + async_xhr.setRequestHeader("X-Test", "True"); + async_xhr.send("foo"); + parent.postMessage("async_xhr", "*"); + } + else if(e.data === "sync_xhr") { + var sync_xhr = new XMLHttpRequest(); + sync_xhr.open("POST", "echo.php?delay_with_signal", false); + sync_xhr.onreadystatechange = function() { if(sync_xhr.readyState == 4) parent.postMessage("sync_xhr(DONE)", "*"); }; + sync_xhr.setRequestHeader("X-Test", "True"); + parent.postMessage("sync_xhr(pre-send)", "*"); + sync_xhr.send("bar"); + parent.postMessage("sync_xhr", "*"); + } +} +</script><body></body></html> diff --git a/dlls/mshtml/tests/xmlhttprequest.c b/dlls/mshtml/tests/xmlhttprequest.c index a3ec54ffef6..2415ac8eb0f 100644 --- a/dlls/mshtml/tests/xmlhttprequest.c +++ b/dlls/mshtml/tests/xmlhttprequest.c @@ -673,18 +673,12 @@ static void test_sync_xhr(IHTMLDocument2 *doc, const WCHAR *xml_url, const WCHAR
SET_EXPECT(xmlhttprequest_onreadystatechange_opened); hres = IHTMLXMLHttpRequest_open(xhr, method, url, vbool, vempty, vempty); - todo_wine ok(hres == S_OK, "open failed: %08lx\n", hres); /* Gecko 30+ only supports async */ - todo_wine CHECK_CALLED(xmlhttprequest_onreadystatechange_opened); + ok(hres == S_OK, "open failed: %08lx\n", hres); + CHECK_CALLED(xmlhttprequest_onreadystatechange_opened);
SysFreeString(method); SysFreeString(url);
- if(FAILED(hres)) { - IHTMLXMLHttpRequest_Release(xhr); - xhr = NULL; - return; - } - text = (BSTR)0xdeadbeef; hres = IHTMLXMLHttpRequest_getAllResponseHeaders(xhr, &text); ok(hres == E_FAIL, "got %08lx\n", hres); @@ -718,11 +712,11 @@ static void test_sync_xhr(IHTMLDocument2 *doc, const WCHAR *xml_url, const WCHAR loading_cnt = 0; hres = IHTMLXMLHttpRequest_send(xhr, vempty); ok(hres == S_OK, "send failed: %08lx\n", hres); - CHECK_CALLED(xmlhttprequest_onreadystatechange_opened); - CHECK_CALLED(xmlhttprequest_onreadystatechange_headers_received); - CHECK_CALLED(xmlhttprequest_onreadystatechange_loading); + todo_wine CHECK_CALLED(xmlhttprequest_onreadystatechange_opened); + todo_wine CHECK_CALLED(xmlhttprequest_onreadystatechange_headers_received); + todo_wine CHECK_CALLED(xmlhttprequest_onreadystatechange_loading); CHECK_CALLED(xmlhttprequest_onreadystatechange_done); - ok(loading_cnt == 1, "loading_cnt = %d\n", loading_cnt); + todo_wine ok(loading_cnt == 1, "loading_cnt = %d\n", loading_cnt);
text = NULL; hres = IHTMLXMLHttpRequest_getResponseHeader(xhr, content_type, &text); diff --git a/dlls/mshtml/xmlhttprequest.c b/dlls/mshtml/xmlhttprequest.c index 8ebf6ad2529..e334d8acb20 100644 --- a/dlls/mshtml/xmlhttprequest.c +++ b/dlls/mshtml/xmlhttprequest.c @@ -141,10 +141,38 @@ struct HTMLXMLHttpRequest { LONG readyState; size_t responseText_length; response_type_t response_type; + BOOL synchronous; + HTMLInnerWindow *window; nsIXMLHttpRequest *nsxhr; XMLHttpReqEventListener *event_listener; };
+struct queued_xhr_event { + struct list entry; + DOMEvent *event; + HTMLXMLHttpRequest *xhr; + size_t responseText_length; + LONG readyState; +}; + +void dispatch_queued_xhr_event(struct list *entry) +{ + struct queued_xhr_event *xhr_event = LIST_ENTRY(entry, struct queued_xhr_event, entry); + + list_remove(entry); + + /* Only dispatch it if it wasn't aborted */ + if(xhr_event->xhr->readyState != 0 || xhr_event->readyState == 1) { + xhr_event->xhr->readyState = xhr_event->readyState; + xhr_event->xhr->responseText_length = xhr_event->responseText_length; + dispatch_event(&xhr_event->xhr->event_target, xhr_event->event); + } + + IHTMLXMLHttpRequest_Release(&xhr_event->xhr->IHTMLXMLHttpRequest_iface); + IDOMEvent_Release(&xhr_event->event->IDOMEvent_iface); + free(xhr_event); +} + static void detach_xhr_event_listener(XMLHttpReqEventListener *event_listener) { nsIDOMEventTarget *event_target; @@ -226,27 +254,34 @@ static nsrefcnt NSAPI XMLHttpReqEventListener_Release(nsIDOMEventListener *iface static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *iface, nsIDOMEvent *nsevent) { XMLHttpReqEventListener *This = impl_from_nsIDOMEventListener(iface); + thread_data_t *thread_data; + size_t responseText_length; const PRUnichar *text; - UINT16 readyState; + nsresult ret = NS_OK; nsAString nsstr; DOMEvent *event; + LONG readyState; HRESULT hres; + UINT16 val;
TRACE("(%p)\n", This);
if(!This->xhr) return NS_OK;
- if(NS_SUCCEEDED(nsIXMLHttpRequest_GetReadyState(This->xhr->nsxhr, &readyState))) { - This->xhr->readyState = readyState; + readyState = This->xhr->readyState; + responseText_length = This->xhr->responseText_length; + + if(NS_SUCCEEDED(nsIXMLHttpRequest_GetReadyState(This->xhr->nsxhr, &val))) { + readyState = val;
- if(This->xhr->readyState >= 3) { + if(readyState >= 3) { nsAString_Init(&nsstr, NULL); if(NS_SUCCEEDED(nsIXMLHttpRequest_GetResponseText(This->xhr->nsxhr, &nsstr))) { /* Avoid recalculating from the beginning, since it can't be shorter */ nsAString_GetData(&nsstr, &text); if(text && text[0]) - This->xhr->responseText_length += wcslen(text + This->xhr->responseText_length); + responseText_length += wcslen(text + responseText_length); nsAString_Finish(&nsstr); } } @@ -254,10 +289,48 @@ static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *i
hres = create_event_from_nsevent(nsevent, dispex_compat_mode(&This->xhr->event_target.dispex), &event); if(SUCCEEDED(hres) ){ + if((thread_data = get_thread_data(FALSE)) && thread_data->blocking_depth) { + if(!This->xhr->synchronous) { + struct queued_xhr_event *queued_event = malloc(sizeof(*queued_event)); + + if(!queued_event) { + IDOMEvent_Release(&event->IDOMEvent_iface); + ret = NS_ERROR_OUT_OF_MEMORY; + goto update_state; + } + + queued_event->event = event; + queued_event->xhr = This->xhr; + IHTMLXMLHttpRequest_AddRef(&This->xhr->IHTMLXMLHttpRequest_iface); + + /* Save snapshot of XHR state (see sync_xhr_send() comments) */ + queued_event->readyState = readyState; + queued_event->responseText_length = responseText_length; + + list_add_tail(&thread_data->queued_xhr_events_list, &queued_event->entry); + return ret; + } + + /* Workaround weird Gecko behavior with nested sync XHRs, where it sends readyState changes + for OPENED (or possibly other states than DONE), unlike IE10+ and non-nested sync XHRs... */ + if(readyState < 4 && event->event_id == EVENTID_READYSTATECHANGE) { + IDOMEvent_Release(&event->IDOMEvent_iface); + goto update_state; + } + } + + This->xhr->readyState = readyState; + This->xhr->responseText_length = responseText_length; + dispatch_event(&This->xhr->event_target, event); IDOMEvent_Release(&event->IDOMEvent_iface); + return ret; } - return NS_OK; + +update_state: + This->xhr->readyState = readyState; + This->xhr->responseText_length = responseText_length; + return ret; }
static const nsIDOMEventListenerVtbl XMLHttpReqEventListenerVtbl = { @@ -320,6 +393,7 @@ static ULONG WINAPI HTMLXMLHttpRequest_Release(IHTMLXMLHttpRequest *iface) if(!ref) { if(This->event_listener) detach_xhr_event_listener(This->event_listener); + IHTMLWindow2_Release(&This->window->base.IHTMLWindow2_iface); release_event_target(&This->event_target); release_dispex(&This->event_target.dispex); nsIXMLHttpRequest_Release(This->nsxhr); @@ -599,15 +673,6 @@ static HRESULT WINAPI HTMLXMLHttpRequest_open(IHTMLXMLHttpRequest *iface, BSTR b } }
- /* Note: Starting with Gecko 30.0 (Firefox 30.0 / Thunderbird 30.0 / SeaMonkey 2.27), - * synchronous requests on the main thread have been deprecated due to the negative - * effects to the user experience. - */ - if(!V_BOOL(&varAsync)) { - FIXME("Synchronous request is not supported yet\n"); - return E_FAIL; - } - hres = variant_to_nsastr(varUser, &user); if(FAILED(hres)) return hres; @@ -631,6 +696,9 @@ static HRESULT WINAPI HTMLXMLHttpRequest_open(IHTMLXMLHttpRequest *iface, BSTR b return hres; }
+ /* Set this here, Gecko dispatches nested sync XHR readyState changes for OPENED (see HandleEvent) */ + This->synchronous = !V_BOOL(&varAsync); + if(V_VT(&varPassword) != VT_EMPTY && V_VT(&varPassword) != VT_ERROR) opt_argc += 2; else if(V_VT(&varUser) != VT_EMPTY && V_VT(&varUser) != VT_ERROR) @@ -644,6 +712,7 @@ static HRESULT WINAPI HTMLXMLHttpRequest_open(IHTMLXMLHttpRequest *iface, BSTR b
if(NS_FAILED(nsres)) { ERR("nsIXMLHttpRequest_Open failed: %08lx\n", nsres); + This->synchronous = FALSE; return E_FAIL; }
@@ -680,13 +749,18 @@ static HRESULT WINAPI HTMLXMLHttpRequest_send(IHTMLXMLHttpRequest *iface, VARIAN return E_NOTIMPL; }
- if(NS_SUCCEEDED(nsres)) - nsres = nsIXMLHttpRequest_Send(This->nsxhr, (nsIVariant*)nsbody); + if(NS_SUCCEEDED(nsres)) { + if(This->synchronous) + nsres = sync_xhr_send(This->nsxhr, This->window, (nsIVariant*)nsbody); + else + nsres = nsIXMLHttpRequest_Send(This->nsxhr, (nsIVariant*)nsbody); + } + if(nsbody) nsIWritableVariant_Release(nsbody); if(NS_FAILED(nsres)) { ERR("nsIXMLHttpRequest_Send failed: %08lx\n", nsres); - return E_FAIL; + return map_nsresult(nsres); }
return S_OK; @@ -1502,6 +1576,8 @@ static HRESULT WINAPI HTMLXMLHttpRequestFactory_create(IHTMLXMLHttpRequestFactor return E_OUTOFMEMORY; } ret->nsxhr = nsxhr; + ret->window = This->window; + IHTMLWindow2_AddRef(&This->window->base.IHTMLWindow2_iface);
ret->IHTMLXMLHttpRequest_iface.lpVtbl = &HTMLXMLHttpRequestVtbl; ret->IHTMLXMLHttpRequest2_iface.lpVtbl = &HTMLXMLHttpRequest2Vtbl;
From: Gabriel Ivăncescu gabrielopcode@gmail.com
Signed-off-by: Gabriel Ivăncescu gabrielopcode@gmail.com --- dlls/mshtml/tests/xhr.js | 2 +- dlls/mshtml/tests/xmlhttprequest.c | 8 ++++---- dlls/mshtml/xmlhttprequest.c | 28 +++++++++++++++++++++++++++- 3 files changed, 32 insertions(+), 6 deletions(-)
diff --git a/dlls/mshtml/tests/xhr.js b/dlls/mshtml/tests/xhr.js index ebe1a44168a..a9750fcfdd9 100644 --- a/dlls/mshtml/tests/xhr.js +++ b/dlls/mshtml/tests/xhr.js @@ -154,7 +154,7 @@ function test_sync_xhr() {
window.setTimeout(function() { var r = a.join(","); - todo_wine_if(document.documentMode < 10 || document.documentMode >= 11). + todo_wine_if(document.documentMode >= 11). ok(r === "0,1,2,3," + (document.documentMode < 10 ? "sync_xhr(1),sync_xhr(2),sync_xhr(3)," : "") + "sync_xhr(4)," + (document.documentMode < 10 ? "nested(1),nested(2),nested(3)," : "") + "nested(4),click," + (document.documentMode < 11 ? "dblclick," : "") + diff --git a/dlls/mshtml/tests/xmlhttprequest.c b/dlls/mshtml/tests/xmlhttprequest.c index 2415ac8eb0f..6226461a502 100644 --- a/dlls/mshtml/tests/xmlhttprequest.c +++ b/dlls/mshtml/tests/xmlhttprequest.c @@ -712,11 +712,11 @@ static void test_sync_xhr(IHTMLDocument2 *doc, const WCHAR *xml_url, const WCHAR loading_cnt = 0; hres = IHTMLXMLHttpRequest_send(xhr, vempty); ok(hres == S_OK, "send failed: %08lx\n", hres); - todo_wine CHECK_CALLED(xmlhttprequest_onreadystatechange_opened); - todo_wine CHECK_CALLED(xmlhttprequest_onreadystatechange_headers_received); - todo_wine CHECK_CALLED(xmlhttprequest_onreadystatechange_loading); + CHECK_CALLED(xmlhttprequest_onreadystatechange_opened); + CHECK_CALLED(xmlhttprequest_onreadystatechange_headers_received); + CHECK_CALLED(xmlhttprequest_onreadystatechange_loading); CHECK_CALLED(xmlhttprequest_onreadystatechange_done); - todo_wine ok(loading_cnt == 1, "loading_cnt = %d\n", loading_cnt); + ok(loading_cnt == 1, "loading_cnt = %d\n", loading_cnt);
text = NULL; hres = IHTMLXMLHttpRequest_getResponseHeader(xhr, content_type, &text); diff --git a/dlls/mshtml/xmlhttprequest.c b/dlls/mshtml/xmlhttprequest.c index e334d8acb20..af6ae457e6d 100644 --- a/dlls/mshtml/xmlhttprequest.c +++ b/dlls/mshtml/xmlhttprequest.c @@ -256,6 +256,7 @@ static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *i XMLHttpReqEventListener *This = impl_from_nsIDOMEventListener(iface); thread_data_t *thread_data; size_t responseText_length; + compat_mode_t compat_mode; const PRUnichar *text; nsresult ret = NS_OK; nsAString nsstr; @@ -287,7 +288,8 @@ static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *i } }
- hres = create_event_from_nsevent(nsevent, dispex_compat_mode(&This->xhr->event_target.dispex), &event); + compat_mode = dispex_compat_mode(&This->xhr->event_target.dispex); + hres = create_event_from_nsevent(nsevent, compat_mode, &event); if(SUCCEEDED(hres) ){ if((thread_data = get_thread_data(FALSE)) && thread_data->blocking_depth) { if(!This->xhr->synchronous) { @@ -317,6 +319,30 @@ static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *i IDOMEvent_Release(&event->IDOMEvent_iface); goto update_state; } + + /* IE10+ only send readystatechange event when it is DONE for sync XHRs, but older modes + send all the others here, including OPENED state change (even if it was opened earlier). */ + if(compat_mode < COMPAT_MODE_IE10 && This->xhr->readyState < 4 && ( + event->event_id == EVENTID_READYSTATECHANGE || event->event_id == EVENTID_PROGRESS || event->event_id == EVENTID_LOADSTART)) { + DOMEvent *readystatechange_event; + unsigned i; + + for(i = 1; i < 4; i++) { + hres = create_document_event(This->xhr->window->doc, EVENTID_READYSTATECHANGE, &readystatechange_event); + if(FAILED(hres)) + break; + + This->xhr->readyState = i; + dispatch_event(&This->xhr->event_target, readystatechange_event); + IDOMEvent_Release(&readystatechange_event->IDOMEvent_iface); + + /* Check if it was aborted (or somehow changed) in the handler */ + if(This->xhr->readyState != i) { + IDOMEvent_Release(&event->IDOMEvent_iface); + return ret; + } + } + } }
This->xhr->readyState = readyState;
Jacek Caban (@jacek) commented about dlls/mshtml/xmlhttprequest.c:
if(!p) return E_POINTER;
- if(This->readyState < 3) {
That's a lot of magic numbers, please use READYSTATE enum values instead.
Jacek Caban (@jacek) commented about dlls/mshtml/xmlhttprequest.c:
nsAString_Init(&nsstr, NULL); nsres = nsIXMLHttpRequest_GetResponseText(This->nsxhr, &nsstr);
- return return_nsstr(nsres, &nsstr, p);
- if(NS_FAILED(nsres))
hres = map_nsresult(nsres);
- else {
nsAString_GetData(&nsstr, &text);
if(!text || !text[0])
*p = NULL;
else if(!(*p = SysAllocStringLen(text, This->responseText_length)))
hres = E_OUTOFMEMORY;
nsAString_Finish(&nsstr);
I'm not sure we want to handle it like that. I guess that you want progress event values to match returned responseText length, but it's not clear to me why would we report all progress events when unblocking. For example, if we blocked three progress events with loaded value of 10, 20, 30, shouldn't we just report a single event with loaded value 30 when unblocking? If we supported it, then this patch would then be redundant.
That rises a question if we want to queue XHR events at all. The alternative would be, when blocked, simply record a pending state in XHR object and queue a single task. That task would then use pending state to synthesize required events when unblocked, somewhat similar to what you do in the last patch for IE9 mode. I think such approach would both improve the above example, but also get rid of other corner cases like: async XHR starts, gets blocked by sync XHR, async finishes loading, scripts sets loadend listener. When XHR sync completes, it unblocks queued events, but loadend does not fire because it wasn't bound then Gecko event happened. What do you think?
Jacek Caban (@jacek) commented about dlls/mshtml/task.c:
* other major browsers don't, including IE, so we have to filter them out during Send.
*
* They will need to be queued and dispatched later, after Send returns, otherwise it
* breaks JavaScript single-threaded expectations (js code will switch from blocking in
* Send to executing some event handler, then returning back to Send, messing its state).
*
* Of course we can't just delay dispatching the events, because the state won't match
* for each event later on to what it's supposed to be (most notably, XHR's readyState).
* So unfortunately we have to hardcode and snapshot the XHR state for async events...
*
* While queuing an event this way would not work correctly with their default behavior
* in Gecko (preventDefault() can't be called because we need to *delay* the default,
* rather than prevent it completely), Gecko does suppress events reaching the document
* during the sync XHR event loop, so all the element/node events should be safe. If we
* find an event that has defaults on Gecko's side and isn't delayed by Gecko, we need
* to figure out a way to handle it...
Since you no longer modify dispatch_event itself, this part of the comment does not seem relevant anymore.
Jacek Caban (@jacek) commented about dlls/mshtml/task.c:
* for each event later on to what it's supposed to be (most notably, XHR's readyState).
* So unfortunately we have to hardcode and snapshot the XHR state for async events...
*
* While queuing an event this way would not work correctly with their default behavior
* in Gecko (preventDefault() can't be called because we need to *delay* the default,
* rather than prevent it completely), Gecko does suppress events reaching the document
* during the sync XHR event loop, so all the element/node events should be safe. If we
* find an event that has defaults on Gecko's side and isn't delayed by Gecko, we need
* to figure out a way to handle it...
*
* For details (and bunch of problems to consider) see: https://bugzil.la/697151
*/
- window->base.outer_window->readystate_locked++;
- window->blocking_depth++;
- thread_data->blocking_depth++;
- nsres = nsIXMLHttpRequest_Send(nsxhr, nsbody);
It's a bit weird to do that call here, maybe we should instead expose block and unblock functions from task.c and leave XHR details out of it.
On Wed Feb 15 16:27:51 2023 +0000, Jacek Caban wrote:
Since you no longer modify dispatch_event itself, this part of the comment does not seem relevant anymore.
I think it's still useful to describe possible issues if we ever find one in the future with some event that's not handled the same as IE does, and forget why it was set this way.
Additionally, I think it's useful to know *why* we don't handle the node/element events. It may seem obvious now since I'm submitting the patch and we had the discussion and thoughts about it, but later on it wouldn't be so obvious to someone else, especially if they haven't looked at the relevant gecko code. Would prevent wasting time asking themselves "why are those events not handled? oversight, or intended?".
I'd rather keep it unless you feel strongly against it.
On Wed Feb 15 16:27:50 2023 +0000, Jacek Caban wrote:
I'm not sure we want to handle it like that. I guess that you want progress event values to match returned responseText length, but it's not clear to me why would we report all progress events when unblocking. For example, if we blocked three progress events with loaded value of 10, 20, 30, shouldn't we just report a single event with loaded value 30 when unblocking? If we supported it, then this patch would then be redundant. That rises a question if we want to queue XHR events at all. The alternative would be, when blocked, simply record a pending state in XHR object and queue a single task. That task would then use pending state to synthesize required events when unblocked, somewhat similar to what you do in the last patch for IE9 mode. I think such approach would both improve the above example, but also get rid of other corner cases like: async XHR starts, gets blocked by sync XHR, async finishes loading, scripts sets loadend listener. When XHR sync completes, it unblocks queued events, but loadend does not fire because it wasn't bound then Gecko event happened. What do you think?
Interesting, that sounds reasonable. I'm not *entirely* certain how Progress is supposed to work here. In your example, it could even set the loadend handler in one of the readystatechange handlers, and yeah that needs fixing for sure.
What I mean with progress events is that, for example, not sure if they're sent after specific time, or after % completion or some other criteria. It's not guaranteed, right? Wouldn't want some script relying on something unreliable like that (e.g. if downloading 1 MB, expect 3 progress events or something). So if "batch" them up and merge them to then send only one at the end, it would "break" this assumption but:
* I doubt it's even how it works or reliable enough. * I doubt scripts would use it, usually they use progress to update some visual indication so it doesn't seem stuck. * I doubt Gecko even precisely matches Windows, since nothing is said in the spec when they're sent. Quick test reveals they are dispatched at different times anyway.
BTW from quick testing it also seems we have to dispatch progress event, even if it goes straight to DONE. progress→readystatechange (DONE)→load.
Sadly this is more complicated than I anticipated. We still need to queue some events, but note that the prior approach was still wrong since it was queuing everything, and this is more than just dispatching multiple progress events rather than just 1 when unblocked: error or timeouts are also "progress events" just like abort is, but should override all the prior ones. This worked for abort only since I had special handling for it (because it's synchronous), but not for the others.
I think simplest way is to just register handlers for all the events we care about, then we can treat them properly and synthesize or send them later, when unblocked.
And also found some other small issues of course. For instance, abort() doesn't always end up aborting in Gecko, so proper way is to use actual readyState from it instead of hardcoding 0 at the end of abort().
It's doable, but I need to figure out a few things how to keep it from getting too messy while still being correct.