From 7a9581c156a8d04493a12eeac0bd211e89ec737d Mon Sep 17 00:00:00 2001
From: James Hobin <hobinjk@mit.edu>
Date: Sun, 13 Aug 2017 09:22:10 -0700
Subject: [PATCH] Asynchronously migrate extension data (#1385)

* Asynchronously migrate extension data

* Add storage migration warning; Default to unlimited storage

* Bump version to 7.7.6-less-broken

* Skip update if current value is longer

* copying -> migrating for clarity

* Bump version

* Excise misleading comment
---
 Firefox/package.json               |  2 +-
 Firefox/sync-data.js               | 46 ++++++++++++++++++++++++------
 Firefox/webextension/background.js | 46 ++++++++++++++++++++++--------
 Firefox/webextension/manifest.json |  2 +-
 WebExtension/bridge.js             | 12 +++++++-
 5 files changed, 84 insertions(+), 24 deletions(-)

diff --git a/Firefox/package.json b/Firefox/package.json
index 7024664cf..f54908386 100644
--- a/Firefox/package.json
+++ b/Firefox/package.json
@@ -2,7 +2,7 @@
   "title": "New XKit",
   "name": "new-xkit",
   "fullName": "New XKit",
-  "version": "7.7.6",
+  "version": "7.7.7",
   "description": "Fork of XKit, the Tumblr extension framework",
   "main": "./main.js",
   "author": "",
diff --git a/Firefox/sync-data.js b/Firefox/sync-data.js
index 0dc9884a8..1e43cef06 100644
--- a/Firefox/sync-data.js
+++ b/Firefox/sync-data.js
@@ -1,16 +1,44 @@
-/* globals require, exports */
+/* globals require, exports, Promise */
 let prefs = require('sdk/preferences/service');
+let {setTimeout} = require('sdk/timers');
 let prefRoot = 'extensions.xkit7.';
 
+function migratePreference(port, prefKey) {
+	let name = prefKey.substring(prefRoot.length);
+	let value = prefs.get(prefKey, null);
+	let msg = {};
+	msg[name] = value;
+	// postMessage doesn't return a promise even though it could :(
+	port.postMessage(msg);
+	return new Promise(function(resolve, reject) {
+		setTimeout(function() {
+			if (port.error) {
+				reject(port.error);
+			} else {
+				resolve();
+			}
+		}, 10);
+	});
+}
+
+function migratePreferences(port, existingKeys) {
+	if (existingKeys.length === 0) {
+		return Promise.resolve();
+	}
+	let key = existingKeys.pop();
+	return migratePreference(port, key).then(function() {
+		return migratePreferences(port, existingKeys);
+	});
+}
+
 exports.setSyncLegacyDataPort = function(port) {
 
-	// Get all and broadcast to webext
+	// Get all and broadcast to webext one at a time
 	let existingKeys = prefs.keys(prefRoot);
-	let xkitStorage = {};
-	for (let properName of existingKeys) {
-		let name = properName.substring(prefRoot.length);
-		let value = prefs.get(properName, null);
-		xkitStorage[name] = value;
-	}
-	port.postMessage(xkitStorage);
+
+	migratePreferences(port, existingKeys).then(() => {
+		return port.postMessage({isProperlyMigrated: true});
+	}).catch(err => {
+		console.error('preferences not migrated', err);
+	});
 };
diff --git a/Firefox/webextension/background.js b/Firefox/webextension/background.js
index beffcc804..fe4b2eb37 100644
--- a/Firefox/webextension/background.js
+++ b/Firefox/webextension/background.js
@@ -1,21 +1,43 @@
-/* global browser */
+/* global browser, Promise */
 
 // From the MDN WebExtensions hybrid addon example
 
 // Ask to the legacy part to dump the needed data and send it back
 // to the background page...
-var port = browser.runtime.connect({name: "sync-legacy-addon-data"});
-port.onMessage.addListener((msg) => {
-	if (msg) {
-		browser.storage.local.get('isMigrated').then(function(item) {
-			if (item.isMigrated) {
-				console.warn('Skipping migration');
-				return;
+browser.storage.local.get('isProperlyMigrated').then(function(item) {
+	if (item.isProperlyMigrated) {
+		console.warn('Skipping migration', item);
+		return;
+	}
+
+	var port = browser.runtime.connect({name: "sync-legacy-addon-data"});
+	function onMessage(msg) {
+		if (!msg) {
+			return;
+		}
+		let setPromises = Object.keys(msg).map(function(key) {
+			return browser.storage.local.get({key: null}).then(function(msgItem) {
+				if (typeof(msgItem[key]) === 'string') {
+					if (msgItem[key].length > msg[key].length) {
+						// Do not update the storage
+						return Promise.resolve();
+					}
+				}
+				// The corresponding code in XKit is GM_setValue(name, value) -> set({name: value})
+				return browser.storage.local.set(msg);
+			});
+		});
+		Promise.all(setPromises).catch(function(err) {
+			let errorMessage = "XKit failed to migrate your preferences to the new storage system. The migration reported the following error messag: " + err + ". Please restart your browser to try again.";
+			if (typeof(XKit) === 'undefined') {
+				alert(errorMessage);
+			} else {
+				XKit.window.show("Storage migration in progress", errorMessage, "error",  "<div class=\"xkit-button default\">OK</div>");
 			}
-			// The corresponding code in XKit is GM_setValue(name, value) -> set({name: value})
-			msg.isMigrated = true;
-			console.log('Migrating pref storage', msg);
-			browser.storage.local.set(msg);
+			browser.storage.local.set({isProperlyMigrated: false});
+			port.onMessage.removeListener(onMessage);
 		});
 	}
+
+	port.onMessage.addListener(onMessage);
 });
diff --git a/Firefox/webextension/manifest.json b/Firefox/webextension/manifest.json
index 9574cff1a..8dc1e47f5 100644
--- a/Firefox/webextension/manifest.json
+++ b/Firefox/webextension/manifest.json
@@ -15,7 +15,7 @@
   "manifest_version": 2,
   "name": "New XKit",
   "permissions": ["*://*/*", "*://*/", "storage", "http://*.tumblr.com/", "https://*.tumblr.com/" ],
-  "version": "7.7.6",
+  "version": "7.7.7",
   "web_accessible_resources": [ "manifest.json", "editor.js" ],
   "background": {
    "scripts": ["background.js"]
diff --git a/WebExtension/bridge.js b/WebExtension/bridge.js
index 9fbfb4b44..8d68c3a51 100644
--- a/WebExtension/bridge.js
+++ b/WebExtension/bridge.js
@@ -26,7 +26,7 @@ try {
 	var storage = browser.storage.local;
 	var storage_loaded = false;
 	var framework_version = getVersion();
-	var storage_used = -1;
+	var storage_used = 0;
 	var storage_max = -1;
 	init_bridge();
 } catch (e) {
@@ -90,6 +90,16 @@ function init_bridge() {
 				});
 				return;
 			}
+			if (typeof(chrome) === 'undefined') {
+				if (!items.isProperlyMigrated) {
+					XKit.window.show("Storage migration in progress", "XKit is still busy migrating your preferences from the old storage system to the new one. Please check back in a couple seconds by refreshing the page.", "warning",  "<div class=\"xkit-button default\" id=\"xkit-bridge-refresh\">Refresh</div>");
+					$("#xkit-bridge-refresh").click(function() {
+						window.location = window.location;
+					});
+					return;
+				}
+
+			}
 			for (var key in items) {
 				xkit_storage[key] = items[key];
 			}