Commit 6a31e4ac authored by Gabriel Ivăncescu's avatar Gabriel Ivăncescu Committed by Alexandre Julliard

mshtml: Implement synchronous XMLHttpRequest.

parent 51a68184
......@@ -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);
}
......
......@@ -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,11 +1306,14 @@ typedef struct {
struct list task_list;
struct list event_task_list;
struct list timer_list;
struct list *pending_xhr_events_tail;
struct wine_rb_tree session_storage_map;
void *blocking_xhr;
} thread_data_t;
thread_data_t *get_thread_data(BOOL) DECLSPEC_HIDDEN;
HWND get_thread_hwnd(void) DECLSPEC_HIDDEN;
void unblock_tasks_and_timers(thread_data_t*) 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;
......
......@@ -96,6 +96,13 @@ HRESULT push_event_task(event_task_t *task, HTMLInnerWindow *window, event_task_
return S_OK;
}
static void unlink_event_task(event_task_t *task, thread_data_t *thread_data)
{
if(thread_data->pending_xhr_events_tail == &task->entry)
thread_data->pending_xhr_events_tail = task->entry.prev;
list_remove(&task->entry);
}
static void release_task_timer(HWND thread_hwnd, task_timer_t *timer)
{
list_remove(&timer->entry);
......@@ -140,7 +147,7 @@ void remove_target_tasks(LONG target)
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);
unlink_event_task(task, thread_data);
release_event_task(task);
}
}
......@@ -303,7 +310,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_xhr) {
KillTimer(thread_data->thread_hwnd, TIMER_ID);
return 0;
}
......@@ -338,7 +345,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_xhr);
KillTimer(thread_data->thread_hwnd, TIMER_ID);
return 0;
......@@ -366,16 +373,19 @@ static LRESULT WINAPI hidden_proc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lPa
continue;
}
head = list_head(&thread_data->event_task_list);
if(head) {
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);
list_remove(&task->entry);
task->proc(task);
release_event_task(task);
continue;
}
break;
if((!task->thread_blocked || !thread_data->blocking_xhr) && !task->window->blocking_depth) {
unlink_event_task(task, thread_data);
task->proc(task);
release_event_task(task);
break;
}
}
if(!head)
break;
}
return 0;
case WM_TIMER:
......@@ -451,6 +461,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);
thread_data->pending_xhr_events_tail = &thread_data->event_task_list;
wine_rb_init(&thread_data->session_storage_map, session_storage_map_cmp);
}
......@@ -467,3 +478,16 @@ ULONGLONG get_time_stamp(void)
GetSystemTimeAsFileTime(&time);
return (((ULONGLONG)time.dwHighDateTime << 32) + time.dwLowDateTime) / 10000 - time_epoch;
}
void unblock_tasks_and_timers(thread_data_t *thread_data)
{
if(!list_empty(&thread_data->event_task_list))
PostMessageW(thread_data->thread_hwnd, WM_PROCESSTASK, 0, 0);
if(!thread_data->blocking_xhr && !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);
}
}
......@@ -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**);
......@@ -5130,11 +5186,31 @@ typedef struct {
IInternetProtocolSink *sink;
IUri *uri;
BOOL replied;
ULONG size;
const char *data;
const char *ptr;
HANDLE delay_event;
ULONG 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 = 0;
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 +5253,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 +5281,35 @@ 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->replied) {
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);
This->replied = TRUE;
if(This->delay || This->delay_event) {
IInternetProtocolEx_AddRef(&This->IInternetProtocolEx_iface);
QueueUserWorkItem(delay_proc, This, 0);
return S_OK;
}
}
hres = IInternetProtocolSink_ReportData(This->sink, BSCF_FIRSTDATANOTIFICATION | BSCF_LASTDATANOTIFICATION,
This->size, This->size);
......@@ -5298,6 +5386,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 +5396,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 +6115,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 +6265,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_ie9_str);
}
DestroyWindow(container_hwnd);
}else {
......
......@@ -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 */
......
......@@ -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);
......
......@@ -76,6 +76,145 @@ 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 ];
var async_xhr_clicked = false, doc_dblclicked = false;
function onmsg(e) { a.push("msg" + e.data); }
document.ondblclick = function() { doc_dblclicked = true; };
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() { async_xhr_clicked = true; });
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);
ok(async_xhr_clicked === false, "async_xhr click fired before dispatch");
async_xhr.dispatchEvent(e);
ok(async_xhr_clicked === true, "async_xhr click not fired immediately");
if(document.fireEvent) {
ok(doc_dblclicked === false, "document dblclick fired before dispatch");
document.fireEvent("ondblclick", document.createEventObject());
ok(doc_dblclicked === true, "document dblclick not fired immediately");
}
}
};
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).
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),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 +430,7 @@ function test_response() {
var tests = [
test_xhr,
test_sync_xhr,
test_content_types,
test_abort,
test_timeout,
......
<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>
......@@ -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);
......
......@@ -137,10 +137,16 @@ struct HTMLXMLHttpRequest {
IWineXMLHttpRequestPrivate IWineXMLHttpRequestPrivate_iface;
IProvideClassInfo2 IProvideClassInfo2_iface;
LONG ref;
LONG task_magic;
LONG ready_state;
response_type_t response_type;
BOOLEAN synchronous;
DWORD magic;
DWORD pending_events_magic;
HTMLInnerWindow *window;
nsIXMLHttpRequest *nsxhr;
XMLHttpReqEventListener *event_listener;
DOMEvent *pending_progress_event;
};
static void detach_xhr_event_listener(XMLHttpReqEventListener *event_listener)
......@@ -166,6 +172,157 @@ static void detach_xhr_event_listener(XMLHttpReqEventListener *event_listener)
nsIDOMEventListener_Release(&event_listener->nsIDOMEventListener_iface);
}
static void synthesize_pending_events(HTMLXMLHttpRequest *xhr)
{
DWORD magic = xhr->pending_events_magic;
UINT16 ready_state = xhr->ready_state;
BOOLEAN send_load, send_loadend;
DOMEvent *event;
HRESULT hres;
if(xhr->magic != magic)
return;
/* Make sure further events are synthesized with a new task */
xhr->pending_events_magic = magic - 1;
/* Synthesize the necessary events that led us to this current state */
nsIXMLHttpRequest_GetReadyState(xhr->nsxhr, &ready_state);
if(ready_state == READYSTATE_UNINITIALIZED)
return;
/* Synchronous XHRs only send readyState changes before DONE in IE9 and below */
if(xhr->synchronous && dispex_compat_mode(&xhr->event_target.dispex) > COMPAT_MODE_IE9) {
if(ready_state < READYSTATE_INTERACTIVE) {
xhr->ready_state = ready_state;
return;
}
xhr->ready_state = max(xhr->ready_state, READYSTATE_INTERACTIVE);
}
IHTMLXMLHttpRequest_AddRef(&xhr->IHTMLXMLHttpRequest_iface);
send_loadend = send_load = (xhr->ready_state != ready_state && ready_state == READYSTATE_COMPLETE);
for(;;) {
if(xhr->pending_progress_event &&
xhr->ready_state == (xhr->pending_progress_event->event_id == EVENTID_PROGRESS ? READYSTATE_INTERACTIVE : READYSTATE_COMPLETE))
{
DOMEvent *pending_progress_event = xhr->pending_progress_event;
xhr->pending_progress_event = NULL;
if(pending_progress_event->event_id != EVENTID_PROGRESS) {
send_load = FALSE;
send_loadend = TRUE;
}
dispatch_event(&xhr->event_target, pending_progress_event);
IDOMEvent_Release(&pending_progress_event->IDOMEvent_iface);
if(xhr->magic != magic)
goto ret;
}
if(xhr->ready_state >= ready_state)
break;
xhr->ready_state++;
hres = create_document_event(xhr->window->doc, EVENTID_READYSTATECHANGE, &event);
if(SUCCEEDED(hres)) {
dispatch_event(&xhr->event_target, event);
IDOMEvent_Release(&event->IDOMEvent_iface);
if(xhr->magic != magic)
goto ret;
}
}
if(send_load) {
hres = create_document_event(xhr->window->doc, EVENTID_LOAD, &event);
if(SUCCEEDED(hres)) {
dispatch_event(&xhr->event_target, event);
IDOMEvent_Release(&event->IDOMEvent_iface);
if(xhr->magic != magic)
goto ret;
}
}
if(send_loadend) {
hres = create_document_event(xhr->window->doc, EVENTID_LOADEND, &event);
if(SUCCEEDED(hres)) {
dispatch_event(&xhr->event_target, event);
IDOMEvent_Release(&event->IDOMEvent_iface);
if(xhr->magic != magic)
goto ret;
}
}
ret:
IHTMLXMLHttpRequest_Release(&xhr->IHTMLXMLHttpRequest_iface);
}
static nsresult sync_xhr_send(HTMLXMLHttpRequest *xhr, nsIVariant *nsbody)
{
thread_data_t *thread_data = get_thread_data(TRUE);
HTMLXMLHttpRequest *prev_blocking_xhr;
HTMLInnerWindow *window = xhr->window;
nsresult nsres;
if(!thread_data)
return NS_ERROR_OUT_OF_MEMORY;
prev_blocking_xhr = thread_data->blocking_xhr;
/* 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).
* We'll keep snapshots and synthesize them when unblocked for async XHR events.
*
* Note that 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 those we do not handle manually. 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_xhr = xhr;
nsres = nsIXMLHttpRequest_Send(xhr->nsxhr, nsbody);
thread_data->blocking_xhr = prev_blocking_xhr;
window->base.outer_window->readystate_locked--;
if(!--window->blocking_depth)
unblock_tasks_and_timers(thread_data);
/* Process any pending events now since they were part of the blocked send() above */
synthesize_pending_events(xhr);
return nsres;
}
struct pending_xhr_events_task {
event_task_t header;
HTMLXMLHttpRequest *xhr;
};
static void pending_xhr_events_proc(event_task_t *_task)
{
struct pending_xhr_events_task *task = (struct pending_xhr_events_task*)_task;
synthesize_pending_events(task->xhr);
}
static void pending_xhr_events_destr(event_task_t *_task)
{
}
static inline XMLHttpReqEventListener *impl_from_nsIDOMEventListener(nsIDOMEventListener *iface)
{
......@@ -221,23 +378,92 @@ static nsrefcnt NSAPI XMLHttpReqEventListener_Release(nsIDOMEventListener *iface
static nsresult NSAPI XMLHttpReqEventListener_HandleEvent(nsIDOMEventListener *iface, nsIDOMEvent *nsevent)
{
XMLHttpReqEventListener *This = impl_from_nsIDOMEventListener(iface);
UINT16 ready_state;
HTMLXMLHttpRequest *blocking_xhr = NULL;
thread_data_t *thread_data;
LONG ready_state;
DOMEvent *event;
HRESULT hres;
UINT16 val;
TRACE("(%p)\n", This);
if(!This->xhr)
return NS_OK;
if(NS_SUCCEEDED(nsIXMLHttpRequest_GetReadyState(This->xhr->nsxhr, &ready_state)))
This->xhr->ready_state = ready_state;
ready_state = This->xhr->ready_state;
if(NS_SUCCEEDED(nsIXMLHttpRequest_GetReadyState(This->xhr->nsxhr, &val)))
ready_state = val;
if((thread_data = get_thread_data(FALSE)))
blocking_xhr = thread_data->blocking_xhr;
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);
IDOMEvent_Release(&event->IDOMEvent_iface);
if(FAILED(hres)) {
if(!blocking_xhr || This->xhr == blocking_xhr)
This->xhr->ready_state = ready_state;
return NS_ERROR_OUT_OF_MEMORY;
}
if(blocking_xhr) {
BOOL has_pending_events = (This->xhr->magic == This->xhr->pending_events_magic);
if(has_pending_events || This->xhr != blocking_xhr) {
switch(event->event_id) {
case EVENTID_PROGRESS:
case EVENTID_ABORT:
case EVENTID_ERROR:
case EVENTID_TIMEOUT:
if(This->xhr->pending_progress_event)
IDOMEvent_Release(&This->xhr->pending_progress_event->IDOMEvent_iface);
This->xhr->pending_progress_event = event;
break;
default:
IDOMEvent_Release(&event->IDOMEvent_iface);
break;
}
if(!has_pending_events) {
if(!This->xhr->synchronous) {
struct pending_xhr_events_task *task;
remove_target_tasks(This->xhr->task_magic);
if(!(task = malloc(sizeof(*task))))
return NS_ERROR_OUT_OF_MEMORY;
task->header.target_magic = This->xhr->task_magic;
task->header.thread_blocked = TRUE;
task->header.proc = pending_xhr_events_proc;
task->header.destr = pending_xhr_events_destr;
task->header.window = This->xhr->window;
task->xhr = This->xhr;
IHTMLWindow2_AddRef(&This->xhr->window->base.IHTMLWindow2_iface);
list_add_after(thread_data->pending_xhr_events_tail, &task->header.entry);
thread_data->pending_xhr_events_tail = &task->header.entry;
}
This->xhr->pending_events_magic = This->xhr->magic;
return NS_OK;
}
/* Synthesize pending events that a nested sync XHR might have blocked us on */
if(This->xhr == blocking_xhr)
synthesize_pending_events(This->xhr);
return NS_OK;
}
/* 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(ready_state < READYSTATE_COMPLETE && event->event_id == EVENTID_READYSTATECHANGE) {
IDOMEvent_Release(&event->IDOMEvent_iface);
This->xhr->ready_state = ready_state;
return NS_OK;
}
}
This->xhr->ready_state = ready_state;
dispatch_event(&This->xhr->event_target, event);
IDOMEvent_Release(&event->IDOMEvent_iface);
return NS_OK;
}
......@@ -299,7 +525,11 @@ static ULONG WINAPI HTMLXMLHttpRequest_Release(IHTMLXMLHttpRequest *iface)
TRACE("(%p) ref=%ld\n", This, ref);
if(!ref) {
remove_target_tasks(This->task_magic);
detach_xhr_event_listener(This->event_listener);
if(This->pending_progress_event)
IDOMEvent_Release(&This->pending_progress_event->IDOMEvent_iface);
IHTMLWindow2_Release(&This->window->base.IHTMLWindow2_iface);
release_event_target(&This->event_target);
release_dispex(&This->event_target.dispex);
nsIXMLHttpRequest_Release(This->nsxhr);
......@@ -510,14 +740,17 @@ static HRESULT WINAPI HTMLXMLHttpRequest_get_onreadystatechange(IHTMLXMLHttpRequ
static HRESULT WINAPI HTMLXMLHttpRequest_abort(IHTMLXMLHttpRequest *iface)
{
HTMLXMLHttpRequest *This = impl_from_IHTMLXMLHttpRequest(iface);
DWORD prev_magic = This->magic;
UINT16 ready_state;
nsresult nsres;
TRACE("(%p)->()\n", This);
This->magic++;
nsres = nsIXMLHttpRequest_SlowAbort(This->nsxhr);
if(NS_FAILED(nsres)) {
ERR("nsIXMLHttpRequest_SlowAbort failed: %08lx\n", nsres);
This->magic = prev_magic;
return E_FAIL;
}
......@@ -553,9 +786,11 @@ 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);
BOOLEAN prev_synchronous;
nsAString user, password;
nsACString method, url;
unsigned opt_argc = 1;
DWORD prev_magic;
nsresult nsres;
HRESULT hres;
......@@ -570,15 +805,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;
......@@ -602,6 +828,12 @@ 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) */
prev_magic = This->magic;
prev_synchronous = This->synchronous;
This->synchronous = !V_BOOL(&varAsync);
This->magic++;
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)
......@@ -615,6 +847,8 @@ static HRESULT WINAPI HTMLXMLHttpRequest_open(IHTMLXMLHttpRequest *iface, BSTR b
if(NS_FAILED(nsres)) {
ERR("nsIXMLHttpRequest_Open failed: %08lx\n", nsres);
This->magic = prev_magic;
This->synchronous = prev_synchronous;
return E_FAIL;
}
......@@ -651,13 +885,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, (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;
......@@ -1445,6 +1684,9 @@ static HRESULT WINAPI HTMLXMLHttpRequestFactory_create(IHTMLXMLHttpRequestFactor
}
ret->nsxhr = nsxhr;
ret->window = This->window;
ret->task_magic = get_task_target_magic();
IHTMLWindow2_AddRef(&This->window->base.IHTMLWindow2_iface);
ret->IHTMLXMLHttpRequest_iface.lpVtbl = &HTMLXMLHttpRequestVtbl;
ret->IHTMLXMLHttpRequest2_iface.lpVtbl = &HTMLXMLHttpRequest2Vtbl;
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment