Bug 385657 - Align Plugin Headers and API with OSGi Bundle where sensible
diff --git a/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testPlugin.html b/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testPlugin.html
index 3b5c2dd..fc376ff 100644
--- a/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testPlugin.html
+++ b/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testPlugin.html
@@ -39,6 +39,7 @@
 					});
 				},
 				testCancel: function(resolve) {
+				console.log("--" +resolve);
 					var d = new Deferred(function(reason) {
 						returnReason = reason;
 					});				
diff --git a/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testcase.js b/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testcase.js
index b7c79f1..861ecfc 100644
--- a/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testcase.js
+++ b/bundles/org.eclipse.orion.client.core/web/js-tests/pluginRegistry/testcase.js
@@ -8,56 +8,139 @@
  * 
  * Contributors: IBM Corporation - initial API and implementation
  ******************************************************************************/
-/*global define navigator Worker*/
+/*global define navigator Worker console*/
 
 
 define(["orion/assert", "orion/serviceregistry", "orion/pluginregistry", "orion/Deferred"], function(assert, mServiceregistry, mPluginregistry, Deferred) {
-	var Plugin = mPluginregistry.Plugin;
 	var tests = {};
 	
-	tests["test empty registry"] = function() {
-		var storage = {};
+	tests["test init/stop empty registry"] = function() {
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-		
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		assert.equal(pluginRegistry.getState(), "installed");
+		pluginRegistry.init();
+		assert.equal(pluginRegistry.getState(), "starting");
 		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
+		return pluginRegistry.stop().then(function() {
+			assert.equal(pluginRegistry.getState(), "resolved");
+		});
 	};
 
-	tests["test install plugin"] = function() {
-		var storage = {};
+	tests["test start/stop empty registry"] = function() {
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
-		
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		assert.equal(pluginRegistry.getState(), "installed");
+		return pluginRegistry.start().then(function() {
+				assert.equal(pluginRegistry.getState(), "active");
+		}).then(function() {
+			return pluginRegistry.stop().then(function() {
+				assert.equal(pluginRegistry.getState(), "resolved");
+			});
+		});
+	};
+
+	tests["test install/uninstall plugin"] = function() {
+		var serviceRegistry = new mServiceregistry.ServiceRegistry();
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		pluginRegistry.init();
 		var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
 			assert.equal(pluginRegistry.getPlugins().length, 1);
-			assert.equal(serviceRegistry.getServiceReferences().length, 1);		
-			
-			plugin.uninstall();
-			
-			assert.equal(pluginRegistry.getPlugins().length, 0);
-			assert.equal(serviceRegistry.getServiceReferences().length, 0);
-			pluginRegistry.shutdown();
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);		
+			assert.equal(plugin.getState(), "installed");
+			return plugin.uninstall().then(function() {
+				assert.equal(plugin.getState(), "uninstalled");
+				assert.equal(pluginRegistry.getPlugins().length, 0);
+			});
 		});
 		return promise;
 	};
+	
+	tests["test install/uninstall initial plugin"] = function() {
+		var serviceRegistry = new mServiceregistry.ServiceRegistry();
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {plugins:{"testPlugin.html": true}, storage:{}});
+		pluginRegistry.init();
+		assert.equal(pluginRegistry.getPlugins().length, 1);
+		assert.equal(serviceRegistry.getServiceReferences().length, 0);
+		var plugin = pluginRegistry.getPlugin("testPlugin.html");
+		assert.ok(plugin);
+		assert.equal(plugin.getState(), "installed");
+		return plugin.uninstall().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+		});
+	};
+
+	tests["test start/stop plugin"] = function() {
+		var serviceRegistry = new mServiceregistry.ServiceRegistry();
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		return pluginRegistry.start().then(function() {
+			return pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				assert.equal(pluginRegistry.getPlugins().length, 1);
+				assert.equal(serviceRegistry.getServiceReferences().length, 0);		
+				assert.equal(plugin.getState(), "installed");
+				pluginRegistry.resolvePlugins();
+				assert.equal(plugin.getState(), "resolved");
+				assert.equal(serviceRegistry.getServiceReferences().length, 0);	
+				return plugin.start({"lazy":true}).then(function() {
+					assert.equal(plugin.getState(), "starting");
+					assert.equal(plugin._getAutostart(), "lazy");
+					assert.equal(serviceRegistry.getServiceReferences().length, 1);	
+					return plugin.stop();
+				}).then(function() {
+					assert.equal(plugin.getState(), "resolved");
+					assert.equal(plugin._getAutostart(), "stopped");
+					assert.equal(serviceRegistry.getServiceReferences().length, 0);	
+					return plugin.uninstall();
+				}).then(function() {
+					assert.equal(plugin.getState(), "uninstalled");
+					assert.equal(pluginRegistry.getPlugins().length, 0);
+					return pluginRegistry.stop();
+				});
+			});
+		}).then(function() {
+			assert.equal("resolved", pluginRegistry.getState());
+		});
+	};
+	
+	tests["test start/stop initial plugin"] = function() {
+		var serviceRegistry = new mServiceregistry.ServiceRegistry();
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {plugins:{"testPlugin.html": true}, storage:{}});
+		return pluginRegistry.start().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 1);
+			assert.equal(serviceRegistry.getServiceReferences().length, 1);
+			var plugin = pluginRegistry.getPlugin("testPlugin.html");
+			assert.ok(plugin);
+			assert.equal(plugin.getState(), "starting");
+			assert.equal(plugin._getAutostart(), "lazy");
+			assert.equal(serviceRegistry.getServiceReferences().length, 1);	
+			return plugin.stop().then(function() {
+				assert.equal(plugin.getState(), "resolved");
+				assert.equal(plugin._getAutostart(), "stopped");
+				assert.equal(serviceRegistry.getServiceReferences().length, 0);	
+				return plugin.uninstall();
+			}).then(function() {
+				assert.equal(plugin.getState(), "uninstalled");
+				assert.equal(pluginRegistry.getPlugins().length, 0);
+				return pluginRegistry.stop();
+			});
+		}).then(function() {
+			assert.equal("resolved", pluginRegistry.getState());
+		});
+	};	
 
 	tests["test install same plugin URL"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		pluginRegistry.init();
 
 		var promise1 = pluginRegistry.installPlugin("testPlugin.html");
 		var promise2 = pluginRegistry.installPlugin("testPlugin.html");
 		return promise1.then(function(plugin1) {
 			return promise2.then(function(plugin2) {
 				assert.equal(plugin1, plugin2, "Got the same Plugin instance");
-				plugin1.uninstall();
-				pluginRegistry.shutdown();
+				return pluginRegistry.stop();
 			});
+		}).then(function() {
+			assert.equal("resolved", pluginRegistry.getState());
 		});
 	};
 	
@@ -66,203 +149,218 @@
 			return;
 		}
 		
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
-		
-		var promise = pluginRegistry.installPlugin("testPlugin.js").then(function(plugin) {
-			assert.equal(pluginRegistry.getPlugins().length, 1);
-			assert.equal(serviceRegistry.getServiceReferences().length, 1);		
-			
-			plugin.uninstall();
-			
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		pluginRegistry.start().then(function() {		
 			assert.equal(pluginRegistry.getPlugins().length, 0);
-			assert.equal(serviceRegistry.getServiceReferences().length, 0);
-			pluginRegistry.shutdown();
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);			
+			return pluginRegistry.installPlugin("testPlugin.js").then(function(plugin) {
+				return plugin.start().then(function() {
+					assert.equal(pluginRegistry.getPlugins().length, 1);
+					assert.equal(serviceRegistry.getServiceReferences().length, 1);		
+					return plugin.uninstall();
+				}).then(function() {
+					assert.equal(pluginRegistry.getPlugins().length, 0);
+					assert.equal(serviceRegistry.getServiceReferences().length, 0);
+					return pluginRegistry.stop();			
+				});
+			}).then(function() {
+				assert.equal("resolved", pluginRegistry.getState());
+			});
 		});
-		return promise;
 	};
 	
 	tests["test reload installed plugin"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);
-
-		var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			var pluginInfo = {
-				location: plugin.getLocation(),
-				data: plugin._getData()
-			};
-
-			assert.equal(pluginRegistry.getPlugins().length, 1);
-			assert.equal(serviceRegistry.getServiceReferences().length, 1);
-
-			plugin.uninstall();
-
-			assert.equal(pluginRegistry.getPlugins().length, 0);
-			assert.equal(serviceRegistry.getServiceReferences().length, 0);
-			return pluginInfo;
-		}).then(function(pluginInfo) {
-			return pluginRegistry.installPlugin(pluginInfo.location, pluginInfo.data);
-		}).then(function(plugin) {
-			assert.equal(pluginRegistry.getPlugins().length, 1);
-			assert.equal(serviceRegistry.getServiceReferences().length, 1);
-
-			plugin.uninstall();
-
-			assert.equal(pluginRegistry.getPlugins().length, 0);
-			assert.equal(serviceRegistry.getServiceReferences().length, 0);
-			pluginRegistry.shutdown();
+		var storage = {};
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:storage});
+		
+		return pluginRegistry.start().then(function() {
+			var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				var pluginInfo = {
+					location: plugin.getLocation(),
+					data: JSON.parse(storage["plugin." + plugin.getLocation()])
+				};
+				
+				return plugin.start().then(function() {
+					assert.equal(pluginRegistry.getPlugins().length, 1);
+					assert.equal(serviceRegistry.getServiceReferences().length, 1);
+					return plugin.uninstall();
+				}).then(function() {
+					assert.equal(pluginRegistry.getPlugins().length, 0);
+					assert.equal(serviceRegistry.getServiceReferences().length, 0);
+					return pluginInfo;
+				});
+			}).then(function(pluginInfo) {
+				return pluginRegistry.installPlugin(pluginInfo.location, pluginInfo.data);
+			}).then(function(plugin) {
+				return plugin.start().then(function() {
+					assert.equal(pluginRegistry.getPlugins().length, 1);
+					assert.equal(serviceRegistry.getServiceReferences().length, 1);
+					return plugin.uninstall();
+				}).then(function() {
+					assert.equal(pluginRegistry.getPlugins().length, 0);
+					assert.equal(serviceRegistry.getServiceReferences().length, 0);
+					return pluginRegistry.stop();
+				});
+			}).then(function() {
+				assert.equal("resolved", pluginRegistry.getState());
+			});
+			return promise;		
 		});
 
