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;