tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

test_setting_control_extension_controlled.html (31593B)


      1 <!doctype html>
      2 <html>
      3  <head>
      4    <meta charset="utf-8" />
      5    <title>setting-control extension controlled tests</title>
      6    <style>
      7      /* Force text color to ensure settings controls unit tested by
      8         this test are visible (eg. to aid investigating test failures
      9         that may only be hit in CI). */
     10      body {
     11        color: black;
     12      }
     13    </style>
     14    <script src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
     15    <link
     16      rel="stylesheet"
     17      href="chrome://mochikit/content/tests/SimpleTest/test.css"
     18    />
     19    <script src="chrome://mochikit/content/tests/SimpleTest/EventUtils.js"></script>
     20    <script src="../../../../../toolkit/content/tests/widgets/lit-test-helpers.js"></script>
     21    <script src="./head.js"></script>
     22    <script
     23      type="module"
     24      src="chrome://browser/content/preferences/widgets/setting-group.mjs"
     25    ></script>
     26    <script
     27      type="module"
     28      src="chrome://browser/content/preferences/widgets/setting-control.mjs"
     29    ></script>
     30    <script
     31      type="module"
     32      src="chrome://global/content/elements/moz-message-bar.mjs"
     33    ></script>
     34    <script
     35      type="application/javascript"
     36      src="chrome://global/content/preferencesBindings.js"
     37    ></script>
     38    <script src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
     39    <script>
     40      const { Assert } = ChromeUtils.importESModule(
     41        "resource://testing-common/Assert.sys.mjs"
     42      );
     43      // Import the ExtensionSettingsStore and AddonManager as we need
     44      // them to correctly disable the extension on click and re-enable
     45      // the extension in the same task.
     46      ChromeUtils.defineESModuleGetters(this, {
     47        ExtensionSettingsStore:
     48          "resource://gre/modules/ExtensionSettingsStore.sys.mjs",
     49        AddonManager: "resource://gre/modules/AddonManager.sys.mjs",
     50        BrowserWindowTracker:
     51          "resource:///modules/BrowserWindowTracker.sys.mjs",
     52      });
     53 
     54      /* import-globals-from /toolkit/content/preferencesBindings.js */
     55      let html, testHelpers;
     56 
     57      const PREF_ID = "test.setting-control.bar";
     58      async function renderTemplate(itemConfig) {
     59        let config = {
     60          items: [itemConfig],
     61        };
     62        let result = await testHelpers.renderTemplate(html`
     63          <setting-group
     64            .config=${config}
     65            .getSetting=${(...args) => Preferences.getSetting(...args)}
     66          ></setting-group>
     67        `);
     68        if (document.hasPendingL10nMutations) {
     69          await BrowserTestUtils.waitForEvent(
     70            document,
     71            "L10nMutationsFinished"
     72          );
     73        }
     74        return result.querySelector("setting-control");
     75      }
     76 
     77      async function disableExtensionByMouse(elem) {
     78        await synthesizeMouseAtCenter(elem, {});
     79      }
     80 
     81      const getExtensionControlledMessageBar = control =>
     82        control.querySelector(".extension-controlled-message-bar");
     83      const getReenableExtensionMessageBar = control =>
     84        control.querySelector(".reenable-extensions-message-bar");
     85 
     86      add_setup(async function setup() {
     87        testHelpers = new InputTestHelpers();
     88        ({ html } = await testHelpers.setupLit());
     89        testHelpers.setupTests({
     90          templateFn: () => html`<setting-group></setting-group>`,
     91        });
     92        MozXULElement.insertFTLIfNeeded("branding/brand.ftl");
     93        MozXULElement.insertFTLIfNeeded("browser/preferences/preferences.ftl");
     94        Preferences.addAll([{ id: PREF_ID }]);
     95        // Add mock Fluent source to define the test-fluent-id l10n id
     96        // used in these tests.
     97        const mockL10nSource = L10nFileSource.createMock(
     98          "test",
     99          "app",
    100          ["en-US"],
    101          "/localization/{locale}/",
    102          [
    103            {
    104              path: "/localization/en-US/mock.ftl",
    105              source:
    106                "test-fluent-id =\n .label = TestLabel\n .description = TestDescription",
    107            },
    108          ]
    109        );
    110        L10nRegistry.getInstance().registerSources([mockL10nSource]);
    111        MozXULElement.insertFTLIfNeeded("mock.ftl");
    112        SimpleTest.registerCleanupFunction(() => {
    113          Preferences.onUnload();
    114          SpecialPowers.popPrefEnv();
    115        });
    116      });
    117 
    118      add_task(async function testExtensionControlledConfigBasedControl() {
    119        // Setup pre-test items: extension, Preferences, ExtensionSettingStore
    120        const SETTING_ID = "extension-controlled-setting";
    121        const ADDON_ID = "ext-controlled@mochi.test";
    122        const ADDON_NAME = "Ext Controlled";
    123        const STORE_ID = "privacy.containers";
    124        const TEST_FLUENT_ID = "test-fluent-id";
    125        const SUPPORT_PAGE = "support-test";
    126        const SUPPORT_URL = Services.prefs.getStringPref("app.support.baseURL");
    127        await SpecialPowers.pushPrefEnv({
    128          set: [[PREF_ID, false]],
    129        });
    130        let extension = ExtensionTestUtils.loadExtension({
    131          manifest: {
    132            browser_specific_settings: { gecko: { id: ADDON_ID } },
    133            name: ADDON_NAME,
    134            permissions: ["contextualIdentities", "cookies"],
    135          },
    136          // We need to be able to find the extension using AddonManager.
    137          useAddonManager: "temporary",
    138        });
    139        await extension.startup();
    140 
    141        // Assert there is no markup that is generated by pre-test setup since we
    142        // don't have a setting that is being controlled by the STORE_ID
    143 
    144        let settingControl = document.getElementById(SETTING_ID);
    145        is(
    146          settingControl,
    147          null,
    148          "The setting control under test should not exist yet."
    149        );
    150 
    151        // Add setting that is being controlled by our test extension
    152        Preferences.addSetting({
    153          id: SETTING_ID,
    154          pref: PREF_ID,
    155          controllingExtensionInfo: {
    156            storeId: STORE_ID,
    157            l10nId: TEST_FLUENT_ID,
    158            supportPage: SUPPORT_PAGE,
    159          },
    160        });
    161 
    162        // Create itemConfig for expected setting-control element
    163        let itemConfig = {
    164          l10nId: "test-fluent-id",
    165          id: SETTING_ID,
    166        };
    167 
    168        // Wait for setting-control element to be rendered
    169        let setting = Preferences.getSetting(SETTING_ID);
    170        let control = await renderTemplate(itemConfig, setting);
    171 
    172        // Checking if the setting is controlled by an extension
    173        // and retrieve the addon details to be shown is asynchronous
    174        // and so there is a chance the control may not be immediately
    175        // disabled right after being created.
    176        await TestUtils.waitForCondition(
    177          () => control.controlEl.disabled,
    178          "Wait for the control to become disabled as a side-effect of controlling extension metadata to be found"
    179        );
    180 
    181        // Assert checkbox control is created and disabled due to extension controlling the pref
    182        is(
    183          control.controlEl.localName,
    184          "moz-checkbox",
    185          "The control rendered the default checkbox"
    186        );
    187        is(
    188          control.controlEl.disabled,
    189          true,
    190          "The control should be disabled since it is controlled by an extension"
    191        );
    192 
    193        // Assert that moz-message-bar appears with the correct Fluent attributes/args
    194        let messageBar = control.querySelector("moz-message-bar");
    195        ok(
    196          messageBar,
    197          "There should be an extension controlled message bar element"
    198        );
    199 
    200        is(
    201          messageBar.messageL10nId,
    202          TEST_FLUENT_ID,
    203          "The l10nId should be the same as the one in the config"
    204        );
    205        is(
    206          messageBar.messageL10nArgs.name,
    207          ADDON_NAME,
    208          "The name used within the message-bar should be the extension name"
    209        );
    210 
    211        // Assert that moz-message-bar appears with an actions button with correct Fluent attributes
    212        let disableExtensionButton = messageBar.querySelector(
    213          "moz-button[slot='actions']"
    214        );
    215        ok(
    216          disableExtensionButton,
    217          "There should be a button to disable the extension"
    218        );
    219        is(
    220          disableExtensionButton.getAttribute("data-l10n-id"),
    221          "disable-extension",
    222          "The disable extension button should have the correct data-l10n-id"
    223        );
    224 
    225        // Assert that moz-message-bar appears with a support link with correct attributes
    226        let supportLink = messageBar.querySelector("a[slot='support-link']");
    227        ok(supportLink, "There should be a slotted support link element");
    228        is(
    229          supportLink.getAttribute("support-page"),
    230          SUPPORT_PAGE,
    231          "The support link should have the correct value for 'support-page'"
    232        );
    233        is(
    234          supportLink.href,
    235          `${SUPPORT_URL}${SUPPORT_PAGE}`,
    236          "The support link should have the correct generated href value"
    237        );
    238 
    239        // Unload the test extension and remove the setting-control element
    240        await extension.unload();
    241        control.remove();
    242      });
    243 
    244      add_task(async function testDisablingExtensionControlledSetting() {
    245        // Setup pre-test items like extension, AddonManager, ExtensionSettingStore
    246        const SETTING_ID = "extension-controlled-setting";
    247        const ADDON_ID = "ext-controlled@mochi.test";
    248        const ADDON_NAME = "Ext Controlled";
    249        const STORE_ID = "privacy.containers";
    250        const TEST_FLUENT_ID = "test-fluent-id";
    251        await SpecialPowers.pushPrefEnv({
    252          set: [[PREF_ID, false]],
    253        });
    254        let extension = ExtensionTestUtils.loadExtension({
    255          manifest: {
    256            browser_specific_settings: { gecko: { id: ADDON_ID } },
    257            name: ADDON_NAME,
    258            permissions: ["contextualIdentities", "cookies"],
    259          },
    260          // We need to be able to find the extension using AddonManager.
    261          useAddonManager: "temporary",
    262        });
    263        await extension.startup();
    264 
    265        // We need to get the addon via the AddonManager to re-enable it later
    266        let addon = await AddonManager.getAddonByID(ADDON_ID);
    267        await ExtensionSettingsStore.initialize();
    268 
    269        // Assert there is no markup that is generated by pre-test setup since we
    270        // don't have a setting that is being controlled by the STORE_ID
    271 
    272        let settingControl = document.getElementById(SETTING_ID);
    273        is(
    274          settingControl,
    275          null,
    276          "The setting control under test should not exist yet."
    277        );
    278 
    279        // Add setting that is being controlled by our test extension
    280        Preferences.addSetting({
    281          id: SETTING_ID,
    282          pref: PREF_ID,
    283          controllingExtensionInfo: {
    284            storeId: STORE_ID,
    285            l10nId: TEST_FLUENT_ID,
    286          },
    287        });
    288 
    289        // Create itemConfig for expected setting-control element
    290        let itemConfig = {
    291          l10nId: "test-fluent-id",
    292          id: SETTING_ID,
    293        };
    294 
    295        // Wait for setting-control element to be rendered
    296        let setting = Preferences.getSetting(SETTING_ID);
    297        let control = await renderTemplate(itemConfig, setting);
    298        // Assert that moz-message-bar appears with the correct Fluent attributes/args
    299        let extensionControlledMessageBar =
    300          getExtensionControlledMessageBar(control);
    301 
    302        ok(
    303          extensionControlledMessageBar,
    304          "There should be an extension controlled message bar element"
    305        );
    306 
    307        is(
    308          extensionControlledMessageBar.messageL10nId,
    309          TEST_FLUENT_ID,
    310          "The l10nId should be the same as the one in the config"
    311        );
    312        is(
    313          extensionControlledMessageBar.messageL10nArgs.name,
    314          ADDON_NAME,
    315          "The name used within the message-bar should be the extension name"
    316        );
    317 
    318        // Assert that moz-message-bar appears with an actions button with correct Fluent attributes
    319        let disableExtensionButton =
    320          extensionControlledMessageBar.querySelector(
    321            "moz-button[slot='actions']"
    322          );
    323        ok(
    324          disableExtensionButton,
    325          "There should be a button to disable the extension"
    326        );
    327        is(
    328          disableExtensionButton.getAttribute("data-l10n-id"),
    329          "disable-extension",
    330          "The disable extension button should have the correct data-l10n-id"
    331        );
    332 
    333        await disableExtensionByMouse(disableExtensionButton);
    334 
    335        await TestUtils.waitForCondition(
    336          () =>
    337            !control.isDisablingExtension &&
    338            !getExtensionControlledMessageBar(control),
    339          "Wait for the extension controlled message bar to be removed after disabling the only controlling extension"
    340        );
    341 
    342        await TestUtils.waitForCondition(
    343          () => getReenableExtensionMessageBar(control),
    344          "Wait for the re-enable extension message bar to be rendered"
    345        );
    346        let enableExtensionMessageBar = getReenableExtensionMessageBar(control);
    347        ok(
    348          enableExtensionMessageBar,
    349          "there should be a message bar for re-enabling extensions"
    350        );
    351 
    352        let addonsLink = enableExtensionMessageBar.querySelector("[slot] a");
    353        is(
    354          enableExtensionMessageBar
    355            .querySelector("[slot]")
    356            .getAttribute("data-l10n-id"),
    357          "extension-controlled-enable-2",
    358          "The re-enable extension message bar should have a slot with the correct data-l10n-id"
    359        );
    360 
    361        is(
    362          addonsLink.getAttribute("data-l10n-name"),
    363          "addons-link",
    364          "The slot within the re-enable extension bar should have the correct data-l10n-name"
    365        );
    366 
    367        await addon.enable();
    368        await TestUtils.waitForCondition(
    369          () => control.controlEl.disabled,
    370          "Wait for the control to become disabled as a side-effect of enabling the controlling extension"
    371        );
    372 
    373        await TestUtils.waitForCondition(
    374          () => !getReenableExtensionMessageBar(control),
    375          "The re-enable extension message bar should be removed when the extension is enabled again."
    376        );
    377        extensionControlledMessageBar =
    378          getExtensionControlledMessageBar(control);
    379        ok(
    380          extensionControlledMessageBar,
    381          "The extension controlled message bar should be rendered when the controlling addon is enabled elsewhere."
    382        );
    383 
    384        await addon.disable();
    385        await TestUtils.waitForCondition(
    386          () => !control.controlEl.disabled,
    387          "Wait for the control to become mutable as a side-effect of disabling the controlling extension"
    388        );
    389        enableExtensionMessageBar = getReenableExtensionMessageBar(control);
    390        is(
    391          enableExtensionMessageBar
    392            .querySelector("[slot]")
    393            .getAttribute("data-l10n-id"),
    394          "extension-controlled-enable-2",
    395          "The re-enable extension message bar should have a slot with the correct data-l10n-id"
    396        );
    397        ok(
    398          !getExtensionControlledMessageBar(control),
    399          "The message bar should not be rendered when the controlling addon is disabled elsewhere."
    400        );
    401 
    402        await addon.enable();
    403        await TestUtils.waitForCondition(
    404          () => control.controlEl.disabled,
    405          "Wait for the control to become disabled as a side-effect of enabling the controlling extension"
    406        );
    407 
    408        control.focus();
    409        // Assert that action button can be navigated to with keyboard
    410        await synthesizeKey("KEY_Tab", {});
    411        // Assert that action button can be activated with keyboard
    412        await synthesizeKey(" ");
    413 
    414        await TestUtils.waitForCondition(
    415          () => !control.isDisablingExtension && !control.controlEl.disabled,
    416          "Wait for the control to become mutable as a side-effect of disabling the controlling extension"
    417        );
    418        extensionControlledMessageBar =
    419          getExtensionControlledMessageBar(control);
    420        ok(
    421          !extensionControlledMessageBar,
    422          "The extension controlled message bar should not be rendered after hitting Space on the disable extension button."
    423        );
    424 
    425        enableExtensionMessageBar = getReenableExtensionMessageBar(control);
    426        ok(
    427          enableExtensionMessageBar,
    428          "The message bar to re-enable the extension should be rendered"
    429        );
    430 
    431        // Unload the test extension and remove the setting control
    432        await extension.unload();
    433        control.remove();
    434      });
    435 
    436      add_task(async function testMultipleControllingExtensions() {
    437        const SETTING_ID = "extension-controlled-setting";
    438        const ADDON_ID = "ext-controlled@mochi.test";
    439        const ADDON_NAME = "Ext Controlled";
    440        const STORE_ID = "privacy.containers";
    441        const TEST_FLUENT_ID = "test-fluent-id";
    442        await SpecialPowers.pushPrefEnv({
    443          set: [[PREF_ID, false]],
    444        });
    445        let extension = ExtensionTestUtils.loadExtension({
    446          manifest: {
    447            browser_specific_settings: { gecko: { id: ADDON_ID } },
    448            name: ADDON_NAME,
    449            permissions: ["contextualIdentities", "cookies"],
    450          },
    451          // We need to be able to find the extension using AddonManager.
    452          useAddonManager: "temporary",
    453        });
    454        await extension.startup();
    455 
    456        const ADDON_ID_2 = "ext-controlled2@mochi.test";
    457        const ADDON_NAME_2 = "Ext Controlled 2";
    458 
    459        let secondExtension = ExtensionTestUtils.loadExtension({
    460          manifest: {
    461            browser_specific_settings: { gecko: { id: ADDON_ID_2 } },
    462            name: ADDON_NAME_2,
    463            permissions: [
    464              "browserSettings",
    465              "contextualIdentities",
    466              "privacy",
    467              "proxy",
    468              "storage",
    469              "<all_urls>",
    470            ],
    471          },
    472          useAddonManager: "temporary",
    473        });
    474        await secondExtension.startup();
    475 
    476        // Assert there is no markup that is generated by pre-test setup since we
    477        // don't have a setting that is being controlled by the STORE_ID
    478 
    479        let settingControl = document.getElementById(SETTING_ID);
    480        is(
    481          settingControl,
    482          null,
    483          "The setting control under test should not exist yet."
    484        );
    485 
    486        // Add setting that is being controlled by both our test extensions
    487        Preferences.addSetting({
    488          id: SETTING_ID,
    489          pref: PREF_ID,
    490          controllingExtensionInfo: {
    491            storeId: STORE_ID,
    492            l10nId: TEST_FLUENT_ID,
    493          },
    494        });
    495 
    496        // Create itemConfig for expected setting-control element
    497        let itemConfig = {
    498          l10nId: "test-fluent-id",
    499          id: SETTING_ID,
    500        };
    501 
    502        // Wait for setting-control element to be rendered
    503        let setting = Preferences.getSetting(SETTING_ID);
    504        let control = await renderTemplate(itemConfig, setting);
    505 
    506        // Assert checkbox control is created and disabled due to extensions controlling the pref
    507        is(
    508          control.controlEl.localName,
    509          "moz-checkbox",
    510          "The control rendered the default checkbox"
    511        );
    512        is(
    513          control.controlEl.disabled,
    514          true,
    515          "The control should be disabled since it is controlled by an extension"
    516        );
    517 
    518        // Assert that moz-message-bar appears with the correct Fluent attributes/args
    519        let extensionControlledMessageBar =
    520          getExtensionControlledMessageBar(control);
    521        ok(
    522          extensionControlledMessageBar,
    523          "There should be an extension controlled message bar element"
    524        );
    525        is(
    526          extensionControlledMessageBar.messageL10nId,
    527          TEST_FLUENT_ID,
    528          "The l10nId should be the same as the one in the config"
    529        );
    530        is(
    531          extensionControlledMessageBar.messageL10nArgs.name,
    532          ADDON_NAME_2,
    533          "The name used within the message-bar should be the most recently enabled extension name"
    534        );
    535 
    536        // Assert that moz-message-bar appears with an actions button with correct Fluent attributes
    537        let disableExtensionButton =
    538          extensionControlledMessageBar.querySelector(
    539            "moz-button[slot='actions']"
    540          );
    541        ok(
    542          disableExtensionButton,
    543          "There should be a button to disable the extension"
    544        );
    545        is(
    546          disableExtensionButton.getAttribute("data-l10n-id"),
    547          "disable-extension",
    548          "The disable extension button should have the correct data-l10n-id"
    549        );
    550 
    551        await disableExtensionByMouse(disableExtensionButton);
    552 
    553        await TestUtils.waitForCondition(
    554          () =>
    555            !control.isDisablingExtension &&
    556            getExtensionControlledMessageBar(control)?.messageL10nArgs.name ===
    557              ADDON_NAME,
    558          "Wait for the message bar to be refreshed after disabling the controlling extension"
    559        );
    560 
    561        extensionControlledMessageBar =
    562          getExtensionControlledMessageBar(control);
    563        disableExtensionButton = extensionControlledMessageBar.querySelector(
    564          "moz-button[slot='actions']"
    565        );
    566        ok(
    567          extensionControlledMessageBar,
    568          "The message bar should still be present due to multiple extensions controlling the setting"
    569        );
    570 
    571        is(
    572          extensionControlledMessageBar.messageL10nArgs.name,
    573          ADDON_NAME,
    574          "The name used within the message-bar should be the oldest enabled extension name"
    575        );
    576 
    577        is(
    578          control.controlEl.disabled,
    579          true,
    580          "The control element should still be disabled since there is still a controlling extension"
    581        );
    582 
    583        await disableExtensionByMouse(disableExtensionButton);
    584 
    585        await TestUtils.waitForCondition(
    586          () => !getExtensionControlledMessageBar(control),
    587          "Wait for the message bar to be removed after disabling the last controlling extension"
    588        );
    589 
    590        is(
    591          control.controlEl.disabled,
    592          false,
    593          "The control element should not be disabled since there are no more controlling extensions"
    594        );
    595 
    596        await TestUtils.waitForCondition(
    597          () => getReenableExtensionMessageBar(control),
    598          "Wait for the re-enable extension message bar to render"
    599        );
    600        let enableExtensionMessageBar = getReenableExtensionMessageBar(control);
    601        ok(
    602          enableExtensionMessageBar,
    603          "The message bar for enabling extensions should be rendered"
    604        );
    605        is(
    606          enableExtensionMessageBar
    607            .querySelector("[slot]")
    608            .getAttribute("data-l10n-id"),
    609          "extension-controlled-enable-2",
    610          "The re-enable extension message bar should have a slot with the correct data-l10n-id"
    611        );
    612 
    613        // Clean up extensions and rendered setting control element
    614        await extension.unload();
    615        await secondExtension.unload();
    616        control.remove();
    617      });
    618 
    619      add_task(async function test_no_initial_controlling_extension() {
    620        // Setup pre-test items: extension, Preferences, ExtensionSettingStore
    621        const SETTING_ID = "extension-controlled-setting";
    622        const STORE_ID = "privacy.containers";
    623        const TEST_FLUENT_ID = "test-fluent-id";
    624        const ADDON_ID = "ext-controlled@mochi.test";
    625        const ADDON_NAME = "Ext Controlled";
    626 
    627        // Assert there is no markup that is generated by pre-test setup since we
    628        // don't have a setting that is being controlled by the STORE_ID
    629 
    630        let settingControl = document.getElementById(SETTING_ID);
    631        is(
    632          settingControl,
    633          null,
    634          "The setting control under test should not exist yet."
    635        );
    636 
    637        // Add setting that is not initially controlled by an extension,
    638        // but is configured so that it can be controlled.
    639        Preferences.addSetting({
    640          id: SETTING_ID,
    641          pref: PREF_ID,
    642          controllingExtensionInfo: {
    643            storeId: STORE_ID,
    644            l10nId: TEST_FLUENT_ID,
    645          },
    646        });
    647 
    648        // Create itemConfig for expected setting-control element
    649        let itemConfig = {
    650          l10nId: "test-fluent-id",
    651          id: SETTING_ID,
    652        };
    653 
    654        // Wait for setting-control element to be rendered
    655        let setting = Preferences.getSetting(SETTING_ID);
    656        let control = await renderTemplate(itemConfig, setting);
    657        ok(await control.updateComplete, "No pending updates");
    658 
    659        // Assert checkbox control is created and NOT disabled due to
    660        // no extension controlling the pref.
    661 
    662        is(
    663          control.controlEl.localName,
    664          "moz-checkbox",
    665          "The control rendered the default checkbox"
    666        );
    667        is(
    668          control.controlEl.disabled,
    669          false,
    670          "The control should not disabled since it is not controlled by an extension"
    671        );
    672 
    673        // Assert that there are no moz-message-bar elements rendered since
    674        // the setting is not controlled by an extension
    675        let messageBars = control.querySelectorAll("moz-message-bar");
    676 
    677        ok(
    678          !messageBars.length,
    679          "There should be no rendered message bar elements"
    680        );
    681 
    682        let extension = ExtensionTestUtils.loadExtension({
    683          manifest: {
    684            browser_specific_settings: { gecko: { id: ADDON_ID } },
    685            name: ADDON_NAME,
    686            permissions: ["contextualIdentities", "cookies"],
    687          },
    688          // We need to be able to find the extension using AddonManager.
    689          useAddonManager: "temporary",
    690        });
    691 
    692        let settingChanged = waitForSettingChange(setting);
    693        await extension.startup();
    694        await settingChanged;
    695        await control.updateComplete;
    696 
    697        is(
    698          control.controlEl.disabled,
    699          true,
    700          "Control is now disabled due to extension"
    701        );
    702 
    703        let messageBar = control.querySelector("moz-message-bar");
    704        ok(messageBar, "Message bar was rendered");
    705 
    706        settingChanged = waitForSettingChange(setting);
    707        await extension.unload();
    708        await settingChanged;
    709        await control.updateComplete;
    710 
    711        is(control.controlEl.disabled, false, "Control is enabled again");
    712        messageBar = control.querySelector("moz-message-bar");
    713        ok(!messageBar, "Message bar was removed");
    714 
    715        control.remove();
    716      });
    717 
    718      add_task(async function test_addons_link_in_message_bar() {
    719        // Setup pre-test items like extension, AddonManager, ExtensionSettingStore
    720        const SETTING_ID = "extension-controlled-setting";
    721        const ADDON_ID = "ext-controlled@mochi.test";
    722        const ADDON_NAME = "Ext Controlled";
    723        const STORE_ID = "privacy.containers";
    724        const TEST_FLUENT_ID = "test-fluent-id";
    725        await SpecialPowers.pushPrefEnv({
    726          set: [[PREF_ID, false]],
    727        });
    728        let extension = ExtensionTestUtils.loadExtension({
    729          manifest: {
    730            browser_specific_settings: { gecko: { id: ADDON_ID } },
    731            name: ADDON_NAME,
    732            permissions: ["contextualIdentities", "cookies"],
    733          },
    734          // We need to be able to find the extension using AddonManager.
    735          useAddonManager: "temporary",
    736        });
    737        await extension.startup();
    738 
    739        await ExtensionSettingsStore.initialize();
    740 
    741        // Assert there is no markup that is generated by pre-test setup since we
    742        // don't have a setting that is being controlled by the STORE_ID
    743 
    744        let settingControl = document.getElementById(SETTING_ID);
    745        is(
    746          settingControl,
    747          null,
    748          "The setting control under test should not exist yet."
    749        );
    750 
    751        // Add setting that is being controlled by our test extension
    752        Preferences.addSetting({
    753          id: SETTING_ID,
    754          pref: PREF_ID,
    755          controllingExtensionInfo: {
    756            storeId: STORE_ID,
    757            l10nId: TEST_FLUENT_ID,
    758          },
    759        });
    760 
    761        // Create itemConfig for expected setting-control element
    762        let itemConfig = {
    763          l10nId: "test-fluent-id",
    764          id: SETTING_ID,
    765        };
    766 
    767        // Wait for setting-control element to be rendered
    768        let setting = Preferences.getSetting(SETTING_ID);
    769        let control = await renderTemplate(itemConfig, setting);
    770        let messageBar = control.querySelector("moz-message-bar");
    771        let disableExtensionButton = messageBar.querySelector(
    772          "moz-button[slot='actions']"
    773        );
    774 
    775        await disableExtensionByMouse(disableExtensionButton);
    776 
    777        await TestUtils.waitForCondition(
    778          () => !getExtensionControlledMessageBar(control),
    779          "Wait for the extension controlled message bar to be removed after disabling the only controlling extension"
    780        );
    781 
    782        await TestUtils.waitForCondition(
    783          () => getReenableExtensionMessageBar(control),
    784          "Wait for the re-enable extension message bar to be rendered"
    785        );
    786        let enableExtensionMessageBar = getReenableExtensionMessageBar(control);
    787        ok(
    788          enableExtensionMessageBar,
    789          "there should be a message bar for re-enabling extensions"
    790        );
    791        let addonsLink = enableExtensionMessageBar.querySelector("[slot] a");
    792 
    793        is(
    794          enableExtensionMessageBar
    795            .querySelector("[slot]")
    796            .getAttribute("data-l10n-id"),
    797          "extension-controlled-enable-2",
    798          "The re-enable extension message bar should have a slot with the correct data-l10n-id"
    799        );
    800 
    801        is(
    802          addonsLink.getAttribute("data-l10n-name"),
    803          "addons-link",
    804          "The slot within the re-enable extension bar should have the correct data-l10n-name"
    805        );
    806 
    807        let gBrowser = BrowserWindowTracker.getTopWindow().top.gBrowser;
    808        let addonsTabPromise = BrowserTestUtils.waitForNewTab(
    809          gBrowser,
    810          "about:addons"
    811        );
    812        await synthesizeMouseAtCenter(addonsLink, {});
    813 
    814        let tab = await addonsTabPromise;
    815        is(
    816          tab.linkedBrowser.currentURI.spec,
    817          "about:addons",
    818          "addons link correctly navigates to about:addons"
    819        );
    820 
    821        BrowserTestUtils.removeTab(tab);
    822        enableExtensionMessageBar = getReenableExtensionMessageBar(control);
    823        addonsLink = enableExtensionMessageBar.querySelector("[slot] a");
    824 
    825        ok(
    826          enableExtensionMessageBar,
    827          "there should still be a message bar for re-enabling extensions after navigating back to about:preferences"
    828        );
    829        is(
    830          enableExtensionMessageBar
    831            .querySelector("[slot]")
    832            .getAttribute("data-l10n-id"),
    833          "extension-controlled-enable-2",
    834          "The re-enable extension message bar should have a slot with the correct data-l10n-id after navigating back to about:preferences"
    835        );
    836 
    837        is(
    838          addonsLink.getAttribute("data-l10n-name"),
    839          "addons-link",
    840          "The slot within the re-enable extension bar should have the correct data-l10n-name after navigating back to about:preferences"
    841        );
    842 
    843        // Unload the test extension and remove the setting control
    844        await extension.unload();
    845        control.remove();
    846      });
    847    </script>
    848  </head>
    849 
    850  <body>
    851    <p id="display"></p>
    852    <div id="content" style="display: none"></div>
    853    <pre id="test"></pre>
    854  </body>
    855 </html>