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;