Merge branch 'release/1.4.0'
diff --git a/weinre.build/build.properties b/weinre.build/build.properties
index 3c0a0f1..caf374a 100644
--- a/weinre.build/build.properties
+++ b/weinre.build/build.properties
@@ -8,7 +8,7 @@
 #-----------------------------------------------------------
 # weinre version
 #-----------------------------------------------------------
-WEINRE_VERSION: 1.3.0
+WEINRE_VERSION: 1.4.0
 
 #-----------------------------------------------------------
 # some common locations used in the ant scripts
diff --git a/weinre.build/build.xml b/weinre.build/build.xml
index 621925e..01274ee 100644
--- a/weinre.build/build.xml
+++ b/weinre.build/build.xml
@@ -177,6 +177,10 @@
         </copy>
         
         <copy todir="${WEB}">
+            <fileset file="${VENDOR}/json2/json2.js"/>
+        </copy>
+        
+        <copy todir="${WEB}">
             <fileset dir="../${PROJECT_WEB}">
                 <exclude name="versions.js"/>
                 <exclude name="common/**/*"/>
@@ -212,6 +216,7 @@
                 <filter token="BUILD_NUMBER_DATE"          value="${BUILD_NUMBER_DATE}"/>
                 <filter token="CLI_VERSION"                value="${CLI_VERSION}"/>
                 <filter token="JSON4J_VERSION"             value="${JSON4J_VERSION}"/>
+                <filter token="JSON2_VERSION"              value="${JSON2_VERSION}"/>
                 <filter token="WEBKIT_VERSION"             value="${WEBKIT_VERSION}"/>
                 <filter token="SWT_VERSION"                value="${SWT_VERSION}"/>
                 <filter token="JETTY_VERSION"              value="${JETTY_VERSION}"/>
@@ -246,6 +251,7 @@
         <build-html name="Installing"   oDir="${WEB}/doc" iDir="../${PROJECT_DOC}"/>
         <build-html name="License"      oDir="${WEB}/doc" iDir="../${PROJECT_DOC}"/>
         <build-html name="Running"      oDir="${WEB}/doc" iDir="../${PROJECT_DOC}"/>
+        <build-html name="MultiUser"    oDir="${WEB}/doc" iDir="../${PROJECT_DOC}"/>
         <build-html name="Security"     oDir="${WEB}/doc" iDir="../${PROJECT_DOC}"/>
         <build-html name="TestDrive"    oDir="${WEB}/doc" iDir="../${PROJECT_DOC}"/>
         <build-html name="ChangeLog"    oDir="${WEB}/doc" iDir="../${PROJECT_DOC}"/>
diff --git a/weinre.build/get-vendor.xml b/weinre.build/get-vendor.xml
index 04103da..3b49f23 100644
--- a/weinre.build/get-vendor.xml
+++ b/weinre.build/get-vendor.xml
@@ -21,6 +21,7 @@
         <delete dir="${VENDOR}"/>
         <mkdir  dir="${VENDOR}"/>
 
+        <antcall target="get-json2"/>
         <antcall target="get-cli"/>
         <antcall target="get-json4j"/>
         <antcall target="get-jetty"/>
@@ -158,6 +159,8 @@
             
     </target>
     
+    <!-- ============================================================ -->
+
     <target name="get-swt-mac">
         <delete dir="${VENDOR}/swt"/>
         <mkdir  dir="${VENDOR}/swt"/>
@@ -174,4 +177,13 @@
         <delete dir="${TMP}"/>
     </target>
 
+    <!-- ============================================================ -->
+
+    <target name="get-json2">
+        <delete dir="${VENDOR}/json2"/>
+        <mkdir  dir="${VENDOR}/json2"/>
+        
+        <get src="${JSON2_URL_PREFIX}/json2.js"  dest="${VENDOR}/json2/json2.js"  verbose="false"/>
+    </target>
+
 </project>
diff --git a/weinre.build/new-release-notes.md b/weinre.build/new-release-notes.md
index 476e2d0..937c66f 100644
--- a/weinre.build/new-release-notes.md
+++ b/weinre.build/new-release-notes.md
@@ -5,7 +5,9 @@
 
 - update the `WEINRE_VERSION` variable in `weinre.build/build.properties` as appropriate
 
-- add changelog to `weinre.doc/ChangeLog.body.html`, built with `weinre.build/scripts/changelog.sh`
+- add changelog to `weinre.doc/ChangeLog.body.html`, built with
+
+    weinre.build/scripts/changelog.sh [previous-version]
 
 - fix other doc as necessary
 
diff --git a/weinre.build/scripts/build-client-html.py b/weinre.build/scripts/build-client-html.py
index fb32e7d..690572a 100644
--- a/weinre.build/scripts/build-client-html.py
+++ b/weinre.build/scripts/build-client-html.py
@@ -67,7 +67,7 @@
             newLines.append("<!-- ========== weinre additions: starting ========== -->\n")
             newLines.append('<link rel="stylesheet" type="text/css" href="weinre/client.css">\n')
             newLines.append('<script type="text/javascript" src="../interfaces/all-json-idls-min.js"></script>\n')
-            newLines.append('<script type="text/javascript">require("weinre/client/Client").main()</script>\n')
+            newLines.append('<script type="text/javascript">require("weinre/client/Client").getClass().main()</script>\n')
             newLines.append("<!-- ========== weinre additions: done ========== -->\n")
             newLines.append(line)
         
diff --git a/weinre.build/scripts/build-css-properties.py b/weinre.build/scripts/build-css-properties.py
index f4e73e5..596e461 100644
--- a/weinre.build/scripts/build-css-properties.py
+++ b/weinre.build/scripts/build-css-properties.py
@@ -36,7 +36,7 @@
     properties.sort()
         
     jsonString = json.dumps(properties, indent=4)
-    jsString = 'require("weinre/common/Weinre").addCSSProperties(%s)' % jsonString
+    jsString = 'require("weinre/common/Weinre").getClass().addCSSProperties(%s)' % jsonString
 
     oFile = open(oFileName, "w")
     oFile.write(jsString)
diff --git a/weinre.build/scripts/build-target-scripts.py b/weinre.build/scripts/build-target-scripts.py
index 6738363..c74c2e7 100644
--- a/weinre.build/scripts/build-target-scripts.py
+++ b/weinre.build/scripts/build-target-scripts.py
@@ -82,7 +82,7 @@
         lines.append(";")
         lines.append("")
 
-    lines.append("require('weinre/target/Target').main()")
+    lines.append("require('weinre/target/Target').getClass().main()")
     lines.append("})();")
     targetScript = "\n".join(lines)
     
diff --git a/weinre.build/scripts/collect-all-json-idl.py b/weinre.build/scripts/collect-all-json-idl.py
index dc9467b..c9c6281 100644
--- a/weinre.build/scripts/collect-all-json-idl.py
+++ b/weinre.build/scripts/collect-all-json-idl.py
@@ -52,7 +52,7 @@
     else:
         jsonString = json.dumps(result, indent=4)
         
-    jsString = "require('weinre/common/Weinre').addIDLs(%s)" % jsonString
+    jsString = "require('weinre/common/Weinre').getClass().addIDLs(%s)" % jsonString
 
     oFile = open(oFileName, "w")
     oFile.write(jsString)
diff --git a/weinre.build/vendor.properties b/weinre.build/vendor.properties
index fd37063..c9084a9 100644
--- a/weinre.build/vendor.properties
+++ b/weinre.build/vendor.properties
@@ -73,12 +73,19 @@
 # location of scooj
 #-----------------------------------------------------------
 
-SCOOJ_VERSION:    master
+SCOOJ_VERSION:    8f3534ba5a1b2fb8ea82a24eb0acedfcc6aca7b4
 SCOOJ_URL_PREFIX: https://github.com/pmuellr/scooj/raw/${SCOOJ_VERSION}
 
 #-----------------------------------------------------------
+# location of json2
+#-----------------------------------------------------------
+
+JSON2_VERSION:    8e0b15cb492f63067a88ad786e4d5fc0fa89a241
+JSON2_URL_PREFIX: https://github.com/douglascrockford/JSON-js/raw/${JSON2_VERSION}
+
+#-----------------------------------------------------------
 # location of modjewel
 #-----------------------------------------------------------
 
-MODJEWEL_VERSION:    master
+MODJEWEL_VERSION:    48f3eb797850e5ab3e2132b19a1db4797559efe7
 MODJEWEL_URL_PREFIX: https://github.com/pmuellr/modjewel/raw/${MODJEWEL_VERSION}
diff --git a/weinre.doc/ChangeLog.body.html b/weinre.doc/ChangeLog.body.html
index c4f52ac..9acd72a 100644
--- a/weinre.doc/ChangeLog.body.html
+++ b/weinre.doc/ChangeLog.body.html
@@ -6,6 +6,25 @@
 -->
 
 <!-- ======================================================================= -->
+<h2>2011/04/09 - version 1.4.0</h2>
+<ul>
+<li> better calculation of object's class name. closes <a href="https://github.com/pmuellr/weinre/issues/41">issue 41</a>.
+<li> initial set of changes in for extensions. closes <a href="https://github.com/pmuellr/weinre/issues/8">issue 8</a>.
+<li> added json2.js to the mix; part of issue <a href="https://github.com/pmuellr/weinre/issues/45">issue 45</a>.
+<li> works with recent scooj release.  closes <a href="https://github.com/pmuellr/weinre/issues/52">issue 52</a>.
+<li> add non-authenticated multi-user support.  closes <a href="https://github.com/pmuellr/weinre/issues/50">issue 50</a>.
+See <a href="MultiUser.html">the MultiUser doc page</a> for more information.
+</ul>
+
+<p>issues closed:
+<ul>
+<li> <a href="https://github.com/pmuellr/weinre/issues/8">issue 8</a> - enable extensions
+<li> <a href="https://github.com/pmuellr/weinre/issues/41">issue 41</a> - in JSC, inspected object's class is always "object"
+<li> <a href="https://github.com/pmuellr/weinre/issues/50">issue 50</a> - add non-authenticated multi-user support
+<li> <a href="https://github.com/pmuellr/weinre/issues/52">issue 52</a> - react to recent modjewel / scooj changes
+</ul>
+
+<!-- ======================================================================= -->
 <h2>2011/04/05 - version 1.3.0</h2>
 <ul>
 <li> style editing now enabled in Elements panel
