/* Any copyright is dedicated to the Public Domain. * http://creativecommons.org/publicdomain/zero/1.0/ */ "use strict"; requestLongerTimeout(4); const { EnterprisePolicyTesting, PoliciesPrefTracker } = ChromeUtils.importESModule( "resource://testing-common/EnterprisePolicyTesting.sys.mjs" ); ChromeUtils.defineESModuleGetters(this, { DoHConfigController: "resource:///modules/DoHConfig.sys.mjs", DoHController: "resource:///modules/DoHController.sys.mjs", DoHTestUtils: "resource://testing-common/DoHTestUtils.sys.mjs", }); const TRR_MODE_PREF = "network.trr.mode"; const TRR_URI_PREF = "network.trr.uri"; const TRR_CUSTOM_URI_PREF = "network.trr.custom_uri"; const ROLLOUT_ENABLED_PREF = "doh-rollout.enabled"; const ROLLOUT_SELF_ENABLED_PREF = "doh-rollout.self-enabled"; const HEURISTICS_DISABLED_PREF = "doh-rollout.disable-heuristics"; const FIRST_RESOLVER_VALUE = DoHTestUtils.providers[0].uri; const SECOND_RESOLVER_VALUE = DoHTestUtils.providers[1].uri; const DEFAULT_RESOLVER_VALUE = FIRST_RESOLVER_VALUE; const defaultPrefValues = Object.freeze({ [TRR_MODE_PREF]: 0, [TRR_CUSTOM_URI_PREF]: "", }); // See bug 1741554. This test should not actually try to create a connection to // the real DoH endpoint. But a background request could do that while the test // is in progress, before we've actually disabled TRR, and would cause a crash // due to connecting to a non-local IP. // To prevent that we override the IP to a local address. Cc["@mozilla.org/network/native-dns-override;1"] .getService(Ci.nsINativeDNSResolverOverride) .addIPOverride("mozilla.cloudflare-dns.com", "127.0.0.1"); async function clearEvents() { Services.telemetry.clearEvents(); await TestUtils.waitForCondition(() => { let events = Services.telemetry.snapshotEvents( Ci.nsITelemetry.DATASET_ALL_CHANNELS, true ).parent; return !events || !events.length; }); } async function getEvent(filter1, filter2) { let event = await TestUtils.waitForCondition(() => { let events = Services.telemetry.snapshotEvents( Ci.nsITelemetry.DATASET_ALL_CHANNELS, true ).parent; return events?.find(e => e[1] == filter1 && e[2] == filter2); }, "recorded telemetry for the load"); event.shift(); return event; } async function resetPrefs() { await DoHTestUtils.resetRemoteSettingsConfig(); await DoHController._uninit(); Services.prefs.clearUserPref(TRR_MODE_PREF); Services.prefs.clearUserPref(TRR_URI_PREF); Services.prefs.clearUserPref(TRR_CUSTOM_URI_PREF); Services.prefs.getChildList("doh-rollout.").forEach(pref => { Services.prefs.clearUserPref(pref); }); // Clear out any telemetry events generated by DoHController so that we don't // confuse tests running after this one that are looking at those. Services.telemetry.clearEvents(); await DoHController.init(); } Services.prefs.setStringPref("network.trr.confirmationNS", "skip"); registerCleanupFunction(async () => { await resetPrefs(); Services.prefs.clearUserPref("network.trr.confirmationNS"); }); add_setup(async function setup() { await SpecialPowers.pushPrefEnv({ set: [["toolkit.telemetry.testing.overrideProductsCheck", true]], }); await DoHTestUtils.resetRemoteSettingsConfig(); }); function waitForPrefObserver(name) { return new Promise(resolve => { const observer = { observe(aSubject, aTopic, aData) { if (aData == name) { Services.prefs.removeObserver(name, observer); resolve(); } }, }; Services.prefs.addObserver(name, observer); }); } async function testWithProperties(props, startTime) { info( Date.now() - startTime + ": testWithProperties: testing with " + JSON.stringify(props) ); // There are two different signals that the DoHController is ready, depending // on the config being tested. If we're setting the TRR mode pref, we should // expect the disable-heuristics pref to be set as the signal. Else, we can // expect the self-enabled pref as the signal. let rolloutReadyPromise; if (props.hasOwnProperty(TRR_MODE_PREF)) { if ( [2, 3, 5].includes(props[TRR_MODE_PREF]) && props.hasOwnProperty(ROLLOUT_ENABLED_PREF) ) { // Only initialize the promise if we're going to enable the rollout - // otherwise we will never await it, which could cause a leak if it doesn't // end up resolving. rolloutReadyPromise = waitForPrefObserver(HEURISTICS_DISABLED_PREF); } Services.prefs.setIntPref(TRR_MODE_PREF, props[TRR_MODE_PREF]); } if (props.hasOwnProperty(ROLLOUT_ENABLED_PREF)) { if (!rolloutReadyPromise) { rolloutReadyPromise = waitForPrefObserver(ROLLOUT_SELF_ENABLED_PREF); } Services.prefs.setBoolPref( ROLLOUT_ENABLED_PREF, props[ROLLOUT_ENABLED_PREF] ); await rolloutReadyPromise; } if (props.hasOwnProperty(TRR_CUSTOM_URI_PREF)) { Services.prefs.setStringPref( TRR_CUSTOM_URI_PREF, props[TRR_CUSTOM_URI_PREF] ); } if (props.hasOwnProperty(TRR_URI_PREF)) { Services.prefs.setStringPref(TRR_URI_PREF, props[TRR_URI_PREF]); } await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); let doc = gBrowser.selectedBrowser.contentDocument; info(Date.now() - startTime + ": testWithProperties: tab now open"); let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup"); let uriTextbox = doc.getElementById("dohEnabledInputField"); let resolverMenulist = doc.getElementById("dohStrictResolverChoices"); let modePrefChangedPromise; let uriPrefChangedPromise; let disableHeuristicsPrefChangedPromise; modeRadioGroup.scrollIntoView(); if (props.hasOwnProperty("expectedSelectedIndex")) { await TestUtils.waitForCondition( () => modeRadioGroup.selectedIndex === props.expectedSelectedIndex ); is( modeRadioGroup.selectedIndex, props.expectedSelectedIndex, "dohCategoryRadioGroup has expected selected index" ); } if (props.hasOwnProperty("expectedUriValue")) { await TestUtils.waitForCondition( () => uriTextbox.value === props.expectedUriValue ); is( uriTextbox.value, props.expectedUriValue, "URI textbox has expected value" ); } if (props.hasOwnProperty("expectedResolverListValue")) { await TestUtils.waitForCondition( () => resolverMenulist.value === props.expectedResolverListValue ); is( resolverMenulist.value, props.expectedResolverListValue, "resolver menulist has expected value" ); } if (props.clickMode) { await clearEvents(); info( Date.now() - startTime + ": testWithProperties: clickMode, waiting for the pref observer" ); modePrefChangedPromise = waitForPrefObserver(TRR_MODE_PREF); if (props.hasOwnProperty("expectedDisabledHeuristics")) { disableHeuristicsPrefChangedPromise = waitForPrefObserver( HEURISTICS_DISABLED_PREF ); } info( Date.now() - startTime + ": testWithProperties: clickMode, pref changed" ); let option = doc.getElementById(props.clickMode); option.scrollIntoView(); let win = doc.ownerGlobal; EventUtils.synthesizeMouseAtCenter(option, {}, win); info( `${Date.now() - startTime} : testWithProperties: clickMode=${ props.clickMode }, mouse click synthesized` ); let clickEvent = await getEvent("security.doh.settings", "mode_changed"); Assert.deepEqual(clickEvent, [ "security.doh.settings", "mode_changed", "button", props.clickMode, ]); } if (props.hasOwnProperty("selectResolver")) { await clearEvents(); info( Date.now() - startTime + ": testWithProperties: selectResolver, creating change event" ); resolverMenulist.focus(); resolverMenulist.value = props.selectResolver; resolverMenulist.dispatchEvent(new Event("input", { bubbles: true })); resolverMenulist.dispatchEvent(new Event("command", { bubbles: true })); info( Date.now() - startTime + ": testWithProperties: selectResolver, item value set and events dispatched" ); let choiceEvent = await getEvent( "security.doh.settings", "provider_choice" ); Assert.deepEqual(choiceEvent, [ "security.doh.settings", "provider_choice", "value", props.selectResolver, ]); } if (props.hasOwnProperty("inputUriKeys")) { info( Date.now() - startTime + ": testWithProperties: inputUriKeys, waiting for the pref observer" ); uriPrefChangedPromise = waitForPrefObserver(TRR_CUSTOM_URI_PREF); info( Date.now() - startTime + ": testWithProperties: inputUriKeys, pref changed, now enter the new value" ); let win = doc.ownerGlobal; uriTextbox.focus(); uriTextbox.value = props.inputUriKeys; uriTextbox.dispatchEvent(new win.Event("input", { bubbles: true })); uriTextbox.dispatchEvent(new win.Event("change", { bubbles: true })); info( Date.now() - startTime + ": testWithProperties: inputUriKeys, input and change events dispatched" ); } info( Date.now() - startTime + ": testWithProperties: waiting for any of uri and mode prefs to change" ); await Promise.all([ uriPrefChangedPromise, modePrefChangedPromise, disableHeuristicsPrefChangedPromise, ]); info(Date.now() - startTime + ": testWithProperties: prefs changed"); if (props.hasOwnProperty("expectedFinalUriPref")) { if (props.expectedFinalUriPref) { let uriPref = Services.prefs.getStringPref(TRR_URI_PREF); is( uriPref, props.expectedFinalUriPref, "uri pref ended up with the expected value" ); } else { ok( !Services.prefs.prefHasUserValue(TRR_URI_PREF), `uri pref ended up with the expected value (unset) got ${Services.prefs.getStringPref( TRR_URI_PREF )}` ); } } if (props.hasOwnProperty("expectedModePref")) { let modePref = Services.prefs.getIntPref(TRR_MODE_PREF); is( modePref, props.expectedModePref, "mode pref ended up with the expected value" ); } if (props.hasOwnProperty("expectedDisabledHeuristics")) { let disabledHeuristicsPref = Services.prefs.getBoolPref( HEURISTICS_DISABLED_PREF ); is( disabledHeuristicsPref, props.expectedDisabledHeuristics, "disable-heuristics pref ended up with the expected value" ); } if (props.hasOwnProperty("expectedFinalCustomUriPref")) { let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF); is( customUriPref, props.expectedFinalCustomUriPref, "custom_uri pref ended up with the expected value" ); } if (props.hasOwnProperty("expectedModeValue")) { let modeValue = Services.prefs.getIntPref(TRR_MODE_PREF); is(modeValue, props.expectedModeValue, "mode pref has expected value"); } gBrowser.removeCurrentTab(); info(Date.now() - startTime + ": testWithProperties: fin"); } add_task(async function default_values() { let customUriPref = Services.prefs.getStringPref(TRR_CUSTOM_URI_PREF); let uriPrefHasUserValue = Services.prefs.prefHasUserValue(TRR_URI_PREF); let modePref = Services.prefs.getIntPref(TRR_MODE_PREF); is( modePref, defaultPrefValues[TRR_MODE_PREF], `Actual value of ${TRR_MODE_PREF} matches expected default value` ); ok( !uriPrefHasUserValue, `Actual value of ${TRR_URI_PREF} matches expected default value (unset)` ); is( customUriPref, defaultPrefValues[TRR_CUSTOM_URI_PREF], `Actual value of ${TRR_CUSTOM_URI_PREF} matches expected default value` ); }); const DEFAULT_OPTION_INDEX = 0; const ENABLED_OPTION_INDEX = 1; const STRICT_OPTION_INDEX = 2; const OFF_OPTION_INDEX = 3; let testVariations = [ // verify state with defaults { name: "default", expectedModePref: 0, expectedSelectedIndex: DEFAULT_OPTION_INDEX, expectedUriValue: "", }, // verify each of the modes maps to the correct checked state { name: "mode 0", [TRR_MODE_PREF]: 0, expectedSelectedIndex: DEFAULT_OPTION_INDEX, }, { name: "mode 1", [TRR_MODE_PREF]: 1, expectedSelectedIndex: OFF_OPTION_INDEX, }, { name: "mode 2", [TRR_MODE_PREF]: 2, expectedSelectedIndex: ENABLED_OPTION_INDEX, expectedFinalUriPref: "", }, { name: "mode 3", [TRR_MODE_PREF]: 3, expectedSelectedIndex: STRICT_OPTION_INDEX, expectedFinalUriPref: "", }, { name: "mode 4", [TRR_MODE_PREF]: 4, expectedSelectedIndex: OFF_OPTION_INDEX, }, { name: "mode 5", [TRR_MODE_PREF]: 5, expectedSelectedIndex: OFF_OPTION_INDEX, }, // verify an out of bounds mode value maps to the correct checked state { name: "mode out-of-bounds", [TRR_MODE_PREF]: 77, expectedSelectedIndex: OFF_OPTION_INDEX, }, // verify automatic heuristics states { name: "heuristics on and mode unset", [TRR_MODE_PREF]: 0, [ROLLOUT_ENABLED_PREF]: true, expectedSelectedIndex: DEFAULT_OPTION_INDEX, }, { name: "heuristics on and mode set to 2", [TRR_MODE_PREF]: 2, [ROLLOUT_ENABLED_PREF]: true, expectedSelectedIndex: ENABLED_OPTION_INDEX, }, { name: "heuristics on but disabled, mode unset", [TRR_MODE_PREF]: 5, [ROLLOUT_ENABLED_PREF]: true, expectedSelectedIndex: OFF_OPTION_INDEX, }, { name: "heuristics on but disabled, mode set to 2", [TRR_MODE_PREF]: 2, [ROLLOUT_ENABLED_PREF]: true, expectedSelectedIndex: ENABLED_OPTION_INDEX, }, // verify picking each radio button option gives the right outcomes { name: "toggle mode on", clickMode: "dohEnabledRadio", expectedModeValue: 2, expectedUriValue: "", expectedFinalUriPref: "", }, { name: "toggle mode off", [TRR_MODE_PREF]: 2, expectedSelectedIndex: ENABLED_OPTION_INDEX, clickMode: "dohOffRadio", expectedModePref: 5, }, { name: "toggle mode off when on due to heuristics", [TRR_MODE_PREF]: 0, [ROLLOUT_ENABLED_PREF]: true, expectedSelectedIndex: DEFAULT_OPTION_INDEX, clickMode: "dohOffRadio", expectedModePref: 5, expectedDisabledHeuristics: true, }, // Test selecting non-default, non-custom TRR provider, NextDNS. { name: "Select NextDNS as TRR provider", [TRR_MODE_PREF]: 2, selectResolver: SECOND_RESOLVER_VALUE, expectedFinalUriPref: SECOND_RESOLVER_VALUE, }, // Test selecting non-default, non-custom TRR provider, NextDNS, // with DoH not enabled. The provider selection should stick. { name: "Select NextDNS as TRR provider in mode 0", [TRR_MODE_PREF]: 0, selectResolver: SECOND_RESOLVER_VALUE, expectedFinalUriPref: SECOND_RESOLVER_VALUE, }, { name: "return to default from NextDNS", [TRR_MODE_PREF]: 2, [TRR_URI_PREF]: SECOND_RESOLVER_VALUE, expectedResolverListValue: SECOND_RESOLVER_VALUE, selectResolver: DEFAULT_RESOLVER_VALUE, expectedFinalUriPref: FIRST_RESOLVER_VALUE, }, // test that selecting Custom, when we have a TRR_CUSTOM_URI_PREF subsequently changes TRR_URI_PREF { name: "select custom with existing custom_uri pref value", [TRR_MODE_PREF]: 2, [TRR_CUSTOM_URI_PREF]: "https://example.com", expectedModeValue: 2, expectedSelectedIndex: ENABLED_OPTION_INDEX, selectResolver: "custom", expectedUriValue: "https://example.com", expectedFinalUriPref: "https://example.com", expectedFinalCustomUriPref: "https://example.com", }, { name: "select custom and enter new custom_uri pref value", [TRR_URI_PREF]: "", [TRR_CUSTOM_URI_PREF]: "", clickMode: "dohEnabledRadio", selectResolver: "custom", inputUriKeys: "https://custom.com", expectedModePref: 2, expectedFinalUriPref: "https://custom.com", expectedFinalCustomUriPref: "https://custom.com", }, { name: "return to default from custom", [TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com", [TRR_CUSTOM_URI_PREF]: "https://custom.com", expectedUriValue: "https://example.com", expectedResolverListValue: "custom", selectResolver: DEFAULT_RESOLVER_VALUE, expectedFinalUriPref: DEFAULT_RESOLVER_VALUE, expectedFinalCustomUriPref: "https://example.com", }, { name: "clear the custom uri", [TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com", [TRR_CUSTOM_URI_PREF]: "https://example.com", expectedUriValue: "https://example.com", expectedResolverListValue: "custom", inputUriKeys: "", expectedFinalUriPref: " ", expectedFinalCustomUriPref: "", }, { name: "empty default resolver list", [TRR_MODE_PREF]: 2, [TRR_URI_PREF]: "https://example.com", [TRR_CUSTOM_URI_PREF]: "", expectedUriValue: "https://example.com", expectedResolverListValue: "custom", expectedFinalUriPref: "https://example.com", expectedFinalCustomUriPref: "https://example.com", }, ]; for (let props of testVariations) { add_task(async function testVariation() { let startTime = Date.now(); info("starting test: " + props.name); await testWithProperties(props, startTime); await resetPrefs(); }); } add_task(async function testRemoteSettingsEnable() { let startTime = Date.now(); // Enable the rollout. await DoHTestUtils.loadRemoteSettingsConfig({ providers: "example-1, example-2", rolloutEnabled: true, steeringEnabled: false, steeringProviders: "", autoDefaultEnabled: false, autoDefaultProviders: "", id: "global", }); await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); let doc = gBrowser.selectedBrowser.contentDocument; info(Date.now() - startTime + ": testWithProperties: tab now open"); let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup"); is(modeRadioGroup.value, "0", "expecting default mode"); let status = doc.getElementById("dohStatus"); await TestUtils.waitForCondition( () => document.l10n.getAttributes(status).args.status == "Active" ); is( document.l10n.getAttributes(status).args.status, "Active", "expecting status active" ); let provider = doc.getElementById("dohResolver"); is( provider.hidden, false, "Provider should not be hidden when status is active" ); await TestUtils.waitForCondition( () => document.l10n.getAttributes(provider).args.name == DoHConfigController.currentConfig.providerList[0].UIName ); is( document.l10n.getAttributes(provider).args.name, DoHConfigController.currentConfig.providerList[0].UIName, "expecting the right provider name" ); let option = doc.getElementById("dohEnabledRadio"); option.scrollIntoView(); let win = doc.ownerGlobal; EventUtils.synthesizeMouseAtCenter(option, {}, win); await TestUtils.waitForCondition(() => Services.prefs.prefHasUserValue("doh-rollout.disable-heuristics") ); is(provider.hidden, false); await TestUtils.waitForCondition( () => document.l10n.getAttributes(provider).args.name == DoHConfigController.currentConfig.providerList[0].UIName ); is( document.l10n.getAttributes(provider).args.name, DoHConfigController.currentConfig.providerList[0].UIName, "expecting the right provider name" ); is( Services.prefs.getIntPref("network.trr.mode"), Ci.nsIDNSService.MODE_TRRFIRST ); option = doc.getElementById("dohOffRadio"); option.scrollIntoView(); win = doc.ownerGlobal; EventUtils.synthesizeMouseAtCenter(option, {}, win); await TestUtils.waitForCondition(() => status.innerHTML == "Status: Off"); is( Services.prefs.getIntPref("network.trr.mode"), Ci.nsIDNSService.MODE_TRROFF ); is(provider.hidden, true, "Expecting provider to be hidden when DoH is off"); gBrowser.removeCurrentTab(); }); add_task(async function testEnterprisePolicy() { async function withPolicy(policy, fn, preFn = () => {}) { await resetPrefs(); PoliciesPrefTracker.start(); await EnterprisePolicyTesting.setupPolicyEngineWithJson(policy); await preFn(); await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); let doc = gBrowser.selectedBrowser.contentDocument; let modeRadioGroup = doc.getElementById("dohCategoryRadioGroup"); let resolverMenulist = doc.getElementById("dohEnabledResolverChoices"); let uriTextbox = doc.getElementById("dohEnabledInputField"); await fn({ modeRadioGroup, resolverMenulist, doc, uriTextbox, }); gBrowser.removeCurrentTab(); EnterprisePolicyTesting.resetRunOnceState(); PoliciesPrefTracker.stop(); } info("Check that a locked policy does not allow any changes in the UI"); await withPolicy( { policies: { DNSOverHTTPS: { Enabled: true, ProviderURL: "https://examplelocked.com/provider", ExcludedDomains: ["examplelocked.com", "example.org"], Locked: true, }, }, }, async res => { is(res.modeRadioGroup.disabled, true, "The mode menu should be locked."); is(res.modeRadioGroup.value, "2", "Should be enabled"); is(res.resolverMenulist.value, "custom", "Resolver list shows custom"); is( res.uriTextbox.value, "https://examplelocked.com/provider", "Custom URI should be set" ); } ); info("Check that an unlocked policy has editable fields in the dialog"); await withPolicy( { policies: { DNSOverHTTPS: { Enabled: true, ProviderURL: "https://example.com/provider", ExcludedDomains: ["example.com", "example.org"], }, }, }, async res => { is( res.modeRadioGroup.disabled, false, "The mode menu should not be locked." ); is(res.modeRadioGroup.value, "2", "Should be enabled"); is(res.resolverMenulist.value, "custom", "Resolver list shows custom"); is( res.uriTextbox.value, "https://example.com/provider", "Expected custom resolver" ); } ); info("Check that a locked disabled policy disables the buttons"); await withPolicy( { policies: { DNSOverHTTPS: { Enabled: false, ProviderURL: "https://example.com/provider", ExcludedDomains: ["example.com", "example.org"], Locked: true, }, }, }, async res => { is(res.modeRadioGroup.disabled, true, "The mode menu should be locked."); is(res.modeRadioGroup.value, "5", "Should be disabled"); } ); info("Check that an unlocked disabled policy has editable fields"); await withPolicy( { policies: { DNSOverHTTPS: { Enabled: false, ProviderURL: "https://example.com/provider", ExcludedDomains: ["example.com", "example.org"], }, }, }, async res => { is( res.modeRadioGroup.disabled, false, "The mode menu should not be locked." ); is(res.modeRadioGroup.value, "5", "Should be disabled"); } ); info("Check that the remote settings config doesn't override the policy"); await withPolicy( { policies: { DNSOverHTTPS: { Enabled: true, ProviderURL: "https://example.com/provider", ExcludedDomains: ["example.com", "example.org"], }, }, }, async res => { is( res.modeRadioGroup.disabled, false, "The mode menu should not be locked." ); is(res.resolverMenulist.value, "custom", "Resolver list shows custom"); is( res.uriTextbox.value, "https://example.com/provider", "Expected custom resolver" ); }, async function runAfterSettingPolicy() { await DoHTestUtils.loadRemoteSettingsConfig({ providers: "example-1, example-2", rolloutEnabled: true, steeringEnabled: false, steeringProviders: "", autoDefaultEnabled: false, autoDefaultProviders: "", id: "global", }); } ); }); add_task(async function clickWarnButton() { Services.prefs.setBoolPref( "network.trr_ui.show_fallback_warning_option", true ); await openPreferencesViaOpenPreferencesAPI("privacy", { leaveOpen: true }); let doc = gBrowser.selectedBrowser.contentDocument; await clearEvents(); let checkbox = doc.getElementById("dohWarnCheckbox1"); checkbox.click(); let event = await getEvent("security.doh.settings", "warn_checkbox"); Assert.deepEqual(event, [ "security.doh.settings", "warn_checkbox", "checkbox", "true", ]); Assert.equal( Services.prefs.getBoolPref("network.trr.display_fallback_warning"), true, "Clicking the checkbox should change the pref" ); checkbox.click(); event = await getEvent("security.doh.settings", "warn_checkbox"); Assert.deepEqual(event, [ "security.doh.settings", "warn_checkbox", "checkbox", "false", ]); Assert.equal( Services.prefs.getBoolPref("network.trr.display_fallback_warning"), false, "Clicking the checkbox should change the pref" ); gBrowser.removeCurrentTab(); });