From e3a9c6779bc0ffea8093cbd19d67f1730325baa2 Mon Sep 17 00:00:00 2001
From: Dan Stillman <dstillman@zotero.org>
Date: Mon, 5 Sep 2016 20:41:35 -0400
Subject: [PATCH] Restore connector mode functionality in Firefox

Non-Zotero for Firefox connector code will probably need to be updated
to handle these changes.
---
 chrome/content/zotero/browser.js              |  22 ++--
 chrome/content/zotero/icon.js                 |   5 +
 chrome/content/zotero/overlay.js              |   4 +
 chrome/content/zotero/xpcom/connector/repo.js |  48 +++++---
 .../zotero/xpcom/connector/translator.js      | 107 +++++++-----------
 .../zotero/xpcom/translation/translate.js     | 103 ++++++++++-------
 .../zotero/xpcom/translation/translator.js    |  28 ++---
 .../zotero/xpcom/translation/translators.js   |   2 +-
 chrome/content/zotero/xpcom/zotero.js         |   9 ++
 chrome/content/zotero/zoteroPane.js           |   6 +-
 components/zotero-service.js                  |  10 +-
 11 files changed, 188 insertions(+), 156 deletions(-)

diff --git a/chrome/content/zotero/browser.js b/chrome/content/zotero/browser.js
index f06c4178c3..d449e5bd99 100644
--- a/chrome/content/zotero/browser.js
+++ b/chrome/content/zotero/browser.js
@@ -613,7 +613,9 @@ var Zotero_Browser = new function() {
 			return;
 		}
 		
-		yield Zotero.DB.waitForTransaction();
+		if (!Zotero.isConnector) {
+			yield Zotero.DB.waitForTransaction();
+		}
 		
 		Zotero_Browser.progress.show();
 		Zotero_Browser.isScraping = true;
@@ -635,14 +637,16 @@ var Zotero_Browser = new function() {
 			collection = ZoteroPane.getSelectedCollection();
 		}
 		
