tor-browser

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

test_extension_storage_actor.js (34883B)


      1 /* Any copyright is dedicated to the Public Domain.
      2   http://creativecommons.org/publicdomain/zero/1.0/ */
      3 
      4 /* globals browser */
      5 
      6 "use strict";
      7 
      8 const { ExtensionTestUtils } = ChromeUtils.importESModule(
      9  "resource://testing-common/ExtensionXPCShellUtils.sys.mjs"
     10 );
     11 
     12 const { TestUtils } = ChromeUtils.importESModule(
     13  "resource://testing-common/TestUtils.sys.mjs"
     14 );
     15 
     16 const {
     17  createMissingIndexedDBDirs,
     18  extensionScriptWithMessageListener,
     19  ext_no_bg,
     20  getExtensionConfig,
     21  openAddonStoragePanel,
     22  shutdown,
     23  startupExtension,
     24 } = require("resource://test/webextension-helpers.js");
     25 
     26 const l10n = new Localization(["devtools/client/storage.ftl"], true);
     27 const sessionString = l10n.formatValueSync("storage-expires-session");
     28 
     29 // Ignore rejection related to the storage.onChanged listener being removed while the extension context is being closed.
     30 const { PromiseTestUtils } = ChromeUtils.importESModule(
     31  "resource://testing-common/PromiseTestUtils.sys.mjs"
     32 );
     33 PromiseTestUtils.allowMatchingRejectionsGlobally(
     34  /Message manager disconnected/
     35 );
     36 PromiseTestUtils.allowMatchingRejectionsGlobally(
     37  /sendRemoveListener on closed conduit/
     38 );
     39 
     40 const { createAppInfo, promiseStartupManager } = AddonTestUtils;
     41 
     42 const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
     43 const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
     44 
     45 AddonTestUtils.init(this);
     46 createAppInfo("xpcshell@tests.mozilla.org", "XPCShell", "1", "42");
     47 
     48 ExtensionTestUtils.init(this);
     49 
     50 add_setup(async function setup() {
     51  await promiseStartupManager();
     52  const dir = createMissingIndexedDBDirs();
     53 
     54  Assert.ok(
     55    dir.exists(),
     56    "Should have a 'storage/permanent' dir in the profile dir"
     57  );
     58 });
     59 
     60 add_task(async function test_extension_store_exists() {
     61  const extension = await startupExtension(getExtensionConfig());
     62 
     63  const { commands, extensionStorage } = await openAddonStoragePanel(
     64    extension.id
     65  );
     66 
     67  ok(extensionStorage, "Should have an extensionStorage store");
     68 
     69  await shutdown(extension, commands);
     70 });
     71 
     72 add_task(
     73  {
     74    // This test currently fails if the extension runs in the main process
     75    // like in Thunderbird (see bug 1575183 comment #15 for details).
     76    skip_if: () => !WebExtensionPolicy.useRemoteWebExtensions,
     77  },
     78  async function test_extension_origin_matches_debugger_target() {
     79    async function background() {
     80      // window is available in background scripts
     81      // eslint-disable-next-line no-undef
     82      browser.test.sendMessage("extension-origin", window.location.origin);
     83    }
     84 
     85    const extension = await startupExtension(
     86      getExtensionConfig({ background })
     87    );
     88 
     89    const { commands, extensionStorage } = await openAddonStoragePanel(
     90      extension.id
     91    );
     92 
     93    const { hosts } = extensionStorage;
     94    const expectedHost = await extension.awaitMessage("extension-origin");
     95    ok(
     96      expectedHost in hosts,
     97      "Should have the expected extension host in the extensionStorage store"
     98    );
     99 
    100    await shutdown(extension, commands);
    101  }
    102 );
    103 
    104 /**
    105 * Test case: Background page modifies items while storage panel is open.
    106 * - Load extension with background page.
    107 * - Open the add-on debugger storage panel.
    108 * - With the panel still open, from the extension background page:
    109 *   - Bulk add storage items
    110 *   - Edit the values of some of the storage items
    111 *   - Remove some storage items
    112 *   - Clear all storage items
    113 * - For each modification, the storage data in the panel should match the
    114 *   changes made by the extension.
    115 */
    116 add_task(async function test_panel_live_updates() {
    117  const extension = await startupExtension(
    118    getExtensionConfig({ background: extensionScriptWithMessageListener })
    119  );
    120 
    121  const { commands, extensionStorage } = await openAddonStoragePanel(
    122    extension.id
    123  );
    124 
    125  const host = await extension.awaitMessage("extension-origin");
    126 
    127  let { data } = await extensionStorage.getStoreObjects(host);
    128  Assert.deepEqual(data, [], "Got the expected results on empty storage.local");
    129 
    130  info("Waiting for extension to bulk add 50 items to storage local");
    131  const bulkStorageItems = {};
    132  // limited by MAX_STORE_OBJECT_COUNT in devtools/server/actors/resources/storage/index.js
    133  const numItems = 2;
    134  for (let i = 1; i <= numItems; i++) {
    135    bulkStorageItems[i] = i;
    136  }
    137 
    138  // fireOnChanged avoids the race condition where the extension
    139  // modifies storage then immediately tries to access storage before
    140  // the storage actor has finished updating.
    141  extension.sendMessage("storage-local-fireOnChanged");
    142  extension.sendMessage("storage-local-set", {
    143    ...bulkStorageItems,
    144    a: 123,
    145    b: [4, 5],
    146    c: { d: 678 },
    147    d: true,
    148    e: "hi",
    149    f: null,
    150  });
    151  await extension.awaitMessage("storage-local-set:done");
    152  await extension.awaitMessage("storage-local-onChanged");
    153 
    154  info(
    155    "Confirming items added by extension match items in extensionStorage store"
    156  );
    157  const bulkStorageObjects = [];
    158  for (const [name, value] of Object.entries(bulkStorageItems)) {
    159    bulkStorageObjects.push({
    160      area: "local",
    161      name,
    162      value: { str: String(value) },
    163      isValueEditable: true,
    164    });
    165  }
    166  data = (await extensionStorage.getStoreObjects(host, null, { sessionString }))
    167    .data;
    168  Assert.deepEqual(
    169    data,
    170    [
    171      ...bulkStorageObjects,
    172      {
    173        area: "local",
    174        name: "a",
    175        value: { str: "123" },
    176        isValueEditable: true,
    177      },
    178      {
    179        area: "local",
    180        name: "b",
    181        value: { str: "[4,5]" },
    182        isValueEditable: true,
    183      },
    184      {
    185        area: "local",
    186        name: "c",
    187        value: { str: '{"d":678}' },
    188        isValueEditable: true,
    189      },
    190      {
    191        area: "local",
    192        name: "d",
    193        value: { str: "true" },
    194        isValueEditable: true,
    195      },
    196      {
    197        area: "local",
    198        name: "e",
    199        value: { str: "hi" },
    200        isValueEditable: true,
    201      },
    202      {
    203        area: "local",
    204        name: "f",
    205        value: { str: "null" },
    206        isValueEditable: true,
    207      },
    208    ],
    209    "Got the expected results on populated storage.local"
    210  );
    211 
    212  info("Waiting for extension to edit a few storage item values");
    213  extension.sendMessage("storage-local-fireOnChanged");
    214  extension.sendMessage("storage-local-set", {
    215    a: ["c", "d"],
    216    b: 456,
    217    c: false,
    218  });
    219  await extension.awaitMessage("storage-local-set:done");
    220  await extension.awaitMessage("storage-local-onChanged");
    221 
    222  info(
    223    "Confirming items edited by extension match items in extensionStorage store"
    224  );
    225  data = (await extensionStorage.getStoreObjects(host, null, { sessionString }))
    226    .data;
    227  Assert.deepEqual(
    228    data,
    229    [
    230      ...bulkStorageObjects,
    231      {
    232        area: "local",
    233        name: "a",
    234        value: { str: '["c","d"]' },
    235        isValueEditable: true,
    236      },
    237      {
    238        area: "local",
    239        name: "b",
    240        value: { str: "456" },
    241        isValueEditable: true,
    242      },
    243      {
    244        area: "local",
    245        name: "c",
    246        value: { str: "false" },
    247        isValueEditable: true,
    248      },
    249      {
    250        area: "local",
    251        name: "d",
    252        value: { str: "true" },
    253        isValueEditable: true,
    254      },
    255      {
    256        area: "local",
    257        name: "e",
    258        value: { str: "hi" },
    259        isValueEditable: true,
    260      },
    261      {
    262        area: "local",
    263        name: "f",
    264        value: { str: "null" },
    265        isValueEditable: true,
    266      },
    267    ],
    268    "Got the expected results on populated storage.local"
    269  );
    270 
    271  info("Waiting for extension to remove a few storage item values");
    272  extension.sendMessage("storage-local-fireOnChanged");
    273  extension.sendMessage("storage-local-remove", ["d", "e", "f"]);
    274  await extension.awaitMessage("storage-local-remove:done");
    275  await extension.awaitMessage("storage-local-onChanged");
    276 
    277  info(
    278    "Confirming items removed by extension were removed in extensionStorage store"
    279  );
    280  data = (await extensionStorage.getStoreObjects(host, null, { sessionString }))
    281    .data;
    282  Assert.deepEqual(
    283    data,
    284    [
    285      ...bulkStorageObjects,
    286      {
    287        area: "local",
    288        name: "a",
    289        value: { str: '["c","d"]' },
    290        isValueEditable: true,
    291      },
    292      {
    293        area: "local",
    294        name: "b",
    295        value: { str: "456" },
    296        isValueEditable: true,
    297      },
    298      {
    299        area: "local",
    300        name: "c",
    301        value: { str: "false" },
    302        isValueEditable: true,
    303      },
    304    ],
    305    "Got the expected results on populated storage.local"
    306  );
    307 
    308  info("Waiting for extension to remove all remaining storage items");
    309  extension.sendMessage("storage-local-fireOnChanged");
    310  extension.sendMessage("storage-local-clear");
    311  await extension.awaitMessage("storage-local-clear:done");
    312  await extension.awaitMessage("storage-local-onChanged");
    313 
    314  info("Confirming extensionStorage store was cleared");
    315  data = (await extensionStorage.getStoreObjects(host)).data;
    316  Assert.deepEqual(
    317    data,
    318    [],
    319    "Got the expected results on populated storage.local"
    320  );
    321 
    322  await shutdown(extension, commands);
    323 });
    324 
    325 /**
    326 * Test case: No bg page. Transient page adds item before storage panel opened.
    327 * - Load extension with no background page.
    328 * - Open an extension page in a tab that adds a local storage item.
    329 * - With the extension page still open, open the add-on storage panel.
    330 * - The data in the storage panel should match the items added by the extension.
    331 */
    332 add_task(
    333  async function test_panel_data_matches_extension_with_transient_page_open() {
    334    const extension = await startupExtension(
    335      getExtensionConfig({ files: ext_no_bg.files })
    336    );
    337 
    338    const url = extension.extension.baseURI.resolve(
    339      "extension_page_in_tab.html"
    340    );
    341    const contentPage = await ExtensionTestUtils.loadContentPage(url, {
    342      extension,
    343    });
    344 
    345    const host = await extension.awaitMessage("extension-origin");
    346 
    347    extension.sendMessage("storage-local-set", { a: 123 });
    348    await extension.awaitMessage("storage-local-set:done");
    349 
    350    const { commands, extensionStorage } = await openAddonStoragePanel(
    351      extension.id
    352    );
    353 
    354    const { data } = await extensionStorage.getStoreObjects(host);
    355    Assert.deepEqual(
    356      data,
    357      [
    358        {
    359          area: "local",
    360          name: "a",
    361          value: { str: "123" },
    362          isValueEditable: true,
    363        },
    364      ],
    365      "Got the expected results on populated storage.local"
    366    );
    367 
    368    await contentPage.close();
    369    await shutdown(extension, commands);
    370  }
    371 );
    372 
    373 /**
    374 * Test case: No bg page. Transient page adds item then closes before storage panel opened.
    375 * - Load extension with no background page.
    376 * - Open an extension page in a tab that adds a local storage item.
    377 * - Close all extension pages.
    378 * - Open the add-on storage panel.
    379 * - The data in the storage panel should match the item added by the extension.
    380 */
    381 add_task(async function test_panel_data_matches_extension_with_no_pages_open() {
    382  const extension = await startupExtension(
    383    getExtensionConfig({ files: ext_no_bg.files })
    384  );
    385 
    386  const url = extension.extension.baseURI.resolve("extension_page_in_tab.html");
    387  const contentPage = await ExtensionTestUtils.loadContentPage(url, {
    388    extension,
    389  });
    390 
    391  const host = await extension.awaitMessage("extension-origin");
    392 
    393  extension.sendMessage("storage-local-fireOnChanged");
    394  extension.sendMessage("storage-local-set", { a: 123 });
    395  await extension.awaitMessage("storage-local-set:done");
    396  await extension.awaitMessage("storage-local-onChanged");
    397  await contentPage.close();
    398 
    399  const { commands, extensionStorage } = await openAddonStoragePanel(
    400    extension.id
    401  );
    402 
    403  const { data } = await extensionStorage.getStoreObjects(host);
    404  Assert.deepEqual(
    405    data,
    406    [
    407      {
    408        area: "local",
    409        name: "a",
    410        value: { str: "123" },
    411        isValueEditable: true,
    412      },
    413    ],
    414    "Got the expected results on populated storage.local"
    415  );
    416 
    417  await shutdown(extension, commands);
    418 });
    419 
    420 /**
    421 * Test case: No bg page. Storage panel live updates when a transient page adds an item.
    422 * - Load extension with no background page.
    423 * - Open the add-on storage panel.
    424 * - With the storage panel still open, open an extension page in a new tab that adds an
    425 *   item.
    426 * - The data in the storage panel should live update to match the item added by the
    427 *   extension.
    428 * - If an extension page adds the same data again, the data in the storage panel should
    429 *   not change.
    430 */
    431 add_task(
    432  async function test_panel_data_live_updates_for_extension_without_bg_page() {
    433    const extension = await startupExtension(
    434      getExtensionConfig({ files: ext_no_bg.files })
    435    );
    436 
    437    const { commands, extensionStorage } = await openAddonStoragePanel(
    438      extension.id
    439    );
    440 
    441    const url = extension.extension.baseURI.resolve(
    442      "extension_page_in_tab.html"
    443    );
    444    const contentPage = await ExtensionTestUtils.loadContentPage(url, {
    445      extension,
    446    });
    447 
    448    const host = await extension.awaitMessage("extension-origin");
    449 
    450    let { data } = await extensionStorage.getStoreObjects(host);
    451    Assert.deepEqual(
    452      data,
    453      [],
    454      "Got the expected results on empty storage.local"
    455    );
    456 
    457    extension.sendMessage("storage-local-fireOnChanged");
    458    extension.sendMessage("storage-local-set", { a: 123 });
    459    await extension.awaitMessage("storage-local-set:done");
    460    await extension.awaitMessage("storage-local-onChanged");
    461 
    462    data = (await extensionStorage.getStoreObjects(host)).data;
    463    Assert.deepEqual(
    464      data,
    465      [
    466        {
    467          area: "local",
    468          name: "a",
    469          value: { str: "123" },
    470          isValueEditable: true,
    471        },
    472      ],
    473      "Got the expected results on populated storage.local"
    474    );
    475 
    476    extension.sendMessage("storage-local-fireOnChanged");
    477    extension.sendMessage("storage-local-set", { a: 123 });
    478    await extension.awaitMessage("storage-local-set:done");
    479    await extension.awaitMessage("storage-local-onChanged");
    480 
    481    data = (await extensionStorage.getStoreObjects(host)).data;
    482    Assert.deepEqual(
    483      data,
    484      [
    485        {
    486          area: "local",
    487          name: "a",
    488          value: { str: "123" },
    489          isValueEditable: true,
    490        },
    491      ],
    492      "The results are unchanged when an extension page adds duplicate items"
    493    );
    494 
    495    await contentPage.close();
    496    await shutdown(extension, commands);
    497  }
    498 );
    499 
    500 /**
    501 * Test case: Bg page adds item while storage panel is open. Panel edits item's value.
    502 * - Load extension with background page.
    503 * - Open the add-on storage panel.
    504 * - With the storage panel still open, add item from the background page.
    505 * - Edit the value of the item in the storage panel
    506 * - The data in the storage panel should match the item added by the extension.
    507 * - The storage actor is correctly parsing and setting the string representation of
    508 *     the value in the storage local database when the item's value is edited in the
    509 *     storage panel
    510 */
    511 add_task(
    512  async function test_editing_items_in_panel_parses_supported_values_correctly() {
    513    const extension = await startupExtension(
    514      getExtensionConfig({ background: extensionScriptWithMessageListener })
    515    );
    516 
    517    const host = await extension.awaitMessage("extension-origin");
    518 
    519    const { commands, extensionStorage } = await openAddonStoragePanel(
    520      extension.id
    521    );
    522 
    523    const oldItem = { a: 123 };
    524    const key = Object.keys(oldItem)[0];
    525    const oldValue = oldItem[key];
    526    // A tuple representing information for a new value entered into the panel for oldItem:
    527    // [
    528    //   value,
    529    //   editItem string representation of value,
    530    //   toStoreObject string representation of value,
    531    // ]
    532    const valueInfo = [
    533      [true, "true", "true"],
    534      ["hi", "hi", "hi"],
    535      [456, "456", "456"],
    536      [{ b: 789 }, "{b: 789}", '{"b":789}'],
    537      [[1, 2, 3], "[1, 2, 3]", "[1,2,3]"],
    538      [null, "null", "null"],
    539    ];
    540    for (const [value, editItemValueStr, toStoreObjectValueStr] of valueInfo) {
    541      info("Setting a storage item through the extension");
    542      extension.sendMessage("storage-local-fireOnChanged");
    543      extension.sendMessage("storage-local-set", oldItem);
    544      await extension.awaitMessage("storage-local-set:done");
    545      await extension.awaitMessage("storage-local-onChanged");
    546 
    547      info(
    548        "Editing the storage item in the panel with a new value of a different type"
    549      );
    550      // When the user edits an item in the panel, they are entering a string into a
    551      // textbox. This string is parsed by the storage actor's editItem method.
    552      await extensionStorage.editItem({
    553        host,
    554        field: "value",
    555        items: { name: key, value: editItemValueStr },
    556        oldValue,
    557      });
    558 
    559      info(
    560        "Verifying item in the storage actor matches the item edited in the panel"
    561      );
    562      const { data } = await extensionStorage.getStoreObjects(host);
    563      Assert.deepEqual(
    564        data,
    565        [
    566          {
    567            area: "local",
    568            name: key,
    569            value: { str: toStoreObjectValueStr },
    570            isValueEditable: true,
    571          },
    572        ],
    573        "Got the expected results on populated storage.local"
    574      );
    575 
    576      // The view layer is separate from the database layer; therefore while values are
    577      // stringified (via toStoreObject) for display in the client, the value (and its type)
    578      // in the database is unchanged.
    579      info(
    580        "Verifying the expected new value matches the value fetched in the extension"
    581      );
    582      extension.sendMessage("storage-local-get", key);
    583      const extItem = await extension.awaitMessage("storage-local-get:done");
    584      Assert.deepEqual(
    585        value,
    586        extItem[key],
    587        `The string value ${editItemValueStr} was correctly parsed to ${value}`
    588      );
    589    }
    590 
    591    await shutdown(extension, commands);
    592  }
    593 );
    594 
    595 /**
    596 * Test case: Modifying storage items from the panel update extension storage local data.
    597 * - Load extension with background page.
    598 * - Open the add-on storage panel. From the panel:
    599 *   - Edit the value of a storage item,
    600 *   - Remove a storage item,
    601 *   - Remove all of the storage items,
    602 * - For each modification, the storage data retrieved by the extension should match the
    603 *   data in the panel.
    604 */
    605 add_task(
    606  async function test_modifying_items_in_panel_updates_extension_storage_data() {
    607    const extension = await startupExtension(
    608      getExtensionConfig({ background: extensionScriptWithMessageListener })
    609    );
    610 
    611    const host = await extension.awaitMessage("extension-origin");
    612 
    613    const { commands, extensionStorage } = await openAddonStoragePanel(
    614      extension.id
    615    );
    616 
    617    const DEFAULT_VALUE = "value"; // global in devtools/server/actors/resources/storage/index.js
    618    let items = {
    619      guid_1: DEFAULT_VALUE,
    620      guid_2: DEFAULT_VALUE,
    621      guid_3: DEFAULT_VALUE,
    622    };
    623 
    624    info("Adding storage items from the extension");
    625    let storesUpdate = extensionStorage.once("single-store-update");
    626    extension.sendMessage("storage-local-set", items);
    627    await extension.awaitMessage("storage-local-set:done");
    628 
    629    info("Waiting for the storage actor to emit a 'stores-update' event");
    630    let data = await storesUpdate;
    631    Assert.deepEqual(
    632      {
    633        added: {
    634          extensionStorage: {
    635            [host]: ["guid_1", "guid_2", "guid_3"],
    636          },
    637        },
    638        changed: undefined,
    639        deleted: undefined,
    640      },
    641      data,
    642      "The change data from the storage actor's 'stores-update' event matches the changes made in the client."
    643    );
    644 
    645    info("Waiting for panel to edit some items");
    646    storesUpdate = extensionStorage.once("single-store-update");
    647    await extensionStorage.editItem({
    648      host,
    649      field: "value",
    650      items: { name: "guid_1", value: "anotherValue" },
    651      DEFAULT_VALUE,
    652    });
    653 
    654    info("Waiting for the storage actor to emit a 'stores-update' event");
    655    data = await storesUpdate;
    656    Assert.deepEqual(
    657      {
    658        added: undefined,
    659        changed: {
    660          extensionStorage: {
    661            [host]: ["guid_1"],
    662          },
    663        },
    664        deleted: undefined,
    665      },
    666      data,
    667      "The change data from the storage actor's 'stores-update' event matches the changes made in the client."
    668    );
    669 
    670    items = {
    671      guid_1: "anotherValue",
    672      guid_2: DEFAULT_VALUE,
    673      guid_3: DEFAULT_VALUE,
    674    };
    675    extension.sendMessage("storage-local-get", Object.keys(items));
    676    let extItems = await extension.awaitMessage("storage-local-get:done");
    677    Assert.deepEqual(
    678      items,
    679      extItems,
    680      `The storage items in the extension match the items in the panel`
    681    );
    682 
    683    info("Waiting for panel to remove an item");
    684    storesUpdate = extensionStorage.once("single-store-update");
    685    await extensionStorage.removeItem(host, "guid_3");
    686 
    687    info("Waiting for the storage actor to emit a 'stores-update' event");
    688    data = await storesUpdate;
    689    Assert.deepEqual(
    690      {
    691        added: undefined,
    692        changed: undefined,
    693        deleted: {
    694          extensionStorage: {
    695            [host]: ["guid_3"],
    696          },
    697        },
    698      },
    699      data,
    700      "The change data from the storage actor's 'stores-update' event matches the changes made in the client."
    701    );
    702 
    703    items = {
    704      guid_1: "anotherValue",
    705      guid_2: DEFAULT_VALUE,
    706    };
    707    extension.sendMessage("storage-local-get", Object.keys(items));
    708    extItems = await extension.awaitMessage("storage-local-get:done");
    709    Assert.deepEqual(
    710      items,
    711      extItems,
    712      `The storage items in the extension match the items in the panel`
    713    );
    714 
    715    info("Waiting for panel to remove all items");
    716    const storesCleared = extensionStorage.once("single-store-cleared");
    717    await extensionStorage.removeAll(host);
    718 
    719    info("Waiting for the storage actor to emit a 'stores-cleared' event");
    720    data = await storesCleared;
    721    Assert.deepEqual(
    722      {
    723        clearedHostsOrPaths: {
    724          [host]: [],
    725        },
    726      },
    727      data,
    728      "The change data from the storage actor's 'stores-cleared' event matches the changes made in the client."
    729    );
    730 
    731    items = {};
    732    extension.sendMessage("storage-local-get", Object.keys(items));
    733    extItems = await extension.awaitMessage("storage-local-get:done");
    734    Assert.deepEqual(
    735      items,
    736      extItems,
    737      `The storage items in the extension match the items in the panel`
    738    );
    739 
    740    await shutdown(extension, commands);
    741  }
    742 );
    743 
    744 /**
    745 * Test case: Storage panel shows extension storage data added prior to extension startup
    746 * - Load extension that adds a storage item
    747 * - Uninstall the extension
    748 * - Reinstall the extension
    749 * - Open the add-on storage panel.
    750 * - The data in the storage panel should match the data added the first time the extension
    751 *   was installed
    752 * Related test case: Storage panel shows extension storage data when an extension that has
    753 * already migrated to the IndexedDB storage backend prior to extension startup adds
    754 * another storage item.
    755 * - (Building from previous steps)
    756 * - The reinstalled extension adds a storage item
    757 * - The data in the storage panel should live update with both items: the item added from
    758 *   the first and the item added from the reinstall.
    759 */
    760 add_task(
    761  async function test_panel_data_matches_data_added_prior_to_ext_startup() {
    762    // The pref to leave the addonid->uuid mapping around after uninstall so that we can
    763    // re-attach to the same storage
    764    Services.prefs.setBoolPref(LEAVE_UUID_PREF, true);
    765 
    766    // The pref to prevent cleaning up storage on uninstall
    767    Services.prefs.setBoolPref(LEAVE_STORAGE_PREF, true);
    768 
    769    let extension = await startupExtension(
    770      getExtensionConfig({ background: extensionScriptWithMessageListener })
    771    );
    772 
    773    const host = await extension.awaitMessage("extension-origin");
    774 
    775    extension.sendMessage("storage-local-set", { a: 123 });
    776    await extension.awaitMessage("storage-local-set:done");
    777 
    778    await shutdown(extension);
    779 
    780    // Reinstall the same extension
    781    extension = await startupExtension(
    782      getExtensionConfig({ background: extensionScriptWithMessageListener })
    783    );
    784 
    785    await extension.awaitMessage("extension-origin");
    786 
    787    const { commands, extensionStorage } = await openAddonStoragePanel(
    788      extension.id
    789    );
    790 
    791    let { data } = await extensionStorage.getStoreObjects(host);
    792    Assert.deepEqual(
    793      data,
    794      [
    795        {
    796          area: "local",
    797          name: "a",
    798          value: { str: "123" },
    799          isValueEditable: true,
    800        },
    801      ],
    802      "Got the expected results on populated storage.local"
    803    );
    804 
    805    // Related test case
    806    extension.sendMessage("storage-local-fireOnChanged");
    807    extension.sendMessage("storage-local-set", { b: 456 });
    808    await extension.awaitMessage("storage-local-set:done");
    809    await extension.awaitMessage("storage-local-onChanged");
    810 
    811    data = (
    812      await extensionStorage.getStoreObjects(host, null, { sessionString })
    813    ).data;
    814    Assert.deepEqual(
    815      data,
    816      [
    817        {
    818          area: "local",
    819          name: "a",
    820          value: { str: "123" },
    821          isValueEditable: true,
    822        },
    823        {
    824          area: "local",
    825          name: "b",
    826          value: { str: "456" },
    827          isValueEditable: true,
    828        },
    829      ],
    830      "Got the expected results on populated storage.local"
    831    );
    832 
    833    Services.prefs.setBoolPref(LEAVE_STORAGE_PREF, false);
    834    Services.prefs.setBoolPref(LEAVE_UUID_PREF, false);
    835 
    836    await shutdown(extension, commands);
    837  }
    838 );
    839 
    840 add_task(
    841  function cleanup_for_test_panel_data_matches_data_added_prior_to_ext_startup() {
    842    Services.prefs.clearUserPref(LEAVE_UUID_PREF);
    843    Services.prefs.clearUserPref(LEAVE_STORAGE_PREF);
    844  }
    845 );
    846 
    847 /**
    848 * Test case: Transient page adds an item to storage. With storage panel open,
    849 * reload extension.
    850 * - Load extension with no background page.
    851 * - Open transient page that adds a storage item on message.
    852 * - Open the add-on storage panel.
    853 * - With the storage panel still open, reload the extension.
    854 * - The data in the storage panel should match the item added prior to reloading.
    855 */
    856 add_task(async function test_panel_live_reload_for_extension_without_bg_page() {
    857  const EXTENSION_ID = "test_local_storage_live_reload@xpcshell.mozilla.org";
    858  let manifest = {
    859    version: "1.0",
    860    browser_specific_settings: {
    861      gecko: {
    862        id: EXTENSION_ID,
    863      },
    864    },
    865  };
    866 
    867  info("Loading and starting extension version 1.0");
    868  const extension = await startupExtension(
    869    getExtensionConfig({
    870      manifest,
    871      files: ext_no_bg.files,
    872    })
    873  );
    874 
    875  info("Opening extension page in a tab");
    876  const url = extension.extension.baseURI.resolve("extension_page_in_tab.html");
    877  const contentPage = await ExtensionTestUtils.loadContentPage(url, {
    878    extension,
    879  });
    880 
    881  const host = await extension.awaitMessage("extension-origin");
    882 
    883  info("Waiting for extension page in a tab to add storage item");
    884  extension.sendMessage("storage-local-fireOnChanged");
    885  extension.sendMessage("storage-local-set", { a: 123 });
    886  await extension.awaitMessage("storage-local-set:done");
    887  await extension.awaitMessage("storage-local-onChanged");
    888  await contentPage.close();
    889 
    890  info("Opening storage panel");
    891  const { commands, extensionStorage } = await openAddonStoragePanel(
    892    extension.id
    893  );
    894 
    895  manifest = {
    896    ...manifest,
    897    version: "2.0",
    898  };
    899  // "Reload" is most similar to an upgrade, as e.g. storage data is preserved
    900  info("Updating extension to version 2.0");
    901  await extension.upgrade(
    902    getExtensionConfig({
    903      manifest,
    904      files: ext_no_bg.files,
    905    })
    906  );
    907 
    908  const { data } = await extensionStorage.getStoreObjects(host);
    909  Assert.deepEqual(
    910    data,
    911    [
    912      {
    913        area: "local",
    914        name: "a",
    915        value: { str: "123" },
    916        isValueEditable: true,
    917      },
    918    ],
    919    "Got the expected results on populated storage.local"
    920  );
    921 
    922  await shutdown(extension, commands);
    923 });
    924 
    925 /**
    926 * Test case: Bg page auto adds item(s). With storage panel open, reload extension.
    927 * - Load extension with background page that automatically adds a storage item on startup.
    928 * - Open the add-on storage panel.
    929 * - With the storage panel still open, reload the extension.
    930 * - The data in the storage panel should match the item(s) added by the reloaded
    931 *   extension.
    932 */
    933 add_task(
    934  async function test_panel_live_reload_when_extension_auto_adds_items() {
    935    async function background() {
    936      await browser.storage.local.set({ a: { b: 123 }, c: { d: 456 } });
    937      // window is available in background scripts
    938      // eslint-disable-next-line no-undef
    939      browser.test.sendMessage("extension-origin", window.location.origin);
    940    }
    941    const EXTENSION_ID = "test_local_storage_live_reload@xpcshell.mozilla.org";
    942    let manifest = {
    943      version: "1.0",
    944      browser_specific_settings: {
    945        gecko: {
    946          id: EXTENSION_ID,
    947        },
    948      },
    949    };
    950 
    951    info("Loading and starting extension version 1.0");
    952    const extension = await startupExtension(
    953      getExtensionConfig({ manifest, background })
    954    );
    955 
    956    info("Waiting for message from test extension");
    957    const host = await extension.awaitMessage("extension-origin");
    958 
    959    info("Opening storage panel");
    960    const { commands, extensionStorage } = await openAddonStoragePanel(
    961      extension.id
    962    );
    963 
    964    manifest = {
    965      ...manifest,
    966      version: "2.0",
    967    };
    968    // "Reload" is most similar to an upgrade, as e.g. storage data is preserved
    969    info("Update to version 2.0");
    970    await extension.upgrade(
    971      getExtensionConfig({
    972        manifest,
    973        background,
    974      })
    975    );
    976 
    977    await extension.awaitMessage("extension-origin");
    978 
    979    const { data } = await extensionStorage.getStoreObjects(host, null, {
    980      sessionString,
    981    });
    982    Assert.deepEqual(
    983      data,
    984      [
    985        {
    986          area: "local",
    987          name: "a",
    988          value: { str: '{"b":123}' },
    989          isValueEditable: true,
    990        },
    991        {
    992          area: "local",
    993          name: "c",
    994          value: { str: '{"d":456}' },
    995          isValueEditable: true,
    996        },
    997      ],
    998      "Got the expected results on populated storage.local"
    999    );
   1000 
   1001    await shutdown(extension, commands);
   1002  }
   1003 );
   1004 
   1005 /**
   1006 * Test case: Bg page adds one storage.local item and one storage.sync item.
   1007 * - Load extension with background page that automatically adds two storage items on startup.
   1008 * - Open the add-on storage panel.
   1009 * - Assert that only the storage.local item is shown in the panel.
   1010 */
   1011 add_task(
   1012  async function test_panel_data_only_updates_for_storage_local_changes() {
   1013    async function background() {
   1014      await browser.storage.local.set({ a: { b: 123 } });
   1015      await browser.storage.sync.set({ c: { d: 456 } });
   1016      // window is available in background scripts
   1017      // eslint-disable-next-line no-undef
   1018      browser.test.sendMessage("extension-origin", window.location.origin);
   1019    }
   1020 
   1021    // Using the storage.sync API requires a non-temporary extension ID, see Bug 1323228.
   1022    const EXTENSION_ID =
   1023      "test_panel_data_only_updates_for_storage_local_changes@xpcshell.mozilla.org";
   1024    const manifest = {
   1025      browser_specific_settings: {
   1026        gecko: {
   1027          id: EXTENSION_ID,
   1028        },
   1029      },
   1030    };
   1031 
   1032    info("Loading and starting extension");
   1033    const extension = await startupExtension(
   1034      getExtensionConfig({ manifest, background })
   1035    );
   1036 
   1037    info("Waiting for message from test extension");
   1038    const host = await extension.awaitMessage("extension-origin");
   1039 
   1040    info("Opening storage panel");
   1041    const { commands, extensionStorage } = await openAddonStoragePanel(
   1042      extension.id
   1043    );
   1044 
   1045    const { data } = await extensionStorage.getStoreObjects(host);
   1046    Assert.deepEqual(
   1047      data,
   1048      [
   1049        {
   1050          area: "local",
   1051          name: "a",
   1052          value: { str: '{"b":123}' },
   1053          isValueEditable: true,
   1054        },
   1055      ],
   1056      "Got the expected results on populated storage.local"
   1057    );
   1058 
   1059    await shutdown(extension, commands);
   1060  }
   1061 );
   1062 
   1063 // This test verifies that Bug 1802929 fix doesn't regress.
   1064 add_task(async function test_live_update_with_no_extension_listener() {
   1065  const EXTENSION_ID = "test_with_no_listeners@xpcshell.mozilla.org";
   1066  let manifest = {
   1067    version: "1.0",
   1068    browser_specific_settings: {
   1069      gecko: {
   1070        id: EXTENSION_ID,
   1071      },
   1072    },
   1073  };
   1074 
   1075  function background() {
   1076    browser.test.onMessage.addListener(async (msg, ...args) => {
   1077      if (msg !== "storage-local-api-call") {
   1078        browser.test.fail(`Got unexpected test message: ${msg}`);
   1079        return;
   1080      }
   1081 
   1082      const [{ method, methodArgs }] = args;
   1083      const res = await browser.storage.local[method](...methodArgs);
   1084      browser.test.sendMessage(`${msg}:done`, res);
   1085    });
   1086  }
   1087 
   1088  const extension = await startupExtension(
   1089    getExtensionConfig({ manifest, background })
   1090  );
   1091 
   1092  const { target, extensionStorage } = await openAddonStoragePanel(
   1093    extension.id
   1094  );
   1095 
   1096  const { baseURI } = extension.extension;
   1097  const host = `${baseURI.scheme}://${baseURI.host}`;
   1098 
   1099  let { data } = await extensionStorage.getStoreObjects(host);
   1100  Assert.deepEqual(data, [], "Got the expected results on empty storage.local");
   1101 
   1102  async function testStorageLocalUpdate(storageValue) {
   1103    info("Store extension data");
   1104    await extension.sendMessage("storage-local-api-call", {
   1105      method: "set",
   1106      methodArgs: [{ storageKeyName: storageValue }],
   1107    });
   1108    await extension.awaitMessage("storage-local-api-call:done");
   1109 
   1110    info("Verify stored extension data");
   1111    await extension.sendMessage("storage-local-api-call", {
   1112      method: "get",
   1113      methodArgs: [],
   1114    });
   1115 
   1116    Assert.deepEqual(
   1117      await extension.awaitMessage("storage-local-api-call:done"),
   1118      { storageKeyName: storageValue },
   1119      "Got the expected value from browser.storage.local.get"
   1120    );
   1121 
   1122    await TestUtils.waitForCondition(async () => {
   1123      const res = await extensionStorage.getStoreObjects(host);
   1124      return res.data?.length > 0;
   1125    }, "Wait for the extension storage panel updates");
   1126 
   1127    data = (await extensionStorage.getStoreObjects(host)).data;
   1128    Assert.deepEqual(
   1129      data,
   1130      [
   1131        {
   1132          area: "local",
   1133          name: "storageKeyName",
   1134          value: { str: `${storageValue}` },
   1135          isValueEditable: true,
   1136        },
   1137      ],
   1138      "Expected DevTools Storage panel data to have been updated"
   1139    );
   1140  }
   1141 
   1142  await testStorageLocalUpdate("aStorageValue 01");
   1143 
   1144  manifest = {
   1145    ...manifest,
   1146    version: "2.0",
   1147  };
   1148  // "Reload" is most similar to an upgrade, as e.g. storage data is preserved
   1149  info("Update to version 2.0");
   1150  await extension.upgrade(getExtensionConfig({ manifest, background }));
   1151 
   1152  await testStorageLocalUpdate("aStorageValue 02");
   1153 
   1154  await shutdown(extension, target);
   1155 });