-		return promise;
 	};
 	
 	
 	tests["test plugin service call"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
 		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
-		
-		var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			return serviceRegistry.getService("test").test("echo");
-		}).then(function(result) {
-			assert.equal(result, "echo");
-			pluginRegistry.shutdown();
+		return pluginRegistry.start().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);	
+			var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				return plugin.start({"lazy":true}).then(function() {
+					return serviceRegistry.getService("test").test("echo");
+				});
+			}).then(function(result) {
+				assert.equal(result, "echo");
+				return pluginRegistry.stop();
+			}).then(function() {
+				assert.equal("resolved", pluginRegistry.getState());
+			});
+			return promise;
 		});
-		return promise;
 	};
 	
 	tests["test plugin service call promise"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
 		
 		var progress = false;
-		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
-		
-		var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			return serviceRegistry.getService("test").testPromise("echo");
-		}).then(function(result) {
-			assert.equal(result, "echo");
-			assert.ok(progress);
-			pluginRegistry.shutdown();
-		}, function(error) {
-			assert.ok(false);
-		}, function (update) {
-			assert.equal(update, "progress");
-			progress = true;
+		return pluginRegistry.start().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);		
+			
+			var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				return plugin.start({"lazy":true}).then(function() {
+					return serviceRegistry.getService("test").testPromise("echo");
+				});
+			}).then(function(result) {
+				assert.equal(result, "echo");
+				assert.ok(progress);
+				return pluginRegistry.stop();
+			}, function(error) {
+				assert.ok(false);
+			}, function (update) {
+				assert.equal(update, "progress");
+				progress = true;
+			});
+			return promise;
 		});
-		return promise;
 	};
 	
 	tests["test plugin service call promise cancel"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
 		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
-		
-		var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			var cancelPromise = serviceRegistry.getService("test").testCancel();
-			cancelPromise.cancel("test");
-			return cancelPromise.then(function(result) {
-				assert.ok(false);
-			}, function(error) {
-				assert.ok(cancelPromise.isCanceled());
-				return true;
-			}).then(function() {
-				return serviceRegistry.getService("test").testCancel(true);
-			}).then(function(result) {
-				assert.equal(result, "test");
-				pluginRegistry.shutdown();
+		return pluginRegistry.start().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);	
+			var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				return plugin.start({"lazy":true}).then(function() {
+					var cancelPromise = serviceRegistry.getService("test").testCancel();
+					cancelPromise.cancel("test");
+					cancelPromise.then(function(result) {
+						assert.ok(false);
+					}, function(error) {
+						assert.ok(cancelPromise.isCanceled());
+					});
+				}).then(function() {
+					return serviceRegistry.getService("test").testCancel(true);
+				}).then(function(result) {
+					assert.equal(result, "test");
+					return pluginRegistry.stop();
+				});
 			});
+			return promise;
 		});
-		return promise;
 	};
-	
+		
 	
 	tests["test plugin event"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
 		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);
-		
-		var eventListenerCalls = 0;
-		function eventListener(event) {
-			if (event.result === "echotest") {
-				eventListenerCalls++;
+		return pluginRegistry.start().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);
+			var eventListenerCalls = 0;
+			function eventListener(event) {
+				if (event.result === "echotest") {
+					eventListenerCalls++;
+				}
 			}
-		}
-		
-		var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			var service = serviceRegistry.getService("test");
-			service.addEventListener("echo", eventListener);
-			return service.testEvent("echo").then(function() {
-				service.removeEventListener("echo", eventListener);
-				return service.testEvent("echo");
+			
+			var promise = pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				return plugin.start({"lazy":true}).then(function() {
+					var service = serviceRegistry.getService("test");
+					service.addEventListener("echo", eventListener);
+					return service.testEvent("echo").then(function() {
+						service.removeEventListener("echo", eventListener);
+						return service.testEvent("echo");
+					});
+				});
+			}).then(function(result) {
+				assert.equal(eventListenerCalls, 1);
+				pluginRegistry.stop();
 			});
-		}).then(function(result) {
-			assert.equal(eventListenerCalls, 1);
-			pluginRegistry.shutdown();
+			return promise;
 		});
-		return promise;
 	};
-	
+/*	
 	tests["test pluginregistry event pluginLoaded - lazy"] = function() {
 		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:storage});
 		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
+		return pluginRegistry.start().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);	
 		
-		var promise = new Deferred();
-		pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			// Dance required to trigger a lazy load: shutdown, recreate (reuse plugin storage), startup
-			pluginRegistry.shutdown();
-			serviceRegistry = new mServiceregistry.ServiceRegistry();
-			pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-			pluginRegistry.addEventListener("pluginLoaded", function(event) {
-				var plugin = event.plugin;
-				try {
-					assert.ok(!!plugin, "plugin not null");
-					assert.equal(plugin.getServiceReferences().length, 1);
-					assert.equal(plugin.getServiceReferences()[0].getProperty("name"), "echotest");
-					promise.resolve();
-				} catch(e) {
-					promise.reject(e);
-				}
-			});
-			pluginRegistry.startup(["testPlugin.html"]).then(function() {
-				// This service call should trigger pluginLoaded listener
-				serviceRegistry.getService("test").test().then(function() {
-					plugin.uninstall();
-					pluginRegistry.shutdown();
+			var promise = new Deferred();
+			pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				return plugin.start({"lazy":true}).then(function() {
+				// Dance required to trigger a lazy load: shutdown, recreate (reuse plugin storage), startup
+				pluginRegistry.shutdown();
+				serviceRegistry = new mServiceregistry.ServiceRegistry();
+				pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+				pluginRegistry.addEventListener("pluginLoaded", function(event) {
+					var plugin = event.plugin;
+					try {
+						assert.ok(!!plugin, "plugin not null");
+						assert.equal(plugin.getServiceReferences().length, 1);
+						assert.equal(plugin.getServiceReferences()[0].getProperty("name"), "echotest");
+						promise.resolve();
+					} catch(e) {
+						promise.reject(e);
+					}
+				});
+				pluginRegistry.startup(["testPlugin.html"]).then(function() {
+					// This service call should trigger pluginLoaded listener
+					serviceRegistry.getService("test").test().then(function() {
+						plugin.uninstall();
+						pluginRegistry.shutdown();
+					});
 				});
 			});
+			return promise;
 		});
-		return promise;
 	};
 
 	tests["test pluginregistry event pluginLoaded - non-lazy"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
 
 		assert.equal(pluginRegistry.getPlugins().length, 0);
 		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
@@ -286,7 +384,7 @@
 	tests["test pluginregistry event pluginLoaded service call ordering"] = function() {
 		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:storage});
 
 		assert.equal(pluginRegistry.getPlugins().length, 0);
 		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
@@ -316,59 +414,54 @@
 			});
 		});
 	};
-
-	tests["test pluginregistry events pluginLoaded"] = function() {
-		var storage = {};
+*/
+	tests["test pluginregistry events started"] = function() {
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-		
-		assert.equal(pluginRegistry.getPlugins().length, 0);
-		assert.equal(serviceRegistry.getServiceReferences().length, 0);		
-		
-		var promise = new Deferred();
-		pluginRegistry.addEventListener("pluginLoaded", function(event) {
-			var plugin = event.plugin;
-			try {
-				assert.ok(!!plugin, "plugin not null");
-					assert.equal(plugin.getServiceReferences().length, 1);
-					assert.equal(plugin.getServiceReferences()[0].getProperty("name"), "echotest");
-				promise.resolve();
-			} catch(e) {
-				promise.reject(e);
-			}
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		return pluginRegistry.start().then(function() {
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+			assert.equal(serviceRegistry.getServiceReferences().length, 0);		
+			
+			var promise = new Deferred();
+			pluginRegistry.addEventListener("started", function(event) {
+				var plugin = event.plugin;
+				try {
+					assert.ok(!!plugin, "plugin not null");
+						assert.equal(plugin.getServiceReferences().length, 1);
+						assert.equal(plugin.getServiceReferences()[0].getProperty("name"), "echotest");
+					promise.resolve();
+				} catch(e) {
+					promise.reject(e);
+				}
+			});
+			pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				return plugin.start();
+			});
+			return promise;
 		});
-		pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			plugin.uninstall();
-			pluginRegistry.shutdown();
-		});
-		return promise;
 	};
 
 	tests["test 404 plugin"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-		
-		var plugins = pluginRegistry.getPlugins();
-		assert.equal(plugins.length, 0);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		pluginRegistry.init();
+		assert.equal(pluginRegistry.getPlugins().length, 0);
 		
 		var promise = pluginRegistry.installPlugin("badURLPlugin.html").then(function() {
 			throw new assert.AssertionError();
 		}, function(e) {
 			assert.ok(e.message.match(/Load timeout for plugin/));
-			plugins = pluginRegistry.getPlugins();
-			assert.equal(plugins.length, 0);
-			pluginRegistry.shutdown();
+			assert.equal(pluginRegistry.getPlugins().length, 0);
+			return pluginRegistry.stop();
 		});
 		return promise;
 	};
 	
 	if (navigator && navigator.userAgent && navigator.userAgent.indexOf("WebKit") !== -1) {
 		tests["test iframe sandbox"] = function() {
-			var storage = {};
 			var serviceRegistry = new mServiceregistry.ServiceRegistry();
-			var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-			
+			var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+			pluginRegistry.init();
 			var plugins = pluginRegistry.getPlugins();
 			assert.equal(plugins.length, 0);
 			
@@ -378,66 +471,66 @@
 				assert.ok(e.message.match(/Load timeout for plugin/));
 				plugins = pluginRegistry.getPlugins();
 				assert.equal(plugins.length, 0);
-				pluginRegistry.shutdown();
+				return pluginRegistry.stop();
 			});
 			return promise;
 		};
 	}
 
 	tests["test __plugin__ property"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
-		return pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
-			var serviceReferences = serviceRegistry.getServiceReferences("test");
-			assert.equal(serviceReferences.length, 1);
-			var __plugin__ = serviceReferences[0].getProperty("__plugin__");
-			assert.equal(__plugin__, plugin.getLocation());
-
-			plugin.uninstall();
-			pluginRegistry.shutdown();
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		return pluginRegistry.start().then(function() {
+			return pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
+				return plugin.start().then(function() {
+					var serviceReferences = serviceRegistry.getServiceReferences("test");
+					assert.equal(serviceReferences.length, 1);
+					var __plugin__ = serviceReferences[0].getProperty("__plugin__");
+					assert.equal(__plugin__, plugin.getLocation());
+					return pluginRegistry.stop();
+				});
+			});
 		});
 	};
 	
 	tests["test plugin headers"] = function() {
-		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:{}});
+		pluginRegistry.init();
 		return pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
 			var headers = plugin.getHeaders();
 			assert.equal(headers.name, "test plugin");
 			assert.equal(headers.description, "This is a test plugin");
