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);
}
}
......@@ -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);
......
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