Backed out 3 changesets (bug 1213990) for problems in browser_ext_pageAction_context.js a=bustage CLOSED TREE
authorWes Kocher <wkocher@mozilla.com>
Wed, 03 Aug 2016 13:49:50 -0700
changeset 351484 265e7ad32cbe8749388575c61399ea34c413525b
parent 351483 db8a3cda72f921e2c41684f739af0bd6776ca511
child 351485 5d2e3f233977a62dacf47b3317a993f698c99a9e
push id1324
push usermtabara@mozilla.com
push dateMon, 16 Jan 2017 13:07:44 +0000
treeherdermozilla-release@a01c49833940 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersbustage
bugs1213990
milestone51.0a1
backs outc9b70a1998fcba46f84850089d29152eff7d9c6f
ffc2455a91358bc34705e590c6bbb60797ed5aa8
3223d0970b9ae76a43f0636ef0cdbbb8eba96d9d
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Backed out 3 changesets (bug 1213990) for problems in browser_ext_pageAction_context.js a=bustage CLOSED TREE Backed out changeset c9b70a1998fc (bug 1213990) Backed out changeset ffc2455a9135 (bug 1213990) Backed out changeset 3223d0970b9a (bug 1213990)
modules/libpref/init/all.js
toolkit/components/extensions/Extension.jsm
toolkit/components/extensions/test/mochitest/chrome.ini
toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html
toolkit/components/extensions/test/mochitest/test_chrome_ext_storage_cleanup.html
--- a/modules/libpref/init/all.js
+++ b/modules/libpref/init/all.js
@@ -4625,20 +4625,16 @@ pref("browser.meta_refresh_when_inactive
 // XPInstall prefs
 pref("xpinstall.whitelist.required", true);
 // Only Firefox requires add-on signatures
 pref("xpinstall.signatures.required", false);
 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);
 
 // Desktop Notification
 pref("notification.feature.enabled", false);
 
 // Web Notification
 pref("dom.webnotifications.enabled", true);
--- a/toolkit/components/extensions/Extension.jsm
+++ b/toolkit/components/extensions/Extension.jsm
@@ -20,19 +20,18 @@ const Ci = Components.interfaces;
 const Cc = Components.classes;
 const Cu = Components.utils;
 const Cr = Components.results;
 
 Cu.importGlobalProperties(["TextEncoder"]);
 
 Cu.import("resource://gre/modules/XPCOMUtils.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",
                                   "resource://gre/modules/Locale.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "Log",
                                   "resource://gre/modules/Log.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchGlobs",
                                   "resource://gre/modules/MatchPattern.jsm");
 XPCOMUtils.defineLazyModuleGetter(this, "MatchPattern",
                                   "resource://gre/modules/MatchPattern.jsm");
