mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-11-29 07:42:04 +00:00
Bug 1213990 Clear storage when webextension is uninstalled r=kmag
MozReview-Commit-ID: BeMOxOCSeru --HG-- extra : rebase_source : 3eb7fc4e61fe1ce698e22e25bd05fc779a6cf7a8
This commit is contained in:
parent
d7af10177b
commit
fa3c0fac00
@ -4639,6 +4639,10 @@ pref("extensions.alwaysUnpack", false);
|
||||
pref("extensions.minCompatiblePlatformVersion", "2.0");
|
||||
pref("extensions.webExtensionsMinPlatformVersion", "42.0a1");
|
||||
|
||||
// Other webextensions prefs
|
||||
pref("extensions.webextensions.keepStorageOnUninstall", false);
|
||||
pref("extensions.webextensions.keepUuidOnUninstall", false);
|
||||
|
||||
pref("network.buffer.cache.count", 24);
|
||||
pref("network.buffer.cache.size", 32768);
|
||||
|
||||
|
@ -32,6 +32,8 @@ XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
|
||||
"resource://gre/modules/AppConstants.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionAPIs",
|
||||
"resource://gre/modules/ExtensionAPI.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
|
||||
"resource://gre/modules/ExtensionStorage.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "FileUtils",
|
||||
"resource://gre/modules/FileUtils.jsm");
|
||||
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
|
||||
@ -89,6 +91,8 @@ var {
|
||||
|
||||
const LOGGER_ID_BASE = "addons.webextension.";
|
||||
const UUID_MAP_PREF = "extensions.webextensions.uuids";
|
||||
const LEAVE_STORAGE_PREF = "extensions.webextensions.keepStorageOnUninstall";
|
||||
const LEAVE_UUID_PREF = "extensions.webextensions.keepUuidOnUninstall";
|
||||
|
||||
const COMMENT_REGEXP = new RegExp(String.raw`
|
||||
^
|
||||
@ -535,24 +539,49 @@ function getExtensionUUID(id) {
|
||||
var UninstallObserver = {
|
||||
initialized: false,
|
||||
|
||||
init: function() {
|
||||
init() {
|
||||
if (!this.initialized) {
|
||||
AddonManager.addAddonListener(this);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "leaveStorage", LEAVE_STORAGE_PREF, false);
|
||||
XPCOMUtils.defineLazyPreferenceGetter(this, "leaveUuid", LEAVE_UUID_PREF, false);
|
||||
this.initialized = true;
|
||||
}
|
||||
},
|
||||
|
||||
uninit: function() {
|
||||
if (this.initialized) {
|
||||
AddonManager.removeAddonListener(this);
|
||||
this.initialized = false;
|
||||
onUninstalling(addon) {
|
||||
let extension = GlobalManager.extensionMap.get(addon.id);
|
||||
if (extension) {
|
||||
// Let any other interested listeners respond
|
||||
// (e.g., display the uninstall URL)
|
||||
Management.emit("uninstall", extension);
|
||||
}
|
||||
},
|
||||
|
||||
onUninstalling: function(addon) {
|
||||
let extension = GlobalManager.extensionMap.get(addon.id);
|
||||
if (extension) {
|
||||
Management.emit("uninstall", extension);
|
||||
onUninstalled(addon) {
|
||||
let uuid = UUIDMap.get(addon.id, false);
|
||||
if (!uuid) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!this.leaveStorage) {
|
||||
// Clear browser.local.storage
|
||||
ExtensionStorage.clear(addon.id);
|
||||
|
||||
// Clear any IndexedDB storage created by the extension
|
||||
let baseURI = NetUtil.newURI(`moz-extension://${uuid}/`);
|
||||
let principal = Services.scriptSecurityManager.createCodebasePrincipal(
|
||||
baseURI, {addonId: addon.id}
|
||||
);
|
||||
Services.qms.clearStoragesForPrincipal(principal);
|
||||
|
||||
// Clear localStorage created by the extension
|
||||
let attrs = JSON.stringify({addonId: addon.id});
|
||||
Services.obs.notifyObservers(null, "clear-origin-data", attrs);
|
||||
}
|
||||
|
||||
if (!this.leaveUuid) {
|
||||
// Clear the entry in the UUID map
|
||||
UUIDMap.remove(addon.id);
|
||||
}
|
||||
},
|
||||
};
|
||||
@ -579,7 +608,6 @@ GlobalManager = {
|
||||
|
||||
if (this.extensionMap.size == 0 && this.initialized) {
|
||||
Services.obs.removeObserver(this, "content-document-global-created");
|
||||
UninstallObserver.uninit();
|
||||
this.initialized = false;
|
||||
}
|
||||
},
|
||||
|
@ -24,3 +24,4 @@ skip-if = buildapp == 'b2g'
|
||||
[test_ext_jsversion.html]
|
||||
skip-if = buildapp == 'b2g'
|
||||
[test_ext_schema.html]
|
||||
[test_chrome_ext_storage_cleanup.html]
|
||||
|
@ -18,18 +18,13 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||
Cu.import("resource://gre/modules/Services.jsm");
|
||||
Cu.import("resource://testing-common/TestUtils.jsm");
|
||||
|
||||
const {
|
||||
GlobalManager,
|
||||
UninstallObserver,
|
||||
} = Cu.import("resource://gre/modules/Extension.jsm");
|
||||
const {GlobalManager} = Cu.import("resource://gre/modules/Extension.jsm");
|
||||
|
||||
/* eslint-disable mozilla/balanced-listeners */
|
||||
|
||||
add_task(function* testShutdownCleanup() {
|
||||
is(GlobalManager.initialized, false,
|
||||
"GlobalManager start as not initialized");
|
||||
is(UninstallObserver.initialized, false,
|
||||
"UninstallObserver start as not initialized");
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: "new " + function() {
|
||||
@ -43,15 +38,11 @@ add_task(function* testShutdownCleanup() {
|
||||
|
||||
is(GlobalManager.initialized, true,
|
||||
"GlobalManager has been initialized once an extension is started");
|
||||
is(UninstallObserver.initialized, true,
|
||||
"UninstallObserver has been initialized once an extension is started");
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
is(GlobalManager.initialized, false,
|
||||
"GlobalManager has been uninitialized once all the webextensions have been stopped");
|
||||
is(UninstallObserver.initialized, false,
|
||||
"UninstallObserver has been uninitialized once all the webextensions have been stopped");
|
||||
});
|
||||
</script>
|
||||
|
||||
|
@ -0,0 +1,161 @@
|
||||
<!DOCTYPE HTML>
|
||||
<html>
|
||||
<head>
|
||||
<title>WebExtension test</title>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SimpleTest.js"></script>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/SpawnTask.js"></script>
|
||||
<script type="text/javascript" src="chrome://mochikit/content/tests/SimpleTest/ExtensionTestUtils.js"></script>
|
||||
<script type="text/javascript" src="head.js"></script>
|
||||
<link rel="stylesheet" type="text/css" href="chrome://mochikit/content/tests/SimpleTest/test.css"/>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<script type="text/javascript">
|
||||
"use strict";
|
||||
|
||||
// Test that storage used by a webextension (through localStorage,
|
||||
// indexedDB, and browser.storage.local) gets cleaned up when the
|
||||
// extension is uninstalled.
|
||||
add_task(function* test_uninstall() {
|
||||
function 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 = e => {
|
||||
reject(new Error(`add to indexedDB failed with ${e.errorCode}`));
|
||||
};
|
||||
addreq.onsuccess = e => {
|
||||
resolve();
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
let browserStoragePromise = browser.storage.local.set({hello: "world"});
|
||||
|
||||
Promise.all([idbPromise, browserStoragePromise]).then(() => {
|
||||
browser.test.sendMessage("finished");
|
||||
});
|
||||
}
|
||||
|
||||
function 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 = e => {
|
||||
reject(new Error(`read from indexedDB failed with ${e.errorCode}`));
|
||||
};
|
||||
addreq.onsuccess = e => {
|
||||
let match = (addreq.result.value == "world");
|
||||
resolve(match);
|
||||
};
|
||||
};
|
||||
});
|
||||
|
||||
let browserStoragePromise = browser.storage.local.get("hello").then(result => {
|
||||
return (Object.keys(result).length == 1 && result.hello == "world");
|
||||
});
|
||||
|
||||
Promise.all([idbPromise, browserStoragePromise])
|
||||
.then(([matchIDB, matchBrowserStorage]) => {
|
||||
let result = {matchLocalStorage, matchIDB, matchBrowserStorage};
|
||||
browser.test.sendMessage("results", result);
|
||||
});
|
||||
}
|
||||
|
||||
const ID = "storage.cleanup@tests.mozilla.org";
|
||||
|
||||
// Use a test-only pref to leave the addonid->uuid mapping around after
|
||||
// uninstall so that we can re-attach to the same storage. Also set
|
||||
// the pref to prevent cleaning up storage on uninstall so we can test
|
||||
// that the "keep uuid" logic works correctly. Do the storage flag in
|
||||
// a separate prefEnv so we can pop it below, leaving the uuid flag set.
|
||||
yield SpecialPowers.pushPrefEnv({
|
||||
set: [["extensions.webextensions.keepUuidOnUninstall", true]],
|
||||
});
|
||||
yield SpecialPowers.pushPrefEnv({
|
||||
set: [["extensions.webextensions.keepStorageOnUninstall", true]],
|
||||
});
|
||||
|
||||
let extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${writeData})()`,
|
||||
manifest: {
|
||||
permissions: ["storage"],
|
||||
},
|
||||
useAddonManager: "temporary",
|
||||
}, ID);
|
||||
|
||||
yield extension.startup();
|
||||
yield extension.awaitMessage("finished");
|
||||
yield 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 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!
|
||||
yield SpecialPowers.popPrefEnv();
|
||||
extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${readData})()`,
|
||||
manifest: {
|
||||
permissions: ["storage"],
|
||||
},
|
||||
useAddonManager: "temporary",
|
||||
}, ID);
|
||||
|
||||
yield extension.startup();
|
||||
let results = yield extension.awaitMessage("results");
|
||||
is(results.matchLocalStorage, true, "localStorage data is still present");
|
||||
is(results.matchIDB, true, "indexedDB data is still present");
|
||||
is(results.matchBrowserStorage, true, "browser.storage.local data is still present");
|
||||
|
||||
yield extension.unload();
|
||||
|
||||
// Read again. This time, our data should be gone.
|
||||
extension = ExtensionTestUtils.loadExtension({
|
||||
background: `(${readData})()`,
|
||||
manifest: {
|
||||
permissions: ["storage"],
|
||||
},
|
||||
useAddonManager: "temporary",
|
||||
}, ID);
|
||||
|
||||
yield extension.startup();
|
||||
results = yield extension.awaitMessage("results");
|
||||
is(results.matchLocalStorage, false, "localStorage data was cleared");
|
||||
is(results.matchIDB, false, "indexedDB data was cleared");
|
||||
is(results.matchBrowserStorage, false, "browser.storage.local data was cleared");
|
||||
yield extension.unload();
|
||||
});
|
||||
</script>
|
||||
|
||||
</body>
|
||||
</html>
|
Loading…
Reference in New Issue
Block a user