-		if (libraryID === Zotero.Libraries.publicationsLibraryID) {
-			Zotero_Browser.progress.Translation.cannotAddToPublications();
-			return;
-		}
-		
-		if (Zotero.Feeds.get(libraryID)) {
-			Zotero_Browser.progress.Translation.cannotAddToFeed();
-			return;
+		if (!Zotero.isConnector) {
+			if (libraryID === Zotero.Libraries.publicationsLibraryID) {
+				Zotero_Browser.progress.Translation.cannotAddToPublications();
+				return;
+			}
+			
+			if (Zotero.Feeds.get(libraryID)) {
+				Zotero_Browser.progress.Translation.cannotAddToFeed();
+				return;
+			}
 		}
 		
 		Zotero_Browser.progress.Translation.scrapingTo(libraryID, collection);
diff --git a/chrome/content/zotero/icon.js b/chrome/content/zotero/icon.js
index 1f3ce8ce9e..34f427ca44 100644
--- a/chrome/content/zotero/icon.js
+++ b/chrome/content/zotero/icon.js
@@ -28,6 +28,11 @@
 Components.utils.import("resource://zotero/config.js");
 Components.utils.import("resource:///modules/CustomizableUI.jsm");
 
+// Necessary for connector mode, for some reason
+var Zotero = Components.classes["@zotero.org/Zotero;1"]
+	.getService(Components.interfaces.nsISupports)
+	.wrappedJSObject;
+
 var comboButtonsID = 'zotero-toolbar-buttons';
 
 CustomizableUI.addListener({
diff --git a/chrome/content/zotero/overlay.js b/chrome/content/zotero/overlay.js
index c12a3a0107..6ee68a6660 100644
--- a/chrome/content/zotero/overlay.js
+++ b/chrome/content/zotero/overlay.js
@@ -42,6 +42,10 @@ var ZoteroOverlay = new function()
 		var self = this;
 		var iconLoaded = false;
 		
+		if (Zotero.isConnector) {
+			return;
+		}
+		
 		Zotero.Promise.try(function () {
 			if (!Zotero) {
 				throw new Error("No Zotero object");
diff --git a/chrome/content/zotero/xpcom/connector/repo.js b/chrome/content/zotero/xpcom/connector/repo.js
index 7b6774956a..b747f6d1e0 100644
--- a/chrome/content/zotero/xpcom/connector/repo.js
+++ b/chrome/content/zotero/xpcom/connector/repo.js
@@ -55,34 +55,54 @@ Zotero.Repo = new function() {
 	/**
 	 * Get translator code from repository
 	 * @param {String} translatorID ID of the translator to retrieve code for
-	 * @param {Function} callback Callback to pass code when retreived
 	 */
-	this.getTranslatorCode = function(translatorID, callback) {
+	this.getTranslatorCode = Zotero.Promise.method(function (translatorID) {
+		var deferred = Zotero.Promise.defer();
+		
 		// try standalone
 		Zotero.Connector.callMethod("getTranslatorCode", {"translatorID":translatorID}, function(result) {
 			if(result) {
-				_haveCode(result, translatorID, Zotero.Repo.SOURCE_ZOTERO_STANDALONE, callback);
+				deferred.resolve(
+					Zotero.Promise.all(
+						[
+							_haveCode(result, translatorID),
+							Zotero.Repo.SOURCE_ZOTERO_STANDALONE
+						]
+					)
+				);
 				return;
 			}
 			
+			
 			// then try repo
-			Zotero.HTTP.doGet(ZOTERO_CONFIG.REPOSITORY_URL + "code/" + translatorID + "?version=" + Zotero.version,
+			Zotero.HTTP.doGet(
+				ZOTERO_CONFIG.REPOSITORY_URL + "code/" + translatorID + "?version=" + Zotero.version,
 				function(xmlhttp) {
-					_haveCode(xmlhttp.status === 200 ? xmlhttp.responseText : false, translatorID,
-						Zotero.Repo.SOURCE_REPO, callback);
+					deferred.resolve(
+						Zotero.Promise.all(
+							[
+								_haveCode(
+									xmlhttp.status === 200 ? xmlhttp.responseText : false,
+									translatorID
+								),
+								Zotero.Repo.SOURCE_REPO
+							]
+						)
+					);
 				}
 			);
 		});
-	};
+		
+		return deferred.promise;
+	});
 	
 	/**
 	 * Called when code has been retrieved from standalone or repo
 	 */
-	function _haveCode(code, translatorID, source, callback) {
+	function _haveCode(code, translatorID) {
 		if(!code) {
 			Zotero.logError(new Error("Code could not be retrieved for " + translatorID));
-			callback(false);
-			return;
+			return false;
 		}
 		
 		if(!Zotero.isFx) {
@@ -91,16 +111,14 @@ Zotero.Repo = new function() {
 			var m = infoRe.exec(code);
 			if (!m) {
 				Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID));
-				callback(false);
-				return;
+				return false;
 			}
 			
 			try {
 				var metadata = JSON.parse(m[0]);
 			} catch(e) {
 				Zotero.logError(new Error("Invalid or missing translator metadata JSON object for " + translatorID));
-				callback(false);
-				return;
+				return false;
 			}
 			
 			var translator = Zotero.Translators.getWithoutCode(translatorID);
@@ -114,7 +132,7 @@ Zotero.Repo = new function() {
 				}
 			}
 		}
-		callback(code, source);
+		return code;
 	}
 	
 	/**
diff --git a/chrome/content/zotero/xpcom/connector/translator.js b/chrome/content/zotero/xpcom/connector/translator.js
index d919313853..121ffbbc49 100644
--- a/chrome/content/zotero/xpcom/connector/translator.js
+++ b/chrome/content/zotero/xpcom/connector/translator.js
@@ -24,7 +24,7 @@
 */
 
 // Enumeration of types of translators
-const TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
+var TRANSLATOR_TYPES = {"import":1, "export":2, "web":4, "search":8};
 
 /**
  * Singleton to handle loading and caching of translators
@@ -92,9 +92,6 @@ Zotero.Translators = new function() {
 	/**
 	 * Gets the translator that corresponds to a given ID, without attempting to retrieve code
 	 * @param {String} id The ID of the translator
-	 * @param {Function} [callback] An optional callback to be executed when translators have been
-	 *                              retrieved. If no callback is specified, translators are
-	 *                              returned.
 	 */
 	this.getWithoutCode = function(id) {
 		if(!_initialized) Zotero.Translators.init();
@@ -103,54 +100,46 @@ Zotero.Translators = new function() {
 	
 	/**
 	 * Gets the translator that corresponds to a given ID
+	 *
 	 * @param {String} id The ID of the translator
-	 * @param {Function} [callback] An optional callback to be executed when translators have been
-	 *                              retrieved. If no callback is specified, translators are
-	 *                              returned.
 	 */
-	this.get = function(id, callback) {
+	this.get = Zotero.Promise.method(function (id) {
 		if(!_initialized) Zotero.Translators.init();
 		var translator = _translators[id];
 		if(!translator) {
-			callback(false);
 			return false;
 		}
 		
 		// only need to get code if it is of some use
 		if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER
 				&& !translator.hasOwnProperty("code")) {
-			translator.getCode(function() { callback(translator) });
+			return translator.getCode().then(() => translator);
 		} else {
-			callback(translator);
+			return translator;
 		}
-	}
+	});
 	
 	/**
 	 * Gets all translators for a specific type of translation
 	 * @param {String} type The type of translators to get (import, export, web, or search)
-	 * @param {Function} callback A required callback to be executed when translators have been
-	 *                            retrieved.
 	 * @param {Boolean} [debugMode] Whether to assume debugging mode. If true, code is included for 
 	 *                              unsupported translators, and code originally retrieved from the
 	 *                              repo is re-retrieved from Zotero Standalone.
 	 */
-	this.getAllForType = function(type, callback, debugMode) {
+	this.getAllForType = Zotero.Promise.method(function (type, debugMode) {
 		if(!_initialized) Zotero.Translators.init()
 		var translators = _cache[type].slice(0);
-		new Zotero.Translators.CodeGetter(translators, callback, translators, debugMode);
-		return true;
-	}
+		var codeGetter = new Zotero.Translators.CodeGetter(translators, debugMode);
+		return codeGetter.getAll();
+	});
 	
 	/**
 	 * Gets web translators for a specific location
 	 * @param {String} uri The URI for which to look for translators
-	 * @param {Function} [callback] An optional callback to be executed when translators have been
-	 *                              retrieved. If no callback is specified, translators are
-	 *                              returned. The callback is passed a set of functions for
-	 *                              converting URLs from proper to proxied forms as the second
-	 *                              argument.
+	 * @return {Promise<Array[]>} - A promise for a 2-item array containing an array of translators and
+	 *     an array of functions for converting URLs from proper to proxied forms
 	 */
-	this.getWebTranslatorsForLocation = function(uri, callback) {
+	this.getWebTranslatorsForLocation = Zotero.Promise.method(function (uri) {
 		if(!_initialized) Zotero.Translators.init();
 		var allTranslators = _cache["web"];
 		var potentialTranslators = [];
@@ -215,10 +204,11 @@ Zotero.Translators = new function() {
 			}
 		}
 		
-		new Zotero.Translators.CodeGetter(potentialTranslators, callback,
-				[potentialTranslators, converterFunctions]);
-		return true;
-	}
+		var codeGetter = new Zotero.Translators.CodeGetter(potentialTranslators);
+		return codeGetter.getAll().then(function () {
+			return [potentialTranslators, converterFunctions];
+		});
+	});
 	
 	/**
 	 * Converts translators to JSON-serializable objects
@@ -331,29 +321,16 @@ Zotero.Translators = new function() {
  * A class to get the code for a set of translators at once
  *
  * @param {Zotero.Translator[]} translators Translators for which to retrieve code
- * @param {Function} callback Callback to call once code has been retrieved
- * @param {Function} callbackArgs All arguments to be passed to callback (including translators)
  * @param {Boolean} [debugMode] If true, include code for unsupported translators
  */
-Zotero.Translators.CodeGetter = function(translators, callback, callbackArgs, debugMode) {
+Zotero.Translators.CodeGetter = function(translators, debugMode) {
 	this._translators = translators;
-	this._callbackArgs = callbackArgs;
-	this._callback = callback;
 	this._debugMode = debugMode;
-	this.getCodeFor(0);
 }
 
-Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) {
-	var me = this;
-	while(true) {
-		if(i === this._translators.length) {
-			// all done; run callback
-			this._callback(this._callbackArgs);
-			return;
-		}
-		
-		var translator = this._translators[i];
-		
+Zotero.Translators.CodeGetter.prototype.getAll = Zotero.Promise.method(function () {
+	var translators = [];
+	for (let translator of this._translators) {
 		// retrieve code if no code and translator is supported locally
 		if((translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER && !translator.hasOwnProperty("code"))
 				// or if debug mode is enabled (even if unsupported locally)
@@ -362,17 +339,13 @@ Zotero.Translators.CodeGetter.prototype.getCodeFor = function(i) {
 				// include test cases)
 				|| (Zotero.Repo && translator.codeSource === Zotero.Repo.SOURCE_REPO)))) {
 				// get next translator
-			translator.getCode(function() { me.getCodeFor(i+1) });
-			return;
+			translators.push(translator.getCode());
 		}
-		
-		// if we are not at end of list and there is no reason to retrieve the code, keep going
-		// through the list of potential translators
-		i++;
 	}
-}
+	return Zotero.Promise.all(translators);
+});
 
-const TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
+var TRANSLATOR_REQUIRED_PROPERTIES = ["translatorID", "translatorType", "label", "creator", "target",
 		"priority", "lastUpdated"];
 var TRANSLATOR_PASSING_PROPERTIES = TRANSLATOR_REQUIRED_PROPERTIES
 		.concat(["browserSupport", "code", "runMode", "itemType"]);
@@ -453,21 +426,23 @@ Zotero.Translator.prototype.init = function(info) {
 
 /**
  * Retrieves code for this translator
+ *
+ * @return {Promise<String|false>} - Promise for translator code or false if none
  */
-Zotero.Translator.prototype.getCode = function(callback) {
-	var me = this;
-	Zotero.Repo.getTranslatorCode(this.translatorID,
-		function(code, source) {
-			if(!code) {
-				callback(false);
-			} else {
-				// cache code for session only (we have standalone anyway)
-				me.code = code;
-				me.codeSource = source;
-				callback(true);
-			}
+Zotero.Translator.prototype.getCode = function () {
+	return Zotero.Repo.getTranslatorCode(this.translatorID)
+	.then(function (args) {
+		var code = args[0];
+		var source = args[1];
+		if (!code) {
+			return false;
 		}
-	);
+		
+		// cache code for session only (we have standalone anyway)
+		this.code = code;
+		this.codeSource = source;
+		return code;
+	}.bind(this));
 }
 
 /**
diff --git a/chrome/content/zotero/xpcom/translation/translate.js b/chrome/content/zotero/xpcom/translation/translate.js
index e93d0dd22b..9f1d966c35 100644
--- a/chrome/content/zotero/xpcom/translation/translate.js
+++ b/chrome/content/zotero/xpcom/translation/translate.js
@@ -1082,7 +1082,7 @@ Zotero.Translate.Base.prototype = {
 	 *     getAllTranslators parameter is meaningless in this context.
 	 * @return {Promise} Promise for an array of {@link Zotero.Translator} objects
 	 */
-	"getTranslators":function(getAllTranslators, checkSetTranslator) {
+	getTranslators: Zotero.Promise.method(function (getAllTranslators, checkSetTranslator) {
 		var potentialTranslators;
 
 		// do not allow simultaneous instances of getTranslators
@@ -1115,68 +1115,74 @@ Zotero.Translate.Base.prototype = {
 		}
 
 		// if detection returns immediately, return found translators
-		var me = this;
 		return potentialTranslators.then(function(result) {
 			var allPotentialTranslators = result[0];
 			var properToProxyFunctions = result[1];
-			me._potentialTranslators = [];
-			me._foundTranslators = [];
+			this._potentialTranslators = [];
+			this._foundTranslators = [];
 			
 			// this gets passed out by Zotero.Translators.getWebTranslatorsForLocation() because it is
 			// specific for each translator, but we want to avoid making a copy of a translator whenever
 			// possible.
-			me._properToProxyFunctions = properToProxyFunctions ? properToProxyFunctions : null;
-			me._waitingForRPC = false;
+			this._properToProxyFunctions = properToProxyFunctions ? properToProxyFunctions : null;
+			this._waitingForRPC = false;
 			
 			for(var i=0, n=allPotentialTranslators.length; i<n; i++) {
 				var translator = allPotentialTranslators[i];
 				if(translator.runMode === Zotero.Translator.RUN_MODE_IN_BROWSER) {
-					me._potentialTranslators.push(translator);
-				} else if(me instanceof Zotero.Translate.Web && Zotero.Connector) {
-					me._waitingForRPC = true;
+					this._potentialTranslators.push(translator);
+				} else if (this instanceof Zotero.Translate.Web && Zotero.Connector) {
+					this._waitingForRPC = true;
 				}
 			}
 			
 			// Attach handler for translators, so that we can return a
 			// promise that provides them.
-			// TODO make me._detect() return a promise
-			var deferred = Zotero.Promise.defer(),
-				translatorsHandler = function(obj, translators) {
-					me.removeHandler("translators", translatorsHandler);
-					deferred.resolve(translators);
-				}
-			me.setHandler("translators", translatorsHandler);
-			me._detect();
+			// TODO make this._detect() return a promise
+			var deferred = Zotero.Promise.defer();
+			var translatorsHandler = function(obj, translators) {
+				this.removeHandler("translators", translatorsHandler);
+				deferred.resolve(translators);
+			}.bind(this);
+			this.setHandler("translators", translatorsHandler);
+			this._detect();
 
-			if(me._waitingForRPC) {
+			if(this._waitingForRPC) {
 				// Try detect in Zotero Standalone. If this fails, it fails; we shouldn't
 				// get hung up about it.
-				Zotero.Connector.callMethod("detect", {"uri":me.location.toString(),
-					"cookie":me.document.cookie,
-					"html":me.document.documentElement.innerHTML}).then(function(rpcTranslators) {
-						me._waitingForRPC = false;
+				Zotero.Connector.callMethod(
+					"detect",
+					{
+						uri: this.location.toString(),
+						cookie: this.document.cookie,
+						html: this.document.documentElement.innerHTML
+					},
+					function (rpcTranslators) {
+						this._waitingForRPC = false;
 						
 						// if there are translators, add them to the list of found translators
 						if(rpcTranslators) {
 							for(var i=0, n=rpcTranslators.length; i<n; i++) {
 								rpcTranslators[i].runMode = Zotero.Translator.RUN_MODE_ZOTERO_STANDALONE;
 							}
-							me._foundTranslators = me._foundTranslators.concat(rpcTranslators);
+							this._foundTranslators = this._foundTranslators.concat(rpcTranslators);
 						}
 						
 						// call _detectTranslatorsCollected to return detected translators
-						if(me._currentState === null) {
-							me._detectTranslatorsCollected();
+						if (this._currentState === null) {
+							this._detectTranslatorsCollected();
 						}
-					});
+					}.bind(this)
+				);
 			}
 
 			return deferred.promise;
-		}).catch(function(e) {
+		}.bind(this))
+		.catch(function(e) {
 			Zotero.logError(e);
-			me.complete(false, e);
-		});
-	},
+			this.complete(false, e);
+		}.bind(this));
+	}),
 
 	/**
 	 * Get all potential translators (without running detect)
@@ -1199,7 +1205,7 @@ Zotero.Translate.Base.prototype = {
 	 * @returns {Promise}                                       Promise resolved with saved items
 	 *                                                          when translation complete
 	 */
-	"translate": function (options = {}, ...args) {		// initialize properties specific to each translation
+	translate: Zotero.Promise.method(function (options = {}, ...args) {		// initialize properties specific to each translation
 		if (typeof options == 'number') {
 			Zotero.debug("Translate: translate() now takes an object -- update your code", 2);
 			options = {
@@ -1256,19 +1262,28 @@ Zotero.Translate.Base.prototype = {
 			this.translator[0] = Zotero.Translators.get(this.translator[0]);
 		}
 		
-		var loadPromise = this._loadTranslator(this.translator[0]);
-		if (this.noWait) {
-			if (!loadPromise.isResolved()) {
-				return Zotero.Promise.reject(new Error("Load promise is not resolved in noWait mode"));
+		// Zotero.Translators.get() returns a promise in the connectors
+		if (this.noWait && this.translator[0].then && !this.translator[0].isResolved()) {
+			throw new Error("Translator promise is not resolved in noWait mode");
+		}
+		
+		Zotero.Promise.resolve(this.translator[0])
+		.then(function (translator) {
+			this.translator[0] = translator;
+			var loadPromise = this._loadTranslator(translator);
+			if (this.noWait) {
+				if (!loadPromise.isResolved()) {
+					return Zotero.Promise.reject(new Error("Load promise is not resolved in noWait mode"));
+				}
+				this._translateTranslatorLoaded();
 			}
-			this._translateTranslatorLoaded();
-		}
-		else {
-			loadPromise.then(() => this._translateTranslatorLoaded());
-		}
-			
+			else {
+				loadPromise.then(() => this._translateTranslatorLoaded());
+			}
+		}.bind(this));
+		
 		return deferred.promise;
-	},
+	}),
 	
 	/**
 	 * Called when translator has been retrieved and loaded
@@ -1586,7 +1601,7 @@ Zotero.Translate.Base.prototype = {
 	 */
 	"_checkIfDone":function() {
 		if(!this._savingItems && !this._savingAttachments.length && (!this._currentState || this._waitingForSave)) {
-			if(this.newCollections) {
+			if(this.newCollections && this._itemSaver.saveCollections) {
 				var me = this;
 				this._itemSaver.saveCollections(this.newCollections).then(function (newCollections) {
 					me.newCollections = newCollections;
@@ -1659,7 +1674,7 @@ Zotero.Translate.Base.prototype = {
 	/**
 	 * Loads the translator into its sandbox
 	 * @param {Zotero.Translator} translator
-	 * @return {Boolean} Whether the translator could be successfully loaded
+	 * @return {Promise<Boolean>} Whether the translator could be successfully loaded
 	 */
 	"_loadTranslator": Zotero.Promise.method(function (translator) {
 		var sandboxLocation = this._getSandboxLocation();
diff --git a/chrome/content/zotero/xpcom/translation/translator.js b/chrome/content/zotero/xpcom/translation/translator.js
index 64b76fd951..3cf69a682c 100644
--- a/chrome/content/zotero/xpcom/translation/translator.js
+++ b/chrome/content/zotero/xpcom/translation/translator.js
@@ -140,36 +140,36 @@ Zotero.Translator.prototype.init = function(info) {
 /**
  * Load code for a translator
  */
-Zotero.Translator.prototype.getCode = function() {
-	if(this.code) return Zotero.Promise.resolve(this.code);
+Zotero.Translator.prototype.getCode = Zotero.Promise.method(function () {
+	if (this.code) return this.code;
 
-	var me = this;
 	if(Zotero.isConnector) {
-		// TODO make this a promise
-		return Zotero.Repo.getTranslatorCode(this.translatorID).
-		spread(function(code, source) {
+		return Zotero.Repo.getTranslatorCode(this.translatorID)
+		.then(function (args) {
+			var code = args[0];
+			var source = args[1];
 			if(!code) {
-				throw "Code for "+me.label+" could not be retrieved";
+				throw new Error("Code for " + this.label + " could not be retrieved");
 			}
 			// Cache any translators for session, since retrieving via
 			// HTTP may be expensive
-			me.code = code;
-			me.codeSource = source;
+			this.code = code;
+			this.codeSource = source;
 			return code;
-		});
+		}.bind(this));
 	} else {
 		var promise = Zotero.File.getContentsAsync(this.path);
 		if(this.cacheCode) {
 			// Cache target-less web translators for session, since we
 			// will use them a lot
-			promise.then(function(code) {
-				me.code = code;
+			return promise.then(function(code) {
+				this.code = code;
 				return code;
-			});
+			}.bind(this));
 		}
 		return promise;
 	}
-}
+});
 
 /**
  * Get metadata block for a translator
diff --git a/chrome/content/zotero/xpcom/translation/translators.js b/chrome/content/zotero/xpcom/translation/translators.js
index 174fd6d65d..9257f35b64 100644
--- a/chrome/content/zotero/xpcom/translation/translators.js
+++ b/chrome/content/zotero/xpcom/translation/translators.js
@@ -260,7 +260,7 @@ Zotero.Translators = new function() {
 	 */
 	this.getAll = function() {
 		return this.init().then(function () {
-			return Object.keys(_translators);
+			return Object.keys(_translators).map(id => _translators[id]);
 		});
 	}
 	
diff --git a/chrome/content/zotero/xpcom/zotero.js b/chrome/content/zotero/xpcom/zotero.js
index 414c424555..7a346ce879 100644
--- a/chrome/content/zotero/xpcom/zotero.js
+++ b/chrome/content/zotero/xpcom/zotero.js
@@ -460,7 +460,16 @@ Components.utils.import("resource://gre/modules/osfile.jsm");
 		this.initializationDeferred.resolve();
 		
 		if(Zotero.isConnector) {
+			// Add toolbar icon
+			try {
+				Services.scriptloader.loadSubScript("chrome://zotero/content/icon.js", {}, "UTF-8");
+			}
+			catch (e) {
+				Zotero.logError(e);
+			}
+			
 			Zotero.Repo.init();
+			Zotero.locked = false;
 		}
 		
 		if(!Zotero.isFirstLoadThisSession) {
diff --git a/chrome/content/zotero/zoteroPane.js b/chrome/content/zotero/zoteroPane.js
index 67eeaed191..4476bc9575 100644
--- a/chrome/content/zotero/zoteroPane.js
+++ b/chrome/content/zotero/zoteroPane.js
@@ -140,7 +140,7 @@ var ZoteroPane = new function()
 	}
 	
 	/**
-	 * Called on window load or when has been reloaded after switching into or out of connector
+	 * Called on window load or when pane has been reloaded after switching into or out of connector
 	 * mode
 	 */
 	function _loadPane() {
@@ -160,6 +160,10 @@ var ZoteroPane = new function()
 		collectionsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true);
 		collectionsTree.addEventListener("click", ZoteroPane_Local.onTreeClick, true);
 		
+		// Clear items view, so that the load registers as a new selected collection when switching
+		// between modes
+		ZoteroPane_Local.itemsView = null;
+		
 		var itemsTree = document.getElementById('zotero-items-tree');
 		itemsTree.controllers.appendController(new Zotero.ItemTreeCommandController(itemsTree));
 		itemsTree.addEventListener("mousedown", ZoteroPane_Local.onTreeMouseDown, true);
diff --git a/components/zotero-service.js b/components/zotero-service.js
index 0672dcf2ac..ca66420860 100644
--- a/components/zotero-service.js
+++ b/components/zotero-service.js
@@ -336,9 +336,6 @@ function ZoteroService() {
 			makeZoteroContext(false);
 			zContext.Zotero.init(zInitOptions)
 			.catch(function (e) {
-				dump(e + "\n\n");
-				Components.utils.reportError(e);
-				
 				if (e === "ZOTERO_SHOULD_START_AS_CONNECTOR") {
 					// if Zotero should start as a connector, reload it
 					return zContext.Zotero.shutdown()
@@ -347,9 +344,10 @@ function ZoteroService() {
 						return zContext.Zotero.init(zInitOptions);
 					})
 				}
-				else {
-					throw e;
-				}
+				
+				dump(e + "\n\n");
+				Components.utils.reportError(e);
+				throw e;
 			})
 			.then(function () {
 				zContext.Zotero.debug("Initialized in "+(Date.now() - start)+" ms");