Index: server-coreless/src/test/java/org/openqa/selenium/server/BrowserSessionFactoryTest.java
===================================================================
--- server-coreless/src/test/java/org/openqa/selenium/server/BrowserSessionFactoryTest.java	(revision 0)
+++ server-coreless/src/test/java/org/openqa/selenium/server/BrowserSessionFactoryTest.java	(revision 0)
@@ -0,0 +1,178 @@
+package org.openqa.selenium.server;
+
+import junit.framework.TestCase;
+
+import org.openqa.selenium.server.BrowserSessionFactory.BrowserSessionInfo;
+import org.openqa.selenium.server.browserlaunchers.BrowserLauncher;
+
+import java.util.HashSet;
+import java.util.Set;
+
+public class BrowserSessionFactoryTest extends TestCase {
+
+  private static final String SESSION_ID_1 = "testLookupByBrowserAndUrl1";
+  private static final String BROWSER_1 = "*firefox";
+  private static final String BASEURL1 = "http://www.google.com";
+  
+  private static final String SESSION_ID_2 = "testLookupByBrowserAndUrl2";
+  private static final String BROWSER2 = "*firefox";
+  private static final String BASEURL2 = "http://maps.google.com";
+  
+  public void testIsValidWithInvalidSessionInfo() {
+    BrowserSessionInfo info = new BrowserSessionInfo("id1", "*firefox",
+        null, null, null);
+  }
+  
+  public void testLookupByBrowserAndUrl() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    Set<BrowserSessionInfo> infos = getTestSessionSet();
+    BrowserSessionInfo result = factory.lookupInfoByBrowserAndUrl(
+        BROWSER_1, BASEURL1, infos);
+    assertEquals(SESSION_ID_1, result.sessionId);
+  }
+  
+  public void testLookupByBrowserAndUrlWithNoMatch() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    Set<BrowserSessionInfo> infos = getTestSessionSet();
+    BrowserSessionInfo result = factory.lookupInfoByBrowserAndUrl(
+        BROWSER_1, "fooey", infos);
+    assertNull(result);
+  }
+  
+  public void testLookupBySessionId() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    Set<BrowserSessionInfo> infos = getTestSessionSet();
+    BrowserSessionInfo result = factory.lookupInfoBySessionId(
+        SESSION_ID_2, infos);
+    assertEquals(BASEURL2, result.baseUrl);
+  }
+  
+  public void testLookupBySessionIdWithNoMatch() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    Set<BrowserSessionInfo> infos = getTestSessionSet();
+    BrowserSessionInfo result = factory.lookupInfoBySessionId(
+        "fooey", infos);
+    assertNull(result);
+  }
+  
+  public void testRegisterValidExternalSession() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    BrowserSessionInfo info1 = getTestSession1();
+    factory.registerExternalSession(info1);
+    assertTrue(factory.hasActiveSession(info1.sessionId));
+  }
+  
+  public void testRegisterInValidExternalSession() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    BrowserSessionInfo info = new BrowserSessionInfo(SESSION_ID_1, "*firefox",
+        null, null, null);
+    factory.registerExternalSession(info);
+    assertFalse(factory.hasActiveSession(info.sessionId));
+  }
+  
+  public void testGrabAvailableSession() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    factory.addToAvailableSessions(getTestSession1());
+    assertTrue(factory.hasAvailableSession(SESSION_ID_1));
+    assertFalse(factory.hasActiveSession(SESSION_ID_1));
+    BrowserSessionInfo result = factory.grabAvailableSession(BROWSER_1, BASEURL1);
+    assertEquals(SESSION_ID_1, result.sessionId);
+    assertFalse(factory.hasAvailableSession(SESSION_ID_1));
+    assertTrue(factory.hasActiveSession(SESSION_ID_1));
+  }
+  
+  public void testEndSessionWithNoCaching() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    factory.registerExternalSession(getTestSession1());
+    assertTrue(factory.hasActiveSession(SESSION_ID_1));
+    factory.endBrowserSession(SESSION_ID_1, false);
+    assertFalse(factory.hasActiveSession(SESSION_ID_1));
+    assertFalse(factory.hasAvailableSession(SESSION_ID_1));
+  }
+  
+  public void testEndSessionWithCaching() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    factory.registerExternalSession(getTestSession1());
+    assertTrue(factory.hasActiveSession(SESSION_ID_1));
+    factory.endBrowserSession(SESSION_ID_1, true);
+    assertFalse(factory.hasActiveSession(SESSION_ID_1));
+    assertTrue(factory.hasAvailableSession(SESSION_ID_1));
+  }
+  
+  public void testEndAllBrowserSessions() {
+    BrowserSessionFactory factory = new BrowserSessionFactory(null);
+    factory.registerExternalSession(getTestSession1());
+    factory.addToAvailableSessions(getTestSession2());
+    factory.endAllBrowserSessions();
+    assertFalse(factory.hasActiveSession(SESSION_ID_1));
+    assertFalse(factory.hasAvailableSession(SESSION_ID_2));
+    assertFalse(factory.hasAvailableSession(SESSION_ID_1));
+  }
+  
+  private Set<BrowserSessionInfo> getTestSessionSet() {
+    Set<BrowserSessionInfo> infos = new HashSet<BrowserSessionInfo>();
+    BrowserSessionInfo info1 = getTestSession1();
+    infos.add(info1);
+    BrowserSessionInfo info2 = getTestSession2();
+    infos.add(info2);
+    return infos;
+  }
+  
+  private BrowserSessionInfo getTestSession1() {
+    DummyLauncher mockLauncher1 = new DummyLauncher();
+    BrowserSessionInfo info1 = new BrowserSessionInfo(
+        SESSION_ID_1, BROWSER_1, BASEURL1, mockLauncher1, null);
+    return info1;
+  }
+  
+  private BrowserSessionInfo getTestSession2() {
+    DummyLauncher mockLauncher2 = new DummyLauncher();
+    BrowserSessionInfo info2 = new BrowserSessionInfo(
+        SESSION_ID_2, BROWSER2, BASEURL2, mockLauncher2, null);
+    return info2;
+  }
+  
+  /**
+   * A teeny tiny no-op launcher to get a non-null launcher for testing.
+   * 
+   * @author jbevan@google.com (Jennifer Bevan)
+   */
+  private static class DummyLauncher implements BrowserLauncher {
+    
+    private boolean closed;
+    
+    public DummyLauncher() {
+      closed = true;
+    }
+    
+    /** noop */
+    public void close() {
+      closed = true;
+    }
+
+    /** noop */
+    public Process getProcess() {
+        return null;
+    }
+
+    /** noop */
+    public void launchHTMLSuite(String startURL, String suiteUrl,
+            boolean multiWindow, String defaultLogLevel) {
+      closed = false;
+    }
+
+    /** noop */
+    public void launchRemoteSession(String url, boolean multiWindow) {
+      closed = false;
+    }
+    
+    protected boolean isClosed() {
+      return closed;
+    }
+    
+    protected void setOpen() {
+      closed = false;
+    }
+  }
+  
+}
Index: server-coreless/src/test/java/org/openqa/selenium/UnitTestSuite.java
===================================================================
--- server-coreless/src/test/java/org/openqa/selenium/UnitTestSuite.java	(revision 2116)
+++ server-coreless/src/test/java/org/openqa/selenium/UnitTestSuite.java	(working copy)
@@ -3,6 +3,8 @@
 import junit.framework.Test;
 import junit.framework.TestCase;
 import junit.framework.TestSuite;