diff --git a/weinre.doc/Home.body.html b/weinre.doc/Home.body.html
index 79c0896..73e4219 100644
--- a/weinre.doc/Home.body.html
+++ b/weinre.doc/Home.body.html
@@ -104,6 +104,13 @@
 Later versions of those things are also hopefully supported,
 but let us know.
 
+<h3>Platforms not supported</h3>
+
+<ul>
+<li>iOS 3.1.3 or earlier
+<li>webOS 1.45 or earlier
+</ul>
+
 <h3>Libraries not supported</h3>
 
 <ul>
@@ -114,7 +121,8 @@
 version of Prototype.js.
 </ul>
 
-<h3>Debug client - the browser where the debugger user interface runs</h3>
+<h3>Platforms supported - debug client</h3>
+<p>The browser where the debugger user interface runs.
 
 <ul>
 <li>weinre Mac application - Mac OS X 10.6 64-bit
@@ -122,12 +130,14 @@
 <li>Apple Safari 5.x 
 </ul>
 
-<h3>Debug target - the browser with the page you are debugging</h3>
+<h3>Platforms supported - debug target</h3>
+<p>The browser with the page you are debugging.
 
 <ul>
 <li>Android 2.2 Browser application
 <li>Android 2.2 w/PhoneGap 0.9.2
 <li>iOS 4.2.x Mobile Safari application
 <li>BlackBerry v6.x simulator
+<li>webOS 2.x (unspecified version)
 </ul>
 
