michael@0: /* This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this file, michael@0: * You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: const { interfaces: Ci, classes: Cc, results: Cr, utils: Cu } = Components; michael@0: michael@0: Cu.import("resource://gre/modules/Services.jsm"); michael@0: michael@0: var cps; michael@0: var asyncRunner; michael@0: var next; michael@0: michael@0: (function init() { michael@0: // There has to be a profile directory before the CPS service is gotten. michael@0: do_get_profile(); michael@0: })(); michael@0: michael@0: function runAsyncTests(tests) { michael@0: do_test_pending(); michael@0: michael@0: cps = Cc["@mozilla.org/content-pref/service;1"]. michael@0: getService(Ci.nsIContentPrefService2); michael@0: michael@0: // Without this the private-browsing service tries to open a dialog when you michael@0: // change its enabled state. michael@0: Services.prefs.setBoolPref("browser.privatebrowsing.keep_current_session", michael@0: true); michael@0: michael@0: let s = {}; michael@0: Cu.import("resource://test/AsyncRunner.jsm", s); michael@0: asyncRunner = new s.AsyncRunner({ michael@0: done: do_test_finished, michael@0: error: function (err) { michael@0: // xpcshell test functions like do_check_eq throw NS_ERROR_ABORT on michael@0: // failure. Ignore those and catch only uncaught exceptions. michael@0: if (err !== Cr.NS_ERROR_ABORT) { michael@0: if (err.stack) { michael@0: err = err + "\n\nTraceback (most recent call first):\n" + err.stack + michael@0: "\nUseless do_throw stack:"; michael@0: } michael@0: do_throw(err); michael@0: } michael@0: }, michael@0: consoleError: function (scriptErr) { michael@0: // As much as possible make sure the error is related to the test. On the michael@0: // other hand if this fails to catch a test-related error, we'll hang. michael@0: let filename = scriptErr.sourceName || scriptErr.toString() || ""; michael@0: if (/contentpref/i.test(filename)) michael@0: do_throw(scriptErr); michael@0: } michael@0: }); michael@0: michael@0: next = asyncRunner.next.bind(asyncRunner); michael@0: michael@0: do_register_cleanup(function () { michael@0: asyncRunner.destroy(); michael@0: asyncRunner = null; michael@0: }); michael@0: michael@0: tests.forEach(function (test) { michael@0: function gen() { michael@0: do_print("Running " + test.name); michael@0: yield test(); michael@0: yield reset(); michael@0: } michael@0: asyncRunner.appendIterator(gen()); michael@0: }); michael@0: michael@0: // reset() ends up calling asyncRunner.next(), starting the tests. michael@0: reset(); michael@0: } michael@0: michael@0: function makeCallback(callbacks) { michael@0: callbacks = callbacks || {}; michael@0: ["handleResult", "handleError"].forEach(function (meth) { michael@0: if (!callbacks[meth]) michael@0: callbacks[meth] = function () { michael@0: do_throw(meth + " shouldn't be called."); michael@0: }; michael@0: }); michael@0: if (!callbacks.handleCompletion) michael@0: callbacks.handleCompletion = function (reason) { michael@0: do_check_eq(reason, Ci.nsIContentPrefCallback2.COMPLETE_OK); michael@0: next(); michael@0: }; michael@0: return callbacks; michael@0: } michael@0: michael@0: function do_check_throws(fn) { michael@0: let threw = false; michael@0: try { michael@0: fn(); michael@0: } michael@0: catch (err) { michael@0: threw = true; michael@0: } michael@0: do_check_true(threw); michael@0: } michael@0: michael@0: function sendMessage(msg, callback) { michael@0: let obj = callback || {}; michael@0: let ref = Cu.getWeakReference(obj); michael@0: cps.QueryInterface(Ci.nsIObserver).observe(ref, "test:" + msg, null); michael@0: return "value" in obj ? obj.value : undefined; michael@0: } michael@0: michael@0: function reset() { michael@0: sendMessage("reset", next); michael@0: } michael@0: michael@0: function set(group, name, val, context) { michael@0: cps.set(group, name, val, context, makeCallback()); michael@0: } michael@0: michael@0: function setGlobal(name, val, context) { michael@0: cps.setGlobal(name, val, context, makeCallback()); michael@0: } michael@0: michael@0: function prefOK(actual, expected, strict) { michael@0: do_check_true(actual instanceof Ci.nsIContentPref); michael@0: do_check_eq(actual.domain, expected.domain); michael@0: do_check_eq(actual.name, expected.name); michael@0: if (strict) michael@0: do_check_true(actual.value === expected.value); michael@0: else michael@0: do_check_eq(actual.value, expected.value); michael@0: } michael@0: michael@0: function getOK(args, expectedVal, expectedGroup, strict) { michael@0: if (args.length == 2) michael@0: args.push(undefined); michael@0: let expectedPrefs = expectedVal === undefined ? [] : michael@0: [{ domain: expectedGroup || args[0], michael@0: name: args[1], michael@0: value: expectedVal }]; michael@0: yield getOKEx("getByDomainAndName", args, expectedPrefs, strict); michael@0: } michael@0: michael@0: function getSubdomainsOK(args, expectedGroupValPairs) { michael@0: if (args.length == 2) michael@0: args.push(undefined); michael@0: let expectedPrefs = expectedGroupValPairs.map(function ([group, val]) { michael@0: return { domain: group, name: args[1], value: val }; michael@0: }); michael@0: yield getOKEx("getBySubdomainAndName", args, expectedPrefs); michael@0: } michael@0: michael@0: function getGlobalOK(args, expectedVal) { michael@0: if (args.length == 1) michael@0: args.push(undefined); michael@0: let expectedPrefs = expectedVal === undefined ? [] : michael@0: [{ domain: null, name: args[0], value: expectedVal }]; michael@0: yield getOKEx("getGlobal", args, expectedPrefs); michael@0: } michael@0: michael@0: function getOKEx(methodName, args, expectedPrefs, strict, context) { michael@0: let actualPrefs = []; michael@0: args.push(makeCallback({ michael@0: handleResult: function (pref) actualPrefs.push(pref) michael@0: })); michael@0: yield cps[methodName].apply(cps, args); michael@0: arraysOfArraysOK([actualPrefs], [expectedPrefs], function (actual, expected) { michael@0: prefOK(actual, expected, strict); michael@0: }); michael@0: } michael@0: michael@0: function getCachedOK(args, expectedIsCached, expectedVal, expectedGroup, michael@0: strict) { michael@0: if (args.length == 2) michael@0: args.push(undefined); michael@0: let expectedPref = !expectedIsCached ? null : { michael@0: domain: expectedGroup || args[0], michael@0: name: args[1], michael@0: value: expectedVal michael@0: }; michael@0: getCachedOKEx("getCachedByDomainAndName", args, expectedPref, strict); michael@0: } michael@0: michael@0: function getCachedSubdomainsOK(args, expectedGroupValPairs) { michael@0: if (args.length == 2) michael@0: args.push(undefined); michael@0: let len = {}; michael@0: args.push(len); michael@0: let actualPrefs = cps.getCachedBySubdomainAndName.apply(cps, args); michael@0: actualPrefs = actualPrefs.sort(function (a, b) { michael@0: return a.domain.localeCompare(b.domain); michael@0: }); michael@0: do_check_eq(actualPrefs.length, len.value); michael@0: let expectedPrefs = expectedGroupValPairs.map(function ([group, val]) { michael@0: return { domain: group, name: args[1], value: val }; michael@0: }); michael@0: arraysOfArraysOK([actualPrefs], [expectedPrefs], prefOK); michael@0: } michael@0: michael@0: function getCachedGlobalOK(args, expectedIsCached, expectedVal) { michael@0: if (args.length == 1) michael@0: args.push(undefined); michael@0: let expectedPref = !expectedIsCached ? null : { michael@0: domain: null, michael@0: name: args[0], michael@0: value: expectedVal michael@0: }; michael@0: getCachedOKEx("getCachedGlobal", args, expectedPref); michael@0: } michael@0: michael@0: function getCachedOKEx(methodName, args, expectedPref, strict) { michael@0: let actualPref = cps[methodName].apply(cps, args); michael@0: if (expectedPref) michael@0: prefOK(actualPref, expectedPref, strict); michael@0: else michael@0: do_check_true(actualPref === null); michael@0: } michael@0: michael@0: function arraysOfArraysOK(actual, expected, cmp) { michael@0: cmp = cmp || function (a, b) do_check_eq(a, b); michael@0: do_check_eq(actual.length, expected.length); michael@0: actual.forEach(function (actualChildArr, i) { michael@0: let expectedChildArr = expected[i]; michael@0: do_check_eq(actualChildArr.length, expectedChildArr.length); michael@0: actualChildArr.forEach(function (actualElt, j) { michael@0: let expectedElt = expectedChildArr[j]; michael@0: cmp(actualElt, expectedElt); michael@0: }); michael@0: }); michael@0: } michael@0: michael@0: function dbOK(expectedRows) { michael@0: let db = sendMessage("db"); michael@0: let stmt = db.createAsyncStatement( michael@0: "SELECT groups.name AS grp, settings.name AS name, prefs.value AS value " + michael@0: "FROM prefs " + michael@0: "LEFT JOIN groups ON groups.id = prefs.groupID " + michael@0: "LEFT JOIN settings ON settings.id = prefs.settingID " + michael@0: "UNION " + michael@0: michael@0: // These second two SELECTs get the rows of the groups and settings tables michael@0: // that aren't referenced by the prefs table. Neither should return any michael@0: // rows if the component is working properly. michael@0: "SELECT groups.name AS grp, NULL AS name, NULL AS value " + michael@0: "FROM groups " + michael@0: "WHERE id NOT IN (" + michael@0: "SELECT DISTINCT groupID " + michael@0: "FROM prefs " + michael@0: "WHERE groupID NOTNULL" + michael@0: ") " + michael@0: "UNION " + michael@0: "SELECT NULL AS grp, settings.name AS name, NULL AS value " + michael@0: "FROM settings " + michael@0: "WHERE id NOT IN (" + michael@0: "SELECT DISTINCT settingID " + michael@0: "FROM prefs " + michael@0: "WHERE settingID NOTNULL" + michael@0: ") " + michael@0: michael@0: "ORDER BY value ASC, grp ASC, name ASC" michael@0: ); michael@0: michael@0: let actualRows = []; michael@0: let cols = ["grp", "name", "value"]; michael@0: michael@0: db.executeAsync([stmt], 1, { michael@0: handleCompletion: function (reason) { michael@0: arraysOfArraysOK(actualRows, expectedRows); michael@0: next(); michael@0: }, michael@0: handleResult: function (results) { michael@0: let row = null; michael@0: while (row = results.getNextRow()) { michael@0: actualRows.push(cols.map(function (c) row.getResultByName(c))); michael@0: } michael@0: }, michael@0: handleError: function (err) { michael@0: do_throw(err); michael@0: } michael@0: }); michael@0: stmt.finalize(); michael@0: } michael@0: michael@0: function on(event, names, dontRemove) { michael@0: let args = { michael@0: reset: function () { michael@0: for (let prop in this) { michael@0: if (Array.isArray(this[prop])) michael@0: this[prop].splice(0, this[prop].length); michael@0: } michael@0: }, michael@0: }; michael@0: michael@0: let observers = {}; michael@0: michael@0: names.forEach(function (name) { michael@0: let obs = {}; michael@0: ["onContentPrefSet", "onContentPrefRemoved"].forEach(function (meth) { michael@0: obs[meth] = function () do_throw(meth + " should not be called"); michael@0: }); michael@0: obs["onContentPref" + event] = function () { michael@0: args[name].push(Array.slice(arguments)); michael@0: }; michael@0: observers[name] = obs; michael@0: args[name] = []; michael@0: args[name].observer = obs; michael@0: cps.addObserverForName(name, obs); michael@0: }); michael@0: michael@0: do_execute_soon(function () { michael@0: if (!dontRemove) michael@0: names.forEach(function (n) cps.removeObserverForName(n, observers[n])); michael@0: next(args); michael@0: }); michael@0: } michael@0: michael@0: function wait() { michael@0: do_execute_soon(next); michael@0: } michael@0: michael@0: function observerArgsOK(actualArgs, expectedArgs) { michael@0: do_check_neq(actualArgs, undefined); michael@0: arraysOfArraysOK(actualArgs, expectedArgs); michael@0: }