зеркало из https://github.com/mozilla/gecko-dev.git
Backed out 3 changesets (bug 1213990) for webextension failures a=backout
Backed out changeset 2b1fdeff506d (bug 1213990) Backed out changeset 157efae8dd8a (bug 1213990) Backed out changeset ea870f4c5a61 (bug 1213990)
This commit is contained in:
Родитель
47b16ee23c
Коммит
f341d12e53
|
@ -4630,10 +4630,6 @@ pref("extensions.alwaysUnpack", false);
|
||||||
pref("extensions.minCompatiblePlatformVersion", "2.0");
|
pref("extensions.minCompatiblePlatformVersion", "2.0");
|
||||||
pref("extensions.webExtensionsMinPlatformVersion", "42.0a1");
|
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.count", 24);
|
||||||
pref("network.buffer.cache.size", 32768);
|
pref("network.buffer.cache.size", 32768);
|
||||||
|
|
||||||
|
|
|
@ -25,9 +25,8 @@ Cu.importGlobalProperties(["TextEncoder"]);
|
||||||
|
|
||||||
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
Cu.import("resource://gre/modules/XPCOMUtils.jsm");
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
|
Cu.import("resource://gre/modules/ExtensionContent.jsm");
|
||||||
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "ExtensionStorage",
|
|
||||||
"resource://gre/modules/ExtensionStorage.jsm");
|
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
|
XPCOMUtils.defineLazyModuleGetter(this, "Locale",
|
||||||
"resource://gre/modules/Locale.jsm");
|
"resource://gre/modules/Locale.jsm");
|
||||||
XPCOMUtils.defineLazyModuleGetter(this, "Log",
|
XPCOMUtils.defineLazyModuleGetter(this, "Log",
|
||||||
|
@ -77,9 +76,6 @@ var {
|
||||||
} = ExtensionUtils;
|
} = ExtensionUtils;
|
||||||
|
|
||||||
const LOGGER_ID_BASE = "addons.webextension.";
|
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`
|
const COMMENT_REGEXP = new RegExp(String.raw`
|
||||||
^
|
^
|
||||||
|
@ -459,101 +455,29 @@ let ParentAPIManager = {
|
||||||
|
|
||||||
ParentAPIManager.init();
|
ParentAPIManager.init();
|
||||||
|
|
||||||
// All moz-extension URIs use a machine-specific UUID rather than the
|
|
||||||
// extension's own ID in the host component. This makes it more
|
|
||||||
// difficult for web pages to detect whether a user has a given add-on
|
|
||||||
// installed (by trying to load a moz-extension URI referring to a
|
|
||||||
// web_accessible_resource from the extension). UUIDMap.get()
|
|
||||||
// returns the UUID for a given add-on ID.
|
|
||||||
let UUIDMap = {
|
|
||||||
_read() {
|
|
||||||
let pref = Preferences.get(UUID_MAP_PREF, "{}");
|
|
||||||
try {
|
|
||||||
return JSON.parse(pref);
|
|
||||||
} catch (e) {
|
|
||||||
Cu.reportError(`Error parsing ${UUID_MAP_PREF}.`);
|
|
||||||
return {};
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
_write(map) {
|
|
||||||
Preferences.set(UUID_MAP_PREF, JSON.stringify(map));
|
|
||||||
},
|
|
||||||
|
|
||||||
get(id, create = true) {
|
|
||||||
let map = this._read();
|
|
||||||
|
|
||||||
if (id in map) {
|
|
||||||
return map[id];
|
|
||||||
}
|
|
||||||
|
|
||||||
let uuid = null;
|
|
||||||
if (create) {
|
|
||||||
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
|
||||||
uuid = uuidGenerator.generateUUID().number;
|
|
||||||
uuid = uuid.slice(1, -1); // Strip { and } off the UUID.
|
|
||||||
|
|
||||||
map[id] = uuid;
|
|
||||||
this._write(map);
|
|
||||||
}
|
|
||||||
return uuid;
|
|
||||||
},
|
|
||||||
|
|
||||||
remove(id) {
|
|
||||||
let map = this._read();
|
|
||||||
delete map[id];
|
|
||||||
this._write(map);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// For extensions that have called setUninstallURL(), send an event
|
// For extensions that have called setUninstallURL(), send an event
|
||||||
// so the browser can display the URL.
|
// so the browser can display the URL.
|
||||||
var UninstallObserver = {
|
var UninstallObserver = {
|
||||||
initialized: false,
|
initialized: false,
|
||||||
|
|
||||||
init() {
|
init: function() {
|
||||||
if (!this.initialized) {
|
if (!this.initialized) {
|
||||||
AddonManager.addAddonListener(this);
|
AddonManager.addAddonListener(this);
|
||||||
XPCOMUtils.defineLazyPreferenceGetter(this, "leaveStorage", LEAVE_STORAGE_PREF, false);
|
|
||||||
XPCOMUtils.defineLazyPreferenceGetter(this, "leaveUuid", LEAVE_UUID_PREF, false);
|
|
||||||
this.initialized = true;
|
this.initialized = true;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onUninstalling(addon) {
|
uninit: function() {
|
||||||
let extension = GlobalManager.extensionMap.get(addon.id);
|
if (this.initialized) {
|
||||||
if (extension) {
|
AddonManager.removeAddonListener(this);
|
||||||
// Let any other interested listeners respond
|
this.initialized = false;
|
||||||
// (e.g., display the uninstall URL)
|
|
||||||
Management.emit("uninstall", extension);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
onUninstalled(addon) {
|
onUninstalling: function(addon) {
|
||||||
let uuid = UUIDMap.get(addon.id, false);
|
let extension = GlobalManager.extensionMap.get(addon.id);
|
||||||
if (!uuid) {
|
if (extension) {
|
||||||
return;
|
Management.emit("uninstall", extension);
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
@ -580,6 +504,7 @@ GlobalManager = {
|
||||||
|
|
||||||
if (this.extensionMap.size == 0 && this.initialized) {
|
if (this.extensionMap.size == 0 && this.initialized) {
|
||||||
Services.obs.removeObserver(this, "content-document-global-created");
|
Services.obs.removeObserver(this, "content-document-global-created");
|
||||||
|
UninstallObserver.uninit();
|
||||||
this.initialized = false;
|
this.initialized = false;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -764,6 +689,36 @@ GlobalManager = {
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// All moz-extension URIs use a machine-specific UUID rather than the
|
||||||
|
// extension's own ID in the host component. This makes it more
|
||||||
|
// difficult for web pages to detect whether a user has a given add-on
|
||||||
|
// installed (by trying to load a moz-extension URI referring to a
|
||||||
|
// web_accessible_resource from the extension). getExtensionUUID
|
||||||
|
// returns the UUID for a given add-on ID.
|
||||||
|
function getExtensionUUID(id) {
|
||||||
|
const PREF_NAME = "extensions.webextensions.uuids";
|
||||||
|
|
||||||
|
let pref = Preferences.get(PREF_NAME, "{}");
|
||||||
|
let map = {};
|
||||||
|
try {
|
||||||
|
map = JSON.parse(pref);
|
||||||
|
} catch (e) {
|
||||||
|
Cu.reportError(`Error parsing ${PREF_NAME}.`);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (id in map) {
|
||||||
|
return map[id];
|
||||||
|
}
|
||||||
|
|
||||||
|
let uuidGenerator = Cc["@mozilla.org/uuid-generator;1"].getService(Ci.nsIUUIDGenerator);
|
||||||
|
let uuid = uuidGenerator.generateUUID().number;
|
||||||
|
uuid = uuid.slice(1, -1); // Strip { and } off the UUID.
|
||||||
|
|
||||||
|
map[id] = uuid;
|
||||||
|
Preferences.set(PREF_NAME, JSON.stringify(map));
|
||||||
|
return uuid;
|
||||||
|
}
|
||||||
|
|
||||||
// Represents the data contained in an extension, contained either
|
// Represents the data contained in an extension, contained either
|
||||||
// in a directory or a zip file, which may or may not be installed.
|
// in a directory or a zip file, which may or may not be installed.
|
||||||
// This class implements the functionality of the Extension class,
|
// This class implements the functionality of the Extension class,
|
||||||
|
@ -818,7 +773,7 @@ ExtensionData.prototype = {
|
||||||
throw new Error("getURL may not be called before an `id` or `uuid` has been set");
|
throw new Error("getURL may not be called before an `id` or `uuid` has been set");
|
||||||
}
|
}
|
||||||
if (!this.uuid) {
|
if (!this.uuid) {
|
||||||
this.uuid = UUIDMap.get(this.id);
|
this.uuid = getExtensionUUID(this.id);
|
||||||
}
|
}
|
||||||
return `moz-extension://${this.uuid}/${path}`;
|
return `moz-extension://${this.uuid}/${path}`;
|
||||||
},
|
},
|
||||||
|
@ -1094,7 +1049,7 @@ ExtensionData.prototype = {
|
||||||
this.Extension = function(addonData) {
|
this.Extension = function(addonData) {
|
||||||
ExtensionData.call(this, addonData.resourceURI);
|
ExtensionData.call(this, addonData.resourceURI);
|
||||||
|
|
||||||
this.uuid = UUIDMap.get(addonData.id);
|
this.uuid = getExtensionUUID(addonData.id);
|
||||||
|
|
||||||
if (addonData.cleanupFile) {
|
if (addonData.cleanupFile) {
|
||||||
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
Services.obs.addObserver(this, "xpcom-shutdown", false);
|
||||||
|
@ -1317,7 +1272,7 @@ MockExtension.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
shutdown() {
|
shutdown() {
|
||||||
this.addon.uninstall();
|
this.addon.uninstall(true);
|
||||||
return this.cleanupGeneratedFile();
|
return this.cleanupGeneratedFile();
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|
|
@ -25,5 +25,4 @@ skip-if = buildapp == 'b2g'
|
||||||
skip-if = buildapp == 'b2g'
|
skip-if = buildapp == 'b2g'
|
||||||
[test_ext_jsversion.html]
|
[test_ext_jsversion.html]
|
||||||
skip-if = buildapp == 'b2g'
|
skip-if = buildapp == 'b2g'
|
||||||
[test_ext_schema.html]
|
[test_ext_schema.html]
|
||||||
[test_chrome_ext_storage_cleanup.html]
|
|
|
@ -18,13 +18,18 @@ const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
|
||||||
Cu.import("resource://gre/modules/Services.jsm");
|
Cu.import("resource://gre/modules/Services.jsm");
|
||||||
Cu.import("resource://testing-common/TestUtils.jsm");
|
Cu.import("resource://testing-common/TestUtils.jsm");
|
||||||
|
|
||||||
const {GlobalManager} = Cu.import("resource://gre/modules/Extension.jsm");
|
const {
|
||||||
|
GlobalManager,
|
||||||
|
UninstallObserver,
|
||||||
|
} = Cu.import("resource://gre/modules/Extension.jsm");
|
||||||
|
|
||||||
/* eslint-disable mozilla/balanced-listeners */
|
/* eslint-disable mozilla/balanced-listeners */
|
||||||
|
|
||||||
add_task(function* testShutdownCleanup() {
|
add_task(function* testShutdownCleanup() {
|
||||||
is(GlobalManager.initialized, false,
|
is(GlobalManager.initialized, false,
|
||||||
"GlobalManager start as not initialized");
|
"GlobalManager start as not initialized");
|
||||||
|
is(UninstallObserver.initialized, false,
|
||||||
|
"UninstallObserver start as not initialized");
|
||||||
|
|
||||||
let extension = ExtensionTestUtils.loadExtension({
|
let extension = ExtensionTestUtils.loadExtension({
|
||||||
background: "new " + function() {
|
background: "new " + function() {
|
||||||
|
@ -38,11 +43,15 @@ add_task(function* testShutdownCleanup() {
|
||||||
|
|
||||||
is(GlobalManager.initialized, true,
|
is(GlobalManager.initialized, true,
|
||||||
"GlobalManager has been initialized once an extension is started");
|
"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();
|
yield extension.unload();
|
||||||
|
|
||||||
is(GlobalManager.initialized, false,
|
is(GlobalManager.initialized, false,
|
||||||
"GlobalManager has been uninitialized once all the webextensions have been stopped");
|
"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>
|
</script>
|
||||||
|
|
||||||
|
|
|
@ -1,161 +0,0 @@
|
||||||
<!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: true,
|
|
||||||
}, 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: true,
|
|
||||||
}, 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: true,
|
|
||||||
}, 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>
|
|
Загрузка…
Ссылка в новой задаче