Bug 911115 - Simple tests for the tab state cache. r=ttaubert

This commit is contained in:
David Rajchenbach-Teller 2013-09-23 17:23:37 -04:00
parent 4dc222fd0e
commit d431f68a6f
4 changed files with 186 additions and 23 deletions

View File

@ -20,6 +20,7 @@ MOCHITEST_BROWSER_FILES = \
browser_input_sample.html \
browser_pageshow.js \
browser_sessionStorage.js \
browser_tabStateCache.js \
browser_upgrade_backup.js \
browser_windowRestore_perwindowpb.js \
browser_248970_b_perwindowpb.js \

View File

@ -7,26 +7,6 @@ Cu.import("resource://gre/modules/Task.jsm", Scope);
Cu.import("resource://gre/modules/Promise.jsm", Scope);
let {Task, Promise} = Scope;
function promiseBrowserLoaded(aBrowser) {
let deferred = Promise.defer();
whenBrowserLoaded(aBrowser, () => deferred.resolve());
return deferred.promise;
}
function forceWriteState() {
let deferred = Promise.defer();
const PREF = "browser.sessionstore.interval";
const TOPIC = "sessionstore-state-write";
Services.obs.addObserver(function observe() {
Services.obs.removeObserver(observe, TOPIC);
Services.prefs.clearUserPref(PREF);
deferred.resolve();
}, TOPIC, false);
Services.prefs.setIntPref(PREF, 0);
return deferred.promise;
}
function waitForStorageChange(aTab) {
let deferred = Promise.defer();
@ -57,7 +37,7 @@ function test() {
// Flush loading and next save, call getBrowserState()
// a few times to ensure that everything is cached.
yield promiseBrowserLoaded(tab.linkedBrowser);
yield forceWriteState();
yield forceSaveState();
info("Calling getBrowserState() to populate cache");
ss.getBrowserState();
@ -66,7 +46,7 @@ function test() {
win.sessionStorage[SESSION_STORAGE_KEY] = SESSION_STORAGE_VALUE;
let storageChanged = yield storageChangedPromise;
ok(storageChanged, "Changing sessionStorage triggered the right message");
yield forceWriteState();
yield forceSaveState();
let state = ss.getBrowserState();
ok(state.indexOf(SESSION_STORAGE_KEY) != -1, "Key appears in state");
@ -78,7 +58,7 @@ function test() {
win.localStorage[LOCAL_STORAGE_KEY] = LOCAL_STORAGE_VALUE;
storageChanged = yield storageChangedPromise;
ok(!storageChanged, "Changing localStorage did not trigger a message");
yield forceWriteState();
yield forceSaveState();
state = ss.getBrowserState();
ok(state.indexOf(LOCAL_STORAGE_KEY) == -1, "Key does not appear in state");

View File

@ -0,0 +1,138 @@
/* 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: ";
let delta = yield getTelemetryDelta(forceSaveState);
is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " we hit one tab");
is(delta.misses, 1, PREFIX + " we missed one tab");
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;
yield forceSaveState();
});
});
is(delta.hits, numberOfUntrackedTabs + 1, PREFIX + " has at least one hit");
is(delta.misses, 1, PREFIX + " has one miss");
is(delta.clears, 0, PREFIX + " has no clear");
}
gBrowser.removeTab(tab2);
gBrowser.removeTab(tab1);
});

View File

@ -240,6 +240,34 @@ function waitForSaveState(aCallback) {
Services.prefs.getIntPref("browser.sessionstore.interval");
return waitForTopic("sessionstore-state-write", timeout, aCallback);
}
function promiseSaveState() {
let deferred = Promise.defer();
waitForSaveState(isSuccessful => {
if (isSuccessful) {
deferred.resolve();
} else {
deferred.reject(new Error("timeout"));
}});
return deferred.promise;
}
function forceSaveState() {
let promise = promiseSaveState();
const PREF = "browser.sessionstore.interval";
// Set interval to an arbitrary non-0 duration
// to ensure that setting it to 0 will notify observers
Services.prefs.setIntPref(PREF, 1000);
Services.prefs.setIntPref(PREF, 0);
return promise.then(
function onSuccess(x) {
Services.prefs.clearUserPref(PREF);
return x;
},
function onError(x) {
Services.prefs.clearUserPref(PREF);
throw x;
}
);
}
function whenBrowserLoaded(aBrowser, aCallback = next) {
aBrowser.addEventListener("load", function onLoad() {
@ -247,6 +275,22 @@ function whenBrowserLoaded(aBrowser, aCallback = next) {
executeSoon(aCallback);
}, true);
}
function promiseBrowserLoaded(aBrowser) {
let deferred = Promise.defer();
whenBrowserLoaded(aBrowser, deferred.resolve);
return deferred.promise;
}
function whenBrowserUnloaded(aBrowser, aContainer, aCallback = next) {
aBrowser.addEventListener("unload", function onUnload() {
aBrowser.removeEventListener("unload", onUnload, true);
executeSoon(aCallback);
}, true);
}
function promiseBrowserUnloaded(aBrowser, aContainer) {
let deferred = Promise.defer();
whenBrowserUnloaded(aBrowser, aContainer, deferred.resolve);
return deferred.promise;
}
function whenWindowLoaded(aWindow, aCallback = next) {
aWindow.addEventListener("load", function windowLoadListener() {