/* 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/. */ "use strict"; // This file tests the functionality of the preference service when using a // shared memory snapshot. In this configuration, a snapshot of the initial // state of the preferences database is made when we first spawn a child // process, and changes after that point are stored as entries in a dynamic hash // table, on top of the snapshot. const { XPCShellContentUtils } = ChromeUtils.importESModule( "resource://testing-common/XPCShellContentUtils.sys.mjs" ); XPCShellContentUtils.init(this); let contentPage; const { prefs } = Services; const defaultPrefs = prefs.getDefaultBranch(""); const FRAME_SCRIPT_INIT = ` var { prefs } = Services; var defaultPrefs = prefs.getDefaultBranch(""); `; function try_(fn) { try { return fn(); } catch (e) { return undefined; } } function getPref(pref) { let flags = { locked: try_(() => prefs.prefIsLocked(pref)), hasUser: try_(() => prefs.prefHasUserValue(pref)), }; switch (prefs.getPrefType(pref)) { case prefs.PREF_INT: return { ...flags, type: "Int", user: try_(() => prefs.getIntPref(pref)), default: try_(() => defaultPrefs.getIntPref(pref)), }; case prefs.PREF_BOOL: return { ...flags, type: "Bool", user: try_(() => prefs.getBoolPref(pref)), default: try_(() => defaultPrefs.getBoolPref(pref)), }; case prefs.PREF_STRING: return { ...flags, type: "String", user: try_(() => prefs.getStringPref(pref)), default: try_(() => defaultPrefs.getStringPref(pref)), }; } return {}; } function getPrefs(prefNames) { let result = {}; for (let pref of prefNames) { result[pref] = getPref(pref); } result.childList = prefs.getChildList(""); return result; } function checkPref( pref, proc, val, type, userVal, defaultVal, expectedFlags = {} ) { info(`Check "${pref}" ${proc} value`); equal(val.type, type, `Expected type for "${pref}"`); equal(val.user, userVal, `Expected user value for "${pref}"`); // We only send changes to the content process when they'll make a visible // difference, so ignore content process default values when we have a defined // user value. if (proc !== "content" || val.user === undefined) { equal(val.default, defaultVal, `Expected default value for "${pref}"`); } for (let [flag, value] of Object.entries(expectedFlags)) { equal(val[flag], value, `Expected ${flag} value for "${pref}"`); } } function getPrefList() { return prefs.getChildList(""); } const TESTS = { "exists.thenDoesNot": { beforeContent(PREF) { prefs.setBoolPref(PREF, true); ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); }, contentStartup(PREF, val, childList) { ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); ok(childList.includes(PREF), `Child list includes "${PREF}"`); prefs.clearUserPref(PREF); ok( !getPrefList().includes(PREF), `Parent list doesn't include "${PREF}"` ); }, contentUpdate1(PREF, val, childList) { ok( !getPrefList().includes(PREF), `Parent list doesn't include "${PREF}"` ); ok(!childList.includes(PREF), `Child list doesn't include "${PREF}"`); prefs.setCharPref(PREF, "foo"); ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); checkPref(PREF, "parent", getPref(PREF), "String", "foo"); }, contentUpdate2(PREF, val, childList) { ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); ok(childList.includes(PREF), `Child list includes "${PREF}"`); checkPref(PREF, "parent", getPref(PREF), "String", "foo"); checkPref(PREF, "child", val, "String", "foo"); }, }, "doesNotExists.thenDoes": { contentStartup(PREF, val, childList) { ok( !getPrefList().includes(PREF), `Parent list doesn't include "${PREF}"` ); ok(!childList.includes(PREF), `Child list doesn't include "${PREF}"`); prefs.setIntPref(PREF, 42); ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); }, contentUpdate1(PREF, val, childList) { ok(getPrefList().includes(PREF), `Parent list includes "${PREF}"`); ok(childList.includes(PREF), `Child list includes "${PREF}"`); checkPref(PREF, "parent", getPref(PREF), "Int", 42); checkPref(PREF, "child", val, "Int", 42); }, }, }; const PREFS = [ { type: "Bool", values: [true, false, true] }, { type: "Int", values: [24, 42, 73] }, { type: "String", values: ["meh", "hem", "hrm"] }, ]; for (let { type, values } of PREFS) { let set = `set${type}Pref`; function prefTest(opts) { function check( pref, proc, val, { expectedVal, defaultVal = undefined, expectedDefault = defaultVal, expectedFlags = {}, } ) { checkPref( pref, proc, val, type, expectedVal, expectedDefault, expectedFlags ); } function updatePref( PREF, { userVal = undefined, defaultVal = undefined, flags = {} } ) { info(`Update "${PREF}"`); if (userVal !== undefined) { prefs[set](PREF, userVal); } if (defaultVal !== undefined) { defaultPrefs[set](PREF, defaultVal); } if (flags.locked === true) { prefs.lockPref(PREF); } else if (flags.locked === false) { prefs.unlockPref(PREF); } } return { beforeContent(PREF) { updatePref(PREF, opts.initial); check(PREF, "parent", getPref(PREF), opts.initial); }, contentStartup(PREF, contentVal) { check(PREF, "content", contentVal, opts.initial); check(PREF, "parent", getPref(PREF), opts.initial); updatePref(PREF, opts.change1); check(PREF, "parent", getPref(PREF), opts.change1); }, contentUpdate1(PREF, contentVal) { check(PREF, "content", contentVal, opts.change1); check(PREF, "parent", getPref(PREF), opts.change1); if (opts.change2) { updatePref(PREF, opts.change2); check(PREF, "parent", getPref(PREF), opts.change2); } }, contentUpdate2(PREF, contentVal) { if (opts.change2) { check(PREF, "content", contentVal, opts.change2); check(PREF, "parent", getPref(PREF), opts.change2); } }, }; } for (let i of [0, 1]) { let userVal = values[i]; let defaultVal = values[+!i]; TESTS[`type.${type}.${i}.default`] = prefTest({ initial: { defaultVal, expectedVal: defaultVal }, change1: { defaultVal: values[2], expectedVal: values[2] }, }); TESTS[`type.${type}.${i}.user`] = prefTest({ initial: { userVal, expectedVal: userVal }, change1: { defaultVal: values[2], expectedVal: userVal }, change2: { userVal: values[2], expectedDefault: values[2], expectedVal: values[2], }, }); TESTS[`type.${type}.${i}.both`] = prefTest({ initial: { userVal, defaultVal, expectedVal: userVal }, change1: { defaultVal: values[2], expectedVal: userVal }, change2: { userVal: values[2], expectedDefault: values[2], expectedVal: values[2], }, }); TESTS[`type.${type}.${i}.both.thenLock`] = prefTest({ initial: { userVal, defaultVal, expectedVal: userVal }, change1: { expectedDefault: defaultVal, expectedVal: defaultVal, flags: { locked: true }, expectFlags: { locked: true }, }, }); TESTS[`type.${type}.${i}.both.thenUnlock`] = prefTest({ initial: { userVal, defaultVal, expectedVal: defaultVal, flags: { locked: true }, expectedFlags: { locked: true }, }, change1: { expectedDefault: defaultVal, expectedVal: userVal, flags: { locked: false }, expectFlags: { locked: false }, }, }); TESTS[`type.${type}.${i}.both.locked`] = prefTest({ initial: { userVal, defaultVal, expectedVal: defaultVal, flags: { locked: true }, expectedFlags: { locked: true }, }, change1: { userVal: values[2], expectedDefault: defaultVal, expectedVal: defaultVal, expectedFlags: { locked: true }, }, change2: { defaultVal: values[2], expectedDefault: defaultVal, expectedVal: defaultVal, expectedFlags: { locked: true }, }, }); } } add_task(async function test_sharedMap_prefs() { let prefValues = {}; async function runChecks(op) { for (let [pref, ops] of Object.entries(TESTS)) { if (ops[op]) { info(`Running ${op} for "${pref}"`); await ops[op]( pref, prefValues[pref] || undefined, prefValues.childList || undefined ); } } } await runChecks("beforeContent"); contentPage = await XPCShellContentUtils.loadContentPage("about:blank", { remote: true, }); registerCleanupFunction(() => contentPage.close()); contentPage.addFrameScriptHelper(FRAME_SCRIPT_INIT); contentPage.addFrameScriptHelper(try_); contentPage.addFrameScriptHelper(getPref); let prefNames = Object.keys(TESTS); prefValues = await contentPage.legacySpawn(prefNames, getPrefs); await runChecks("contentStartup"); prefValues = await contentPage.legacySpawn(prefNames, getPrefs); await runChecks("contentUpdate1"); prefValues = await contentPage.legacySpawn(prefNames, getPrefs); await runChecks("contentUpdate2"); });