+
+import org.openqa.selenium.server.BrowserSessionFactoryTest;
 import org.openqa.selenium.server.ClasspathResourceLocatorTest;
 import org.openqa.selenium.server.CommandHolderTest;
 import org.openqa.selenium.server.CommandQueueTest;
@@ -30,6 +32,7 @@
         suite.addTestSuite(CommandHolderTest.class);
         suite.addTestSuite(CommandResultHolderTest.class);
         suite.addTestSuite(CommandQueueTest.class);
+        suite.addTestSuite(BrowserSessionFactoryTest.class);
         suite.addTestSuite(SeleniumServerTest.class);
         suite.addTestSuite(ClasspathResourceLocatorTest.class);
         suite.addTestSuite(FrameGroupCommandQueueTest.class);
Index: server-coreless/src/main/java/org/openqa/selenium/server/htmlrunner/HTMLLauncher.java
===================================================================
--- server-coreless/src/main/java/org/openqa/selenium/server/htmlrunner/HTMLLauncher.java	(revision 2116)
+++ server-coreless/src/main/java/org/openqa/selenium/server/htmlrunner/HTMLLauncher.java	(working copy)
@@ -19,6 +19,7 @@
 import org.openqa.selenium.server.SeleniumCommandTimedOutException;
 import org.openqa.selenium.server.SeleniumServer;
 import org.openqa.selenium.server.StaticContentHandler;
+import org.openqa.selenium.server.BrowserSessionFactory.BrowserSessionInfo;
 import org.openqa.selenium.server.browserlaunchers.AsyncExecute;
 import org.openqa.selenium.server.browserlaunchers.BrowserLauncher;
 import org.openqa.selenium.server.browserlaunchers.BrowserLauncherFactory;
@@ -48,7 +49,7 @@
      * @param browserURL - the start URL for the browser
      * @param suiteURL - the relative URL to the HTML suite
      * @param outputFile - The file to which we'll output the HTML results
-     * @param timeoutInMs - the amount of time (in milliseconds) to wait for the browser to finish
+     * @param timeoutInSeconds - the amount of time (in seconds) to wait for the browser to finish
      * @param multiWindow TODO
      * @return PASS or FAIL
      * @throws IOException if we can't write the output file
@@ -66,7 +67,7 @@
      * @param outputFile - The file to which we'll output the HTML results
      * @param multiWindow TODO
      * @param defaultLogLevel TODO
-     * @param timeoutInMs - the amount of time (in milliseconds) to wait for the browser to finish
+     * @param timeoutInSeconds - the amount of time (in seconds) to wait for the browser to finish
      * @return PASS or FAIL
      * @throws IOException if we can't write the output file
      */
@@ -84,7 +85,11 @@
         BrowserLauncherFactory blf = new BrowserLauncherFactory();
         String sessionId = Long.toString(System.currentTimeMillis() % 1000000);
         BrowserLauncher launcher = blf.getBrowserLauncher(browser, sessionId);
-        server.registerBrowserLauncher(sessionId, launcher);
+        BrowserSessionInfo sessionInfo = new BrowserSessionInfo(sessionId, 
+            browser, browserURL, launcher, null);
+        server.registerBrowserSession(sessionInfo);
+        
+        // JB: -- aren't these URLs in the wrong order according to declaration?
         launcher.launchHTMLSuite(suiteURL, browserURL, multiWindow, defaultLogLevel);
         long now = System.currentTimeMillis();
         long end = now + timeoutInMs;
@@ -92,6 +97,7 @@
             AsyncExecute.sleepTight(500);
         }
         launcher.close();
+        server.deregisterBrowserSession(sessionInfo);
         if (results == null) {
             throw new SeleniumCommandTimedOutException();
         }