-			plugin.uninstall();
-			pluginRegistry.shutdown();
+			return pluginRegistry.stop();
 		});
 	};
 	
-
+/*
 	tests["test plugin states"] = function() {
 		var storage = {};
 		var serviceRegistry = new mServiceregistry.ServiceRegistry();
-		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+		var pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:storage});
 		// Eager-load case
 		return pluginRegistry.installPlugin("testPlugin.html").then(function(plugin) {
 			var pluginLocation = plugin.getLocation();
-			assert.equal(plugin.getState(), Plugin.LOADED, "Plugin loaded (eager)");
+			assert.equal(plugin.getState(), "active", "Plugin loaded (eager)");
 			pluginRegistry.shutdown();
 
 			// Lazy-load case
 			serviceRegistry = new mServiceregistry.ServiceRegistry();
-			pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, storage);
+			pluginRegistry = new mPluginregistry.PluginRegistry(serviceRegistry, {storage:storage});
 			return pluginRegistry.startup(["testPlugin.html"]).then(function() {
 				plugin = pluginRegistry.getPlugin(pluginLocation);
-				assert.equal(plugin.getState(), Plugin.INSTALLED, "Plugin installed");
+				assert.equal(plugin.getState(), "lazy", "Plugin installed");
 				return serviceRegistry.getService("test").test().then(function() {
-					assert.equal(plugin.getState(), Plugin.LOADED, "Plugin loaded (lazy)");
+					assert.equal(plugin.getState(), "active", "Plugin loaded (lazy)");
 					plugin.uninstall();
-					assert.equal(plugin.getState(), Plugin.UNINSTALLED, "Plugin uninstalled");
+					assert.equal(plugin.getState(), "uninstall", "Plugin uninstalled");
 					pluginRegistry.shutdown();
 				});
 			});
 		});
 	};
-
+*/
 	return tests;
 });
diff --git a/bundles/org.eclipse.orion.client.core/web/js-tests/serviceRegistry/testcase.js b/bundles/org.eclipse.orion.client.core/web/js-tests/serviceRegistry/testcase.js
index b12a660..7dfaaea 100644
--- a/bundles/org.eclipse.orion.client.core/web/js-tests/serviceRegistry/testcase.js
+++ b/bundles/org.eclipse.orion.client.core/web/js-tests/serviceRegistry/testcase.js
@@ -24,7 +24,7 @@
 		}, {
 			test : 1
 		});
-		var reference = registration.getServiceReference();
+		var reference = registration.getReference();
 		assert.equal("testRegister", reference.getProperty("objectClass")[0]);
 		assert.equal(1, reference.getProperty("test"));
 
@@ -136,7 +136,7 @@
 		assert.equal(1, serviceAddedCount);
 		assert.equal(0, serviceRemovedCount);
 
-		var service = registry.getService(registration.getServiceReference());
+		var service = registry.getService(registration.getReference());
 		var eventHandler = function(event) {
 			eventResult = event.result;
 		};
diff --git a/bundles/org.eclipse.orion.client.core/web/orion/asyncTestWrapper.js b/bundles/org.eclipse.orion.client.core/web/orion/asyncTestWrapper.js
index 0605174..f5d74ec 100644
--- a/bundles/org.eclipse.orion.client.core/web/orion/asyncTestWrapper.js
+++ b/bundles/org.eclipse.orion.client.core/web/orion/asyncTestWrapper.js
@@ -43,8 +43,10 @@
 			}
 			
 			var loaderServiceRegistry = new mServiceregistry.ServiceRegistry();
