Bug 960903 - Tests for broadcasting shistory r=yoric

This commit is contained in:
Tim Taubert 2014-01-20 17:36:59 +01:00
parent fbcd10e678
commit 3fe36ac353
15 changed files with 198 additions and 321 deletions

View File

@ -64,13 +64,12 @@ support-files =
[browser_global_store.js]
[browser_label_and_icon.js]
[browser_merge_closed_tabs.js]
[browser_pageshow.js]
[browser_pageStyle.js]
[browser_privatetabs.js]
[browser_scrollPositions.js]
[browser_sessionHistory.js]
[browser_sessionStorage.js]
[browser_swapDocShells.js]
[browser_tabStateCache.js]
[browser_telemetry.js]
[browser_upgrade_backup.js]
[browser_windowRestore_perwindowpb.js]
@ -149,7 +148,6 @@ skip-if = true
[browser_618151.js]
[browser_623779.js]
[browser_624727.js]
[browser_625257.js]
[browser_628270.js]
[browser_635418.js]
[browser_636279.js]

View File

@ -18,6 +18,7 @@ add_task(function test_set_tabstate() {
yield promiseBrowserLoaded(tab.linkedBrowser);
// get the tab's state
SyncHandlers.get(tab.linkedBrowser).flush();
let state = ss.getTabState(tab);
ok(state, "get the tab's state");

View File

@ -29,6 +29,7 @@ function test() {
whenBrowserLoaded(newWin.gBrowser.selectedBrowser, function() {
// get the sessionstore state for the window
SyncHandlers.get(newWin.gBrowser.selectedBrowser).flush();
let state = ss.getWindowState(newWin);
// verify our cookie got set during pageload

View File

@ -18,18 +18,16 @@ function test() {
ss.setTabState(tab, JSON.stringify(tabState));
whenTabRestored(tab, function() {
SyncHandlers.get(tab.linkedBrowser).flush();
tabState = JSON.parse(ss.getTabState(tab));
is(tabState.entries.length, max_entries, "session history filled to the limit");
is(tabState.entries[0].url, baseURL + 0, "... but not more");
// visit yet another anchor (appending it to session history)
let doc = tab.linkedBrowser.contentDocument;
let event = doc.createEvent("MouseEvents");
event.initMouseEvent("click", true, true, doc.defaultView, 1,
0, 0, 0, 0, false, false, false, false, 0, null);
doc.querySelector("a").dispatchEvent(event);
tab.linkedBrowser.contentDocument.querySelector("a").click();
function check() {
SyncHandlers.get(tab.linkedBrowser).flush();
tabState = JSON.parse(ss.getTabState(tab));
if (tabState.entries[tabState.entries.length - 1].url != baseURL + "end") {
// It may take a few passes through the event loop before we

View File

@ -89,6 +89,7 @@ function test() {
history.pushState({obj2:2}, "title-obj2", "?page2");
history.replaceState({obj3:/^a$/}, "title-obj3");
SyncHandlers.get(tab.linkedBrowser).flush();
let state = ss.getTabState(tab);
gBrowser.removeTab(tab);

View File

@ -1,79 +0,0 @@
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/. */
let Scope = {};
Cu.import("resource://gre/modules/Task.jsm", Scope);
Cu.import("resource://gre/modules/Promise.jsm", Scope);
let {Task, Promise} = Scope;
// This tests that a tab which is closed while loading is not lost.
// Specifically, that session store does not rely on an invalid cache when
// constructing data for a tab which is loading.
// This test steps through the following parts:
// 1. Tab has been created is loading URI_TO_LOAD.
// 2. Before URI_TO_LOAD finishes loading, browser.currentURI has changed and
// tab is scheduled to be removed.
// 3. After the tab has been closed, undoCloseTab() has been called and the tab
// should fully load.
const URI_TO_LOAD = "about:mozilla";
function waitForLoadStarted(aTab) {
return promiseContentMessage(aTab.linkedBrowser, "SessionStore:loadStart");
}
function waitForTabLoaded(aTab) {
let deferred = Promise.defer();
whenBrowserLoaded(aTab.linkedBrowser, deferred.resolve);
return deferred.promise;
}
function waitForTabClosed() {
let deferred = Promise.defer();
let observer = function() {
gBrowser.tabContainer.removeEventListener("TabClose", observer, true);
deferred.resolve();
};
gBrowser.tabContainer.addEventListener("TabClose", observer, true);
return deferred.promise;
}
function test() {
waitForExplicitFinish();
Task.spawn(function() {
try {
// Open a new tab
let tab = gBrowser.addTab("about:blank");
yield waitForTabLoaded(tab);
// Trigger a save state, to initialize any caches
ss.getBrowserState();
is(gBrowser.tabs[1], tab, "newly created tab should exist by now");
// Start a load and interrupt it by closing the tab
tab.linkedBrowser.loadURI(URI_TO_LOAD);
yield waitForLoadStarted(tab);
let tabClosing = waitForTabClosed();
gBrowser.removeTab(tab);
info("Now waiting for TabClose to close");
yield tabClosing;
// Undo the tab, ensure that it proceeds with loading
tab = ss.undoCloseTab(window, 0);
yield waitForTabLoaded(tab);
is(tab.linkedBrowser.currentURI.spec, URI_TO_LOAD, "loading proceeded as expected");
gBrowser.removeTab(tab);
executeSoon(finish);
} catch (ex) {
ok(false, ex);
info(ex.stack);
}
});
}

View File

@ -27,6 +27,7 @@ function test() {
whenChildCount(entry, 1, function () {
whenChildCount(entry, 2, function () {
whenBrowserLoaded(browser, function () {
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 1, "tab has one history entry");
ok(!entries[0].children, "history entry has no subframes");

View File

@ -24,6 +24,7 @@ function runTests() {
// Open a second tab and close the first one.
let tab = win.gBrowser.addTab("about:mozilla");
yield whenBrowserLoaded(tab.linkedBrowser);
SyncHandlers.get(tab.linkedBrowser).flush();
win.gBrowser.removeTab(win.gBrowser.tabs[0]);
// Make sure our window is still tracked by sessionstore

View File

@ -1,87 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
function test() {
TestRunner.run();
}
/**
* This test ensures that loading a page from bfcache (by going back or forward
* in history) marks the window as dirty and causes data about the tab that
* changed to be re-collected.
*
* We will do this by creating a tab with two history entries and going back
* to the first. When we now request the current browser state from the
* session store service the first history entry must be selected.
*/
const URL = "data:text/html,<h1>first</h1>";
const URL2 = "data:text/html,<h1>second</h1>";
function runTests() {
// Create a dummy window that is regarded as active. We need to do this
// because we always collect data for tabs of active windows no matter if
// the window is dirty or not.
let win = OpenBrowserWindow();
yield whenDelayedStartupFinished(win, next);
// Create a tab with two history entries.
let tab = gBrowser.selectedTab = gBrowser.addTab("about:blank");
yield loadURI(URL);
yield loadURI(URL2);
// All windows currently marked as dirty will be written to disk
// and thus marked clean afterwards.
yield forceWriteState();
// Go back to 'about:robots' - which is loaded from the bfcache and thus
// will not fire a 'load' event but a 'pageshow' event with persisted=true.
waitForPageShow();
yield gBrowser.selectedBrowser.goBack();
is(tab.linkedBrowser.currentURI.spec, URL, "correct url after going back");
// If by receiving the 'pageshow' event the first window has correctly
// been marked as dirty, getBrowserState() should return the tab we created
// with the right history entry (about:robots) selected.
let state = JSON.parse(ss.getBrowserState());
is(state.windows[0].tabs[1].index, 1, "first history entry is selected");
// Clean up after ourselves.
gBrowser.removeTab(tab);
win.close();
}
function forceWriteState() {
const PREF = "browser.sessionstore.interval";
const TOPIC = "sessionstore-state-write";
Services.obs.addObserver(function observe() {
Services.obs.removeObserver(observe, TOPIC);
Services.prefs.clearUserPref(PREF);
executeSoon(next);
}, TOPIC, false);
Services.prefs.setIntPref(PREF, 0);
}
function loadURI(aURI) {
let browser = gBrowser.selectedBrowser;
waitForLoad(browser);
browser.loadURI(aURI);
}
function waitForLoad(aElement) {
aElement.addEventListener("load", function onLoad() {
aElement.removeEventListener("load", onLoad, true);
executeSoon(next);
}, true);
}
function waitForPageShow() {
let mm = gBrowser.selectedBrowser.messageManager;
mm.addMessageListener("SessionStore:pageshow", function onPageShow() {
mm.removeMessageListener("SessionStore:pageshow", onPageShow);
executeSoon(next);
});
}

View File

@ -30,6 +30,7 @@ add_task(function() {
yield promiseBrowserLoaded(tab2.linkedBrowser);
info("Flush to make sure chrome received all data.");
SyncHandlers.get(tab1.linkedBrowser).flush();
SyncHandlers.get(tab2.linkedBrowser).flush();
info("Checking out state");

View File

@ -0,0 +1,167 @@
/* Any copyright is dedicated to the Public Domain.
* http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
/**
* Ensure that starting a load invalidates shistory.
*/
add_task(function test_load_start() {
// Create a new tab.
let tab = gBrowser.addTab("about:blank");
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Load a new URI but remove the tab before it has finished loading.
browser.loadURI("about:mozilla");
yield promiseContentMessage(browser, "ss-test:onFrameTreeReset");
gBrowser.removeTab(tab);
// Undo close the tab.
tab = ss.undoCloseTab(window, 0);
browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Check that the correct URL was restored.
is(browser.currentURI.spec, "about:mozilla", "url is correct");
// Cleanup.
gBrowser.removeTab(tab);
});
/**
* Ensure that purging shistory invalidates.
*/
add_task(function test_purge() {
// Create a new tab.
let tab = gBrowser.addTab("about:mozilla");
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Create a second shistory entry.
browser.loadURI("about:robots");
yield promiseBrowserLoaded(browser);
// Check that we now have two shistory entries.
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 2, "there are two shistory entries");
// Purge session history.
yield sendMessage(browser, "ss-test:purgeSessionHistory");
// Check that we are left with a single shistory entry.
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 1, "there is one shistory entry");
// Cleanup.
gBrowser.removeTab(tab);
});
/**
* Ensure that anchor navigation invalidates shistory.
*/
add_task(function test_hashchange() {
const URL = "data:text/html;charset=utf-8,<a id=a href=%23>clickme</a>";
// Create a new tab.
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Check that we start with a single shistory entry.
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 1, "there is one shistory entry");
// Click the link and wait for a hashchange event.
browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a"});
yield promiseContentMessage(browser, "ss-test:hashchange");
// Check that we now have two shistory entries.
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 2, "there are two shistory entries");
// Cleanup.
gBrowser.removeTab(tab);
});
/**
* Ensure that loading pages from the bfcache invalidates shistory.
*/
add_task(function test_pageshow() {
const URL = "data:text/html;charset=utf-8,<h1>first</h1>";
const URL2 = "data:text/html;charset=utf-8,<h1>second</h1>";
// Create a new tab.
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Create a second shistory entry.
browser.loadURI(URL2);
yield promiseBrowserLoaded(browser);
// Go back to the previous url which is loaded from the bfcache.
browser.goBack();
yield promiseContentMessage(browser, "ss-test:onFrameTreeCollected");
is(browser.currentURI.spec, URL, "correct url after going back");
// Check that loading from bfcache did invalidate shistory.
SyncHandlers.get(browser).flush();
let {index} = JSON.parse(ss.getTabState(tab));
is(index, 1, "first history entry is selected");
// Cleanup.
gBrowser.removeTab(tab);
});
/**
* Ensure that subframe navigation invalidates shistory.
*/
add_task(function test_subframes() {
const URL = "data:text/html;charset=utf-8," +
"<iframe src=http%3A//example.com/ name=t></iframe>" +
"<a id=a1 href=http%3A//example.com/1 target=t>clickme</a>" +
"<a id=a2 href=http%3A//example.com/%23 target=t>clickme</a>";
// Create a new tab.
let tab = gBrowser.addTab(URL);
let browser = tab.linkedBrowser;
yield promiseBrowserLoaded(browser);
// Check that we have a single shistory entry.
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 1, "there is one shistory entry");
is(entries[0].children.length, 1, "the entry has one child");
// Navigate the subframe.
browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a1"});
yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
// Check shistory.
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 2, "there now are two shistory entries");
is(entries[1].children.length, 1, "the second entry has one child");
// Go back in history.
browser.goBack();
yield promiseBrowserLoaded(browser, false /* don't ignore subframes */);
// Navigate the subframe again.
browser.messageManager.sendAsyncMessage("ss-test:click", {id: "a2"});
yield promiseContentMessage(browser, "ss-test:hashchange");
// Check shistory.
SyncHandlers.get(browser).flush();
let {entries} = JSON.parse(ss.getTabState(tab));
is(entries.length, 2, "there now are two shistory entries");
is(entries[1].children.length, 1, "the second entry has one child");
// Cleanup.
gBrowser.removeTab(tab);
});

View File

@ -1,139 +0,0 @@
/* Any copyright is dedicated to the Public Domain.
http://creativecommons.org/publicdomain/zero/1.0/ */
"use strict";
let wrapper = {};
Cu.import("resource:///modules/sessionstore/TabStateCache.jsm", wrapper);
let {TabStateCache} = wrapper;
// The number of tabs that are present in the browser but that we're not dealing
// with. This should be one (for an empty about:blank), but let's not make this
// a magic number.
let numberOfUntrackedTabs;
// Arbitrary URL prefix, used to generate the URL of pages we visit
const URL_PREFIX = "http://example.org:80/";
/**
* Check tab state cache telemetry statistics before and after an operation.
*
* @param {function} f The operation being measured. If it returns a promise,
* we wait until the promise is resolved before proceeding.
* @return {promise}
*/
function getTelemetryDelta(f) {
return Task.spawn(function() {
let KEYS = ["hits", "misses", "clears"];
let old = {};
for (let key of KEYS) {
old[key] = TabStateCache[key];
}
yield f();
let result = {};
for (let key of KEYS) {
result[key] = TabStateCache[key] - old[key];
}
ok(result.hits >= 0, "Sanity check: hits have not decreased");
ok(result.misses >= 0, "Sanity check: misses have not decreased");
ok(result.clears >= 0, "Sanity check: clears have not decreased");
throw new Task.Result(result);
});
}
add_task(function init() {
// Start with an empty cache
closeAllButPrimaryWindow();
TabStateCache.clear();
numberOfUntrackedTabs = gBrowser.tabs.length;
info("Starting with " + numberOfUntrackedTabs + " tabs");
});
add_task(function add_remove() {
info("Adding the first tab");
// Initialize one tab, save to initialize cache
let tab1 = gBrowser.addTab(URL_PREFIX + "?tab1");
yield promiseBrowserLoaded(tab1.linkedBrowser);
yield getTelemetryDelta(forceSaveState);
// Save/collect again a few times, ensure that we always hit
info("Save/collect a few times with one tab");
for (let collector of [forceSaveState, ss.getBrowserState]) {
for (let i = 0; i < 5; ++i) {
let PREFIX = "Trivial test " + i + " using " + collector.name + ": ";
let delta = yield getTelemetryDelta(collector);
is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " has at least one hit " + delta.hits);
is(delta.misses, 0, PREFIX + " has no miss");
is(delta.clears, 0, PREFIX + " has no clear");
}
}
// Add a second tab, ensure that we have both hits and misses
info("Adding the second tab");
let tab2 = gBrowser.addTab(URL_PREFIX + "?tab2");
yield promiseBrowserLoaded(tab2.linkedBrowser);
let PREFIX = "Adding second tab: ";
ok(!TabStateCache.has(tab2), PREFIX + " starts out of the cache");
let delta = yield getTelemetryDelta(forceSaveState);
is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " we hit all tabs, thanks to prefetching");
is(delta.misses, 0, PREFIX + " we missed no tabs, thanks to prefetching");
is(delta.clears, 0, PREFIX + " has no clear");
// Save/collect again a few times, ensure that we always hit
info("Save/collect a few times with two tabs");
for (let collector of [forceSaveState, ss.getBrowserState]) {
for (let i = 0; i < 5; ++i) {
let PREFIX = "With two tabs " + i + " using " + collector.name + ": ";
let delta = yield getTelemetryDelta(collector);
is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " both tabs hit");
is(delta.misses, 0, PREFIX + " has no miss");
is(delta.clears, 0, PREFIX + " has no clear");
}
}
info("Removing second tab");
gBrowser.removeTab(tab2);
PREFIX = "Removing second tab: ";
delta = yield getTelemetryDelta(forceSaveState);
is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " we hit for one tab");
is(delta.misses, 0, PREFIX + " has no miss");
is(delta.clears, 0, PREFIX + " has no clear");
info("Removing first tab");
gBrowser.removeTab(tab1);
});
add_task(function browsing() {
info("Opening first browsing tab");
let tab1 = gBrowser.addTab(URL_PREFIX + "?do_not_move_from_here");
let browser1 = tab1.linkedBrowser;
yield promiseBrowserLoaded(browser1);
yield forceSaveState();
info("Opening second browsing tab");
let tab2 = gBrowser.addTab(URL_PREFIX + "?start_browsing_from_here");
let browser2 = tab2.linkedBrowser;
yield promiseBrowserLoaded(browser2);
for (let i = 0; i < 4; ++i) {
let url = URL_PREFIX + "?browsing" + i; // Arbitrary url, easy to recognize
let PREFIX = "Browsing to " + url;
info(PREFIX);
let delta = yield getTelemetryDelta(function() {
return Task.spawn(function() {
// Move to new URI then save session
let promise = promiseBrowserLoaded(browser2);
browser2.loadURI(url);
yield promise;
ok(!TabStateCache.has(browser2), PREFIX + " starts out of the cache");
yield forceSaveState();
});
});
is(delta.hits, numberOfUntrackedTabs + 2, PREFIX + " has all hits, thanks to prefetching");
is(delta.misses, 0, PREFIX + " has no miss, thanks to prefetching");
is(delta.clears, 0, PREFIX + " has no clear");
}
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab1);
});

View File

@ -4,8 +4,7 @@
let tmp = {};
Cu.import("resource:///modules/sessionstore/SessionFile.jsm", tmp);
Cu.import("resource:///modules/sessionstore/TabStateCache.jsm", tmp);
let {SessionFile, TabStateCache} = tmp;
let {SessionFile} = tmp;
// Shortcuts for histogram names
let Keys = {};
@ -63,7 +62,8 @@ add_task(function history() {
let statistics = yield promiseStats();
info("Now changing history");
tab.linkedBrowser.contentWindow.history.pushState({foo:1}, "ref");
tab.linkedBrowser.loadURI("http://example.org:80/1");
yield promiseBrowserLoaded(tab.linkedBrowser);
SyncHandlers.get(tab.linkedBrowser).flush();
let statistics2 = yield promiseStats();
@ -224,7 +224,6 @@ add_task(function formdata() {
yield setInputValue(tab.linkedBrowser, {id: "input", value: "This is some form data"});
SyncHandlers.get(tab.linkedBrowser).flush();
TabStateCache.delete(tab.linkedBrowser);
let statistics2 = yield promiseStats();

View File

@ -25,6 +25,10 @@ gFrameTree.addObserver({
* to modify and query docShell data when running with multiple processes.
*/
addEventListener("hashchange", function () {
sendAsyncMessage("ss-test:hashchange");
});
addEventListener("MozStorageChanged", function () {
sendSyncMessage("ss-test:MozStorageChanged");
});
@ -46,6 +50,11 @@ addMessageListener("ss-test:purgeDomainData", function ({data: domain}) {
content.setTimeout(() => sendAsyncMessage("ss-test:purgeDomainData"));
});
addMessageListener("ss-test:purgeSessionHistory", function () {
Services.obs.notifyObservers(null, "browser:purge-session-history", "");
content.setTimeout(() => sendAsyncMessage("ss-test:purgeSessionHistory"));
});
addMessageListener("ss-test:getStyleSheets", function (msg) {
let sheets = content.document.styleSheets;
let titles = Array.map(sheets, ss => [ss.title, ss.disabled]);
@ -149,3 +158,8 @@ addMessageListener("ss-test:mapFrameTree", function (msg) {
let result = gFrameTree.map(frame => ({href: frame.location.href}));
sendAsyncMessage("ss-test:mapFrameTree", result);
});
addMessageListener("ss-test:click", function ({data}) {
content.document.getElementById(data.id).click();
sendAsyncMessage("ss-test:click");
});

View File

@ -288,17 +288,17 @@ function forceSaveState() {
);
}
function whenBrowserLoaded(aBrowser, aCallback = next) {
function whenBrowserLoaded(aBrowser, aCallback = next, ignoreSubFrames = true) {
aBrowser.addEventListener("load", function onLoad(event) {
if (event.target == aBrowser.contentDocument) {
if (!ignoreSubFrames || event.target == aBrowser.contentDocument) {
aBrowser.removeEventListener("load", onLoad, true);
executeSoon(aCallback);
}
}, true);
}
function promiseBrowserLoaded(aBrowser) {
function promiseBrowserLoaded(aBrowser, ignoreSubFrames = true) {
let deferred = Promise.defer();
whenBrowserLoaded(aBrowser, deferred.resolve);
whenBrowserLoaded(aBrowser, deferred.resolve, ignoreSubFrames);
return deferred.promise;
}
function whenBrowserUnloaded(aBrowser, aContainer, aCallback = next) {