@@ -110,7 +116,7 @@
      * @param browserURL - the start URL for the browser
      * @param suiteFile - a file containing the HTML suite to run
      * @param outputFile - The file to which we'll output the HTML results
-     * @param timeoutInMs - the amount of time (in milliseconds) to wait for the browser to finish
+     * @param timeoutInSeconds - the amount of time (in seconds) to wait for the browser to finish
      * @param multiWindow - whether to run the browser in multiWindow or else framed mode
      * @return PASSED or FAIL
      * @throws IOException if we can't write the output file
Index: server-coreless/src/main/java/org/openqa/selenium/server/FrameGroupCommandQueueSet.java
===================================================================
--- server-coreless/src/main/java/org/openqa/selenium/server/FrameGroupCommandQueueSet.java	(revision 2116)
+++ server-coreless/src/main/java/org/openqa/selenium/server/FrameGroupCommandQueueSet.java	(working copy)
@@ -17,8 +17,10 @@
  */
 
 
+import java.io.File;
 import java.util.ArrayList;
 import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashSet;
 import java.util.LinkedList;
 import java.util.List;
@@ -61,6 +63,7 @@
     private FrameAddress currentFrameAddress = null;
     private String currentUniqueId = null;
     
+    private final Set<File> tempFilesForSession = Collections.synchronizedSet(new HashSet<File>());
     private Map<String, CommandQueue> uniqueIdToCommandQueue = new ConcurrentHashMap<String, CommandQueue>();
     static private final Map<String, FrameGroupCommandQueueSet> queueSets = new ConcurrentHashMap<String, FrameGroupCommandQueueSet>();
     
@@ -72,9 +75,11 @@
     private AtomicInteger millisecondDelayBetweenOperations;
     
     /**
-     * A unique string denoting a session with a browser.  In most cases this session begins with the
-     * selenium server configuring and starting a browser process, and ends with a selenium server killing 
-     * that process.
+     * A unique string denoting a session with a browser.  
+     * 
+     * In most cases this session begins with the
+     * selenium server configuring and starting a browser process, and ends 
+     * with a selenium server killing that process.
      */
     private final String sessionId;
     /**
@@ -667,6 +672,7 @@
      *
      */
     public void endOfLife() {
+      removeTemporaryFiles();
       for (CommandQueue frameQ : uniqueIdToCommandQueue.values()) {
           frameQ.endOfLife();
       }
@@ -772,6 +778,7 @@
             uniqueIdToCommandQueue.remove(frameAddress);
         }
       }
+      removeTemporaryFiles();
       selectWindow(DEFAULT_SELENIUM_WINDOW_NAME);
       String defaultUrl = "http://localhost:" + SeleniumServer.getPortDriversShouldContact()
           + "/selenium-server/core/InjectedRemoteRunner.html";
@@ -781,6 +788,21 @@
         log.debug("RemoteCommandException in reset: " + rce.getMessage());
       }
     }
