michael@0: /* Any copyright is dedicated to the Public Domain. michael@0: * http://creativecommons.org/publicdomain/zero/1.0/ */ michael@0: michael@0: "use strict"; michael@0: michael@0: const INITIAL_VALUE = "browser_broadcast.js-initial-value-" + Date.now(); michael@0: michael@0: /** michael@0: * This test ensures we won't lose tab data queued in the content script when michael@0: * closing a tab. michael@0: */ michael@0: add_task(function flush_on_tabclose() { michael@0: let tab = yield createTabWithStorageData(["http://example.com"]); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: yield modifySessionStorage(browser, {test: "on-tab-close"}); michael@0: gBrowser.removeTab(tab); michael@0: michael@0: let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window)); michael@0: is(storage["http://example.com"].test, "on-tab-close", michael@0: "sessionStorage data has been flushed on TabClose"); michael@0: }); michael@0: michael@0: /** michael@0: * This test ensures we won't lose tab data queued in the content script when michael@0: * the application tries to quit. michael@0: */ michael@0: add_task(function flush_on_quit_requested() { michael@0: let tab = yield createTabWithStorageData(["http://example.com"]); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: yield modifySessionStorage(browser, {test: "on-quit-requested"}); michael@0: michael@0: // Note that sending quit-application-requested should not interfere with michael@0: // other tests and code. We're just notifying about a shutdown request but michael@0: // we will not send quit-application-granted. Observers will thus assume michael@0: // that some other observer has canceled the request. michael@0: sendQuitApplicationRequested(); michael@0: michael@0: let {storage} = JSON.parse(ss.getTabState(tab)); michael@0: is(storage["http://example.com"].test, "on-quit-requested", michael@0: "sessionStorage data has been flushed when a quit is requested"); michael@0: michael@0: gBrowser.removeTab(tab); michael@0: }); michael@0: michael@0: /** michael@0: * This test ensures we won't lose tab data queued in the content script when michael@0: * duplicating a tab. michael@0: */ michael@0: add_task(function flush_on_duplicate() { michael@0: let tab = yield createTabWithStorageData(["http://example.com"]); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: yield modifySessionStorage(browser, {test: "on-duplicate"}); michael@0: let tab2 = ss.duplicateTab(window, tab); michael@0: let {storage} = JSON.parse(ss.getTabState(tab2)); michael@0: is(storage["http://example.com"].test, "on-duplicate", michael@0: "sessionStorage data has been flushed when duplicating tabs"); michael@0: michael@0: yield promiseTabRestored(tab2); michael@0: gBrowser.removeTab(tab2) michael@0: let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window)); michael@0: is(storage["http://example.com"].test, "on-duplicate", michael@0: "sessionStorage data has been flushed when duplicating tabs"); michael@0: michael@0: gBrowser.removeTab(tab); michael@0: }); michael@0: michael@0: /** michael@0: * This test ensures we won't lose tab data queued in the content script when michael@0: * a window is closed. michael@0: */ michael@0: add_task(function flush_on_windowclose() { michael@0: let win = yield promiseNewWindow(); michael@0: let tab = yield createTabWithStorageData(["http://example.com"], win); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: yield modifySessionStorage(browser, {test: "on-window-close"}); michael@0: yield closeWindow(win); michael@0: michael@0: let [{tabs: [_, {storage}]}] = JSON.parse(ss.getClosedWindowData()); michael@0: is(storage["http://example.com"].test, "on-window-close", michael@0: "sessionStorage data has been flushed when closing a window"); michael@0: }); michael@0: michael@0: /** michael@0: * This test ensures that stale tab data is ignored when reusing a tab michael@0: * (via e.g. setTabState) and does not overwrite the new data. michael@0: */ michael@0: add_task(function flush_on_settabstate() { michael@0: let tab = yield createTabWithStorageData(["http://example.com"]); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: // Flush to make sure our tab state is up-to-date. michael@0: SyncHandlers.get(browser).flush(); michael@0: michael@0: let state = ss.getTabState(tab); michael@0: yield modifySessionStorage(browser, {test: "on-set-tab-state"}); michael@0: michael@0: // Flush all data contained in the content script but send it using michael@0: // asynchronous messages. michael@0: SyncHandlers.get(browser).flushAsync(); michael@0: michael@0: ss.setTabState(tab, state); michael@0: yield promiseTabRestored(tab); michael@0: michael@0: let {storage} = JSON.parse(ss.getTabState(tab)); michael@0: is(storage["http://example.com"].test, INITIAL_VALUE, michael@0: "sessionStorage data has not been overwritten"); michael@0: michael@0: gBrowser.removeTab(tab); michael@0: }); michael@0: michael@0: /** michael@0: * This test ensures that we won't lose tab data that has been sent michael@0: * asynchronously just before closing a tab. Flushing must re-send all data michael@0: * that hasn't been received by chrome, yet. michael@0: */ michael@0: add_task(function flush_on_tabclose_racy() { michael@0: let tab = yield createTabWithStorageData(["http://example.com"]); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: // Flush to make sure we start with an empty queue. michael@0: SyncHandlers.get(browser).flush(); michael@0: michael@0: yield modifySessionStorage(browser, {test: "on-tab-close-racy"}); michael@0: michael@0: // Flush all data contained in the content script but send it using michael@0: // asynchronous messages. michael@0: SyncHandlers.get(browser).flushAsync(); michael@0: gBrowser.removeTab(tab); michael@0: michael@0: let [{state: {storage}}] = JSON.parse(ss.getClosedTabData(window)); michael@0: is(storage["http://example.com"].test, "on-tab-close-racy", michael@0: "sessionStorage data has been merged correctly to prevent data loss"); michael@0: }); michael@0: michael@0: function promiseNewWindow() { michael@0: let deferred = Promise.defer(); michael@0: whenNewWindowLoaded({private: false}, deferred.resolve); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function closeWindow(win) { michael@0: let deferred = Promise.defer(); michael@0: let outerID = win.QueryInterface(Ci.nsIInterfaceRequestor) michael@0: .getInterface(Ci.nsIDOMWindowUtils) michael@0: .outerWindowID; michael@0: michael@0: Services.obs.addObserver(function obs(subject, topic) { michael@0: let id = subject.QueryInterface(Ci.nsISupportsPRUint64).data; michael@0: if (id == outerID) { michael@0: Services.obs.removeObserver(obs, topic); michael@0: deferred.resolve(); michael@0: } michael@0: }, "outer-window-destroyed", false); michael@0: michael@0: win.close(); michael@0: return deferred.promise; michael@0: } michael@0: michael@0: function createTabWithStorageData(urls, win = window) { michael@0: return Task.spawn(function task() { michael@0: let tab = win.gBrowser.addTab(); michael@0: let browser = tab.linkedBrowser; michael@0: michael@0: for (let url of urls) { michael@0: browser.loadURI(url); michael@0: yield promiseBrowserLoaded(browser); michael@0: yield modifySessionStorage(browser, {test: INITIAL_VALUE}); michael@0: } michael@0: michael@0: throw new Task.Result(tab); michael@0: }); michael@0: } michael@0: michael@0: function waitForStorageEvent(browser) { michael@0: return promiseContentMessage(browser, "ss-test:MozStorageChanged"); michael@0: } michael@0: michael@0: function sendQuitApplicationRequested() { michael@0: let cancelQuit = Cc["@mozilla.org/supports-PRBool;1"] michael@0: .createInstance(Ci.nsISupportsPRBool); michael@0: Services.obs.notifyObservers(cancelQuit, "quit-application-requested", null); michael@0: } michael@0: michael@0: function modifySessionStorage(browser, data) { michael@0: browser.messageManager.sendAsyncMessage("ss-test:modifySessionStorage", data); michael@0: return waitForStorageEvent(browser); michael@0: }