summaryrefslogtreecommitdiffstats
path: root/toolkit/components/extensions/test/mochitest/test_ext_storage_cleanup.html
blob: 33029cf61ea090bb5f14e94aa5cf786a6349c87b (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
<!DOCTYPE HTML>
<html>
<head>
  <title>WebExtension test</title>
  <script src="/tests/SimpleTest/SimpleTest.js"></script>
  <script src="/tests/SimpleTest/ExtensionTestUtils.js"></script>
  <script type="text/javascript" src="head.js"></script>
  <link rel="stylesheet" type="text/css" href="/tests/SimpleTest/test.css"/>
</head>
<body>

<script type="text/javascript">
"use strict";

const { ExtensionStorageIDB } = SpecialPowers.ChromeUtils.importESModule(
  "resource://gre/modules/ExtensionStorageIDB.sys.mjs"
);

const storageTestHelpers = {
  storageLocal: {
    async writeData() {
      await browser.storage.local.set({hello: "world"});
      browser.test.sendMessage("finished");
    },

    async readData() {
      const matchBrowserStorage = await browser.storage.local.get("hello").then(result => {
        return (Object.keys(result).length == 1 && result.hello == "world");
      });

      browser.test.sendMessage("results", {matchBrowserStorage});
    },

    assertResults({results, keepOnUninstall}) {
      if (keepOnUninstall) {
        is(results.matchBrowserStorage, true, "browser.storage.local data is still present");
      } else {
        is(results.matchBrowserStorage, false, "browser.storage.local data was cleared");
      }
    },
  },
  storageSync: {
    async writeData() {
      await browser.storage.sync.set({hello: "world"});
      browser.test.sendMessage("finished");
    },

    async readData() {
      const matchBrowserStorage = await browser.storage.sync.get("hello").then(result => {
        return (Object.keys(result).length == 1 && result.hello == "world");
      });

      browser.test.sendMessage("results", {matchBrowserStorage});
    },

    assertResults({results, keepOnUninstall}) {
      if (keepOnUninstall) {
        is(results.matchBrowserStorage, true, "browser.storage.sync data is still present");
      } else {
        is(results.matchBrowserStorage, false, "browser.storage.sync data was cleared");
      }
    },
  },
  webAPIs: {
    async readData() {
      let matchLocalStorage = (localStorage.getItem("hello") == "world");

      let idbPromise = new Promise((resolve, reject) => {
        let req = indexedDB.open("test");
        req.onerror = e => {
          reject(new Error(`indexedDB open failed with ${e.errorCode}`));
        };

        req.onupgradeneeded = e => {
          // no database, data is not present
          resolve(false);
        };

        req.onsuccess = e => {
          let db = e.target.result;
          let transaction = db.transaction("store", "readwrite");
          let addreq = transaction.objectStore("store").get("hello");
          addreq.onerror = addreqError => {
            reject(new Error(`read from indexedDB failed with ${addreqError.errorCode}`));
          };
          addreq.onsuccess = () => {
            let match = (addreq.result.value == "world");
            resolve(match);
          };
        };
      });

      await idbPromise.then(matchIDB => {
        let result = {matchLocalStorage, matchIDB};
        browser.test.sendMessage("results", result);
      });
    },

    async writeData() {
      localStorage.setItem("hello", "world");

      let idbPromise = new Promise((resolve, reject) => {
        let req = indexedDB.open("test");
        req.onerror = e => {
          reject(new Error(`indexedDB open failed with ${e.errorCode}`));
        };

        req.onupgradeneeded = e => {
          let db = e.target.result;
          db.createObjectStore("store", {keyPath: "name"});
        };

        req.onsuccess = e => {
          let db = e.target.result;
          let transaction = db.transaction("store", "readwrite");
          let addreq = transaction.objectStore("store")
                                  .add({name: "hello", value: "world"});
          addreq.onerror = addreqError => {
            reject(new Error(`add to indexedDB failed with ${addreqError.errorCode}`));
          };
          addreq.onsuccess = () => {
            resolve();
          };
        };
      });

      await idbPromise.then(() => {
        browser.test.sendMessage("finished");
      });
    },

    assertResults({results, keepOnUninstall}) {
      if (keepOnUninstall) {
        is(results.matchLocalStorage, true, "localStorage data is still present");
        is(results.matchIDB, true, "indexedDB data is still present");
      } else {
        is(results.matchLocalStorage, false, "localStorage data was cleared");
        is(results.matchIDB, false, "indexedDB data was cleared");
      }
    },
  },
};

async function test_uninstall({extensionId, writeData, readData, assertResults}) {
  // Set the pref to prevent cleaning up storage on uninstall in a separate prefEnv
  // so we can pop it below, leaving flags set in the previous prefEnvs unmodified.
  await SpecialPowers.pushPrefEnv({
    set: [["extensions.webextensions.keepStorageOnUninstall", true]],
  });

  let extension = ExtensionTestUtils.loadExtension({
    background: writeData,
    manifest: {
      browser_specific_settings: {gecko: {id: extensionId}},
      permissions: ["storage"],
    },
    useAddonManager: "temporary",
  });

  await extension.startup();
  await extension.awaitMessage("finished");
  await extension.unload();

  // Check that we can still see data we wrote to storage but clear the
  // "leave storage" flag so our storaged gets cleared on the next uninstall.
  // This effectively tests the keepUuidOnUninstall logic, which ensures
  // that when we read storage again and check that it is cleared, that
  // it is actually a meaningful test!
  await SpecialPowers.popPrefEnv();

  extension = ExtensionTestUtils.loadExtension({
    background: readData,
    manifest: {
      browser_specific_settings: {gecko: {id: extensionId}},
      permissions: ["storage"],
    },
    useAddonManager: "temporary",
  });

  await extension.startup();
  let results = await extension.awaitMessage("results");

  assertResults({results, keepOnUninstall: true});

  await extension.unload();

  // Read again.  This time, our data should be gone.
  extension = ExtensionTestUtils.loadExtension({
    background: readData,
    manifest: {
      browser_specific_settings: {gecko: {id: extensionId}},
      permissions: ["storage"],
    },
    useAddonManager: "temporary",
  });

  await extension.startup();
  results = await extension.awaitMessage("results");

  assertResults({results, keepOnUninstall: false});

  await extension.unload();
}


add_task(async function test_setup_keep_uuid_on_uninstall() {
  // Use a test-only pref to leave the addonid->uuid mapping around after
  // uninstall so that we can re-attach to the same storage (this prefEnv
  // is kept for this entire file and cleared automatically once all the
  // tests in this file have been executed).
  await SpecialPowers.pushPrefEnv({
    set: [["extensions.webextensions.keepUuidOnUninstall", true]],
  });
});

// Test extension indexedDB and localStorage storages get cleaned up when the
// extension is uninstalled.
add_task(async function test_uninstall_with_webapi_storages() {
  await test_uninstall({
    extensionId: "storage.cleanup-WebAPIStorages@tests.mozilla.org",
    ...(storageTestHelpers.webAPIs),
  });
});

// Test browser.storage.local with JSONFile backend gets cleaned up when the
// extension is uninstalled.
add_task(async function test_uninistall_with_storage_local_file_backend() {
  await SpecialPowers.pushPrefEnv({
    set: [[ExtensionStorageIDB.BACKEND_ENABLED_PREF, false]],
  });

  await test_uninstall({
    extensionId: "storage.cleanup-JSONFileBackend@tests.mozilla.org",
    ...(storageTestHelpers.storageLocal),
  });

  await SpecialPowers.popPrefEnv();
});

// Repeat the cleanup test when the storage.local IndexedDB backend is enabled.
add_task(async function test_uninistall_with_storage_local_idb_backend() {
  await SpecialPowers.pushPrefEnv({
    set: [[ExtensionStorageIDB.BACKEND_ENABLED_PREF, true]],
  });

  await test_uninstall({
    extensionId: "storage.cleanup-IDBBackend@tests.mozilla.org",
    ...(storageTestHelpers.storageLocal),
  });

  await SpecialPowers.popPrefEnv();
});

// Legacy storage.sync backend is still being used on GeckoView builds.
const storageSyncOldKintoBackend = SpecialPowers.Services.prefs.getBoolPref(
  "webextensions.storage.sync.kinto",
  false
);

// Verify browser.storage.sync rust backend is also cleared on uninstall.
async function test_uninistall_with_storage_sync() {
  await test_uninstall({
    extensionId: "storage.cleanup-sync@tests.mozilla.org",
    ...(storageTestHelpers.storageSync),
  });
}

// NOTE: ideally we would be using a skip_if option on the add_task call,
// but we don't support that in the add_task defined in mochitest-plain.
if (!storageSyncOldKintoBackend) {
  add_task(test_uninistall_with_storage_sync);
}

</script>

</body>
</html>