+    
+    protected void removeTemporaryFiles() {
+      for (File file : tempFilesForSession) {
+        boolean deleteSuccessful = file.delete();
+        if (!deleteSuccessful) {
+            log.warn("temp file for session " + sessionId 
+                + " not deleted " + file.getAbsolutePath());
+        }
+      }
+      tempFilesForSession.clear();
+    }
+    
+    protected void addTemporaryFile(File tf) {
+      tempFilesForSession.add(tf);   
+    }
 
 	private boolean queueMatchesFrameAddress(CommandQueue queue, String currentLocalFrameAddress, String newFrameAddressExpression) {
 		boolean result;
Index: server-coreless/src/main/java/org/openqa/selenium/server/SeleniumDriverResourceHandler.java
===================================================================
--- server-coreless/src/main/java/org/openqa/selenium/server/SeleniumDriverResourceHandler.java	(revision 2116)
+++ server-coreless/src/main/java/org/openqa/selenium/server/SeleniumDriverResourceHandler.java	(working copy)
@@ -34,8 +34,6 @@
 import java.net.MalformedURLException;
 import java.net.URL;
 import java.net.URLDecoder;
-import java.util.ArrayList;
-import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -58,6 +56,7 @@
 import org.mortbay.http.handler.ResourceHandler;
 import org.mortbay.log.LogFactory;
 import org.mortbay.util.StringUtil;
+import org.openqa.selenium.server.BrowserSessionFactory.BrowserSessionInfo;
 import org.openqa.selenium.server.browserlaunchers.AsyncExecute;
 import org.openqa.selenium.server.browserlaunchers.BrowserLauncher;
 import org.openqa.selenium.server.browserlaunchers.BrowserLauncherFactory;
@@ -77,17 +76,16 @@
 public class SeleniumDriverResourceHandler extends ResourceHandler {
     static Log log = LogFactory.getLog(SeleniumDriverResourceHandler.class);
     static Log browserSideLog = LogFactory.getLog(SeleniumDriverResourceHandler.class.getName()+".browserSideLog");
-    private final Map<String, BrowserLauncher> launchers = new HashMap<String, BrowserLauncher>();
-    private final Map<String, List<File>> sessionIdToListOfTempFiles = new HashMap<String, List<File>>();
-
+    
     private SeleniumServer server;
     private static String lastSessionId = null;
     private Map<String, String> domainsBySessionId = new HashMap<String, String>();
-    private Map<String, String> sessionIdsToBrowserStrings =
-        Collections.synchronizedMap(new HashMap<String, String>());
     private StringBuffer logMessagesBuffer = new StringBuffer();
+    
     private BrowserLauncherFactory browserLauncherFactory = new BrowserLauncherFactory();
-
+    private final BrowserSessionFactory browserSessionFactory = 
+      new BrowserSessionFactory(browserLauncherFactory);
+    
     public SeleniumDriverResourceHandler(SeleniumServer server) {
         this.server = server;
         
@@ -419,7 +417,8 @@
             results = "OK," + logMessagesBuffer.toString();
             logMessagesBuffer.setLength(0);
         } else if ("testComplete".equals(cmd)) {
-            results = endBrowserSession(sessionId, SeleniumServer.reusingBrowserSessions());
+            browserSessionFactory.endBrowserSession(sessionId);
+            results = "OK";
         } else if ("shutDown".equals(cmd) || "shutDownSeleniumServer".equals(cmd)) {
             results = null;
             shutDown(res);
@@ -427,8 +426,7 @@
           FrameGroupCommandQueueSet queue = FrameGroupCommandQueueSet.getQueueSet(sessionId);
           try {
             File downloadedFile = downloadFile(values.get(1));
-            List<File> tempFilesForSession = getTempFiles(sessionId);            
-            tempFilesForSession.add(downloadedFile);
+            queue.addTemporaryFile(downloadedFile);
             results = queue.doCommand("type", values.get(0), downloadedFile.getAbsolutePath());
           } catch (Exception e) {
             results = e.toString();
@@ -515,8 +513,9 @@
             	String browser = values.get(0);
                 String newSessionId = generateNewSessionId();
                 BrowserLauncher simpleLauncher = browserLauncherFactory.getBrowserLauncher(browser, newSessionId);
-                server.registerBrowserLauncher(newSessionId, simpleLauncher);
                 String baseUrl = "http://localhost:" + server.getPort();
+                server.registerBrowserSession(new BrowserSessionInfo(
+                    newSessionId, browser, baseUrl, simpleLauncher, null));
                 simpleLauncher.launchHTMLSuite("TestPrompt.html?thisIsSeleniumServer=true", baseUrl, false, "info");
                 results = "OK";
             }
@@ -553,6 +552,7 @@
             throw new RuntimeException("Malformed URL <" + urlString + ">, " + e.getMessage());
         }
         File outputFile = FileUtils.getFileUtils().createTempFile("se-",".file",null);
+        outputFile.deleteOnExit(); // to be on the safe side.
         Project p = new Project();
         p.addBuildListener(new AntJettyLoggerBuildListener(log));
         Get g = new Get();
@@ -629,47 +629,6 @@
         
     }
 
-    private String endBrowserSession(String sessionId, boolean cacheUnused) {
-        if (cacheUnused) {
-          FrameGroupCommandQueueSet.getQueueSet(sessionId).reset();
-        }
-        else {
-            BrowserLauncher launcher = getLauncher(sessionId);
-            List<File> tempFilesForSession = getTempFiles(sessionId);
-            if (launcher == null) {
-                return "ERROR: No launcher found for sessionId " + sessionId;
-            } 
-            if (tempFilesForSession == null) {
-                return "ERROR: Can't find temp file storage for sessionId " + sessionId; //TODO invariant violated, never should have null, as we set up a new ArrayList when creating the session
-            }
-            try {
-               launcher.close(); 
-            } catch (RuntimeException re) {
-              throw re;
-            } finally {
-              // recover memory
-              synchronized(launchers) {
-                  launchers.remove(sessionId);
-              }
-              FrameGroupCommandQueueSet.clearQueueSet(sessionId);
-            }
-    
-            try {
-                sessionIdToListOfTempFiles.remove(sessionId);            
-            } catch(RuntimeException re) {
-                throw re;
-            } finally {
-                for (File file : tempFilesForSession) {
-                    boolean deleteSuccessful = file.delete();
-                    if (! deleteSuccessful) {
-                        log.warn("temp file not deleted " + file.getAbsolutePath());
-                    }
-                }
-            }
-        }
-        return "OK";
-    }
-
     private void warnIfApparentDomainChange(String sessionId, String url) {
         if (url.startsWith("http://")) {
             String urlDomain = url.replaceFirst("^(http://[^/]+, url)/.*", "$1");
@@ -691,65 +650,13 @@
 
     protected String getNewBrowserSession(String browserString, String startURL) 
           throws RemoteCommandException {
-
-        browserString = validateBrowserString(browserString);
-
-        if (SeleniumServer.isProxyInjectionMode()) {
-            InjectionHelper.init();
-        }
-
-        String sessionId = UUID.randomUUID().toString().replace("-", "");
-        setLastSessionId(sessionId); 
-        FrameGroupCommandQueueSet queueSet = FrameGroupCommandQueueSet.makeQueueSet(sessionId);
-        BrowserLauncher launcher = browserLauncherFactory.getBrowserLauncher(browserString, sessionId);
-        registerBrowserLauncher(sessionId, launcher);
-        sessionIdsToBrowserStrings.put(sessionId, browserString);
-        log.info("Allocated session " + sessionId + " for " + startURL + ", launching...");
-            
-        boolean multiWindow = server.isMultiWindow();
-        launcher.launchRemoteSession(startURL, multiWindow);
-        
-        try {
-          queueSet.waitForLoad(SeleniumServer.getTimeoutInSeconds() * 1000l);
-  
-          // TODO DGF log4j only
-          // NDC.push("sessionId="+sessionId);
-          FrameGroupCommandQueueSet queue = FrameGroupCommandQueueSet.getQueueSet(sessionId);
-          queue.doCommand("setContext", sessionId, "");
-          return sessionId;
-        } catch (RemoteCommandException rce) {
-          log.debug("Failed to start new browser session: " + rce.getMessage());
-          synchronized(launchers) {
-            endBrowserSession(sessionId, false);
-          }
-          sessionIdsToBrowserStrings.remove(sessionId);
-          throw rce;
-        }
+        BrowserSessionInfo sessionInfo = 
+            browserSessionFactory.getNewBrowserSession(
+            browserString, startURL, server.isMultiWindow());
+        setLastSessionId(sessionInfo.sessionId); 
+        return sessionInfo.sessionId;
     }
 
-    private String validateBrowserString(String inputString) throws IllegalArgumentException {
-        String browserString = inputString;
-        if (SeleniumServer.getForcedBrowserMode()!=null) {
-            browserString = SeleniumServer.getForcedBrowserMode(); 
-            log.info("overriding browser mode w/ forced browser mode setting: " + browserString);
-        }
-        if (SeleniumServer.isProxyInjectionMode() && browserString.equals("*iexplore")) {
-            log.warn("running in proxy injection mode, but you used a *iexplore browser string; this is " +
-                    "almost surely inappropriate, so I'm changing it to *piiexplore...");
-            browserString = "*piiexplore";
-        }
-        else if (SeleniumServer.isProxyInjectionMode() && browserString.equals("*firefox")) {
-            log.warn("running in proxy injection mode, but you used a *firefox browser string; this is " +
-                    "almost surely inappropriate, so I'm changing it to *pifirefox...");
-            browserString = "*pifirefox";
-        }
-
-        if (null == browserString) {
-            throw new IllegalArgumentException("browser string may not be null");
-        }
-        return browserString;
-    }
-
     /** Perl and Ruby hang forever when they see "Connection: close" in the HTTP headers.
      * They see that and they think that Jetty will close the socket connection, but
      * Jetty doesn't appear to do that reliably when we're creating a process while
@@ -787,46 +694,38 @@
             }
         }
     }
-
-    /** Retrieves a launcher for the specified sessionId, or <code>null</code> if there is no such launcher. */
-    private BrowserLauncher getLauncher(String sessionId) {
-        synchronized (launchers) {
-            return launchers.get(sessionId);
-        }
+    
+    /**
+     * Registers the given browser session among the active sessions
+     * to handle.
+     * 
+     * Usually externally created browser sessions are managed themselves,
+     * but registering them allows the shutdown procedures to be simpler.
+     * 
+     * @param sessionInfo the externally created browser session to register.
+     */
+    public void registerBrowserSession(BrowserSessionInfo sessionInfo) {
+      browserSessionFactory.registerExternalSession(sessionInfo);
     }
     
-    /** Retrieves the temp files for the specified sessionId, or <code>null</code> if there are no such files. */
-    private List<File> getTempFiles(String sessionId) {
-        synchronized (sessionIdToListOfTempFiles) {
-        	List<File> list = sessionIdToListOfTempFiles.get(sessionId);
-        	if (list == null) { // first time we call it
-        		list = new ArrayList<File>();
-        		sessionIdToListOfTempFiles.put(sessionId, list);
-        	}
-            return sessionIdToListOfTempFiles.get(sessionId);
-        }
+    /**
+     * De-registers the given browser session from among the active sessions.
+     * 
+     * When an externally managed but registered session is closed, 
+     * this method should be called to keep the set of active sessions 
+     * up to date.
+     * 
+     * @param sessionInfo the session to deregister.
+     */
+    public void deregisterBrowserSession(BrowserSessionInfo sessionInfo) {
+      browserSessionFactory.deregisterExternalSession(sessionInfo);
     }
-   
-    public void registerBrowserLauncher(String sessionId, BrowserLauncher launcher) {
-        synchronized (launchers) {
-            launchers.put(sessionId, launcher);
-        }
-    }
     
     /** Kills all running browsers */
     public void stopAllBrowsers() {
-      synchronized(launchers) {
-          List<String> sessions = new ArrayList<String>(launchers.keySet());
-        for (String sessionId : sessions) {
-          endBrowserSession(sessionId, false);
-        }
-      }
+      browserSessionFactory.endAllBrowserSessions();
     }
 
-    public Map<String, BrowserLauncher> getLaunchers() {
-        return launchers;
-    }
-
     /** Sets all the don't-cache headers on the HttpResponse */
     private void setNoCacheHeaders(HttpResponse res) {
         res.setField(HttpFields.__CacheControl, "no-cache");
Index: server-coreless/src/main/java/org/openqa/selenium/server/SeleniumServer.java
===================================================================
--- server-coreless/src/main/java/org/openqa/selenium/server/SeleniumServer.java	(revision 2116)
+++ server-coreless/src/main/java/org/openqa/selenium/server/SeleniumServer.java	(working copy)
@@ -44,6 +44,7 @@
 import org.mortbay.http.handler.SecurityHandler;
 import org.mortbay.jetty.Server;
 import org.mortbay.log.LogFactory;
+import org.openqa.selenium.server.BrowserSessionFactory.BrowserSessionInfo;
 import org.openqa.selenium.server.browserlaunchers.AsyncExecute;
 import org.openqa.selenium.server.browserlaunchers.BrowserLauncher;
 import org.openqa.selenium.server.htmlrunner.HTMLLauncher;
@@ -931,15 +932,7 @@
             }
         }
     }
-
-    /**
-     * Returns a map of session IDs and their associated browser launchers for all active sessions.
-     * @return
-     */
-    public Map<String, BrowserLauncher> getBrowserLaunchers() {
-        return driver.getLaunchers();
-    }
-
+    
     public int getPort() {
         return port;
     }
@@ -965,11 +958,16 @@
     	return staticContentHandler.getResource(path).getInputStream();
     }
     
-    /** Registers a running browser with a specific sessionID */
-    public void registerBrowserLauncher(String sessionId, BrowserLauncher launcher) {
-    	driver.registerBrowserLauncher(sessionId, launcher);
+    /** Registers a running browser session */
+    public void registerBrowserSession(BrowserSessionInfo sessionInfo) {
+    	driver.registerBrowserSession(sessionInfo);
     }
     
+    /** De-registers a previously registered running browser session */
+    public void deregisterBrowserSession(BrowserSessionInfo sessionInfo) {
+        driver.deregisterBrowserSession(sessionInfo);
+    }
+    
     /**
      * Get the number of threads that the server will use to configure the embedded Jetty instance.
      * 
Index: server-coreless/src/main/java/org/openqa/selenium/server/BrowserSessionFactory.java
===================================================================
--- server-coreless/src/main/java/org/openqa/selenium/server/BrowserSessionFactory.java	(revision 0)
+++ server-coreless/src/main/java/org/openqa/selenium/server/BrowserSessionFactory.java	(revision 0)
@@ -0,0 +1,432 @@
+package org.openqa.selenium.server;
+
+import org.apache.commons.logging.Log;
+import org.mortbay.log.LogFactory;
+import org.openqa.selenium.server.browserlaunchers.BrowserLauncher;
+import org.openqa.selenium.server.browserlaunchers.BrowserLauncherFactory;
+
+import java.util.Collections;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.UUID;
+
+/**
+ * Manages browser sessions, their creation, and their closure.
+ * 
+ * Maintains a cache of unused and available browser sessions in case
+ * the server is reusing sessions.  Also manages the creation and
+ * finalization of all browser sessions.
+ * 
+ * @author jbevan@google.com (Jennifer Bevan)
+ */
+public class BrowserSessionFactory {
+  
+  static Log log = LogFactory.getLog(BrowserSessionFactory.class);
+  
+  // cached, unused, already-launched browser sessions.
+  private final Set<BrowserSessionInfo> availableSessions = 
+    Collections.synchronizedSet(new HashSet<BrowserSessionInfo>());
+  
+  // active browser sessions.
+  private final Set<BrowserSessionInfo> activeSessions = 
+    Collections.synchronizedSet(new HashSet<BrowserSessionInfo>());
+
+  private final BrowserLauncherFactory browserLauncherFactory;
+  
+  public BrowserSessionFactory(BrowserLauncherFactory blf) {
+    browserLauncherFactory = blf;
+  }
+  
+  /**
+   * Gets a new browser session, using the SeleniumServer static fields
+   * to populate parameters.
+   * 
+   * @param browserString
+   * @param startURL
+   * @param multiWindow if a new session should be started in multiWindow mode
+   * @return the BrowserSessionInfo for the new browser session.
+   * @throws RemoteCommandException
+   */
+  public BrowserSessionInfo getNewBrowserSession(String browserString, String startURL, 
+      boolean multiWindow) throws RemoteCommandException {
+    return getNewBrowserSession(browserString, startURL, multiWindow,
+        SeleniumServer.reusingBrowserSessions(),
+        SeleniumServer.isEnsureCleanSession());
+  }
+  
+  /**
+   * Gets a new browser session 
+   * 
+   * @param browserString
+   * @param startURL
+   * @param multiWindow if a new session should be started in multiWindow mode
+   * @param useCached if a cached session should be used if one is available
+   * @param ensureClean if a clean session (e.g. no previous cookies) is required.
+   * @return the BrowserSessionInfo for the new browser session.
+   * @throws RemoteCommandException
+   */
+  protected BrowserSessionInfo getNewBrowserSession(String browserString, 
+      String startURL, boolean multiWindow, boolean useCached, boolean ensureClean) 
+      throws RemoteCommandException {
+  
+    BrowserSessionInfo sessionInfo = null;
+    browserString = validateBrowserString(browserString);
+    
+    if (SeleniumServer.isProxyInjectionMode()) {
+        InjectionHelper.init();
+    }
+    
+    if (useCached) {
+      log.info("grabbing available session...");
+      sessionInfo = grabAvailableSession(browserString, startURL);
+    }
+    
+    // couldn't find one in the cache, or not reusing sessions.
+    if (null == sessionInfo) {
+      log.info("creating new remote session");
+      sessionInfo = createNewRemoteSession(browserString, startURL, 
+          multiWindow, ensureClean);
+    }
+    
+    assert null != sessionInfo;
+    if (ensureClean) {
+      // need to add this to the launcher API.
+      // sessionInfo.launcher.hideCurrentSessionData();
+    }
+    return sessionInfo;
+  }
+  
+  /**
+   * Ends all browser sessions.
+   * 
+   * Active and available but inactive sessions are ended.
+   */
+  protected void endAllBrowserSessions() {
+    boolean done = false;
+    Set<BrowserSessionInfo> allSessions = new HashSet<BrowserSessionInfo>();
+    while (!done) {
+      // to avoid concurrent modification exceptions...
+      synchronized(activeSessions) {
+        for (BrowserSessionInfo sessionInfo : activeSessions) {
+          allSessions.add(sessionInfo);
+        }
+      }
+      synchronized(availableSessions) {
+        for (BrowserSessionInfo sessionInfo : availableSessions) {
+          allSessions.add(sessionInfo);
+        }
+      }
+      for (BrowserSessionInfo sessionInfo : allSessions) {
+        endBrowserSession(sessionInfo.sessionId, false);
+      }
+      done = (0 == activeSessions.size() && 0 == availableSessions.size());
+      allSessions.clear();
+    }
+  }
+  
+  /**
+   * Ends a browser session, using SeleniumServer static fields to populate
+   * parameters.
+   * 
+   * @param sessionId the id of the session to be ended
+   */
+  public void endBrowserSession(String sessionId) {
+    endBrowserSession(sessionId,
+      SeleniumServer.reusingBrowserSessions(),
+      SeleniumServer.isEnsureCleanSession());
+  }
+  
+  /**
+   * Ends a browser session, using SeleniumServer static fields to populate
+   * parameters.
+   * 
+   * @param sessionId the id of the session to be ended
+   * @param cacheUnused if the session should be made available for reuse.
+   */
+  public void endBrowserSession(String sessionId, boolean cacheUnused) {
+    endBrowserSession(sessionId, cacheUnused,
+        SeleniumServer.isEnsureCleanSession());
+  }
+  
+  /**
+   * Ends a browser session.
+   * 
+   * @param sessionId the id of the session to be ended
+   * @param cacheUnused if this session should be made available for reuse
+   * @param ensureClean if clean sessions (e.g. no leftover cookies) are required.
+   */
+  protected void endBrowserSession(String sessionId, boolean cacheUnused,
+      boolean ensureClean) {
+    BrowserSessionInfo sessionInfo = lookupInfoBySessionId(sessionId, activeSessions);
+    if (null != sessionInfo) {
+      activeSessions.remove(sessionInfo);
+      try {
+        if (cacheUnused) {
+          if (null != sessionInfo.session) { // only optional field
+            sessionInfo.session.reset();
+          }
+          availableSessions.add(sessionInfo);
+        } else {
+          endBrowserSession(sessionInfo);
+        }
+      } finally {
+        if (ensureClean) {
+          // need to add this to the launcher API.
+          // sessionInfo.launcher.restoreOriginalSessionData();
+        }
+      }
+    } else {
+      // look for it in the available sessions.
+      sessionInfo = lookupInfoBySessionId(sessionId, availableSessions);
+      if (null != sessionInfo && !cacheUnused) {
+        try {
+          availableSessions.remove(sessionInfo);
+          endBrowserSession(sessionInfo);
+        } finally {
+          if (ensureClean) {
+            // sessionInfo.launcher.restoreOriginalSessionData();
+          }
+        }
+      }
+    }
+  }
+  
+  /**
+   * Shuts down this browser session's launcher and clears out its session
+   * data (if session is not null).
+   * 
+   * @param sessionInfo the browser session to end.
+   */
+  protected void endBrowserSession(BrowserSessionInfo sessionInfo) {
+    try {
+      sessionInfo.launcher.close(); // can throw RuntimeException
+    } finally {
+      if (null != sessionInfo.session) {
+        FrameGroupCommandQueueSet.clearQueueSet(sessionInfo.sessionId);
+      }
+    }
+  }
+  
+  /**
+   * Rewrites the given browser string based on server settings.
+   * 
+   * @param inputString the input browser string
+   * @return a possibly-modified browser string.
+   * @throws IllegalArgumentException if inputString is null.
+   */
+  private String validateBrowserString(String inputString) throws IllegalArgumentException {
+    String browserString = inputString;
+    if (SeleniumServer.getForcedBrowserMode()!=null) {
+        browserString = SeleniumServer.getForcedBrowserMode(); 
+        log.info("overriding browser mode w/ forced browser mode setting: " + browserString);
+    }
+    if (SeleniumServer.isProxyInjectionMode() && browserString.equals("*iexplore")) {
+        log.warn("running in proxy injection mode, but you used a *iexplore browser string; this is " +
+                "almost surely inappropriate, so I'm changing it to *piiexplore...");
+        browserString = "*piiexplore";
+    }
+    else if (SeleniumServer.isProxyInjectionMode() && browserString.equals("*firefox")) {
+        log.warn("running in proxy injection mode, but you used a *firefox browser string; this is " +
+                "almost surely inappropriate, so I'm changing it to *pifirefox...");
+        browserString = "*pifirefox";
+    }
+
+    if (null == browserString) {
+        throw new IllegalArgumentException("browser string may not be null");
+    }
+    return browserString;
+  }
+  
+  /**
+   * Retrieves an available, unused session from the cache.
+   * 
+   * @param browserString the necessary browser for a suitable session
+   * @param baseUrl the necessary baseUrl for a suitable session
+   * @return the session info of the cached session, null if none found.
+   */
+  protected BrowserSessionInfo grabAvailableSession(String browserString, 
+      String baseUrl) {
+    BrowserSessionInfo sessionInfo = null;
+    synchronized (availableSessions) {
+      sessionInfo = lookupInfoByBrowserAndUrl(browserString, baseUrl, 
+          availableSessions);
+      if (null != sessionInfo) {
+        availableSessions.remove(sessionInfo);
+      }
+    } 
+    if (null != sessionInfo) {
+      activeSessions.add(sessionInfo);
+    }
+    return sessionInfo;
+  }
+  
+  /**
+   * Creates and tries to open a new session.  
+   * 
+   * @param browserString
+   * @param startURL
+   * @param multiWindow if new session should be opened -multiWindow
+   * @param ensureClean if a clean session is required
+   * @return the BrowserSessionInfo of the new session.
+   * @throws RemoteCommandException if the browser failed to launch and
+   *                        request work in the required amount of time.
+   */
+  protected BrowserSessionInfo createNewRemoteSession(String browserString, 
+      String startURL, boolean multiWindow, boolean ensureClean) 
+      throws RemoteCommandException {
+    String sessionId = UUID.randomUUID().toString().replace("-", "");
+    FrameGroupCommandQueueSet queueSet = FrameGroupCommandQueueSet.makeQueueSet(sessionId);
+    BrowserLauncher launcher = browserLauncherFactory.getBrowserLauncher(browserString, sessionId);
+    BrowserSessionInfo sessionInfo = new BrowserSessionInfo(sessionId,
+        browserString, startURL, launcher, queueSet);
+    log.info("Allocated session " + sessionId + " for " + startURL + ", launching...");
+    
+    launcher.launchRemoteSession(startURL, multiWindow);
+    try {
+      queueSet.waitForLoad(SeleniumServer.getTimeoutInSeconds() * 1000l);
+    
+      // TODO DGF log4j only
+      // NDC.push("sessionId="+sessionId);
+      FrameGroupCommandQueueSet queue = FrameGroupCommandQueueSet.getQueueSet(sessionId);
+      queue.doCommand("setContext", sessionId, "");
+      
+      activeSessions.add(sessionInfo);
+      return sessionInfo;
+    } catch (RemoteCommandException rce) {
+      log.debug("Failed to start new browser session: " + rce.getMessage());
+      endBrowserSession(sessionId, false, ensureClean);
+      throw rce;
+    }    
+  }
+  
+  /**
+   * Adds a browser session that was not created by this factory to the
+   * set of active sessions.
+   * 
+   * Allows for creation of unmanaged sessions (i.e. no FrameGroupCommandQueueSet)
+   * for task such as running the HTML tests (see HTMLLauncher.java).  All 
+   * fields other than session are required to be non-null.
+   * 
+   * @param sessionInfo  the session info to register.
+   */
+  protected boolean registerExternalSession(BrowserSessionInfo sessionInfo) {
+    boolean result = false;
+    if (BrowserSessionInfo.isValid(sessionInfo)) {
+      activeSessions.add(sessionInfo);
+      result = true;
+    }
+    return result;
+  }
+  
+  /**
+   * Removes a previously registered external browser session from the
+   * list of active sessions.
+   * 
+   * @param sessionInfo the session to remove.
+   */
+  protected void deregisterExternalSession(BrowserSessionInfo sessionInfo) {
+    activeSessions.remove(sessionInfo);
+  }
+  
+  /**
+   * Looks up a session in the named set by session id
+   * 
+   * @param sessionId the session id to find
+   * @param set the Set to inspect
+   * @return the matching BrowserSessionInfo or null if not found.
+   */
+  protected BrowserSessionInfo lookupInfoBySessionId(String sessionId,
+      Set<BrowserSessionInfo> set) {
+    BrowserSessionInfo result = null;
+    synchronized (set) {
+      for (BrowserSessionInfo info : set) {
+        if (info.sessionId.equals(sessionId)) {
+          result = info;
+          break;
+        }
+      }
+    }
+    return result;
+  }
+  
+  /**
+   * Looks up a session in the named set by browser string and base URL
+   * 
+   * @param browserString the browser string to match
+   * @param baseUrl the base URL to match.
+   * @param set the Set to inspect
+   * @return the matching BrowserSessionInfo or null if not found.
+   */
+  protected BrowserSessionInfo lookupInfoByBrowserAndUrl(String browserString,
+      String baseUrl, Set<BrowserSessionInfo> set) {
+    BrowserSessionInfo result = null;
+    synchronized (set) {
+      for (BrowserSessionInfo info : set) {
+        if (info.browserString.equals(browserString)
+            && info.baseUrl.equals(baseUrl)) {
+          result = info;
+          break;
+        }
+      }
+    }
+    return result;
+  }
+  
+  /** for testing only */
+  protected boolean hasActiveSession(String sessionId) {
+    BrowserSessionInfo info = lookupInfoBySessionId(sessionId, activeSessions);
+    return (null != info);
+  }
+  
+  /** for testing only */
+  protected boolean hasAvailableSession(String sessionId) {
+    BrowserSessionInfo info = lookupInfoBySessionId(sessionId, availableSessions);
+    return (null != info);
+  }
+  
+  /** for testing only */
+  protected void addToAvailableSessions(BrowserSessionInfo sessionInfo) {
+    availableSessions.add(sessionInfo);
+  }
+  
+  /**
+   * Collection class to hold the objects associated with a browser session.
+   * 
+   * @author jbevan@google.com (Jennifer Bevan)
+   */
+  public static class BrowserSessionInfo {
+    
+    public BrowserSessionInfo(String sessionId, String browserString, 
+        String baseUrl, BrowserLauncher launcher, 
+        FrameGroupCommandQueueSet session) {
+      this.sessionId = sessionId;
+      this.browserString = browserString;
+      this.baseUrl = baseUrl;
+      this.launcher = launcher;
+      this.session = session; // optional field; may be null.
+    }
+    
+    public final String sessionId;
+    public final String browserString;
+    public final String baseUrl;
+    public final BrowserLauncher launcher;
+    public final FrameGroupCommandQueueSet session;
+    
+    /**
+     * Browser sessions require the session id, the browser, the base URL,
+     * and the launcher.  They don't actually require the session to be set
+     * up as a FrameGroupCommandQueueSet.
+     * 
+     * @param sessionInfo the sessionInfo to validate.
+     * @return true if all fields excepting session are non-null.
+     */
+    protected static boolean isValid(BrowserSessionInfo sessionInfo) {
+      boolean result = (null != sessionInfo.sessionId 
+          && null != sessionInfo.browserString
+          && null != sessionInfo.baseUrl 
+          && null != sessionInfo.launcher);
+      return result;
+    }
+  }
+
+}