-			var loaderPluginRegistry = new mPluginregistry.PluginRegistry(loaderServiceRegistry, {});
-			loaderPluginRegistry.installPlugin(test).then(function() {
+			var loaderPluginRegistry = new mPluginregistry.PluginRegistry(loaderServiceRegistry, {storage:{}});
+			loaderPluginRegistry.installPlugin(test).then(function(plugin) {
+				return plugin.start();
+			}).then(function() {
 				var references = loaderServiceRegistry.getServiceReferences("orion.test.runner");
 				var testRunDeferreds = [];
 				
@@ -151,10 +153,12 @@
 	function _loadTests(fileURI) {	 
 		var loader = new TestLoader(fileURI);
 		var testServiceRegistry = new mServiceregistry.ServiceRegistry();
-		var testPluginRegistry = new mPluginregistry.PluginRegistry(testServiceRegistry, {});
+		var testPluginRegistry = new mPluginregistry.PluginRegistry(testServiceRegistry, {storage:{}});
 		
 		/* Install the test plugin and get the list of tests it contains */
-		return testPluginRegistry.installPlugin(fileURI).then(function() {
+		return testPluginRegistry.installPlugin(fileURI).then(function(plugin) {
+			return plugin.start();
+		}).then(function() {
 			var references = testServiceRegistry.getServiceReferences("orion.test.runner");
 			var testRunDeferreds = [];
 
diff --git a/bundles/org.eclipse.orion.client.core/web/orion/bootstrap.js b/bundles/org.eclipse.orion.client.core/web/orion/bootstrap.js
index fd3c603..cc36161 100644
--- a/bundles/org.eclipse.orion.client.core/web/orion/bootstrap.js
+++ b/bundles/org.eclipse.orion.client.core/web/orion/bootstrap.js
@@ -9,7 +9,7 @@
  * Contributors:

  *     IBM Corporation - initial API and implementation

  *******************************************************************************/

-/*global define document dojo dijit window eclipse orion serviceRegistry:true widgets alert*/

+/*global define document dojo dijit window eclipse orion serviceRegistry:true widgets alert console*/

 /*browser:true*/

 

 define(['require', 'orion/Deferred', 'orion/serviceregistry', 'orion/preferences', 'orion/pluginregistry', 'orion/config'], function(require, Deferred, mServiceregistry, mPreferences, mPluginRegistry, mConfig) {

@@ -28,50 +28,54 @@
 		// This is code to ensure the first visit to orion works

 		// we read settings and wait for the plugin registry to fully startup before continuing

 		var preferences = new mPreferences.PreferencesService(serviceRegistry);

-		var pluginRegistry = new mPluginRegistry.PluginRegistry(serviceRegistry);

 		return preferences.getPreferences("/plugins").then(function(pluginsPreference) { //$NON-NLS-0$

-			var pluginURLs = pluginsPreference.keys();

-			for (var i=0; i < pluginURLs.length; ++i) {				

-				if (pluginURLs[i].indexOf("://") === -1) { //$NON-NLS-0$

-					pluginURLs[i] = require.toUrl(pluginURLs[i]);

-				}

-			}		

-			return pluginRegistry.startup(pluginURLs);

-		}).then(function() {

-			if (serviceRegistry.getServiceReferences("orion.core.preference.provider").length > 0) { //$NON-NLS-0$

-				return preferences.getPreferences("/plugins", preferences.USER_SCOPE).then(function(pluginsPreference) { //$NON-NLS-0$

-					var pluginURLs = pluginsPreference.keys();

-					for (var i=0; i < pluginURLs.length; ++i) {				

-						if (pluginURLs[i].indexOf("://") === -1) { //$NON-NLS-0$

-							pluginURLs[i] = require.toUrl(pluginURLs[i]);

-						}

-					}		

-					return pluginRegistry.startup(pluginURLs);

-				});

-			}

-		}).then(function() {

-			return new mConfig.ConfigurationAdminFactory(serviceRegistry, pluginRegistry, preferences).getConfigurationAdmin().then(

-				serviceRegistry.registerService.bind(serviceRegistry, "orion.cm.configadmin") //$NON-NLS-0$

-			);

-		}).then(function() {

-			var auth = serviceRegistry.getService("orion.core.auth"); //$NON-NLS-0$

-			if (auth) {

-				auth.getUser().then(function(user) {

-					if (!user) {

-						auth.getAuthForm(window.location.href).then(function(formURL) {

-							window.location = formURL;

+			var configuration = {plugins:{}};

+			pluginsPreference.keys().forEach(function(key) {

+				var url = require.toUrl(key);

+				configuration.plugins[url] = pluginsPreference[key];

+			});

+			var pluginRegistry = new mPluginRegistry.PluginRegistry(serviceRegistry, configuration);	

+			return pluginRegistry.start().then(function() {

+				if (serviceRegistry.getServiceReferences("orion.core.preference.provider").length > 0) { //$NON-NLS-0$

+					return preferences.getPreferences("/plugins", preferences.USER_SCOPE).then(function(pluginsPreference) { //$NON-NLS-0$

+						var installs = [];

+						pluginsPreference.keys().forEach(function(key) {

+							var url = require.toUrl(key);

+							if (!pluginRegistry.getPlugin(url)) {

+								installs.push(pluginRegistry.installPlugin(url).then(function(plugin) {

+									plugin.start({lazy:true});

+								}));

+							}

+						});	

+						return Deferred.all(installs, function(e){

+							console.log(e);

 						});

-					}

-				});

-			}

-		}).then(function() {

-			var result = {

-				serviceRegistry: serviceRegistry,

-				preferences: preferences,

-				pluginRegistry: pluginRegistry

-			};

-			once.resolve(result);

-			return result;

+					});

+				}

+			}).then(function() {

+				return new mConfig.ConfigurationAdminFactory(serviceRegistry, pluginRegistry, preferences).getConfigurationAdmin().then(

+					serviceRegistry.registerService.bind(serviceRegistry, "orion.cm.configadmin") //$NON-NLS-0$

+				);

+			}).then(function() {

+				var auth = serviceRegistry.getService("orion.core.auth"); //$NON-NLS-0$

+				if (auth) {

+					auth.getUser().then(function(user) {

+						if (!user) {

+							auth.getAuthForm(window.location.href).then(function(formURL) {

+								window.location = formURL;

+							});

+						}

+					});

+				}

+			}).then(function() {

+				var result = {

+					serviceRegistry: serviceRegistry,

+					preferences: preferences,

+					pluginRegistry: pluginRegistry

+				};

+				once.resolve(result);

+				return result;

+			});

 		});

 	}

 	return {startup: startup};

diff --git a/bundles/org.eclipse.orion.client.core/web/orion/pluginregistry.js b/bundles/org.eclipse.orion.client.core/web/orion/pluginregistry.js
index 7a79e72..5c50fe5 100644
--- a/bundles/org.eclipse.orion.client.core/web/orion/pluginregistry.js
+++ b/bundles/org.eclipse.orion.client.core/web/orion/pluginregistry.js
@@ -10,12 +10,48 @@
  *     IBM Corporation - initial API and implementation
  *******************************************************************************/
 
-/*global define setTimeout clearTimeout addEventListener document console localStorage Worker*/
+/*global define setTimeout clearTimeout addEventListener removeEventListener document console localStorage Worker*/
 
-define(["orion/Deferred", "orion/serviceregistry", "orion/EventTarget", "orion/es5shim"], function(Deferred, mServiceregistry, EventTarget){
-	var INSTALLED = 1;
-	var LOADED = 2;
-	var UNINSTALLED = 3;
+define(["orion/Deferred", "orion/EventTarget"], function(Deferred, EventTarget){
+
+	function _equal(obj1, obj2) {
+		var keys1 = Object.keys(obj1);
+		var keys2 = Object.keys(obj2);
+		if (keys1.length !== keys2.length) {
+			return false;
+		}
+		keys1.sort();
+		keys2.sort();
+		for (var i = 0, len = keys1.length; i < len; i++) {
+			var key = keys1[i];
+			if (key !== keys2[i]) {
+				return false;
+			}
+			var value1 = obj1[key], value2 = obj2[key];
+			if (value1 === value2) {
+				continue;
+			}
+			if (JSON.stringify(value1) !== JSON.stringify(value2)) {
+				return false;
+			}
+		}
+		return true;		
+	}
+	
+	function _normalizeURL(location) {
+		if (location.indexOf("://") === -1) { //$NON-NLS-0$
+			var temp = document.createElement('a'); //$NON-NLS-0$
+			temp.href = location;
+	        return temp.href;
+		}
+		return location;
+	}
+
+
+	function PluginEvent(type, plugin) {
+		return {type: type, plugin: plugin};
+	}
+	
 	/**
 	 * Creates a new plugin. This constructor is private and should only be called by the plugin registry.
 	 * @class Represents a single plugin in the plugin registry.
@@ -43,32 +79,38 @@
 	 * </dd>
 	 * @name orion.pluginregistry.Plugin
 	 */
-	function Plugin(url, data, internalRegistry) {
-		var _self = this;
+	function Plugin(_url, _manifest, _internalRegistry) {
+		var _this = this;
+		_manifest = _manifest || {};
+		var _headers = _manifest.headers || {};
+		var _services = _manifest.services || {};
+		var _autostart = _manifest.autostart;
+		var _lastModified = _manifest.lastModified || 0;
 		
-		var _channel = null;
-		var _deferredLoad = new Deferred();
-		var _deferredUpdate = null;
-		var _state = 0;
+		var _state = "installed";
 		
+		var _deferredStateChange;
+		var _deferredLoad;
+		
+		var _channel;
+		var _registeredServices = {};
 		var _currentMessageId = 0;
 		var _deferredResponses = {};
-		var _services = {};
 		
 		function _callService(serviceId, method, params) {
 			if (!_channel) {
-				throw new Error("plugin not connected");
+				return new Deferred().reject(new Error("plugin not connected"));
 			}
+
 			var requestId = _currentMessageId++;			
 			var onCancel = function(reason) {
-				if (_state === LOADED) {
-					internalRegistry.postMessage({
+				if (_state === "active") {
+					_internalRegistry.postMessage({
 						id: requestId,
 						cancel: typeof reason === "string" ? reason : "canceled"
 					}, _channel);
 				}
 			};
-			
 			var d = new Deferred(onCancel);
 			_deferredResponses[String(requestId)] = d;
 			var message = {
@@ -77,7 +119,7 @@
 				method: method,
 				params: params
 			};
-			internalRegistry.postMessage(message, _channel);
+			_internalRegistry.postMessage(message, _channel);
 			return d.promise;
 		}
 	
@@ -87,10 +129,10 @@
 				service.methods.forEach(function(method) {
 					serviceProxy[method] = function() {
 						var params = Array.prototype.slice.call(arguments);
-						if (_state === LOADED) {
+						if (_state === "active") {
 							return _callService(service.serviceId, method, params);
 						} else {
-							return _self._load().then(function() {
+							return _this.start({"transient":true}).then(function() {
 								return _callService(service.serviceId, method, params);
 							});
 						}
@@ -119,112 +161,60 @@
 			return serviceProxy;
 		}
 		
-		function _setState(state) {
-			_state = state;
-			switch (state) {
-				case UNINSTALLED:
-					internalRegistry.dispatchEvent({type:"pluginUninstalled", plugin:_self}); //$NON-NLS-0$
-					break;
-				case INSTALLED:
-					internalRegistry.dispatchEvent({type:"pluginInstalled", plugin:_self}); //$NON-NLS-0$
-					break;
-				case LOADED:
-					internalRegistry.dispatchEvent({type:"pluginLoaded", plugin:_self}); //$NON-NLS-0$
-					break;
+		function _createServiceProperties(service) {
+			var properties = JSON.parse(JSON.stringify(service.properties));
+			properties.__plugin__ = _url; //TODO: eliminate
+			if (!properties.objectClass) {
+				var objectClass = service.names || service.type || [];
+				if (!Array.isArray(objectClass)) {
+					objectClass = [objectClass];
+				}
+				properties.objectClass = objectClass;
 			}
-		}
-	
-		function checkNotUninstalled() {
-			if (_state === UNINSTALLED) {
-				throw new Error("Plugin is uninstalled");
-			}
-		}
-	
-		function _parseData() {
-			var services = data.services;
-			if (services) {
-				services.forEach(function(service) {
-					var serviceProxy = _createServiceProxy(service);
-					var properties = service.properties ? JSON.parse(JSON.stringify(service.properties)) : {};
-					properties.__plugin__ = _self.getLocation();
-					var registration = internalRegistry.registerService(service.names || service.type, serviceProxy, properties);
-					_services[service.serviceId] = {registration: registration, proxy: serviceProxy};
-				});
-			}
-			if (!data._lastModified) {
-				data._lastModified = new Date().getTime();
-			}
+			return properties;
 		}
 		
-		function _checkForUpdate(newData) {
-			if (data.headers && newData.headers) {
-				if (JSON.stringify(data.headers) !== JSON.stringify(newData.headers)) {
-					return true;
-				}
-			} else if (data.headers || newData.headers) {
-				return true;
-			}
-			
-			if (data.services && newData.services) {
-				if (JSON.stringify(data.services) !== JSON.stringify(newData.services)) {
-					return true;
-				}
-			} else if (data.services || newData.services) {
-				return true;
-			}
-			return false;
+		function _registerService(service) {
+			var serviceProxy = _createServiceProxy(service);
+			var properties = _createServiceProperties(service);
+			var registration = _internalRegistry.registerService(service.names || service.type, serviceProxy, properties);
+			_registeredServices[service.serviceId] = {registration: registration, proxy: serviceProxy};
+		}
+	
+		function _persist() {
+			_internalRegistry.persist(_url, {
+				headers:_headers,
+				services: _services,
+				autostart: _autostart,
+				lastModified: _lastModified
+			});
 		}
 		
 		function _responseHandler(message) {
 			var deferred;
 			try {
 				if (message.method) {
-					if ("plugin" === message.method) { //$NON-NLS-0$
-						if (!data) {
-							data = message.params[0];
-							_parseData();
-						} else if (_checkForUpdate(message.params[0])) {
-							// check if the data has been updated
-							for (var serviceId in _services) {
-								if (_services.hasOwnProperty(serviceId)) {
-									_services[serviceId].registration.unregister();
-									delete _services[serviceId];
-								}
-							}
-							data = {};
-							if (message.params[0].headers) {
-								data.headers = message.params[0].headers;
-							}
-							if (message.params[0].services) {
-								data.services = message.params[0].services;
-							}
-							_parseData();
-							internalRegistry.updatePlugin(_self);						
-						}
+					if ("manifest" === message.method || "plugin" === message.method) { //$NON-NLS-0$
+						var manifest = message.params[0];
+						manifest.headers = manifest.headers || {};
+						manifest.services = manifest.services || {};
 						
-						if (_state === INSTALLED) {
-							_setState(LOADED);
-							_deferredLoad.resolve(_self);
-						}
-						
-						if (_deferredUpdate) {
-							_deferredUpdate.resolve(_self);
-							_deferredUpdate = null;
+						if (_equal(manifest.headers, _headers) && _equal(manifest.services, _services)) {
+							_deferredLoad.resolve(_this);
+						} else {
+							_this.update({headers: manifest.headers, services: manifest.services}).then(function() {
+								_deferredLoad.resolve(_this);
+							});
 						}
 					} else if ("dispatchEvent" === message.method){ //$NON-NLS-0$
-						var proxy = _services[message.serviceId].proxy;
+						var proxy = _registeredServices[message.serviceId].proxy;
 						proxy.dispatchEvent.apply(proxy, message.params);		
 					} else if ("progress" === message.method){ //$NON-NLS-0$
 						deferred = _deferredResponses[String(message.requestId)];
 						deferred.progress.apply(deferred, message.params);	
-					} else if ("timeout"){
-						if (_state === INSTALLED) {
-							_deferredLoad.reject(new Error("Load timeout for plugin: " + url));
-						}
-						
-						if (_deferredUpdate) {
-							_deferredUpdate.reject(new Error("Load timeout for plugin: " + url));
-							_deferredUpdate = null;
+					} else if ("timeout" === message.method){
+						if (_deferredLoad) {
+							_deferredLoad.reject(new Error("Load timeout for plugin: " + _url));
 						}
 					} else {
 						throw new Error("Bad response method: " + message.method);
@@ -239,12 +229,19 @@
 					}
 				}
 			} catch (e) {
-				console.log(e);
+				console.log("Plugin._responseHandler " + e);
 			}
 		}
+		this._persist = _persist;
 		
-		this._getData = function() {
-			return data;
+		this._resolve = function() {
+			// check manifest dependencies when we support them
+			_state = "resolved";
+			_internalRegistry.dispatchEvent(new PluginEvent("resolved", _this));
+		};
+		
+		this._getAutostart = function() {
+			return _autostart;
 		};
 	
 		/**
@@ -254,7 +251,7 @@
 		 * @function
 		 */
 		this.getLocation = function() {
-			return url;
+			return _url;
 		};
 		
 		/**
@@ -264,10 +261,7 @@
 		 * @function
 		 */
 		this.getHeaders = function() {
-			if (data) {
-				return data.headers || {};
-			}
-			return null;
+			return JSON.parse(JSON.stringify(_headers));
 		};
 		
 		this.getName = function() {
@@ -287,28 +281,7 @@
 		};
 		
 		this.getLastModified = function() {
-			return data && data._lastModified ? data._lastModified : 0;
-		};
-		
-		/**
-		 * Uninstalls this plugin
-		 * @name orion.pluginregistry.Plugin#uninstall
-		 * @function
-		 */
-		this.uninstall = function() {
-			checkNotUninstalled();
-			for (var serviceId in _services) {
-				if (_services.hasOwnProperty(serviceId)) {
-					_services[serviceId].registration.unregister();
-					delete _services[serviceId];
-				}
-			}
-			if (_channel) {
-				internalRegistry.disconnect(_channel);
-				_channel = null;
-			}
-			internalRegistry.uninstallPlugin(this);
-			_setState(UNINSTALLED);
+			return _lastModified;
 		};
 		
 		/**
@@ -320,30 +293,12 @@
 		 */
 		this.getServiceReferences = function() {
 			var result = [];
-			var serviceId;
-			for (serviceId in _services) {
-				if (_services.hasOwnProperty(serviceId)) {
-					result.push(_services[serviceId].registration.getServiceReference());
-				}
-			}
+			Object.keys(_registeredServices).forEach(function(serviceId){
+				result.push(_registeredServices[serviceId].registration.getReference());
+			});
 			return result;
 		};
 		
-		this.update = function() {
-			checkNotUninstalled();
-			if (_state === INSTALLED) {
-				return this._load();
-			}
-			
-			var updatePromise;
-			if (_deferredUpdate === null) {
-				_deferredUpdate = new Deferred();
-				updatePromise = _deferredUpdate;
-				internalRegistry.disconnect(_channel);
-				_channel = internalRegistry.connect(url, _responseHandler);
-			}
-			return _deferredUpdate.promise;
-		};
 		
 		/**
 		 * Returns this plugin's current state.
@@ -360,56 +315,230 @@
 			return _state;
 		};
 	
-		this._load = function(isInstall, optTimeout) {
-			checkNotUninstalled();
-			if (!_channel) {
-				_channel = internalRegistry.connect(url, _responseHandler, optTimeout);
-				_deferredLoad.then(null, function() {
-					if (!isInstall) {
-						data = {};
-						internalRegistry.updatePlugin(_self);
+		this.start = function(optOptions) {
+			if (_state === "uninstalled") {
+				return new Deferred().reject(new Error("Plugin is uninstalled"));
+			}
+		
+			if (_deferredStateChange) {
+				return _deferredStateChange.promise.then(this.start.bind(this, optOptions));
+			}
+
+			if (_state === "active") {
+				return new Deferred().resolve();
+			}
+			
+			if (!optOptions || !optOptions["transient"]) {
+				var autostart = optOptions && optOptions.lazy ? "lazy" : "started";
+				if (autostart !== _autostart) {
+					_autostart = autostart;
+					_persist();
+				}
+			}
+			
+			var frameworkState = _internalRegistry.getState();
+			if (frameworkState !== "starting" && frameworkState !== "active") {
+				if (optOptions["transient"]) {
+					return new Deferred().reject(new Error("start transient error"));
+				}
+				return new Deferred().resolve();
+			}
+			
+			if (_state === "installed") {
+				try {
+					this._resolve();
+				} catch (e) {
+					return new Deferred().reject(e);
+				}
+			}
+			
+			if (_state === "resolved") {
+				_services.forEach(function(service) {
+					_registerService(service);
+				});
+			}
+			
+			if (optOptions && optOptions.lazy) {
+				if (_state !== "starting") {
+					_state = "starting";
+					_internalRegistry.dispatchEvent(new PluginEvent("lazy activation", _this));
+				}
+				return new Deferred().resolve();				
+			}
+			var deferredStateChange = new Deferred();
+			_deferredStateChange = deferredStateChange;
+			_state = "starting";
+			_internalRegistry.dispatchEvent(new PluginEvent("starting", _this));
+			_deferredLoad = new Deferred();
+			_channel = _internalRegistry.connect(_url, _responseHandler);
+			_deferredLoad.then(function() {
+				_deferredLoad = null;
+				_state = "active";
+				_internalRegistry.dispatchEvent(new PluginEvent("started", _this));
+				_deferredStateChange = null;
+				deferredStateChange.resolve();
+			}, function() {
+				_deferredLoad = null;
+				_state = "stopping";
+				_internalRegistry.dispatchEvent(new PluginEvent("stopping", _this));
+				Object.keys(_registeredServices).forEach(function(serviceId) {
+					_registeredServices[serviceId].registration.unregister();
+					delete _registeredServices[serviceId];
+				});
+				_internalRegistry.disconnect(_channel);
+				_channel = null;
+				_state = "resolved";
+				_deferredStateChange = null;
+				_internalRegistry.dispatchEvent(new PluginEvent("stopped", _this));
+				deferredStateChange.reject(new Error("plugin activation error"));
+			});
+			return deferredStateChange.promise;
+		};
+		
+		this.stop = function(optOptions) {
+			if (_state === "uninstalled") {
+				return new Deferred().reject(new Error("Plugin is uninstalled"));
+			}
+			
+			if (_deferredStateChange) {
+				return _deferredStateChange.promise.then(this.stop.bind(this, optOptions));
+			}
+
+			if (!optOptions || !optOptions["transient"]) {
+				if ("stopped" !== _autostart) {
+					_autostart = "stopped";
+					_persist();
+				}
+			}
+
+			if (_state !== "active" && _state !== "starting") {
+				return new Deferred().resolve();
+			}
+			
+			var deferredStateChange = new Deferred();
+			_deferredStateChange = deferredStateChange;
+			
+			_state = "stopping";
+			_internalRegistry.dispatchEvent(new PluginEvent("stopping", _this));
+			Object.keys(_registeredServices).forEach(function(serviceId) {
+				_registeredServices[serviceId].registration.unregister();
+				delete _registeredServices[serviceId];
+			});
+			if (_channel) {
+				_internalRegistry.disconnect(_channel);
+				_channel = null;
+			}
+			_state = "resolved";
+			_deferredStateChange = null;
+			_internalRegistry.dispatchEvent(new PluginEvent("stopped", _this));
+			deferredStateChange.resolve();
+			
+			return deferredStateChange.promise;
+		};
+		
+		this.update = function(input) {
+			if (_state === "uninstalled") {
+				return new Deferred().reject(new Error("Plugin is uninstalled"));
+			}
+			
+			if (!input) {
+				if (_lastModified === 0) {
+					_lastModified = new Date().getTime();
+					_persist();
+				}
+				return _internalRegistry.loadManifest(_url).then(this.update.bind(this));
+			}
+			
+			_headers = input.headers || {};
+			_services = input.services || {};
+			_autostart = input.autostart || _autostart;
+			
+			if (input.lastModified) {
+				_lastModified = input.lastModified;
+			} else {
+				_lastModified = new Date().getTime();
+				_persist();
+			}
+			
+			if (_state === "active" || _state === "starting") {
+				var serviceIds = [];
+				Object.keys(_services).forEach(function(service) {
+					var serviceId = service.serviceId;
+					serviceIds.push(serviceId);
+					var registeredService = _registeredServices[serviceId];
+					if (registeredService) {
+						if (_equal(service.methods, Object.keys(registeredService.proxy))) {
+							var properties = _createServiceProperties(service);
+							var reference = registeredService.registration.getReference();
+							var currentProperties = {};
+							reference.getPropertyNames().forEach(function(name){
+								currentProperties[name] = reference.getProperty(name);
+							});
+							if (!_equal(properties, currentProperties)) {
+								registeredService.registration.setProperties(properties);
+							}
+							return;
+						}
+						registeredService.registration.unregister();
+						delete _registeredServices[serviceId];
+					}
+					_registerService(service);
+				});
+				Object.keys(_registeredServices).forEach(function(serviceId) {
+					if (serviceIds.indexOf(serviceId) === -1) {
+						_registeredServices[serviceId].registration.unregister();
+						delete _registeredServices[serviceId];
 					}
 				});
 			}
-			return _deferredLoad.promise;
+			
+			if (_state === "active") {
+				_internalRegistry.disconnect(_channel);
+				_deferredLoad = new Deferred();
+				_channel = _internalRegistry.connect(_url, _responseHandler);
+				_deferredLoad.then(function() {
+					_deferredLoad = null;
+				}, function() {
+					_deferredLoad = null;
+					_state = "stopping";
+					_internalRegistry.dispatchEvent(new PluginEvent("stopping"), _this);
+					Object.keys(_registeredServices).forEach(function(serviceId) {
+						_registeredServices[serviceId].registration.unregister();
+						delete _registeredServices[serviceId];
+					});
+					_internalRegistry.disconnect(_channel);
+					_channel = null;
+					_state = "resolved";
+					_internalRegistry.dispatchEvent(new PluginEvent("stopped", _this));
+				});
+			}
+
+			_internalRegistry.dispatchEvent(new PluginEvent("updated", _this));
+			return new Deferred().resolve();
 		};
 		
-		if (typeof url !== "string") { //$NON-NLS-0$
-			throw new Error("invalid url:" + url); //$NON-NLS-0$
-		}
-		
-		if (data) {
-			_parseData();
-		}
-		_setState(INSTALLED);
+		/**
+		 * Uninstalls this plugin
+		 * @name orion.pluginregistry.Plugin#uninstall
+		 * @function
+		 */
+		this.uninstall = function() {
+			if (_state === "uninstalled") {
+				return new Deferred().reject(new Error("Plugin is uninstalled"));
+			}
+
+			if (_state === "active" || _state === "starting" || _state === "stopping") {				
+				return this.stop().then(this.uninstall.bind(this), this.uninstall.bind(this));
+			}
+
+			_internalRegistry.removePlugin(this);
+			_state = "uninstalled";
+			_internalRegistry.dispatchEvent(new PluginEvent("uninstalled", _this));
+			return new Deferred().resolve();
+		};
 	}
 	
 	/**
-	 * State constant for the <code>INSTALLED</code> state.
-	 * @name orion.pluginregistry.Plugin.INSTALLED
-	 * @static
-	 * @constant
-	 * @type Number
-	 */
-	Plugin.INSTALLED = INSTALLED;
-	/**
-	 * State constant for the <code>LOADED</code> state.
-	 * @name orion.pluginregistry.Plugin.LOADED
-	 * @static
-	 * @constant
-	 * @type Number
-	 */
-	Plugin.LOADED = LOADED;
-	/**
-	 * State constant for the <code>UNINSTALLED</code> state.
-	 * @name orion.pluginregistry.Plugin.UNINSTALLED
-	 * @static
-	 * @constant
-	 * @type Number
-	 */
-	Plugin.UNINSTALLED = UNINSTALLED;
-	
-	/**
 	 * Dispatched when a plugin has been installed. The type of this event is <code>'pluginInstalled'</code>.
 	 * @name orion.pluginregistry.PluginRegistry#pluginInstalled
 	 * @event
@@ -444,13 +573,152 @@
 	 * @borrows orion.serviceregistry.EventTarget#addEventListener as #addEventListener
 	 * @borrows orion.serviceregistry.EventTarget#removeEventListener as #removeEventListener
 	 */
-	function PluginRegistry(serviceRegistry, opt_storage, opt_visible) {
-		var _storage = opt_storage || localStorage || {};
+	function PluginRegistry(serviceRegistry, configuration) {
+		configuration = configuration || {};
+		var _storage = configuration.storage || localStorage;
+		var _state = "installed";
 		var _plugins = [];
 		var _channels = [];
 		var _pluginEventTarget = new EventTarget();
+		var _installing = {};
 	
-		addEventListener("message", function(event) { //$NON-NLS-0$
+		var internalRegistry = {
+			registerService: serviceRegistry.registerService.bind(serviceRegistry),
+			connect: function(url, handler, timeout) {
+				var channel = {
+					handler: handler,
+					url: url
+				};
+				
+				function sendTimeout() {
+					handler({method:"timeout"});
+				}
+				
+				var loadTimeout = setTimeout(sendTimeout, timeout || 15000);
+				
+				if (url.match(/\.js$/) && typeof(Worker) !== "undefined") { //$NON-NLS-0$
+					var worker = new Worker(url);
+					worker.onmessage = function(event) {
+						if (typeof channel.useStructuredClone === "undefined") { //$NON-NLS-0$
+							channel.useStructuredClone = typeof event.data !== "string"; //$NON-NLS-0$
+						}
+						channel.handler(channel.useStructuredClone ? event.data : JSON.parse(event.data));
+					};
+					channel.target = worker;
+					channel.close = function() {
+						worker.terminate();
+					};
+				} else {
+					var iframe = document.createElement("iframe"); //$NON-NLS-0$
+					iframe.name = url + "_" + new Date().getTime();
+					if (!configuration.visible) {
+						iframe.style.display = "none"; //$NON-NLS-0$
+						iframe.style.visibility = "hidden"; //$NON-NLS-0$
+					}
+					iframe.src = url;
+					iframe.onload = function() {
+						clearTimeout(loadTimeout);
+						setTimeout(sendTimeout, 5000);
+					};
+					iframe.sandbox = "allow-scripts allow-same-origin";
+					document.body.appendChild(iframe);
+					channel.target = iframe.contentWindow;
+					channel.close = function() {
+						if (iframe) {
+							document.body.removeChild(iframe);
+							iframe = null;
+						}
+					};
+				}
+				_channels.push(channel);
+				return channel;
+			},
+			disconnect: function(channel) {
+				for (var i = 0; i < _channels.length; i++) {
+					if (channel === _channels[i]) {
+						_channels.splice(i,1);
+						try {
+							channel.close();
+						} catch(e) {
+							// best effort
+						}
+						break;
+					}
+				}
+			},
+			removePlugin: function(plugin) {
+				for (var i = 0; i < _plugins.length; i++) {
+					if (plugin === _plugins[i]) {
+						_plugins.splice(i,1);
+						break;
+					}
+				}
+				delete _storage["plugin."+plugin.getLocation()];
+			},
+			persist: function(url, manifest) {
+				_storage["plugin."+url] = JSON.stringify(manifest); //$NON-NLS-0$
+			},
+			postMessage: function(message, channel) {
+				channel.target.postMessage((channel.useStructuredClone ? message : JSON.stringify(message)), channel.url);
+			},
+			dispatchEvent: function(event) {
+				try {
+					_pluginEventTarget.dispatchEvent(event);
+				} catch (e) {
+					if (console) {
+						console.log("PluginRegistry.dispatchEvent " +  e);
+					}
+				}
+			},
+			loadManifest: function(url) {
+				var d = new Deferred();
+				var channel = internalRegistry.connect(url, function(message) {
+					if (!channel || !message.method) {
+						return;
+					}
+					if ("manifest" === message.method || "plugin" === message.method) { //$NON-NLS-0$
+						var manifest = message.params[0];
+						internalRegistry.disconnect(channel);
+						channel = null;
+						d.resolve(manifest);
+					} else if ("timeout" === message.method){
+						internalRegistry.disconnect(channel);
+						channel = null;
+						d.reject(new Error("Load timeout for plugin: " + url));
+					}
+				});
+				return d.promise;
+			},
+			getState : function() {
+				return _state;
+			}
+		};
+		
+		this.getLocation = function() {
+			return "System";
+		};
+
+		this.getHeaders = function() {
+			return {};
+		};
+		
+		this.getName = function() {
+			return "System";
+		};
+		
+		this.getVersion = function() {
+			return "0.0.0";
+		};
+		
+		this.getLastModified = function() {
+			return 0;
+		};
+		
+		
+		this.getState = internalRegistry.getState;
+
+
+		function _messageHandler(event) { //$NON-NLS-0$
 			var source = event.source;
 			_channels.some(function(channel){
 				if (source === channel.target) {
@@ -461,155 +729,78 @@
 					return true; // e.g. break
 				}
 			});
-		}, false);
-		
-		function _normalizeURL(location) {
-			if (location.indexOf("://") === -1) { //$NON-NLS-0$
-				var temp = document.createElement('a'); //$NON-NLS-0$
-				temp.href = location;
-		        return temp.href;
-			}
-			return location;
 		}
-		
-		function _clear(plugin) {
-			delete _storage["plugin."+plugin.getLocation()]; //$NON-NLS-0$
-		}
-		
-		function _persist(plugin) {
-			var expiresSeconds = 60 * 60;
-			plugin._getData()._expires = new Date().getTime() + 1000 * expiresSeconds;
-			_storage["plugin."+plugin.getLocation()] = JSON.stringify(plugin._getData()); //$NON-NLS-0$
-		}
-	
-		var internalRegistry = {
-				registerService: serviceRegistry.registerService.bind(serviceRegistry),
-				connect: function(url, handler, timeout) {
-					var channel = {
-						handler: handler,
-						url: url
-					};
-					
-					function sendTimeout() {
-						handler({method:"timeout"});
-					}
-					
-					var loadTimeout = setTimeout(sendTimeout, timeout || 15000);
-					
-					if (url.match(/\.js$/) && typeof(Worker) !== "undefined") { //$NON-NLS-0$
-						var worker = new Worker(url);
-						worker.onmessage = function(event) {
-								if (typeof channel.useStructuredClone === "undefined") { //$NON-NLS-0$
-									channel.useStructuredClone = typeof event.data !== "string"; //$NON-NLS-0$
-								}
-								channel.handler(channel.useStructuredClone ? event.data : JSON.parse(event.data));
-						};
-						channel.target = worker;
-						channel.close = function() {
-							worker.terminate();
-						};
-					} else {
-						var iframe = document.createElement("iframe"); //$NON-NLS-0$
-						iframe.id = url;
-						iframe.name = url;
-						if (!opt_visible) {
-							iframe.style.display = "none"; //$NON-NLS-0$
-							iframe.style.visibility = "hidden"; //$NON-NLS-0$
-						}
-						iframe.src = url;
-						iframe.onload = function() {
-							clearTimeout(loadTimeout);
-							setTimeout(sendTimeout, 5000);
-						};
-						iframe.sandbox = "allow-scripts allow-same-origin";
-						document.body.appendChild(iframe);
-						channel.target = iframe.contentWindow;
-						channel.close = function() {
-							if (iframe) {
-								document.body.removeChild(iframe);
-								iframe = null;
-							}
-						};
-					}
-					_channels.push(channel);
-					return channel;
-				},
-				disconnect: function(channel) {
-					for (var i = 0; i < _channels.length; i++) {
-						if (channel === _channels[i]) {
-							_channels.splice(i,1);
-							try {
-								channel.close();
-							} catch(e) {
-								// best effort
-							}
-							break;
-						}
-					}
-				},
-				uninstallPlugin: function(plugin) {
-					_clear(plugin);
-					for (var i = 0; i < _plugins.length; i++) {
-						if (plugin === _plugins[i]) {
-							_plugins.splice(i,1);
-							break;
-						}
-					}
-				},
-				updatePlugin: function(plugin) {
-					_persist(plugin);
-					_pluginEventTarget.dispatchEvent({type:"pluginUpdated", plugin: plugin}); //$NON-NLS-0$
-				},
-				postMessage: function(message, channel) {
-					channel.target.postMessage((channel.useStructuredClone ? message : JSON.stringify(message)), channel.url);
-				},
-				dispatchEvent: function(event) {
-					try {
-						_pluginEventTarget.dispatchEvent(event);
-					} catch (e) {
-						if (console) {
-							console.log(e);
-						}
-					}
-				}
-		};
-		
-		function _getPlugin(url) {
-			var result = null;
-			url = _normalizeURL(url);
-			_plugins.some(function(plugin){
-				if (url === plugin.getLocation()) {
-					result = plugin;
-					return true;
-				}
-			});
-			return result;
-		}
-		
+
 		/**
 		 * Starts the plugin registry
 		 * @name orion.pluginregistry.PluginRegistry#startup
 		 * @return A promise that will resolve when the registry has been fully started
 		 * @function 
 		 */
-		this.startup = function(pluginURLs) {	
-			var installList = [];
-			pluginURLs.forEach(function(pluginURL) {
-				pluginURL = _normalizeURL(pluginURL);
-				var key = "plugin." + pluginURL; //$NON-NLS-0$
-				var pluginData = _storage[key] ? JSON.parse(_storage[key]) : null;
-				if (pluginData && pluginData._expires && pluginData._expires > new Date().getTime()) {
-					if (_getPlugin(pluginURL) === null) {
-						_plugins.push(new Plugin(pluginURL, pluginData, internalRegistry));
-					}
-				} else {
-					_storage[key] ="{}"; //$NON-NLS-0$
-					var plugin = new Plugin(pluginURL, {}, internalRegistry); 
-					_plugins.push(plugin);
-					installList.push(plugin._load(false, 5000)); // _load(false) because we want to ensure the plugin is updated
+		this.init = function() {
+			if (_state === "starting" || _state === "active" || _state === "stopping") {
+				return;
+			}
+			addEventListener("message", _messageHandler, false);
+			Object.keys(_storage).forEach(function(key) {
+				if (key.indexOf("plugin.") === 0) {
+					var url = key.substring("plugin.".length);
+					var manifest = JSON.parse(_storage[key]);
+					_plugins.push(new Plugin(url, manifest, internalRegistry));
 				}
 			});
-			return Deferred.all(installList, function(){});
+			if (configuration.plugins) {
+				Object.keys(configuration.plugins).forEach(function(url) {
+					url = _normalizeURL(url);
+					var plugin = this.getPlugin(url);
+					if (!plugin) {
+						var manifest = configuration.plugins[url];
+						manifest = typeof manifest === "object" || {};
+						manifest.autostart = manifest.autostart || configuration.defaultAutostart || "lazy";
+						_plugins.push(new Plugin(url, manifest, internalRegistry));
+					}
+				}.bind(this));
+			}
+			_state = "starting";
+		};
+
+		this.start = function() {
+			if (_state !== "starting") {
+				this.init();
+			}
+			if (_state !== "starting") {
+				return new Deferred().reject("Cannot start framework. Framework is already " + _state + ".");
+			}
+
+			var deferreds = [];
+			_plugins.forEach(function(plugin) {
+				var autostart = plugin._getAutostart();
+				if (plugin.getLastModified() === 0) {
+					deferreds.push(plugin.update().then(function() {
+						if ("started" === autostart) {
+							return plugin.start({"transient":true});
+						}
+						if ("lazy" === autostart) {
+							return plugin.start({"lazy":true, "transient":true});
+						}
+						plugin._resolve();
+					}));
+					return;
+				}
+			
+				if ("started" === autostart) {
+					deferreds.push(plugin.start({"transient":true}));
+				} else if ("lazy" === autostart) {
+					deferreds.push(plugin.start({"lazy":true, "transient":true}));
+				} else {
+					plugin._resolve();
+				}
+			});
+			return Deferred.all(deferreds, function(e){
+				console.log("PluginRegistry.stop " + e);
+			}).then(function() {
+				_state = "active";
+			});
 		};
 		
 		/**
@@ -617,57 +808,72 @@
 		 * @name orion.pluginregistry.PluginRegistry#shutdown
 		 * @function 
 		 */
-		this.shutdown = function() {
-			_channels.forEach(function(channel) {
-				try {
-					channel.close();
-				} catch(e) {
-					// best effort
-				}
+		this.stop = function() {
+			if (_state !== "starting" && _state !== "active") {
+				return new Deferred().reject("Cannot stop registry. Registry is already " + _state + ".");
+			}
+			_state = "stopping";
+			var deferreds = [];
+			_plugins.forEach(function(plugin) {
+				deferreds.push(plugin.stop({"transient":true}));
+			});
+			return Deferred.all(deferreds, function(e){
+				console.log("PluginRegistry.stop " +  e);
+			}).then(function() {
+				removeEventListener("message", _messageHandler);
+				_state = "resolved";
 			});
 		};
 		
+		this.update = function() {
+			this.stop().then(this.start.bind(this));
+		};
+		
+		this.uninstall = function() {
+			return new Deferred().reject("Cannot uninstall registry");
+		};
+		
+		
 		/**
 		 * Installs the plugin at the given location into the plugin registry
 		 * @name orion.pluginregistry.PluginRegistry#installPlugin
 		 * @param {String} url The location of the plugin
-		 * @param {Object} [opt_data] The plugin metadata
+		 * @param {Object} [optManifest] The plugin metadata
 		 * @returns A promise that will resolve when the plugin has been installed.
 		 * @function 
 		 */
-		this.installPlugin = function(url, opt_data) {
+		this.installPlugin = function(url, optManifest) {
 			url = _normalizeURL(url);
-			var d = new Deferred();
-			var plugin = _getPlugin(url);
+			var plugin = this.getPlugin(url);
 			if (plugin) {
-				if(plugin.getHeaders()) {
-					d.resolve(plugin);
-				} else {
-					var pluginTracker = function(event) {
-						var plugin = event.plugin;
-						if (plugin.getLocation() === url) {
-							d.resolve(plugin);
-							_pluginEventTarget.removeEventListener("pluginLoaded", pluginTracker); //$NON-NLS-0$
-						}
-					};
-					_pluginEventTarget.addEventListener("pluginLoaded", pluginTracker); //$NON-NLS-0$
-				}
-			} else {
-				plugin = new Plugin(url, opt_data, internalRegistry);
-				_plugins.push(plugin);
-				if(plugin.getHeaders()) {
-					_persist(plugin);
-					d.resolve(plugin);
-				} else {				
-					plugin._load(true).then(function() {
-						_persist(plugin);
-						d.resolve(plugin);
-					}, function(e) {
-						d.reject(e);
-					});
-				}
+				return new Deferred().resolve(plugin);
 			}
-			return d.promise;	
+			
+			if (_installing[url]) {
+				return _installing[url];
+			}
+			
+			if (optManifest) {
+				plugin = new Plugin(url, optManifest, internalRegistry);
+				_plugins.push(plugin);
+				plugin._persist();
+				internalRegistry.dispatchEvent(new PluginEvent("installed", plugin));
+				return new Deferred().resolve(plugin);
+			}
+						
+			var promise = internalRegistry.loadManifest(url).then(function(manifest) {
+				plugin = new Plugin(url, manifest, internalRegistry);
+				_plugins.push(plugin);
+				plugin._persist();
+				delete _installing[url];
+				internalRegistry.dispatchEvent(new PluginEvent("installed", plugin));
+				return plugin;
+			}, function(error) {
+				delete _installing[url];
+				throw error;
+			});
+			_installing[url] = promise;
+			return promise;	
 		};
 		
 		/**
@@ -677,13 +883,7 @@
 		 * @function 
 		 */
 		this.getPlugins = function() {
-			var result =[];
-			_plugins.forEach(function(plugin) {
-				if (plugin.getHeaders()) {
-					result.push(plugin);
-				}
-			});
-			return result;
+			return _plugins.slice();
 		};
 	
 		/**
@@ -694,19 +894,27 @@
 		 * @function 
 		 */
 		this.getPlugin = function(url) {
-			var plugin = _getPlugin(url);
-			if (plugin && plugin.getHeaders()) {
-				return plugin;
-			}
-			return null;
+			var result = null;
+			url = _normalizeURL(url);
+			_plugins.some(function(plugin){
+				if (url === plugin.getLocation()) {
+					result = plugin;
+					return true;
+				}
+			});
+			return result;
 		};
 		
-		this.addEventListener = function(eventName, listener) {
-			_pluginEventTarget.addEventListener(eventName, listener);
-		};
+		this.addEventListener = _pluginEventTarget.addEventListener.bind(_pluginEventTarget);
 		
-		this.removeEventListener = function(eventName, listener) {
-			_pluginEventTarget.removeEventListener(eventName, listener);
+		this.removeEventListener = _pluginEventTarget.removeEventListener.bind(_pluginEventTarget);
+		
+		this.resolvePlugins = function() {
+			var allResolved = true;
+			_plugins.forEach(function(plugin){
+				allResolved = allResolved && plugin._resolve();
+			});
+			return allResolved;
 		};
 	}
 	return {
diff --git a/bundles/org.eclipse.orion.client.core/web/orion/serviceregistry.js b/bundles/org.eclipse.orion.client.core/web/orion/serviceregistry.js
index 10ccd16..99abfbf 100644
--- a/bundles/org.eclipse.orion.client.core/web/orion/serviceregistry.js
+++ b/bundles/org.eclipse.orion.client.core/web/orion/serviceregistry.js
@@ -84,7 +84,7 @@
 		/**
 		 * Returns a reference to this registered service.
 		 */
-		getServiceReference: function() {
+		getReference: function() {
 			if (!this._internalRegistry.isRegistered(this._serviceId)) {
 				throw new Error("already unregistered");
 			}
diff --git a/bundles/org.eclipse.orion.client.core/web/orion/widgets/plugin/PluginList.js b/bundles/org.eclipse.orion.client.core/web/orion/widgets/plugin/PluginList.js
index 5b4bb38..f5bb8d8 100644
--- a/bundles/org.eclipse.orion.client.core/web/orion/widgets/plugin/PluginList.js
+++ b/bundles/org.eclipse.orion.client.core/web/orion/widgets/plugin/PluginList.js
@@ -206,7 +206,9 @@
 				if( this.settings.pluginRegistry.getPlugin(newPluginUrl) ){

 					this.statusService.setErrorMessage(messages["Already installed"]);

 				} else {

-					this.settings.pluginRegistry.installPlugin(newPluginUrl).then( dojo.hitch( this, 'addPlugin' ), dojo.hitch( this, 'pluginError' ) ); //$NON-NLS-1$ //$NON-NLS-0$

+					this.settings.pluginRegistry.installPlugin(newPluginUrl).then(function(plugin) {

+						return plugin.start({lazy:true});

+					}).then(this.addPlugin.bind(this), this.pluginError.bind(this)); //$NON-NLS-1$ //$NON-NLS-0$

 				}

 			}

 		},

diff --git a/bundles/org.eclipse.orion.client.core/web/test/unittest.js b/bundles/org.eclipse.orion.client.core/web/test/unittest.js
index 191ae23..27deec2 100644
--- a/bundles/org.eclipse.orion.client.core/web/test/unittest.js
+++ b/bundles/org.eclipse.orion.client.core/web/test/unittest.js
@@ -154,9 +154,11 @@
 			
 			// these are isolated from the regular service and plugin registry
 			var testServiceRegistry = new mServiceRegistry.ServiceRegistry();
-			var testPluginRegistry = new mPluginRegistry.PluginRegistry(testServiceRegistry, {});
+			var testPluginRegistry = new mPluginRegistry.PluginRegistry(testServiceRegistry, {storage:{}});
 			
-			testPluginRegistry.installPlugin(fileURI).then(function() {
+			testPluginRegistry.installPlugin(fileURI).then(function(plugin) {
+				return plugin.start();
+			}).then(function() {
 				var service = testServiceRegistry.getService("orion.test.runner");
 				//console.log("got service: " + service);
 
diff --git a/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js b/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js
index 4d5be5b..5a2834e 100644
--- a/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js
+++ b/bundles/org.eclipse.orion.client.users/web/orion/profile/profile.js
@@ -147,7 +147,8 @@
 				var pluginReference= this.pluginRegistry.getPlugin(pluginsList[i].Url);
 				if(pluginReference===null){
 					var registry = this.registry;
-					dojo.hitch(this, function(div){this.pluginRegistry.installPlugin(pluginsList[i].Url).then(
+					dojo.hitch(this, function(div){this.pluginRegistry.installPlugin(pluginsList[i].Url).then(function(plugin) {
+						return plugin.start({lazy:true}).then(
 							function(ref){
 								var pluginService = registry.getService(ref.getServiceReferences()[0]);
 								if(pluginService.getDivContent) {
@@ -156,6 +157,7 @@
 									});
 								}
 							});
+						});
 					})(pluginDiv);
 					continue;
 				}