diff --git a/weinre.doc/MultiUser.body.html b/weinre.doc/MultiUser.body.html
new file mode 100644
index 0000000..460367a
--- /dev/null
+++ b/weinre.doc/MultiUser.body.html
@@ -0,0 +1,97 @@
+<!--
+ * weinre is available under *either* the terms of the modified BSD license *or* the
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
+ * 
+ * Copyright (c) 2010, 2011 IBM Corporation
+-->
+
+<p><span class="weinre">weinre</span> was originally designed so that each user
+that wanted to use <span class="weinre">weinre</span> would run their own 
+<span class="weinre">weinre</span> server.  The system has since been enhanced
+to support muliple users using the same server.
+
+<!-- ======================================================== -->
+<h2>Your debug id</h2>
+
+<p>To use a multi-user server, you'll need to select an id for yourself.  This
+id will be used in the client and target URLs of <span class="weinre">weinre</span>
+to scope your clients and targets from the clients and targets of other users.
+
+<p>There is no password, just an id.  There is no real security here.  
+The id is a secret shared between your
+debug target and client, but it is sent in the clear when your target and
+client connect to the server.  If someone else knows your <b>id</b>, they can
+connect to clients or targets you are running with that <b>id</b>.
+
+<p>So don't give your <b>id</b> to anyone else that you don't want to share 
+with.
+
+<p>To keep your <b>id</b>'s familar but non-guessable, you might use a string
+consisting of your typical userid, followed by a dash, followed by another 
+string not likely to be guessed by other people.
+
+<p>If you are paranoid, you can change your <b>id</b> every time you start
+a debug session, of course.
+
+<!-- ======================================================== -->
+<h2>Using your id with the client</h2>
+
+<p>Typically when start a <span class="weinre">weinre</span> client session
+by visiting a URL like 
+
+<pre>
+http://some.server.xyz/client/
+</pre>
+
+<p>To start
+a client with a particular id, append the hash character (<code>#</code>) and
+the id you want to use the URL.  For instance, to connect with the id 
+<code>itsReallyMe</code>, launch the client with the URL 
+
+<pre>
+http://some.server.xyz/client/#itsReallyMe
+</pre>
+
+<p>The id which is used will be displayed
+in the connected clients list for each connected client.
+
+<!-- ======================================================== -->
+<h2>Using your id with the target</h2>
+
+<p>As with the server, append the hash character (<code>#</code>) to the
+URL of the script being injected into your web page.  If you had previously
+used a &lt;script src&gt; element of:
+
+<pre>
+&lt;script src="http://some.server.xyz/target/target-script-min.js"&gt;&lt;/script&gt;
+</pre>
+
+<p>use this script element to connect with id <code>itsReallyMe</code>
+
+<pre>
+&lt;script src="http://some.server.xyz/target/target-script-min.js#itsReallyMe"&gt;&lt;/script&gt;
+</pre>
+
+<!-- ======================================================== -->
+<h2>Notes</h2>
+
+<ul class="spaced">
+
+<li>The <span class="weinre">weinre</span> server always runs in multi-user
+mode.  If you don't otherwise specify an <b>id</b>, the value of 
+<code>anonymous</code> will be used for the <b>id</b>.
+
+<li>The <b>id</b> is passed in the HTTP body of a POST request during the
+initial connection of the target and client to the server, and is not sent
+thereafter.  Thus, it should remain out of view in server logs and the like,
+though it will be visible for anyone who has access to the contents of 
+HTTP request bodies.
+
+<li>There is no programmatic interface on the <span class="weinre">weinre</span>
+to list <b>id</b>'s in current use, for security reasons.  From the Remote panel
+of weinre, each connected client and target have their channel and id's listed,
+if you can't remember what id you used with the client.
+
+<li>The client with the Mac application currently only supports the <code>anonymous</code> id.
+
+</ul>
\ No newline at end of file
diff --git a/weinre.doc/boilerplate-header.html.txt b/weinre.doc/boilerplate-header.html.txt
index 32adc4f..ed36aff 100644
--- a/weinre.doc/boilerplate-header.html.txt
+++ b/weinre.doc/boilerplate-header.html.txt
@@ -39,6 +39,7 @@
 - <a href="TestDrive.html">Test Drive</a>
 - <a href="Installing.html">Installing</a>
 - <a href="Running.html">Running</a>
+- <a href="MultiUser.html">Multi-User</a>
 - <a href="Security.html">Security</a>
 - <a href="Building.html">Building</a>
 - <a href="ChangeLog.html">ChangeLog</a>
diff --git a/weinre.doc/boilerplate-trailer.html.txt b/weinre.doc/boilerplate-trailer.html.txt
index 531ee60..b16be62 100644
--- a/weinre.doc/boilerplate-trailer.html.txt
+++ b/weinre.doc/boilerplate-trailer.html.txt
@@ -14,6 +14,7 @@
 - <a href="TestDrive.html">Test Drive</a>
 - <a href="Installing.html">Installing</a>
 - <a href="Running.html">Running</a>
+- <a href="MultiUser.html">Multi-User</a>
 - <a href="Security.html">Security</a>
 - <a href="Building.html">Building</a>
 - <a href="ChangeLog.html">ChangeLog</a>
diff --git a/weinre.server/interfaces/WeinreClientCommands.idl b/weinre.server/interfaces/WeinreClientCommands.idl
index 35d5a74..b912cdf 100644
--- a/weinre.server/interfaces/WeinreClientCommands.idl
+++ b/weinre.server/interfaces/WeinreClientCommands.idl
@@ -18,6 +18,8 @@
         void connectTarget(in string clientId, in string targetId);
         void disconnectTarget(in string clientId);
         
+        void getExtensions(out string[] extensions)
+        
         void logDebug(   in string message );
         void logInfo(    in string message );
         void logWarning( in string message );
diff --git a/weinre.server/src/weinre/server/Channel.java b/weinre.server/src/weinre/server/Channel.java
index 86ca840..71294d2 100644
--- a/weinre.server/src/weinre/server/Channel.java
+++ b/weinre.server/src/weinre/server/Channel.java
@@ -22,8 +22,11 @@
 //-------------------------------------------------------------------
 public class Channel {
     
+    static public final String AnonymousId = "anonymous";
+    
     private String                pathPrefix;
     private String                name;
+    private String                id;
     private MessageQueue<String>  requestQueue;
     private MessageQueue<String>  responseQueue;
     private boolean               isClosed;
@@ -34,9 +37,10 @@
     private String                remoteAddress;
     
     //---------------------------------------------------------------
-    public Channel(String pathPrefix, String name, String remoteHost, String remoteAddress) {
+    public Channel(String pathPrefix, String name, String id, String remoteHost, String remoteAddress) {
         this.pathPrefix         = pathPrefix;
         this.name               = name;
+        this.id                 = id;
         this.remoteHost         = remoteHost;
         this.remoteAddress      = remoteAddress;
         this.requestQueue       = new MessageQueue<String>();
@@ -163,6 +167,11 @@
     }
     
     //---------------------------------------------------------------
+    public String getId() {
+        return id;
+    }
+    
+    //---------------------------------------------------------------
     public long getLastRead() {
         return lastRead;
     }
diff --git a/weinre.server/src/weinre/server/ChannelManager.java b/weinre.server/src/weinre/server/ChannelManager.java
index 277be1d..77f326a 100644
--- a/weinre.server/src/weinre/server/ChannelManager.java
+++ b/weinre.server/src/weinre/server/ChannelManager.java
@@ -73,10 +73,10 @@
     }
     
     //---------------------------------------------------------------
-    public Channel registerChannel(String pathPrefix, String channelName, String remoteHost, String remoteAddress) {
+    public Channel registerChannel(String pathPrefix, String channelName, String id, String remoteHost, String remoteAddress) {
         if (channelMap.containsKey(channelName)) return null;
         
-        Channel channel = new Channel(pathPrefix, channelName, remoteHost, remoteAddress);
+        Channel channel = new Channel(pathPrefix, channelName, id, remoteHost, remoteAddress);
         channelMap.put(channelName, channel);
         
         fireRegisteredEvent(channel);
diff --git a/weinre.server/src/weinre/server/Client.java b/weinre.server/src/weinre/server/Client.java
index e14bfaf..4eed575 100644
--- a/weinre.server/src/weinre/server/Client.java
+++ b/weinre.server/src/weinre/server/Client.java
@@ -65,7 +65,8 @@
       JSONObject result = new JSONObject();
       
       try {
-          result.put("id",                 getChannel().getName());
+          result.put("channel",            getChannel().getName());
+          result.put("id",                 getChannel().getId());
           result.put("hostName",           getChannel().getRemoteHost());
           result.put("connectedTargetIds", new JSONArray());
       } 
diff --git a/weinre.server/src/weinre/server/ConnectionManager.java b/weinre.server/src/weinre/server/ConnectionManager.java
index 0d27e2f..7a3ccc1 100644
--- a/weinre.server/src/weinre/server/ConnectionManager.java
+++ b/weinre.server/src/weinre/server/ConnectionManager.java
@@ -55,51 +55,59 @@
     //---------------------------------------------------------------
     public void addClient(Client client) {
         clientMap.put(client.getName(), client);
-        _sendAllClientsEvent("WeinreClientEvents", "clientRegistered", client.getDescription());
+        _sendAllClientsEvent("WeinreClientEvents", client.getId(), "clientRegistered", client.getDescription());
     }
 
     //---------------------------------------------------------------
     public void addTarget(Target target) {
         targetMap.put(target.getName(), target);
-        _sendAllClientsEvent("WeinreClientEvents", "targetRegistered", target.getDescription());
+        _sendAllClientsEvent("WeinreClientEvents", target.getId(), "targetRegistered", target.getDescription());
     }
 
     //---------------------------------------------------------------
     private void _removeClient(Client client) {
-        _sendAllClientsEvent("WeinreClientEvents", "clientUnregistered", client.getName());
+        _sendAllClientsEvent("WeinreClientEvents", client.getId(), "clientUnregistered", client.getName());
         clientMap.remove(client.getName());
     }
 
     //---------------------------------------------------------------
     private void _removeTarget(Target target) {
-        _sendAllClientsEvent("WeinreClientEvents", "targetUnregistered", target.getName());
+        _sendAllClientsEvent("WeinreClientEvents", target.getId(), "targetUnregistered", target.getName());
         targetMap.remove(target.getName());
     }
 
     //---------------------------------------------------------------
-    public Client getClient(String id) {
-        return clientMap.get(id);
+    public Client getClient(String name) {
+        return clientMap.get(name);
     }
 
     //---------------------------------------------------------------
-    public Target getTarget(String id) {
-        return targetMap.get(id);
+    public Target getTarget(String name) {
+        return targetMap.get(name);
     }
 
     //---------------------------------------------------------------
-    public List<Client> getClients() {
+    public List<Client> getClients(String id) {
         List<Client> result = new ArrayList<Client>();
         
-        result.addAll(clientMap.values());
+        for (Client client: clientMap.values()) {
+            if (client.getId().equals(id)) {
+                result.add(client);
+            }
+        }
         
         return result;
     }
 
     //---------------------------------------------------------------
-    public List<Target> getTargets() {
+    public List<Target> getTargets(String id) {
         List<Target> result = new ArrayList<Target>();
         
-        result.addAll(targetMap.values());
+        for (Target target: targetMap.values()) {
+            if (target.getId().equals(id)) {
+                result.add(target);
+            }
+        }
         
         return result;
     }
@@ -144,13 +152,13 @@
         String clientName = client.getChannel().getName();
         String targetName = target.getChannel().getName();
 
-        _sendAllClientsEvent("WeinreClientEvents", message, clientName, targetName);
+        _sendAllClientsEvent("WeinreClientEvents", client.getId(), message, clientName, targetName);
         target.getChannel().sendEvent("WeinreTargetEvents", message, clientName, targetName);
     }
     
     //---------------------------------------------------------------
-    private void _sendAllClientsEvent(String intfName, String message, Object... args) {
-        for (Client aClient: getClients()) {
+    private void _sendAllClientsEvent(String intfName, String id, String message, Object... args) {
+        for (Client aClient: getClients(id)) {
             aClient.getChannel().sendEvent(intfName, message, args);
         }
     }
diff --git a/weinre.server/src/weinre/server/Connector.java b/weinre.server/src/weinre/server/Connector.java
index b428ca0..80c2895 100644
--- a/weinre.server/src/weinre/server/Connector.java
+++ b/weinre.server/src/weinre/server/Connector.java
@@ -46,6 +46,11 @@
     }
     
     //---------------------------------------------------------------
+    public String getId() {
+        return channel.getId();
+    }
+    
+    //---------------------------------------------------------------
     public List<Connector> getConnections() {
         List<Connector> result = new ArrayList<Connector>();
         
diff --git a/weinre.server/src/weinre/server/ExtensionManager.java b/weinre.server/src/weinre/server/ExtensionManager.java
new file mode 100644
index 0000000..3977e86
--- /dev/null
+++ b/weinre.server/src/weinre/server/ExtensionManager.java
@@ -0,0 +1,95 @@
+/*
+ * weinre is available under *either* the terms of the modified BSD license *or* the
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
+ * 
+ * Copyright (c) 2011 IBM Corporation
+ */
+
+package weinre.server;
+
+import java.io.File;
+import java.io.IOException;
+import java.net.MalformedURLException;
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.jetty.util.resource.Resource;
+
+//-------------------------------------------------------------------
+public class ExtensionManager {
+    static private File     weinreHomeDir = null;     
+    static private File     weinreExtDir  = null;    
+    static private long     weinreExtDate = 0L;
+    static private String[] extensions    = null;
+    
+    static private String[] EMPTY_STRING_ARRAY = {};
+
+    //---------------------------------------------------------------
+    static {
+        initExtensions();
+    }
+    
+    //---------------------------------------------------------------
+    static public String[] getExtensions() {
+        if (null == weinreExtDir) return extensions;
+        
+        if (weinreExtDate != weinreExtDir.lastModified()) {
+            initExtensions();
+        }
+        
+        return extensions;
+    }
+    
+    //---------------------------------------------------------------
+    // path: /client/extensions/weinre-ext-sample/extension.html
+    static public Resource getResource(String path) throws MalformedURLException {
+        if (null == weinreExtDir) return null;
+        
+        File resourceFile = new File(weinreExtDir, path.substring(18));
+        if (!resourceFile.exists()) return null;
+        
+        try {
+            return Resource.newResource(resourceFile.toURI().toURL().toExternalForm(), false);
+        } 
+        catch (IOException e) {
+            throw new MalformedURLException();
+        }
+    }
+    
+    //---------------------------------------------------------------
+    static private void initExtensions() {
+        
+        extensions = EMPTY_STRING_ARRAY;
+            
+        weinreHomeDir = new File(System.getProperty("user.home"), ".weinre");
+        if (!weinreHomeDir.isDirectory()) {
+            Main.info("extensions not enabled: ~/.weinre is not a directory");
+            return;
+        }
+
+        weinreExtDir  = new File(weinreHomeDir, "extensions");
+        weinreExtDate = weinreExtDir.lastModified();
+        if (!weinreExtDir.isDirectory()) {
+            Main.info("extensions not enabled: ~/.weinre/extensions is not a directory");
+            return;
+        }
+        
+        List<String> extensionList = new ArrayList<String>();
+        
+        String[] entries = weinreExtDir.list();
+        for (String entry: entries) {
+            if (entry.startsWith(".")) continue;
+            
+            File extDir = new File(weinreExtDir, entry);
+            if (!extDir.isDirectory()) continue;
+            
+            File extensionHtml = new File(extDir, "extension.html");
+            if (!extensionHtml.isFile()) continue;
+            
+            extensionList.add(entry);
+        }
+        
+        extensions = extensionList.toArray(EMPTY_STRING_ARRAY);
+    }
+    
+}
diff --git a/weinre.server/src/weinre/server/Target.java b/weinre.server/src/weinre/server/Target.java
index 54920ff..ec6a986 100644
--- a/weinre.server/src/weinre/server/Target.java
+++ b/weinre.server/src/weinre/server/Target.java
@@ -93,7 +93,8 @@
         JSONObject result = new JSONObject();
         
         try {
-            result.put("id",                 getChannel().getName());
+            result.put("channel",            getChannel().getName());
+            result.put("id",                 getChannel().getId());
             result.put("hostName",           getChannel().getRemoteHost());
             result.put("url",                this.url);
             result.put("connectedClientIds", new JSONArray());
diff --git a/weinre.server/src/weinre/server/http/ClassPathResourceHandler.java b/weinre.server/src/weinre/server/http/ClassPathResourceHandler.java
index 13b0072..f6eccda 100644
--- a/weinre.server/src/weinre/server/http/ClassPathResourceHandler.java
+++ b/weinre.server/src/weinre/server/http/ClassPathResourceHandler.java
@@ -14,6 +14,8 @@
 import org.eclipse.jetty.server.handler.ResourceHandler;
 import org.eclipse.jetty.util.resource.Resource;
 
+import weinre.server.ExtensionManager;
+
 //-------------------------------------------------------------------
 public class ClassPathResourceHandler extends ResourceHandler {
 
@@ -32,6 +34,11 @@
             throw new MalformedURLException(path);
         }
 
+        // handle extensions
+        if (path.startsWith("/client/extensions/")) {
+            return ExtensionManager.getResource(path);
+        }
+        
         path = pathPrefix + path;
         URL url = getClass().getClassLoader().getResource(path);
         if (url != null)
diff --git a/weinre.server/src/weinre/server/http/HttpSocketHandler.java b/weinre.server/src/weinre/server/http/HttpSocketHandler.java
index df8a0e5..4e39082 100644
--- a/weinre.server/src/weinre/server/http/HttpSocketHandler.java
+++ b/weinre.server/src/weinre/server/http/HttpSocketHandler.java
@@ -20,6 +20,7 @@
 
 import org.apache.wink.json4j.JSONArray;
 import org.apache.wink.json4j.JSONException;
+import org.apache.wink.json4j.JSONObject;
 import org.eclipse.jetty.server.Request;
 import org.eclipse.jetty.server.handler.AbstractHandler;
 
@@ -138,14 +139,54 @@
     //---------------------------------------------------------------
     public void handleCreate(String target, Request baseRequest, HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException {
         String channelName = "" + Utility.getNextSequenceNumber();
+        String id;
+
+        if (0 == baseRequest.getContentLength()) {
+            id = Channel.AnonymousId;
+        }
         
-        ChannelManager.$.registerChannel(pathPrefix, channelName, request.getRemoteHost(), request.getRemoteAddr());
+        else {
+            try {
+                String json = readRequestBody(request.getInputStream());
+                JSONObject obj = new JSONObject(json);
+                if (!obj.has("id")) {
+                    id = Channel.AnonymousId;
+                }
+                else {
+                    id = obj.getString("id");
+                    if (id.isEmpty()) {
+                        id = Channel.AnonymousId;
+                    }
+                }
+            }
+            catch (IOException e) {
+                response.setStatus(400);
+                return;
+            }
+            catch (JSONException e) {
+                response.setStatus(400);
+                return;
+            }
+        }
+        
+        ChannelManager.$.registerChannel(pathPrefix, channelName, id, request.getRemoteHost(), request.getRemoteAddr());
         
         response.setStatus(200);
         response.setContentType("application/json");
         
         ServletOutputStream oStream = response.getOutputStream();
-        oStream.print("{\"channel\": " + channelName + "}");
+        JSONObject obj = new JSONObject();
+        
+        try {
+            obj.put("channel", channelName);
+            obj.put("id", id);
+        }
+        catch (JSONException e) {
+            throw new RuntimeException(e);
+        }
+        
+        String result = obj.toString();
+        oStream.print(result);
         oStream.close();
     }
 
diff --git a/weinre.server/src/weinre/server/service/WeinreClientCommands.java b/weinre.server/src/weinre/server/service/WeinreClientCommands.java
index 1c58cc2..7dcf1a8 100644
--- a/weinre.server/src/weinre/server/service/WeinreClientCommands.java
+++ b/weinre.server/src/weinre/server/service/WeinreClientCommands.java
@@ -11,10 +11,13 @@
 import java.util.List;
 
 import org.apache.wink.json4j.JSONArray;
+import org.apache.wink.json4j.JSONException;
+import org.apache.wink.json4j.JSONObject;
 
 import weinre.server.Channel;
 import weinre.server.Client;
 import weinre.server.ConnectionManager;
+import weinre.server.ExtensionManager;
 import weinre.server.Main;
 import weinre.server.Target;
 
@@ -25,13 +28,15 @@
     public void registerClient(Channel channel, String callbackId) throws IOException {
         Client client = new Client(channel);
         
-        channel.sendCallback("WeinreClientEvents", callbackId, client.getName());
+        JSONObject description = client.getDescription();
+        
+        channel.sendCallback("WeinreClientEvents", callbackId, description);
         channel.sendEvent("WeinreClientEvents", "serverProperties", Main.getSettings().asProperties());
     }
 
     //---------------------------------------------------------------
     public void getTargets(Channel channel, String callbackId) throws IOException {
-        List<Target> targets = ConnectionManager.$.getTargets();
+        List<Target> targets = ConnectionManager.$.getTargets(channel.getId());
         JSONArray targetResults = new JSONArray();
         
         for (Target target: targets) {
@@ -43,7 +48,7 @@
 
     //---------------------------------------------------------------
     public void getClients(Channel channel, String callbackId) throws IOException {
-        List<Client> clients = ConnectionManager.$.getClients();
+        List<Client> clients = ConnectionManager.$.getClients(channel.getId());
         JSONArray clientResults = new JSONArray();
         
         for (Client client: clients) {
@@ -54,6 +59,26 @@
     }
 
     //---------------------------------------------------------------
+    public void getExtensions(Channel channel, String callbackId) throws IOException {
+        String[]  extensions = ExtensionManager.getExtensions();
+        JSONArray result     = new JSONArray();
+        
+        try {
+            for (String extension: extensions) {
+                JSONObject extensionObject = new JSONObject();
+                extensionObject.put("startPage", "extensions/" + extension + "/extension.html");
+                
+                result.add(extensionObject);
+            }
+        }
+        catch(JSONException e) {
+            throw new RuntimeException(e);
+        }
+        
+        channel.sendCallback("WeinreClientEvents", callbackId, result);
+    }
+
+    //---------------------------------------------------------------
     public void connectTarget(Channel channel, String clientId, String targetId, String callbackId) {
         Client client = ConnectionManager.$.getClient(clientId);
         if (client == null) return;
diff --git a/weinre.server/src/weinre/server/service/WeinreTargetCommands.java b/weinre.server/src/weinre/server/service/WeinreTargetCommands.java
index bd6d3ee..80e9a22 100644
--- a/weinre.server/src/weinre/server/service/WeinreTargetCommands.java
+++ b/weinre.server/src/weinre/server/service/WeinreTargetCommands.java
@@ -10,6 +10,8 @@
 import java.io.IOException;
 import java.util.List;
 
+import org.apache.wink.json4j.JSONObject;
+
 import weinre.server.Channel;
 import weinre.server.ChannelManager;
 import weinre.server.Main;
@@ -22,7 +24,9 @@
     public void registerTarget(Channel channel, String url, String callbackId) throws IOException {
         Target target = new Target(channel, url);
         
-        channel.sendCallback("WeinreTargetEvents", callbackId, target.getName());
+        JSONObject description = target.getDescription();
+        
+        channel.sendCallback("WeinreTargetEvents", callbackId, description);
     }
     
     //---------------------------------------------------------------
diff --git a/weinre.server/weinre server using ~weinre-server.settings.launch b/weinre.server/weinre server using ~weinre-server.settings.launch
index 4b2d2ab..88ae404 100644
--- a/weinre.server/weinre server using ~weinre-server.settings.launch
+++ b/weinre.server/weinre server using ~weinre-server.settings.launch
@@ -11,5 +11,6 @@
 <listEntry value="org.eclipse.debug.ui.launchGroup.run"/>
 </listAttribute>
 <stringAttribute key="org.eclipse.jdt.launching.MAIN_TYPE" value="weinre.server.Main"/>
+<stringAttribute key="org.eclipse.jdt.launching.PROGRAM_ARGUMENTS" value="--verbose   true --deathTimeout 120"/>
 <stringAttribute key="org.eclipse.jdt.launching.PROJECT_ATTR" value="weinre.server"/>
 </launchConfiguration>
diff --git a/weinre.web/client/ExtensionRegistryStub.js b/weinre.web/client/ExtensionRegistryStub.js
new file mode 100644
index 0000000..8e30a7e
--- /dev/null
+++ b/weinre.web/client/ExtensionRegistryStub.js
@@ -0,0 +1,11 @@
+/*
+ * weinre is available under *either* the terms of the modified BSD license *or* the
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
+ * 
+ * Copyright (c) 2011 IBM Corporation
+ */
+
+(function() {
+    var ExtensionRegistryImpl = require("weinre/client/ExtensionRegistryImpl").getClass()
+    window.InspectorExtensionRegistry = new ExtensionRegistryImpl()
+})()
diff --git a/weinre.web/client/InspectorBackendStub.js b/weinre.web/client/InspectorBackendStub.js
index 8b6532a..091c2a4 100644
--- a/weinre.web/client/InspectorBackendStub.js
+++ b/weinre.web/client/InspectorBackendStub.js
@@ -6,6 +6,6 @@
  */
 
 (function() {
-    var InspectorBackendImpl = require("weinre/client/InspectorBackendImpl")
+    var InspectorBackendImpl = require("weinre/client/InspectorBackendImpl").getClass()
     window.InspectorBackend = new InspectorBackendImpl()
 })()
diff --git a/weinre.web/client/InspectorFrontendHostStub.js b/weinre.web/client/InspectorFrontendHostStub.js
index e409743..d964918 100644
--- a/weinre.web/client/InspectorFrontendHostStub.js
+++ b/weinre.web/client/InspectorFrontendHostStub.js
@@ -6,6 +6,6 @@
  */
 
 (function() {
-    var InspectorFrontEndHostImpl = require("weinre/client/InspectorFrontendHostImpl")
+    var InspectorFrontEndHostImpl = require("weinre/client/InspectorFrontendHostImpl").getClass()
     window.InspectorFrontendHost = new InspectorFrontEndHostImpl() 
 })()
diff --git a/weinre.web/client/web-inspector-API.js b/weinre.web/client/web-inspector-API.js
new file mode 100644
index 0000000..e7fd7b0
--- /dev/null
+++ b/weinre.web/client/web-inspector-API.js
@@ -0,0 +1,8 @@
+/*
+ * weinre is available under *either* the terms of the modified BSD license *or* the
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
+ * 
+ * Copyright (c) 2011 IBM Corporation
+ */
+ 
+eval(window.top.installWebInspectorAPIsource())
diff --git a/weinre.web/demo/weinre-demo-min.html b/weinre.web/demo/weinre-demo-min.html
index 9adcc3b..95532ee 100644
--- a/weinre.web/demo/weinre-demo-min.html
+++ b/weinre.web/demo/weinre-demo-min.html
@@ -10,13 +10,13 @@
 <meta name="viewport" content="user-scalable=no, width=device-width, height=device-height">
 <title>weinre demo</title>
 <link rel="stylesheet" href="weinre-demo.css">
+<script src="weinre-demo.js"></script>
 <script src="/target/target-script-min.js"></script>
 
 <script type="text/javascript">
-    require("weinre/common/Weinre").showNotImplemented()
+    require("weinre/common/Weinre").getClass().showNotImplemented()
 </script>
 
-<script src="weinre-demo.js"></script>
 </head>
 
 <body onload="onLoad()">
diff --git a/weinre.web/demo/weinre-demo-pieces.html b/weinre.web/demo/weinre-demo-pieces.html
index 2b87785..f572778 100644
--- a/weinre.web/demo/weinre-demo-pieces.html
+++ b/weinre.web/demo/weinre-demo-pieces.html
@@ -10,7 +10,9 @@
 <meta name="viewport" content="user-scalable=no, width=device-width, height=device-height">
 <title>weinre demo</title>
 <link rel="stylesheet" href="weinre-demo.css">
+<script src="weinre-demo.js"></script>
 <script src="/modjewel-require.js"></script>
+<script src="/json2.js"></script>
 <script type="text/javascript">require("modjewel").warnOnRecursiveRequire(true)</script>
 <script src="/scooj.transportd.js"></script>
 <script src="/weinre/common/Ex.transportd.js"></script>
@@ -45,11 +47,9 @@
 <script src="/interfaces/all-json-idls-min.js"></script>
 
 <script type="text/javascript">
-    require("weinre/common/Weinre").showNotImplemented()
-    require('weinre/target/Target').main()
+    require("weinre/common/Weinre").getClass().showNotImplemented()
+    require('weinre/target/Target').getClass().main()
 </script>
-
-<script src="weinre-demo.js"></script>
 </head>
 
 <body onload="onLoad()">
diff --git a/weinre.web/demo/weinre-demo.html b/weinre.web/demo/weinre-demo.html
index ebdd2de..5c93023 100644
--- a/weinre.web/demo/weinre-demo.html
+++ b/weinre.web/demo/weinre-demo.html
@@ -10,13 +10,12 @@
 <meta name="viewport" content="user-scalable=no, width=device-width, height=device-height">
 <title>weinre demo</title>
 <link rel="stylesheet" href="weinre-demo.css">
+<script src="weinre-demo.js"></script>
 <script src="/target/target-script.js"></script>
 
 <script type="text/javascript">
-    require("weinre/common/Weinre").showNotImplemented()
+    require("weinre/common/Weinre").getClass().showNotImplemented()
 </script>
-
-<script src="weinre-demo.js"></script>
 </head>
 
 <body onload="onLoad()">
diff --git a/weinre.web/demo/weinre-demo.js b/weinre.web/demo/weinre-demo.js
index 6be2622..e0b95e3 100644
--- a/weinre.web/demo/weinre-demo.js
+++ b/weinre.web/demo/weinre-demo.js
@@ -13,6 +13,11 @@
 var outputElement 
 var storageIndex = 0
 
+// set the id based on the hash
+var hash = location.href.split("#")[1]
+if (!hash) hash = "anonymous"
+window.WeinreServerId = hash
+
 //------------------------------------------------------------------------------
 function onLoad() {
     if (!buttonStartStuff)  buttonStartStuff  = document.getElementById("button-start-stuff")
diff --git a/weinre.web/index.html b/weinre.web/index.html
index ac7e757..ed4f2a8 100644
--- a/weinre.web/index.html
+++ b/weinre.web/index.html
@@ -33,6 +33,9 @@
 textarea {
     width:                  100%;
 }
+pre, .indent {
+    margin-left:            2em;
+}
 </style>
 </head>
 <body>
@@ -47,15 +50,36 @@
 <tr><td>target demo:                 <td> <span id="url-target-demo-min">???</span> <br><i>(<span id="url-target-demo-pieces"></span>)</i>
 </table>
 
+<h2>Target Script</h2>
+
+<p>You can use this script to inject the weinre target code into your web page.
+
+<p class="indent"><code><span id="url-target-script">???</span></code>
+
+<p>Example:
+
+<pre>
+&ltscript src="<span id="url-target-script-raw">???</span>"&gt;&lt;/script&gt;
+</pre>
+
 <h2>Target Bookmarklet</h2>
 
-<p>link: <span id="url-target-bookmarklet">???</span> (drag to your bookmarks)
+<p>You can use this bookmarklet to inject the weinre target code into any 
+web page you are viewing.
+
+<p>link you can drag to your bookmarks: 
+
+<div class="indent">
+<span id="url-target-bookmarklet">???</span> 
+</div>
 
 <p>bookmarklet url in a pre: 
 <pre id="target-bookmarklet-src-pre"></pre>
 
 <p>bookmarklet url in a textarea: 
+<div class="indent">
 <textarea id="target-bookmarklet-src-text-area"></textarea> 
+</div>
 
 <h2>Development</h2>
 
@@ -71,6 +95,7 @@
 <tr><td>jetty servlet:  <td> <span id="version-servlet">???</span>
 <tr><td>apache cli:     <td> <span id="version-cli">???</span>
 <tr><td>apache json4j:  <td> <span id="version-json4j">???</span>
+<tr><td>json2:          <td> <span id="version-json2">???</span>
 <tr><td>scooj:          <td> <span id="version-scooj">???</span>
 <tr><td>modjewel:       <td> <span id="version-modjewel">???</span>
 </table>
diff --git a/weinre.web/index.js b/weinre.web/index.js
index b42f890..3948f97 100644
--- a/weinre.web/index.js
+++ b/weinre.web/index.js
@@ -9,12 +9,19 @@
 var weinre_host     = location.hostname
 var weinre_port     = location.port
 var weinre_pathname = location.pathname
+var weinre_id       = "anonymous"
 
-replaceURL("url-client-ui",              buildHttpURL("client/"))
+var hash = location.href.split("#")[1]
+if (hash) {
+    weinre_id = hash
+}
+
+replaceURL("url-client-ui",              buildHttpURL("client/#" + weinre_id))
 replaceURL("url-interfaces",             buildHttpURL("interfaces/interfaces.html"))
-replaceURL("url-target-demo",            buildHttpURL("demo/weinre-demo.html"))
-replaceURL("url-target-demo-min",        buildHttpURL("demo/weinre-demo-min.html"))
-replaceURL("url-target-demo-pieces",     buildHttpURL("demo/weinre-demo-pieces.html"), "pieces version")
+replaceURL("url-target-demo",            buildHttpURL("demo/weinre-demo.html#" + weinre_id))
+replaceURL("url-target-demo-min",        buildHttpURL("demo/weinre-demo-min.html#" + weinre_id))
+replaceURL("url-target-demo-pieces",     buildHttpURL("demo/weinre-demo-pieces.html#" + weinre_id), "pieces version")
+replaceURL("url-target-script",          buildHttpURL("target/target-script-min.js#" + weinre_id))
 replaceURL("url-target-bookmarklet",     getTargetBookmarklet(), "weinre target debug")
 replaceURL("url-target-documentation",   buildHttpURL("doc/"))
 //replaceURL("url-client-protocol",        buildHttpURL("ws/client/"))
@@ -27,6 +34,7 @@
 replaceText("version-servlet",   Weinre.Versions.servlet)
 replaceText("version-cli",       Weinre.Versions.cli)
 replaceText("version-json4j",    Weinre.Versions.json4j)
+replaceText("version-json2",     Weinre.Versions.json2)
 replaceText("version-swt",       Weinre.Versions.swt)
 replaceText("version-scooj",     Weinre.Versions.scooj)
 replaceText("version-modjewel",  Weinre.Versions.modjewel)
@@ -34,6 +42,8 @@
 replaceText("target-bookmarklet-src-pre",       getTargetBookmarklet())
 replaceText("target-bookmarklet-src-text-area", getTargetBookmarklet())
 
+replaceText("url-target-script-raw",  buildHttpURL("target/target-script-min.js#" + weinre_id))
+
 //---------------------------------------------------------------------
 function buildHttpURL(uri) {
     var port     = weinre_port
@@ -70,7 +80,7 @@
     script = script.replace(/\n/g,   "")
     script = script.replace("targetBookmarkletFunction","")
     script = script.replace(/\s*/g, "")
-    script = script.replace("???", buildHttpURL("target/target-script-min.js"))
+    script = script.replace("???", buildHttpURL("target/target-script-min.js#" + weinre_id))
     script = "(" + script + ')(document.createElement("script"));void(0);'
     return 'javascript:' + script
 }
diff --git a/weinre.web/interfaces/interfaces.js b/weinre.web/interfaces/interfaces.js
index 206a562..08d60fe 100644
--- a/weinre.web/interfaces/interfaces.js
+++ b/weinre.web/interfaces/interfaces.js
@@ -22,7 +22,7 @@
 
 var NativeTypes = "int any number boolean string void".split(" ")
 
-var IDLTools = require("weinre/common/IDLTools")
+var IDLTools = require("weinre/common/IDLTools").getClass()
 
 if (!window.localStorage) {
     window.localStorage = {
diff --git a/weinre.web/modules/weinre/client/Client.scoop b/weinre.web/modules/weinre/client/Client.scoop
index 08cb40f..f1368d8 100644
--- a/weinre.web/modules/weinre/client/Client.scoop
+++ b/weinre.web/modules/weinre/client/Client.scoop
@@ -13,6 +13,7 @@
 requireClass ../common/Weinre
 requireClass ../common/MessageDispatcher
 requireClass ../common/Binding
+requireClass ../common/IDGenerator
 
 requireClass ./InspectorBackendImpl
 requireClass ./InspectorFrontendHostImpl
@@ -38,7 +39,7 @@
     window.addEventListener("load", Binding(this, "onLoaded"), false)
 
     // create the socket
-    var messageDispatcher = new MessageDispatcher("../ws/client")
+    var messageDispatcher = new MessageDispatcher("../ws/client", this._getId())
     Weinre.messageDispatcher = messageDispatcher
 
     // finish setting up InspectorBackend
@@ -55,6 +56,12 @@
     WebInspector.mainResource.url = location.href
 
 //-----------------------------------------------------------------------------
+method _getId
+    var hash = location.href.split("#")[1]
+    if (hash) return hash
+    return "anonymous"
+
+//-----------------------------------------------------------------------------
 method uiAvailable
     return WebInspector.panels && WebInspector.panels.remote
     
@@ -74,6 +81,7 @@
     
     var toolbar = document.getElementById("toolbar")
     WebInspector.addPanelToolbarIcon(toolbar, panel, toolbar.childNodes[1])
+    WebInspector.panelOrder.unshift(WebInspector.panelOrder.pop())
     
     WebInspector.currentPanel = panel
     
@@ -90,6 +98,8 @@
 //-----------------------------------------------------------------------------
 method onLoaded
 
+    Weinre.WeinreClientCommands.registerClient(Binding(this, this.cb_registerClient))
+
     this._installRemotePanel()
 
     var messageDispatcher = Weinre.messageDispatcher
@@ -99,16 +109,12 @@
     messageDispatcher.registerInterface("WeinreClientEvents",    new WeinreClientEventsImpl(this), false)
     messageDispatcher.registerInterface("InspectorFrontendHost", InspectorFrontendHost, false)
 
-    Weinre.WeinreClientCommands.registerClient(Binding(this, this.cb_registerClient))
-
 //-----------------------------------------------------------------------------
-method cb_registerClient(clientId)
-    Weinre.clientId = clientId
-    Weinre.connectorId = clientId
-    Callback.setConnectorId(clientId)
+method cb_registerClient(clientDescription)
+    Weinre.clientDescription = clientDescription
     
     if (this.uiAvailable()) {
-        WebInspector.panels.remote.setCurrentClient(clientId)
+        WebInspector.panels.remote.setCurrentClient(clientDescription.channel)
         WebInspector.panels.remote.afterInitialConnection()
     }
     
@@ -125,6 +131,27 @@
     }, 1000)
 
 //-----------------------------------------------------------------------------
+function installWebInspectorAPIsource
+
+    if ("webInspector" in window) return
+    
+    var extensionAPI = window.parent.InspectorFrontendHost.getExtensionAPI()
+    extensionAPI = extensionAPI.replace("location.hostname + location.port", "location.hostname + ':' + location.port")
+    
+    // parms are: InjectedScriptHost, inspectedWindow, injectedScriptId
+    // InjectedScriptHost is not directly referenced
+    // inspectedWindow is not directly referenced
+    // injectedScriptId is used to scope object references, appears that
+    //     each extension should have a unique value for this
+    
+    var id = IDGenerator.next()
+    console.log("installing webInspector with injectedScriptId: " + id)
+    extensionAPI += "(null,null," + id + ")"
+    return extensionAPI
+
+//-----------------------------------------------------------------------------
 static method main
     Weinre.client = new Client()
     Weinre.client.initialize()
+
+    window.installWebInspectorAPIsource = installWebInspectorAPIsource
\ No newline at end of file
diff --git a/weinre.web/modules/weinre/client/ConnectorList.scoop b/weinre.web/modules/weinre/client/ConnectorList.scoop
index 92558cb..b2ad6a5 100644
--- a/weinre.web/modules/weinre/client/ConnectorList.scoop
+++ b/weinre.web/modules/weinre/client/ConnectorList.scoop
@@ -23,9 +23,9 @@
 
 //-----------------------------------------------------------------------------
 method add(connector)
-    if (this.connectors[connector.id]) return
+    if (this.connectors[connector.channel]) return
     
-    this.connectors[connector.id] = connector
+    this.connectors[connector.channel] = connector
     
     var li = this.getListItem(connector)
 
@@ -44,12 +44,16 @@
     }
 
 //-----------------------------------------------------------------------------
-method getNewestConnectorId(ignoring)
+method get(channel)
+    return this.connectors[channel]
+
+//-----------------------------------------------------------------------------
+method getNewestConnectorChannel(ignoring)
     var newest = 0
     
-    for (var connectorId in this.connectors) {
-        if (connectorId == ignoring) continue
-        if (connectorId > newest) newest = connectorId
+    for (var connectorChannel in this.connectors) {
+        if (connectorChannel == ignoring) continue
+        if (connectorChannel > newest) newest = connectorChannel
     }
     
     if (newest == 0) return null
@@ -59,8 +63,8 @@
 method getConnectorInsertionPoint(connector)
     for (var i=0; i<this.ulConnectors.childNodes.length; i++) {
         var childNode = this.ulConnectors.childNodes[i]
-        if (null == childNode.connectorId) continue
-        if (childNode.connectorId < connector.id) {
+        if (null == childNode.connectorChannel) continue
+        if (childNode.connectorChannel < connector.channel) {
             return childNode
         }
     }
@@ -68,17 +72,17 @@
     return null
 
 //-----------------------------------------------------------------------------
-method remove(id, fast)
+method remove(channel, fast)
     var self = this
-    var element = this.getConnectorElement(id)
+    var element = this.getConnectorElement(channel)
     if (!element) return
 
-    var connector = this.connectors[id]
+    var connector = this.connectors[channel]
     if (connector) {
         connector.closed = true
     }
     
-    delete this.connectors[id]
+    delete this.connectors[channel]
     
     if (fast) {
         this._remove(element)
@@ -103,45 +107,45 @@
 //-----------------------------------------------------------------------------
 method removeAll()
     this.getConnectors().forEach(function(connector) {
-        this.remove(connector.id, true)
+        this.remove(connector.channel, true)
     }, this)
 
 //-----------------------------------------------------------------------------
 method getConnectors()
     var result = []
-    for (var id in this.connectors) {
-        if (!this.connectors.hasOwnProperty(id)) continue
+    for (var channel in this.connectors) {
+        if (!this.connectors.hasOwnProperty(channel)) continue
         
-        result.push(this.connectors[id])
+        result.push(this.connectors[channel])
     }
     
     return result
 
 //-----------------------------------------------------------------------------
-method getConnectorElement(id)
-    var connector = this.connectors[id]
+method getConnectorElement(channel)
+    var connector = this.connectors[channel]
     if (!connector) return null
     
     return connector.element
 
 //-----------------------------------------------------------------------------
-method setCurrent(id)
+method setCurrent(channel)
     this.getConnectors().forEach(function(connector) {
         connector.element.removeStyleClass("current")
     })
     
-    var element = this.getConnectorElement(id)
+    var element = this.getConnectorElement(channel)
     if (null == element) return
     
     element.addStyleClass("current")
 
 //-----------------------------------------------------------------------------
-method setState(id, state)
-    if (typeof id == "string") {
-        var element = this.getConnectorElement(id)
+method setState(channel, state)
+    if (typeof channel == "string") {
+        var element = this.getConnectorElement(channel)
     }
     else {
-        element = id
+        element = channel
     }
     
     if (!element) return
diff --git a/weinre.web/modules/weinre/client/ExtensionRegistryImpl.scoop b/weinre.web/modules/weinre/client/ExtensionRegistryImpl.scoop
new file mode 100644
index 0000000..bee7c42
--- /dev/null
+++ b/weinre.web/modules/weinre/client/ExtensionRegistryImpl.scoop
@@ -0,0 +1,35 @@
+
+/*
+ * weinre is available under *either* the terms of the modified BSD license *or* the
+ * MIT License (2008). See http://opensource.org/licenses/alphabetical for full text.
+ * 
+ * Copyright (c) 2011 IBM Corporation
+ */
+
+requireClass ../common/Ex
+requireClass ../common/Binding
+requireClass ../common/Weinre
+
+//-----------------------------------------------------------------------------
+class ExtensionRegistryImpl
+
+//-----------------------------------------------------------------------------
+init
+    var extensions = []
+
+//-----------------------------------------------------------------------------
+method getExtensionsAsync
+    if (extensions.length) return
+    
+    Weinre.WeinreClientCommands.getExtensions(Binding(this, this._cb_getExtensions))
+        
+//-----------------------------------------------------------------------------
+method _cb_getExtensions(extensionsResult)
+    extensions = extensionsResult
+    this._installExtensions()
+    
+//-----------------------------------------------------------------------------
+method _installExtensions    
+    WebInspector.addExtensions(extensions)
+    
+    
\ No newline at end of file
diff --git a/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.scoop b/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.scoop
index bb5093e..cbdb6c1 100644
--- a/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.scoop
+++ b/weinre.web/modules/weinre/client/InspectorFrontendHostImpl.scoop
@@ -15,6 +15,10 @@
     this._getPlatformAndPort()
 
 //-----------------------------------------------------------------------------
+init
+    var _extensionAPI
+    
+//-----------------------------------------------------------------------------
 method loaded
 
 //-----------------------------------------------------------------------------
@@ -44,6 +48,17 @@
     Weinre.logInfo(arguments.callee.signature + "(" + JSON.stringify(object,null,4) + ")")
 
 //-----------------------------------------------------------------------------
+method setExtensionAPI(extensionAPI)
+    _extensionAPI = extensionAPI
+
+//-----------------------------------------------------------------------------
+method getExtensionAPI
+    return _extensionAPI
+
+//-----------------------------------------------------------------------------
+method inspectedURLChanged
+
+//-----------------------------------------------------------------------------
 method _getPlatformAndPort
     this._platform       = "weinre"
     this._platformFlavor = "weinre"
diff --git a/weinre.web/modules/weinre/client/RemotePanel.scoop b/weinre.web/modules/weinre/client/RemotePanel.scoop
index 2c612ef..fe7c5b8 100644
--- a/weinre.web/modules/weinre/client/RemotePanel.scoop
+++ b/weinre.web/modules/weinre/client/RemotePanel.scoop
@@ -64,27 +64,30 @@
 method addTarget(target) 
     this.targetList.add(target)
 
-method removeClient(id) 
-    this.clientList.remove(id)
+method getTarget(channel)
+    return this.targetList.get(channel)
     
-method removeTarget(id) 
-    this.targetList.remove(id)
+method removeClient(channel) 
+    this.clientList.remove(channel)
+    
+method removeTarget(channel) 
+    this.targetList.remove(channel)
 
-method setCurrentClient(id) 
-    this.clientList.setCurrent(id)
+method setCurrentClient(channel) 
+    this.clientList.setCurrent(channel)
     
-method setCurrentTarget(id) 
-    this.targetList.setCurrent(id)
+method setCurrentTarget(channel) 
+    this.targetList.setCurrent(channel)
 
-method setClientState(id, state) 
-    this.clientList.setState(id, state)
+method setClientState(channel, state) 
+    this.clientList.setState(channel, state)
     
-method setTargetState(id, state) 
-    this.targetList.setState(id, state)
+method setTargetState(channel, state) 
+    this.targetList.setState(channel, state)
 
 //-----------------------------------------------------------------------------
-method getNewestTargetId(ignoring)
-    return this.targetList.getNewestConnectorId(ignoring)
+method getNewestTargetChannel(ignoring)
+    return this.targetList.getNewestConnectorChannel(ignoring)
 
 //-----------------------------------------------------------------------------
 method afterInitialConnection
@@ -111,10 +114,11 @@
    
     if (!Weinre.client.autoConnect()) return
     
-    var newestTargetId = this.getNewestTargetId()
-    if (!newestTargetId) return
-    
-    Weinre.WeinreClientCommands.connectTarget(Weinre.connectorId, newestTargetId)
+    var newestTargetChannel = this.getNewestTargetChannel()
+    if (!newestTargetChannel) return
+    if (!Weinre.messageDispatcher) return
+
+    Weinre.WeinreClientCommands.connectTarget(Weinre.messageDispatcher.channel, newestTargetChannel)
     
     
 //-----------------------------------------------------------------------------
@@ -183,34 +187,34 @@
 //-----------------------------------------------------------------------------
 method getListItem(target)
     var self = this
-    var text = target.hostName + " [id: " + target.id + "]" + " - " + target.url 
+    var text = target.hostName + " [channel: " + target.channel + " id: " + target.id + "]" + " - " + target.url 
 
     var item = dt.LI(
-        { $connectorId: target.id },
+        { $connectorChannel: target.channel },
         text
     )
     
     item.addStyleClass("weinre-connector-item")
     item.addStyleClass("target")
     
-    item.addEventListener("click", function(e) {self.connectToTarget(target.id, e)}, false)
+    item.addEventListener("click", function(e) {self.connectToTarget(target.channel, e)}, false)
 
     target.element = item
 
     return item
 
 //-----------------------------------------------------------------------------
-method connectToTarget(targetId, event)
+method connectToTarget(targetChannel, event)
     if (event) {
         event.preventDefault()
         event.stopPropagation()
     }
     
-    var target = this.connectors[targetId]
+    var target = this.connectors[targetChannel]
     if (!target) return false
     if (target.closed) return false
     
-    Weinre.WeinreClientCommands.connectTarget(Weinre.clientId, targetId)
+    Weinre.WeinreClientCommands.connectTarget(Weinre.messageDispatcher.channel, targetChannel)
     
     return false
 
@@ -230,18 +234,20 @@
 
 //-----------------------------------------------------------------------------
 method getListItem(client)
-    var text = client.hostName + " [id: " + client.id + "]"
+    var text = client.hostName + " [channel: " + client.channel + " id: " + client.id  + "]"
     
     var item = dt.LI(
-        { $connectorId: client.id },
+        { $connectorChannel: client.channel },
         text
     )
 
     item.addStyleClass("weinre-connector-item")
     item.addStyleClass("client")
     
-    if (client.id == Weinre.clientId) {
-        item.addStyleClass("current")
+    if (Weinre.messageDispatcher) {
+        if (client.channel == Weinre.messageDispatcher.channel) {
+            item.addStyleClass("current")
+        }
     }
 
     client.element = item
diff --git a/weinre.web/modules/weinre/client/WeinreClientEventsImpl.scoop b/weinre.web/modules/weinre/client/WeinreClientEventsImpl.scoop
index 8c35aeb..459373d 100644
--- a/weinre.web/modules/weinre/client/WeinreClientEventsImpl.scoop
+++ b/weinre.web/modules/weinre/client/WeinreClientEventsImpl.scoop
@@ -14,6 +14,13 @@
     this.client = client
 
 //-----------------------------------------------------------------------------
+init
+    var titleNotConnected    = "weinre: target not connected"
+    var titleConnectedPrefix = "weinre: "
+    
+    document.title = titleNotConnected
+     
+//-----------------------------------------------------------------------------
 method clientRegistered(clientDescription)
     if (this.client.uiAvailable()) {
         WebInspector.panels.remote.addClient(clientDescription)
@@ -26,55 +33,60 @@
     }
     
     if (!Weinre.client.autoConnect()) return
+    if (!Weinre.messageDispatcher) return
     
-    Weinre.WeinreClientCommands.connectTarget(Weinre.clientId, targetDescription.id)
+    Weinre.WeinreClientCommands.connectTarget(Weinre.messageDispatcher.channel, targetDescription.channel)
 
 //-----------------------------------------------------------------------------
-method clientUnregistered(/*int*/ clientId)
+method clientUnregistered(/*int*/ clientChannel)
     if (this.client.uiAvailable()) {
-        WebInspector.panels.remote.removeClient(clientId)
+        WebInspector.panels.remote.removeClient(clientChannel)
     }
 
 //-----------------------------------------------------------------------------
-method targetUnregistered(/*int*/ targetId)
+method targetUnregistered(/*int*/ targetChannel)
     if (this.client.uiAvailable()) {
-        WebInspector.panels.remote.removeTarget(targetId)
+        WebInspector.panels.remote.removeTarget(targetChannel)
     }
 
 //-----------------------------------------------------------------------------
-method connectionCreated(/*int*/ clientId, /*int*/ targetId)
+method connectionCreated(/*int*/ clientChannel, /*int*/ targetChannel)
     if (this.client.uiAvailable()) {
-        WebInspector.panels.remote.setClientState(clientId, "connected") 
-        WebInspector.panels.remote.setTargetState(targetId, "connected")
+        WebInspector.panels.remote.setClientState(clientChannel, "connected") 
+        WebInspector.panels.remote.setTargetState(targetChannel, "connected")
     }
     
-    if (clientId != Weinre.clientId) return
+    if (clientChannel != Weinre.messageDispatcher.channel) return
 
-    Weinre.targetId = targetId
-    
     WebInspector.panels.elements.reset()
     WebInspector.panels.timeline._clearPanel()
     WebInspector.panels.resources.reset()
+    
+    var target = WebInspector.panels.remote.getTarget(targetChannel)
+    document.title = titleConnectedPrefix + target.url
+    
+    WebInspector.inspectedURLChanged(target.url)
 
 //-----------------------------------------------------------------------------
-method connectionDestroyed(/*int*/ clientId, /*int*/ targetId)
+method connectionDestroyed(/*int*/ clientChannel, /*int*/ targetChannel)
+
     if (this.client.uiAvailable()) {
-        WebInspector.panels.remote.setClientState(clientId, "not-connected") 
-        WebInspector.panels.remote.setTargetState(targetId, "not-connected")
+        WebInspector.panels.remote.setClientState(clientChannel, "not-connected") 
+        WebInspector.panels.remote.setTargetState(targetChannel, "not-connected")
     }
     
-    if (clientId != Weinre.clientId) return
-    
-    Weinre.targetId = null
+    if (clientChannel != Weinre.messageDispatcher.channel) return
+
+    document.title = titleNotConnected
     
     if (!Weinre.client.autoConnect()) return
     if (!this.client.uiAvailable()) return
     
-    var nextTargetId = WebInspector.panels.remote.getNewestTargetId(targetId)
-    if (!nextTargetId) return
+    var nextTargetChannel = WebInspector.panels.remote.getNewestTargetChannel(targetChannel)
+    if (!nextTargetChannel) return
     
-    Weinre.WeinreClientCommands.connectTarget(Weinre.clientId, nextTargetId)
-    Weinre.logInfo("autoconnecting to " + nextTargetId)
+    Weinre.WeinreClientCommands.connectTarget(Weinre.messageDispatcher.channel, nextTargetChannel)
+    Weinre.logInfo("autoconnecting to " + nextTargetChannel)
 
 //-----------------------------------------------------------------------------
 method sendCallback(/*int*/ callbackId, /*any*/ result)
diff --git a/weinre.web/modules/weinre/common/Callback.scoop b/weinre.web/modules/weinre/common/Callback.scoop
index b355714..b2edba1 100644
--- a/weinre.web/modules/weinre/common/Callback.scoop
+++ b/weinre.web/modules/weinre/common/Callback.scoop
@@ -14,13 +14,13 @@
 
 //-----------------------------------------------------------------------------
 init 
-    var CallbackTable = {}
-    var CallbackIndex = 1
-    var ConnectorId   = "???"
+    var CallbackTable    = {}
+    var CallbackIndex    = 1
+    var ConnectorChannel = "???"
 
 //-----------------------------------------------------------------------------
-static method setConnectorId(connectorId)
-    ConnectorId = ""  + connectorId
+static method setConnectorChannel(connectorChannel)
+    ConnectorChannel = ""  + connectorChannel
 
 //-----------------------------------------------------------------------------
 static method register(callback)
@@ -34,7 +34,7 @@
     if (typeof func == "string") func = receiver.func
     if (typeof func != "function") throw new Ex(arguments, "callback function was null or not found")
 
-    var index = ConnectorId + "::" + CallbackIndex
+    var index = ConnectorChannel + "::" + CallbackIndex
     
     CallbackIndex++
     if (CallbackIndex >= 65536 * 65536) CallbackIndex = 1
@@ -62,7 +62,7 @@
     catch (e) {
         var funcName = func.name
         if (!funcName) funcName = "<unnamed>"
-        require("./Weinre").logError(arguments.callee.signature + " exception invoking callback: " + funcName + "(" + args.join(",") + "): " + e)
+        require("./Weinre").getClass().logError(arguments.callee.signature + " exception invoking callback: " + funcName + "(" + args.join(",") + "): " + e)
     }
     finally {
         Callback.deregister(index)
diff --git a/weinre.web/modules/weinre/common/IDGenerator.scoop b/weinre.web/modules/weinre/common/IDGenerator.scoop
index 4829267..5af26cd 100644
--- a/weinre.web/modules/weinre/common/IDGenerator.scoop
+++ b/weinre.web/modules/weinre/common/IDGenerator.scoop
@@ -11,8 +11,8 @@
 
 //-----------------------------------------------------------------------------
 init
-    var nextId = 1
-    var idName = "__weinre__id"
+    var nextIdValue = 1
+    var idName      = "__weinre__id"
 
 //-----------------------------------------------------------------------------
 static method checkId(object)
@@ -23,7 +23,7 @@
     var id = IDGenerator.checkId(object)
     
     if (!id) {
-        id = next()
+        id = nextId()
         
         // note:
         // attempted to use Object.defineProperty() to make
@@ -40,10 +40,14 @@
     }
     
     return id
-    
+
 //-----------------------------------------------------------------------------
-function next
-    var result = nextId
-    nextId += 1
+static method next
+    return nextId()
+
+//-----------------------------------------------------------------------------
+function nextId
+    var result = nextIdValue
+    nextIdValue += 1
     return result
 
diff --git a/weinre.web/modules/weinre/common/IDLTools.scoop b/weinre.web/modules/weinre/common/IDLTools.scoop
index 6e6f856..9dcbe7a 100644
--- a/weinre.web/modules/weinre/common/IDLTools.scoop
+++ b/weinre.web/modules/weinre/common/IDLTools.scoop
@@ -84,7 +84,7 @@
     if (!errors.length) return
     
     errors.forEach(function(error){
-        require("./Weinre").logError(error)
+        require("./Weinre").getClass().logError(error)
     })
 
 //-----------------------------------------------------------------------------
diff --git a/weinre.web/modules/weinre/common/MessageDispatcher.scoop b/weinre.web/modules/weinre/common/MessageDispatcher.scoop
index f6adc72..c207e01 100644
--- a/weinre.web/modules/weinre/common/MessageDispatcher.scoop
+++ b/weinre.web/modules/weinre/common/MessageDispatcher.scoop
@@ -11,10 +11,16 @@
 requireClass ./IDLTools
 requireClass ./Binding
 requireClass ./Ex
+requireClass ./Callback
 
 //-----------------------------------------------------------------------------
-class MessageDispatcher(url)
+class MessageDispatcher(url, id)
+    if (!id) {
+        id = "anonymous"
+    }
+
     this._url        = url
+    this._id         = id
     
     this.error       = null
     this._opening    = false
@@ -47,7 +53,7 @@
     if (this._closed) throw new Ex(arguments, "socket has already been closed")
     
     this._opening = true 
-    this._socket = new WebSocketXhr(this._url)
+    this._socket = new WebSocketXhr(this._url, this._id)
     this._socket.addEventListener("open",    Binding(this, "_handleOpen"))
     this._socket.addEventListener("error",   Binding(this, "_handleError"))
     this._socket.addEventListener("message", Binding(this, "_handleMessage"))
@@ -122,9 +128,12 @@
     return this._opened == true
 
 //-----------------------------------------------------------------------------
-method _handleOpen
+method _handleOpen(event)
     this._opening = false
     this._opened  = true
+    this.channel  = event.channel
+    
+    Callback.setConnectorChannel(this.channel)
     
     if (Verbose) {
         Weinre.logDebug(this.constructor.name + "[" + this._url + "]: opened")
diff --git a/weinre.web/modules/weinre/common/WebSocketXhr.scoop b/weinre.web/modules/weinre/common/WebSocketXhr.scoop
index 0eb5132..813e6a9 100644
--- a/weinre.web/modules/weinre/common/WebSocketXhr.scoop
+++ b/weinre.web/modules/weinre/common/WebSocketXhr.scoop
@@ -12,8 +12,8 @@
 requireClass ./Native
 
 //-----------------------------------------------------------------------------
-class WebSocketXhr(url)
-    this.initialize(url)
+class WebSocketXhr(url, id)
+    this.initialize(url, id)
     
 //-----------------------------------------------------------------------------
 init
@@ -25,9 +25,14 @@
     WebSocketXhr.CLOSED     = 3
 
 //-----------------------------------------------------------------------------
-method initialize(url)
+method initialize(url, id)
+    if (!id) {
+        id = "anonymous"
+    }
+    
     this.readyState      = WebSocketXhr.CONNECTING 
     this._url            = url
+    this._id             = id
     this._urlChannel     = null
     this._queuedSends    = []
     this._sendInProgress = true
@@ -43,7 +48,8 @@
 
 //-----------------------------------------------------------------------------
 method _getChannel
-    this._xhr(this._url, "POST", "", this._handleXhrResponseGetChannel)
+    var body = JSON.stringify({ id: this._id})
+    this._xhr(this._url, "POST", body, this._handleXhrResponseGetChannel)
 
 //-----------------------------------------------------------------------------
 method _handleXhrResponseGetChannel(xhr)
@@ -64,12 +70,10 @@
         return
     }
     
-    Weinre.connectorId = "" + object.channel
-    
     this._urlChannel = this._url + "/" + object.channel
     this.readyState = WebSocketXhr.OPEN
 
-    this._fireEventListeners("open", { message: "open" })
+    this._fireEventListeners("open", { message: "open", channel: object.channel })
     
     this._sendInProgress = false
     this._sendQueued()
diff --git a/weinre.web/modules/weinre/target/InjectedScriptHostImpl.scoop b/weinre.web/modules/weinre/target/InjectedScriptHostImpl.scoop
index 48cf458..0c9b8af 100644
--- a/weinre.web/modules/weinre/target/InjectedScriptHostImpl.scoop
+++ b/weinre.web/modules/weinre/target/InjectedScriptHostImpl.scoop
@@ -42,4 +42,11 @@
 method internalConstructorName(object)
     var ctor = object.constructor
     
-    return ctor.fullClassName || ctor.displayName || ctor.name || "Object"
\ No newline at end of file
+    var ctorName = ctor.fullClassName || ctor.displayName || ctor.name
+    if (ctorName && (ctorName != "Object")) return ctorName
+    
+    var pattern = /\[object (.*)\]/
+    var match = pattern.exec(ctor.toString())
+    if (match) return match[1]
+    
+    return "Object"
diff --git a/weinre.web/modules/weinre/target/Target.scoop b/weinre.web/modules/weinre/target/Target.scoop
index d51ce41..0a4d81d 100644
--- a/weinre.web/modules/weinre/target/Target.scoop
+++ b/weinre.web/modules/weinre/target/Target.scoop
@@ -47,7 +47,34 @@
 //------------------------------------------------------------------------------
 method setWeinreServerURLFromScriptSrc()
     if (window.WeinreServerURL) return
+
+    var element = this.getTargetScriptElement()
+    var pattern = /(http:\/\/(.*?)\/)/
+    var match   = pattern.exec(element.src)
+    if (match) {
+        window.WeinreServerURL = match[1]
+        return 
+    }
     
+    var message = "unable to calculate the weinre server url; explicity set the variable window.WeinreServerURL instead" 
+    alert(message)
+    throw new Ex(arguments, message)
+
+
+//-----------------------------------------------------------------------------
+method setWeinreServerIdFromScriptSrc()
+    if (window.WeinreServerId) return
+
+    var element = this.getTargetScriptElement()
+
+    var hash = element.src.split("#")[1]
+    if (!hash) hash = "anonymous"
+    
+    window.WeinreServerId = hash
+
+//-----------------------------------------------------------------------------
+method getTargetScriptElement
+
     var elements = document.getElementsByTagName("script")
 
     var scripts = ["Target.", "target-script.", "target-script-min."]
@@ -56,23 +83,17 @@
         
         for (j=0; j<scripts.length; j++) {
             if (-1 != element.src.indexOf("/" + scripts[j])) {
-                var pattern = /(http:\/\/(.*?)\/)/
-                var match   = pattern.exec(element.src)
-                if (match) {
-                    window.WeinreServerURL = match[1]
-                    return 
-                }
+                return element
             }
         }
     }
-    
-    var message = "unable to calculate the weinre server url; explicity set the variable window.WeinreServerURL instead" 
-    alert(message)
-    throw new Ex(arguments, message)
 
 //-----------------------------------------------------------------------------
 method initialize()
+    var self = this
+
     this.setWeinreServerURLFromScriptSrc()
+    this.setWeinreServerIdFromScriptSrc()
     
     if (window.WeinreServerURL[window.WeinreServerURL.length-1] != "/") {
         window.WeinreServerURL += "/"
@@ -82,8 +103,20 @@
     Weinre.injectedScript = injectedScriptConstructor(injectedScriptHost, window, 0, "?")
     
     window.addEventListener("load", Binding(this, "onLoaded"), false)
+    document.addEventListener("DOMContentLoaded", Binding(this, "onDOMContent"), false)
+    
+    this._startTime = currentTime()
+    
+    if (document.readyState == "loaded") {
+        setTimeout(function() { self.onDOMContent() }, 10)
+    }
+    
+    if (document.readyState == "complete") {
+        setTimeout(function() { self.onDOMContent() }, 10)
+        setTimeout(function() { self.onLoaded() }, 20)
+    }
 
-    var messageDispatcher = new MessageDispatcher(window.WeinreServerURL + "ws/target")
+    var messageDispatcher = new MessageDispatcher(window.WeinreServerURL + "ws/target", window.WeinreServerId)
     Weinre.messageDispatcher = messageDispatcher
 
     Weinre.wi = {}
@@ -137,14 +170,16 @@
     Weinre.WeinreTargetCommands.registerTarget(window.location.href, Binding(this, this.cb_registerTarget))
 
 //-----------------------------------------------------------------------------
-method cb_registerTarget(targetId)
-    Weinre.targetId    = targetId
-    Weinre.connectorId = targetId
-    
-    Callback.setConnectorId(targetId)
+method cb_registerTarget(targetDescription)
+    Weinre.targetDescription    = targetDescription
 
 //-----------------------------------------------------------------------------
-method onLoaded()
+method onLoaded
+    Weinre.wi.InspectorNotify.loadEventFired(currentTime() - this._startTime)
+
+//-----------------------------------------------------------------------------
+method onDOMContent
+    Weinre.wi.InspectorNotify.domContentEventFired(currentTime() - this._startTime)
 
 //-----------------------------------------------------------------------------
 method setDocument()
@@ -154,3 +189,8 @@
     var nodeData = Weinre.nodeStore.getNodeData(nodeId, 2)
 
     Weinre.wi.DOMNotify.setDocument(nodeData)
+    
+//-----------------------------------------------------------------------------
+// WebKit's currentTime() seems to return time in seconds
+function currentTime()
+    return (new Date().getMilliseconds()) / 1000.0
\ No newline at end of file
diff --git a/weinre.web/modules/weinre/target/WeinreTargetEventsImpl.scoop b/weinre.web/modules/weinre/target/WeinreTargetEventsImpl.scoop
index 9adf1e9..6f8a5e3 100644
--- a/weinre.web/modules/weinre/target/WeinreTargetEventsImpl.scoop
+++ b/weinre.web/modules/weinre/target/WeinreTargetEventsImpl.scoop
@@ -15,8 +15,8 @@
 class WeinreTargetEventsImpl
 
 //-----------------------------------------------------------------------------
-method connectionCreated(/*string*/ clientId, /*string*/ targetId)
-    var message = "weinre: target " + targetId + " connected to client " + clientId
+method connectionCreated(/*string*/ clientChannel, /*string*/ targetChannel)
+    var message = "weinre: target " + targetChannel + " connected to client " + clientChannel
     
     Weinre.logInfo(message)
     var oldValue = Console.useRemote(true)
@@ -28,8 +28,8 @@
     Weinre.wi.DOMStorage.initialize()
 
 //-----------------------------------------------------------------------------
-method connectionDestroyed(/*string*/ clientId, /*string*/ targetId)
-    var message = "weinre: target " + targetId + " disconnected from client " + clientId
+method connectionDestroyed(/*string*/ clientChannel, /*string*/ targetChannel)
+    var message = "weinre: target " + targetChannel + " disconnected from client " + clientChannel
     
     Weinre.logInfo(message)
     var oldValue = Console.useRemote(false)
diff --git a/weinre.web/versions.js b/weinre.web/versions.js
index 40f3c2e..1b62da4 100644
--- a/weinre.web/versions.js
+++ b/weinre.web/versions.js
@@ -15,6 +15,7 @@
     webkit:   "@WEBKIT_VERSION@",
     cli:      "@CLI_VERSION@",
     json4j:   "@JSON4J_VERSION@",
+    json2:    "@JSON2_VERSION@",
     swt:      "@SWT_VERSION@",
     modjewel: "@MODJEWEL_VERSION@",
     scooj:    "@SCOOJ_VERSION@"