@@ -72,19 +71,16 @@ var {
   Messenger,
   injectAPI,
   instanceOf,
   extend,
   flushJarCache,
 } = ExtensionUtils;
 
 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`
     ^
     (
       (?:
         [^"] |
         " (?:[^"\\] | \\.)* "
       )*?
@@ -454,118 +450,39 @@ let ParentAPIManager = {
     let ref = data.path.concat(data.name).join(".");
     let listener = context.listenerProxies.get(ref);
     findPathInObject(context.apiObj, data.path)[data.name].removeListener(listener);
   },
 };
 
 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.
-var 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);
-  },
-};
-
-// This is the old interface that UUIDMap replaced, to be removed when
-// the references listed in bug 1291399 are updated.
-/* exported getExtensionUUID */
-function getExtensionUUID(id) {
-  return UUIDMap.get(id, true);
-}
-
 // For extensions that have called setUninstallURL(), send an event
 // so the browser can display the URL.
 var UninstallObserver = {
   initialized: false,
 
-  init() {
+  init: function() {
     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;
     }
   },
 
-  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);
+  uninit: function() {
+    if (this.initialized) {
+      AddonManager.removeAddonListener(this);
+      this.initialized = false;
     }
   },
 
-  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);
+  onUninstalling: function(addon) {
+    let extension = GlobalManager.extensionMap.get(addon.id);
+    if (extension) {
+      Management.emit("uninstall", extension);
     }
   },
 };
 
 // Responsible for loading extension APIs into the right globals.
 GlobalManager = {
   // Map[extension ID -> Extension]. Determines which extension is
   // responsible for content under a particular extension ID.
@@ -582,16 +499,17 @@ GlobalManager = {
     this.extensionMap.set(extension.id, extension);
   },
 
   uninit(extension) {
     this.extensionMap.delete(extension.id);
 
     if (this.extensionMap.size == 0 && this.initialized) {
       Services.obs.removeObserver(this, "content-document-global-created");
+      UninstallObserver.uninit();
       this.initialized = false;
     }
   },
 
   getExtension(extensionId) {
     return this.extensionMap.get(extensionId);
   },
 
@@ -766,16 +684,46 @@ GlobalManager = {
       }
 
       contentWindow.console.log(text);
     };
     Cu.exportFunction(alertOverwrite, contentWindow, {defineAs: "alert"});
   },
 };
 
+// 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
 // in a directory or a zip file, which may or may not be installed.
 // This class implements the functionality of the Extension class,
 // primarily related to manifest parsing and localization, which is
 // useful prior to extension installation or initialization.
 //
 // No functionality of this class is guaranteed to work before
 // |readManifest| has been called, and completed.
@@ -820,17 +768,17 @@ ExtensionData.prototype = {
    * @param {string} path The path portion of the URL.
    * @returns {string}
    */
   getURL(path = "") {
     if (!(this.id || this.uuid)) {
       throw new Error("getURL may not be called before an `id` or `uuid` has been set");
     }
     if (!this.uuid) {
-      this.uuid = UUIDMap.get(this.id);
+      this.uuid = getExtensionUUID(this.id);
     }
     return `moz-extension://${this.uuid}/${path}`;
   },
 
   readDirectory: Task.async(function* (path) {
     if (this.rootURI instanceof Ci.nsIFileURL) {
       let uri = NetUtil.newURI(this.rootURI.resolve("./" + path));
       let fullPath = uri.QueryInterface(Ci.nsIFileURL).file.path;
@@ -1096,17 +1044,17 @@ ExtensionData.prototype = {
   }),
 };
 
 // We create one instance of this class per extension. |addonData|
 // comes directly from bootstrap.js when initializing.
 this.Extension = function(addonData) {
   ExtensionData.call(this, addonData.resourceURI);
 
-  this.uuid = UUIDMap.get(addonData.id);
+  this.uuid = getExtensionUUID(addonData.id);
 
   if (addonData.cleanupFile) {
     Services.obs.addObserver(this, "xpcom-shutdown", false);
     this.cleanupFile = addonData.cleanupFile || null;
     delete addonData.cleanupFile;
   }
 
   this.addonData = addonData;
@@ -1319,17 +1267,17 @@ MockExtension.prototype = {
         });
       });
     } else {
       throw Error("installType must be one of: temporary, permanent");
     }
   },
 
   shutdown() {
-    this.addon.uninstall();
+    this.addon.uninstall(true);
     return this.cleanupGeneratedFile();
   },
 
   cleanupGeneratedFile() {
     flushJarCache(this.file);
     return OS.File.remove(this.file.path);
   },
 };
--- a/toolkit/components/extensions/test/mochitest/chrome.ini
+++ b/toolkit/components/extensions/test/mochitest/chrome.ini
@@ -20,10 +20,9 @@ skip-if = (os == 'android') # browser.ta
 [test_chrome_native_messaging_paths.html]
 skip-if = os != "mac" && os != "linux"
 [test_ext_cookies_expiry.html]
 skip-if = buildapp == 'b2g'
 [test_ext_cookies_permissions.html]
 skip-if = buildapp == 'b2g'
 [test_ext_jsversion.html]
 skip-if = buildapp == 'b2g'
-[test_ext_schema.html]
-[test_chrome_ext_storage_cleanup.html]
+[test_ext_schema.html]
\ No newline at end of file
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html
+++ b/toolkit/components/extensions/test/mochitest/test_chrome_ext_shutdown_cleanup.html
@@ -13,38 +13,47 @@
 <script type="text/javascript">
 "use strict";
 
 const {classes: Cc, interfaces: Ci, utils: Cu} = Components;
 
 Cu.import("resource://gre/modules/Services.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 */
 
 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() {
       browser.test.notifyPass("background page loaded");
     },
   });
 
   yield extension.startup();
 
   yield extension.awaitFinish("background page loaded");
 
   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>
 
 </body>
 </html>
deleted file mode 100644
--- a/toolkit/components/extensions/test/mochitest/test_chrome_ext_storage_cleanup.html
+++ /dev/null
@@ -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: "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>