diff --git a/dlls/mshtml/htmlstorage.c b/dlls/mshtml/htmlstorage.c index 751afd6155d..f411c6cb122 100644 --- a/dlls/mshtml/htmlstorage.c +++ b/dlls/mshtml/htmlstorage.c @@ -197,6 +197,8 @@ static inline HTMLStorage *impl_from_IHTMLStorage(IHTMLStorage *iface) return CONTAINING_RECORD(iface, HTMLStorage, IHTMLStorage_iface); } +static HRESULT build_session_origin(IUri*,BSTR,BSTR*); + struct storage_event_task { task_t header; HTMLInnerWindow *window; @@ -226,22 +228,22 @@ static void storage_event_destr(task_t *_task) IHTMLWindow2_Release(&task->window->base.IHTMLWindow2_iface); } -/* Takes ownership of old_value */ -static HRESULT send_storage_event(HTMLStorage *storage, BSTR key, BSTR old_value, BSTR new_value) +struct send_storage_event_ctx { + HTMLInnerWindow *skip_window; + const WCHAR *origin; + UINT origin_len; + BSTR key; + BSTR old_value; + BSTR new_value; +}; + +static HRESULT push_storage_event_task(struct send_storage_event_ctx *ctx, HTMLInnerWindow *window, BOOL commit) { - HTMLInnerWindow *window = storage->window; - BOOL local = !!storage->filename; struct storage_event_task *task; DOMEvent *event; HRESULT hres; - if(!window) { - SysFreeString(old_value); - return S_OK; - } - - hres = create_storage_event(window->doc, key, old_value, new_value, local, &event); - SysFreeString(old_value); + hres = create_storage_event(window->doc, ctx->key, ctx->old_value, ctx->new_value, commit, &event); if(FAILED(hres)) return hres; @@ -256,6 +258,102 @@ static HRESULT send_storage_event(HTMLStorage *storage, BSTR key, BSTR old_value return push_task(&task->header, storage_event_proc, storage_event_destr, window->task_magic); } +static HRESULT send_storage_event_impl(struct send_storage_event_ctx *ctx, HTMLInnerWindow *window) +{ + HTMLOuterWindow *child; + const WCHAR *origin; + UINT origin_len; + BOOL matches; + HRESULT hres; + BSTR bstr; + + if(!window) + return S_OK; + + LIST_FOR_EACH_ENTRY(child, &window->children, HTMLOuterWindow, sibling_entry) { + hres = send_storage_event_impl(ctx, child->base.inner_window); + if(FAILED(hres)) + return hres; + } + + if(window == ctx->skip_window) + return S_OK; + + /* Try it quick from session storage first, if available */ + if(window->session_storage) { + HTMLStorage *storage = impl_from_IHTMLStorage(window->session_storage); + origin = storage->session_storage->origin; + origin_len = ctx->skip_window ? wcslen(origin) : storage->session_storage->origin_len; + bstr = NULL; + }else { + hres = IUri_GetHost(window->base.outer_window->uri, &bstr); + if(hres != S_OK) { + if(SUCCEEDED(hres)) + SysFreeString(bstr); + return S_OK; + } + if(ctx->skip_window) + _wcslwr(bstr); + else { + BSTR tmp = bstr; + hres = build_session_origin(window->base.outer_window->uri, tmp, &bstr); + SysFreeString(tmp); + if(hres != S_OK) { + if(SUCCEEDED(hres)) + SysFreeString(bstr); + return S_OK; + } + } + origin = bstr; + origin_len = SysStringLen(bstr); + } + + matches = (origin_len == ctx->origin_len && !memcmp(origin, ctx->origin, origin_len * sizeof(WCHAR))); + SysFreeString(bstr); + + return matches ? push_storage_event_task(ctx, window, FALSE) : S_OK; +} + +/* Takes ownership of old_value */ +static HRESULT send_storage_event(HTMLStorage *storage, BSTR key, BSTR old_value, BSTR new_value) +{ + HTMLInnerWindow *window = storage->window; + struct send_storage_event_ctx ctx; + HTMLOuterWindow *top_window; + BSTR hostname = NULL; + HRESULT hres = S_OK; + + if(!window) + goto done; + get_top_window(window->base.outer_window, &top_window); + + ctx.key = key; + ctx.old_value = old_value; + ctx.new_value = new_value; + if(!storage->filename) { + ctx.origin = storage->session_storage->origin; + ctx.origin_len = storage->session_storage->origin_len; + ctx.skip_window = NULL; + }else { + hres = IUri_GetHost(window->base.outer_window->uri, &hostname); + if(hres != S_OK) + goto done; + _wcslwr(hostname); + ctx.origin = hostname; + ctx.origin_len = SysStringLen(hostname); + ctx.skip_window = top_window->base.inner_window; /* localStorage on native skips top window */ + } + hres = send_storage_event_impl(&ctx, top_window->base.inner_window); + + if(ctx.skip_window && hres == S_OK) + hres = push_storage_event_task(&ctx, window, TRUE); + +done: + SysFreeString(hostname); + SysFreeString(old_value); + return hres; +} + static HRESULT WINAPI HTMLStorage_QueryInterface(IHTMLStorage *iface, REFIID riid, void **ppv) { HTMLStorage *This = impl_from_IHTMLStorage(iface); diff --git a/dlls/mshtml/tests/documentmode.js b/dlls/mshtml/tests/documentmode.js index 91d5e38c59a..9532f529332 100644 --- a/dlls/mshtml/tests/documentmode.js +++ b/dlls/mshtml/tests/documentmode.js @@ -1253,6 +1253,130 @@ sync_test("storage", function() { sessionStorage.clear(); }); +async_test("storage events", function() { + var iframe = document.createElement("iframe"), iframe2 = document.createElement("iframe"); + var local = false, storage, storage2, v = document.documentMode, i = 0; + + var tests = [ + function() { + expect(); + storage.removeItem("foobar"); + }, + function() { + expect(0, "foobar", "", "test"); + storage.setItem("foobar", "test"); + }, + function() { + expect(1, "foobar", "test", "TEST", true); + storage2.setItem("foobar", "TEST"); + }, + function() { + expect(0, "foobar", "TEST", ""); + storage.removeItem("foobar"); + }, + function() { + expect(1, "winetest", "", "WineTest"); + storage2.setItem("winetest", "WineTest"); + }, + function() { + expect(0, "", "", ""); + storage.clear(); + } + ]; + + function next() { + if(++i < tests.length) + tests[i](); + else if(local) + next_test(); + else { + // w10pro64 testbot VM throws WININET_E_INTERNAL_ERROR for some reason + storage = null, storage2 = null; + try { + storage = window.localStorage, storage2 = iframe.contentWindow.localStorage; + }catch(e) { + ok(e.number === 0x72ee4 - 0x80000000, "localStorage threw " + e.number + ": " + e); + } + if(!storage || !storage2) { + win_skip("localStorage is buggy and not available, skipping"); + next_test(); + return; + } + i = 0, local = true; + + if(!storage.length) + setTimeout(function() { tests[0](); }); + else { + // Get rid of any entries first, since native doesn't update immediately + var w = [ window, iframe.contentWindow ]; + for(var j = 0; j < w.length; j++) + w[j].onstorage = w[j].document.onstorage = w[j].document.onstoragecommit = null; + document.onstoragecommit = function() { + if(!storage.length) + setTimeout(function() { tests[0](); }); + else + storage.clear(); + }; + storage.clear(); + } + } + } + + function test_event(e, key, oldValue, newValue) { + if(v < 9) { + ok(e === undefined, "event not undefined in legacy mode: " + e); + return; + } + var s = Object.prototype.toString.call(e); + todo_wine. + ok(s === "[object StorageEvent]", "Object.toString = " + s); + ok(e.key === key, "key = " + e.key + ", expected " + key); + ok(e.oldValue === oldValue, "oldValue = " + e.oldValue + ", expected " + oldValue); + ok(e.newValue === newValue, "newValue = " + e.newValue + ", expected " + newValue); + } + + function expect(idx, key, oldValue, newValue, quirk) { + var window2 = iframe.contentWindow, document2 = window2.document; + window.onstorage = function() { ok(false, "window.onstorage called"); }; + document.onstorage = function() { ok(false, "doc.onstorage called"); }; + document.onstoragecommit = function() { ok(false, "doc.onstoragecommit called"); }; + window2.onstorage = function() { ok(false, "iframe window.onstorage called"); }; + document2.onstorage = function() { ok(false, "iframe doc.onstorage called"); }; + document2.onstoragecommit = function() { ok(false, "iframe doc.onstoragecommit called"); }; + + if(idx === undefined) { + setTimeout(function() { next(); }); + }else { + // Native sometimes calls this for some reason + if(local && quirk) document.onstoragecommit = null; + + (v < 9 ? document2 : window2)["onstorage"] = function(e) { + (local && idx ? document2 : (local || v < 9 ? document : window))[local ? "onstoragecommit" : "onstorage"] = function(e) { + test_event(e, local ? "" : key, local ? "" : oldValue, local ? "" : newValue); + next(); + } + test_event(e, key, oldValue, newValue); + } + } + } + + iframe.onload = function() { + iframe2.onload = function() { + var w = iframe2.contentWindow; + w.onstorage = function() { ok(false, "about:blank window.onstorage called"); }; + w.document.onstorage = function() { ok(false, "about:blank document.onstorage called"); }; + w.document.onstoragecommit = function() { ok(false, "about:blank document.onstoragecommit called"); }; + + storage = window.sessionStorage, storage2 = iframe.contentWindow.sessionStorage; + tests[0](); + }; + iframe2.src = "about:blank"; + document.body.appendChild(iframe2); + }; + iframe.src = "blank.html"; + document.body.appendChild(iframe); +}); + sync_test("elem_attr", function() { var v = document.documentMode; var elem = document.createElement("div"), r;