<?xml version="1.0" encoding="UTF-8"?>
<commit>
  <added type="array">
    <added>
      <filename>selenium-core/lib/snapsie.js</filename>
    </added>
    <added>
      <filename>selenium-core/scripts/ui-doc.html</filename>
    </added>
    <added>
      <filename>selenium-core/scripts/ui-element.js</filename>
    </added>
    <added>
      <filename>selenium-core/scripts/ui-map-sample.js</filename>
    </added>
    <added>
      <filename>selenium-core/scripts/user-extensions.js</filename>
    </added>
    <added>
      <filename>selenium-core/xpath/javascript-xpath-0.1.11.js</filename>
    </added>
    <added>
      <filename>selenium-core/xpath/util.js</filename>
    </added>
    <added>
      <filename>selenium-core/xpath/xmltoken.js</filename>
    </added>
  </added>
  <modified type="array">
    <modified>
      <diff>@@ -3,4 +3,56 @@ gems
 test_data/html.html
 test_data/html.rsel
 test_data/html.sel
-coverage/*
\ No newline at end of file
+coverage/*
+
++== VERSION 40[http://github.com/jtrupiano/selenium-on-rails]
+0
++
+0
++* Jan 20, 2009 (inauguration day, baby!)
+0
++
+0
++* [jtrupiano] updated the CHANGELOG to include all of my changes to my branch from the last couple of months.
+0
++
+0
++* [jkrall] applied the following commits from jkrall (mostly related to updating prototype and scriptaculous versions): [http://github.com/jtrupiano/selenium-on-rails/commit/2084c9ad1740deb920483025e65db58a7cf9c573], [http://github.com/jtrupiano/selenium-on-rails/commit/d07d932437b4340db34b85d75e6f9527186b7f69], [http://github.com/jtrupiano/selenium-on-rails/commit/6738790308cb8531744513107b1a37ad26734c63]
+0
++
+0
++* [jtrupiano] Tweaks to make my recent changes environment-independent. Also now allowing password-protected access to the database (to backup and restore the database in between test cases). [http://github.com/jtrupiano/selenium-on-rails/commit/6ec035a183adc9f1bb40a04fba9104f211bc4647]
+0
++
+0
++
+0
++== VERSION 39[http://github.com/jtrupiano/selenium-on-rails/commit/47653c3bdb2f5ab434697ed9a5d50e0bf3759fbd]
+0
++
+0
++* Dec 12. 2008
+0
++
+0
++* [jtrupiano] Tweaked the javascript teardownServer() function to not use show()/hide() (from prototype). Unsure why this blows up in IE. On this note, the label itself notifying the user that the teardown is in progress does not properly display for IE. [http://github.com/jtrupiano/selenium-on-rails/commit/47653c3bdb2f5ab434697ed9a5d50e0bf3759fbd]
+0
++
+0
++* [jtrupiano] Added in support for a server-side teardown. Makes a synchronous call to /selenium/setup, which maps to my previously committed action augmentation to the selenium_controller. This server-side teardown is now called after each test is completed (on failure or success), providing us an easy mechanism for restoring the state of server-side assets (db/filesystem/etc) without having to use fixtures. [http://github.com/jtrupiano/selenium-on-rails/commit/6b03f347c584a65c5787127e8930487f1e7b1e13]
+0
++
+0
++* [jtrupiano] Added in a rakefile that exposes tasks for simulating a transactional db across selenium tests (essentially dumps a mysqldump in tmp/ and re-uses). [http://github.com/jtrupiano/selenium-on-rails/commit/2e1fee675b9c89f4a093fd0c0ae8d73943dc3f89]
+0
++
+0
++* [jtrupiano] Added a route to match /selenium/setup/:action to invoke a controller (selenium_setup_controller) that is expected to be found in the rails app itself. Changed some of the language describing the :keep_db querystring parameter that replaced the fixture/db-specific params from the original implementation. [http://github.com/jtrupiano/selenium-on-rails/commit/4b40d05e66524bab8949a9b62c47ab1673bd06d9]
+0
++
+0
++* [jtrupiano] Replaced references to fixtures w/ StoryHelper-based transactional behavior (currently only works with mysql) [http://github.com/jtrupiano/selenium-on-rails/commit/290d3a1da54a43f162551de8c5dece26de9a7d66]
+0
++
+0
++
\ No newline at end of file</diff>
      <filename>.gitignore</filename>
    </modified>
    <modified>
      <diff>@@ -3,6 +3,5 @@
         &lt;meta content=&quot;text/html; charset=ISO-8859-1&quot; http-equiv=&quot;content-type&quot;&gt;
     &lt;/head&gt;
 &lt;body&gt;
-    &lt;h3&gt;selenium-rc initial page&lt;/h3&gt;
 &lt;/body&gt;
 &lt;/html&gt;</diff>
      <filename>selenium-core/Blank.html</filename>
    </modified>
    <modified>
      <diff>@@ -19,12 +19,14 @@ Copyright 2004 ThoughtWorks, Inc
 &lt;head&gt;
 &lt;meta content=&quot;text/html; charset=ISO-8859-1&quot;
 http-equiv=&quot;content-type&quot;&gt;
-&lt;title&gt;Selenium Functional Test Runner&lt;/title&gt;
+&lt;title&gt;Selenium Remote Control&lt;/title&gt;
 &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;selenium.css&quot; /&gt;
-&lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;jsunit/app/jsUnitCore.js&quot;&gt;&lt;/script&gt;
 &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/xmlextras.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/prototype.js&quot;&gt;&lt;/script&gt;
+&lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/cssQuery/cssQuery-p.js&quot;&gt;&lt;/script&gt;
+&lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/snapsie.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/htmlutils.js&quot;&gt;&lt;/script&gt;
+&lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/ui-element.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-browserdetect.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-browserbot.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/find_matching_child.js&quot;&gt;&lt;/script&gt;
@@ -34,13 +36,14 @@ http-equiv=&quot;content-type&quot;&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-remoterunner.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-logging.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-version.js&quot;&gt;&lt;/script&gt;
-&lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/misc.js&quot;&gt;&lt;/script&gt;
+&lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/util.js&quot;&gt;&lt;/script&gt;
+&lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/xmltoken.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/dom.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/xpath.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/user-extensions.js&quot;&gt;&lt;/script&gt;
 &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot;&gt;
     function openDomViewer() {
-        var autFrame = document.getElementById('myiframe');
+        var autFrame = document.getElementById('selenium_myiframe');
         var autFrameDocument = getIframeDocument(autFrame);
         var domViewer = window.open(getDocumentBase(document) + 'domviewer/domviewer.html');
         domViewer.rootDocument = autFrameDocument;
@@ -58,29 +61,25 @@ http-equiv=&quot;content-type&quot;&gt;
 
 &lt;body onLoad=&quot;setTimeout(function(){runSeleniumTest();},1000)&quot; onUnload=&quot;cleanUp()&quot;&gt;
 
-&lt;table border=&quot;1&quot; style=&quot;height: 100%;&quot;&gt;
+&lt;table border=&quot;1&quot; style=&quot;height: 100%; width: 100%;&quot;&gt;               
   &lt;tr&gt;
-    &lt;td width=&quot;50%&quot; height=&quot;30%&quot;&gt;
+    &lt;td width=&quot;50%&quot;&gt;
       &lt;table&gt;
       &lt;tr&gt;
-        &lt;td&gt;
-          &lt;img src=&quot;selenium-logo.png&quot;&gt;
-        &lt;/td&gt;
-        &lt;td&gt;
-          &lt;h1&gt;&lt;a href=&quot;http://selenium.thoughtworks.com&quot; &gt;Selenium&lt;/a&gt; Functional Testing for Web Apps&lt;/h1&gt;
-          Open Source From &lt;a href=&quot;http://www.thoughtworks.com&quot;&gt;ThoughtWorks, Inc&lt;/a&gt; and Friends
+        &lt;td class=&quot;remoterunner&quot;&gt;
+          &lt;h4&gt;&lt;a href=&quot;http://selenium.openqa.org&quot;&gt;Selenium&lt;/a&gt; Functional Testing for Web Apps&lt;/h4&gt;
+          Open Source From &lt;a href=&quot;http://selenium.openqa.org/thoughtworks-and-friends.html&quot;&gt;ThoughtWorks and Friends&lt;/a&gt;
           &lt;form action=&quot;&quot;&gt;
-          &lt;br/&gt;Slow Mode:&lt;INPUT TYPE=&quot;CHECKBOX&quot; NAME=&quot;FASTMODE&quot; VALUE=&quot;YES&quot; onmouseup=&quot;slowClicked()&quot;&gt;
-
+          &lt;br/&gt;
+          &lt;iframe id=&quot;seleniumLoggingFrame&quot; name=&quot;seleniumLoggingFrame&quot; src=&quot;Blank.html&quot; style=&quot;border: 0; height: 0; width: 0; &quot;&gt;&lt;/iframe&gt;
           &lt;fieldset&gt;
-            &lt;legend&gt;Tools&lt;/legend&gt;
-
             &lt;button type=&quot;button&quot; id=&quot;domViewer1&quot; onclick=&quot;openDomViewer();&quot;&gt;
               View DOM
             &lt;/button&gt;
             &lt;button type=&quot;button&quot; onclick=&quot;LOG.show();&quot;&gt;
               Show Log
             &lt;/button&gt;
+            &lt;label&gt;&lt;INPUT TYPE=&quot;CHECKBOX&quot; NAME=&quot;FASTMODE&quot; VALUE=&quot;YES&quot; onmouseup=&quot;slowClicked()&quot;&gt; Slow Mode&lt;/label&gt;			
           &lt;/fieldset&gt;
 
           &lt;/form&gt;
@@ -92,14 +91,16 @@ http-equiv=&quot;content-type&quot;&gt;
         &lt;label id=&quot;context&quot; name=&quot;context&quot;&gt;&lt;/label&gt;
       &lt;/form&gt;
     &lt;/td&gt;
-    &lt;td width=&quot;50%&quot; height=&quot;30%&quot;&gt;
-      &lt;b&gt;Last Four Test Commands:&lt;/b&gt;&lt;br/&gt;
-      &lt;div id=&quot;commandList&quot;&gt;&lt;/div&gt;
+    &lt;td width=&quot;50%&quot; class=&quot;remoterunner&quot;&gt;
+      &lt;h4&gt;Command History:&lt;/h4&gt;
+      &lt;form name=&quot;commands&quot;&gt;
+        &lt;textarea style=&quot;overflow:auto; height:8em; width:100%&quot; wrap=&quot;off&quot; id=&quot;commandList&quot;&gt;&lt;/textarea&gt;
+      &lt;/form&gt;
     &lt;/td&gt;
   &lt;/tr&gt;
     &lt;tr&gt;
-    &lt;td colspan=&quot;2&quot; height=&quot;70%&quot;&gt;
-      &lt;iframe name=&quot;myiframe&quot; id=&quot;myiframe&quot; src=&quot;&quot; height=&quot;100%&quot; width=&quot;100%&quot;&gt;&lt;/iframe&gt;
+    &lt;td colspan=&quot;2&quot; height=&quot;100%&quot;&gt;
+      &lt;iframe name=&quot;selenium_myiframe&quot; id=&quot;selenium_myiframe&quot; src=&quot;Blank.html&quot; height=&quot;100%&quot; width=&quot;100%&quot;&gt;&lt;/iframe&gt;
     &lt;/td&gt;
   &lt;/tr&gt;
 &lt;/table&gt;</diff>
      <filename>selenium-core/RemoteRunner.html</filename>
    </modified>
    <modified>
      <diff>@@ -3,11 +3,42 @@
 &lt;head&gt;
 &lt;title&gt;Selenium Log Console&lt;/title&gt;
 &lt;link id=&quot;cssLink&quot; rel=&quot;stylesheet&quot; href=&quot;selenium.css&quot; /&gt;
+&lt;script src=&quot;scripts/htmlutils.js&quot;&gt;&lt;/script&gt;
+&lt;script language=&quot;JavaScript&quot;&gt;
 
-&lt;/head&gt;
-&lt;body id=&quot;logging-console&quot;&gt;
+var disabled = true;
 
-&lt;script language=&quot;JavaScript&quot;&gt;
+function logOnLoad() {
+    var urlConfig = new URLConfiguration();
+    urlConfig.queryString = window.location.search.substr(1);
+    var startingThreshold = urlConfig._getQueryParameter(&quot;startingThreshold&quot;);
+    setThresholdLevel(startingThreshold);
+    var buttons = document.getElementsByTagName(&quot;input&quot;);
+    for (var i = 0; i &lt; buttons.length; i++) {
+        addChangeListener(buttons[i]);
+    }
+}
+
+function enableButtons() {
+    var buttons = document.getElementsByTagName(&quot;input&quot;);
+    for (var i = 0; i &lt; buttons.length; i++) {
+        buttons[i].disabled = false;
+        disabled = false;
+    }
+}
+
+function callBack() {}
+
+function changeHandler() {
+    callBack(getThresholdLevel());
+}
+
+function addChangeListener(element) {
+    if (window.addEventListener &amp;&amp; !window.opera)
+        element.addEventListener(&quot;click&quot;, changeHandler, true);
+    else if (window.attachEvent)
+        element.attachEvent(&quot;onclick&quot;, changeHandler);
+}
 
 var logLevels = {
     debug: 0,
@@ -53,16 +84,20 @@ function append(message, logLevel) {
 }
 
 &lt;/script&gt;
+&lt;/head&gt;
+&lt;body id=&quot;logging-console&quot; onload=&quot;logOnLoad();&quot;&gt;
+
+
 
 &lt;div id=&quot;banner&quot;&gt;
   &lt;form id=&quot;logLevelChooser&quot;&gt;
-      &lt;input id=&quot;level-error&quot; type=&quot;radio&quot; name=&quot;level&quot; 
+      &lt;input id=&quot;level-error&quot; type=&quot;radio&quot; name=&quot;level&quot; disabled='true'
              value=&quot;error&quot; /&gt;&lt;label for=&quot;level-error&quot;&gt;Error&lt;/label&gt;
-      &lt;input id=&quot;level-warn&quot; type=&quot;radio&quot; name=&quot;level&quot;
+      &lt;input id=&quot;level-warn&quot; type=&quot;radio&quot; name=&quot;level&quot; disabled='true'
              value=&quot;warn&quot; /&gt;&lt;label for=&quot;level-warn&quot;&gt;Warn&lt;/label&gt;
-      &lt;input id=&quot;level-info&quot; type=&quot;radio&quot; name=&quot;level&quot;
+      &lt;input id=&quot;level-info&quot; type=&quot;radio&quot; name=&quot;level&quot; disabled='true'
              value=&quot;info&quot; /&gt;&lt;label for=&quot;level-info&quot;&gt;Info&lt;/label&gt;
-      &lt;input id=&quot;level-debug&quot; type=&quot;radio&quot; name=&quot;level&quot; checked=&quot;yes&quot;
+      &lt;input id=&quot;level-debug&quot; type=&quot;radio&quot; name=&quot;level&quot; checked=&quot;yes&quot; disabled='true'
              value=&quot;debug&quot; /&gt;&lt;label for=&quot;level-debug&quot;&gt;Debug&lt;/label&gt;
   &lt;/form&gt;
   &lt;h1&gt;Selenium Log Console&lt;/h1&gt;</diff>
      <filename>selenium-core/SeleniumLog.html</filename>
    </modified>
    <modified>
      <diff>@@ -20,12 +20,19 @@ Copyright 2004 ThoughtWorks, Inc
           http-equiv=&quot;content-type&quot;&gt;
     &lt;title&gt;Select a Test Suite&lt;/title&gt;
     &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-browserdetect.js&quot;&gt;&lt;/script&gt;
+    &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/xmlextras.js&quot;&gt;&lt;/script&gt;
     &lt;script&gt;
 
         function load() {
             if (browserVersion.isHTA) {
                 document.getElementById(&quot;save-div&quot;).style.display = &quot;inline&quot;;
             }
+            if (/thisIsSeleniumServer/.test(window.location.search)) {
+                document.getElementById(&quot;slowResources-div&quot;).style.display = &quot;inline&quot;;
+                if (browserVersion.isHTA || browserVersion.isChrome) {
+                    document.getElementById(&quot;test&quot;).value = &quot;http://localhost:4444/selenium-server/tests/TestSuite.html&quot;;
+                }
+            }
         }
 
         function autoCheck() {
@@ -38,6 +45,15 @@ Copyright 2004 ThoughtWorks, Inc
             }
         }
 
+        function slowCheck() {
+            var slowResourcesCheckbox = document.getElementById(&quot;slowResources&quot;);
+            var slowResources = slowResourcesCheckbox.checked ? true : false;
+            var xhr = XmlHttp.create();
+            var driverUrl = &quot;http://localhost:4444/selenium-server/driver/?cmd=slowResources&amp;1=&quot; + slowResources;
+            xhr.open(&quot;GET&quot;, driverUrl, true);
+            xhr.send(null);
+        }
+
         function saveCheck() {
             var results = document.getElementById(&quot;results&quot;);
             var check = document.getElementById(&quot;save&quot;).checked;
@@ -51,7 +67,7 @@ Copyright 2004 ThoughtWorks, Inc
         }
 
         function go() {
-            if (!browserVersion.isHTA) return true;
+            if (!browserVersion.isHTA &amp;&amp; !browserVersion.isChrome) return true;
             var inputs = document.getElementsByTagName(&quot;input&quot;);
             var queryString = &quot;&quot;;
             for (var i = 0; i &lt; inputs.length; i++) {
@@ -94,6 +110,12 @@ Copyright 2004 ThoughtWorks, Inc
 
         &lt;p&gt;
 
+        &lt;div id=&quot;slowResources-div&quot; style=&quot;display: none&quot;&gt;
+            &lt;p&gt;
+                &lt;input id=&quot;slowResources&quot; type=&quot;checkbox&quot; name=&quot;slowResources&quot; onclick=&quot;slowCheck();&quot; /&gt; &lt;label for=&quot;slowResources&quot;&gt;Slow down web server&lt;/label&gt;
+            &lt;/p&gt;
+        &lt;/div&gt;
+
         &lt;p&gt;
             &lt;input id=&quot;auto&quot; type=&quot;checkbox&quot; name=&quot;auto&quot; onclick=&quot;autoCheck();&quot;/&gt; &lt;label for=&quot;auto&quot;&gt;Run
             automatically&lt;/label&gt;</diff>
      <filename>selenium-core/TestPrompt.html</filename>
    </modified>
    <modified>
      <diff>@@ -26,13 +26,13 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
 
         &lt;title&gt;Selenium Functional Test Runner&lt;/title&gt;
         &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;selenium.css&quot;/&gt;
-        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/narcissus-defs.js&quot;&gt;&lt;/script&gt;
-        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/narcissus-parse.js&quot;&gt;&lt;/script&gt;
-        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/narcissus-exec.js&quot;&gt;&lt;/script&gt;
+        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/xmlextras.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/prototype.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/cssQuery/cssQuery-p.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/snapsie.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/htmlutils.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/ui-element.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/scriptaculous/scriptaculous.js&quot;&gt;&lt;/script&gt;
-        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/cssQuery/cssQuery-p.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-browserdetect.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-browserbot.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/find_matching_child.js&quot;&gt;&lt;/script&gt;
@@ -42,13 +42,15 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-testrunner.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-logging.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-version.js&quot;&gt;&lt;/script&gt;
-        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/misc.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/util.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/xmltoken.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/dom.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/xpath.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/javascript-xpath-0.1.11.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/user-extensions.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot;&gt;
             function openDomViewer() {
-                var autFrame = document.getElementById('myiframe');
+                var autFrame = document.getElementById('selenium_myiframe');
                 var autFrameDocument = new SeleniumFrame(autFrame).getDocument();
                 this.rootDocument = autFrameDocument;
                 var domViewer = window.open(getDocumentBase(document) + 'domviewer/domviewer.html');
@@ -68,14 +70,14 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
     &lt;iframe name=&quot;testSuiteFrame&quot; id=&quot;testSuiteFrame&quot; src=&quot;./TestPrompt.html&quot; application=&quot;yes&quot;&gt;&lt;/iframe&gt;
 &lt;/td&gt;
 &lt;td width=&quot;50%&quot; height=&quot;30%&quot;&gt;
-    &lt;iframe name=&quot;testFrame&quot; id=&quot;testFrame&quot; application=&quot;yes&quot;&gt;&lt;/iframe&gt;
+    &lt;iframe name=&quot;testFrame&quot; id=&quot;testFrame&quot; application=&quot;yes&quot; src=&quot;Blank.html&quot;&gt;&lt;/iframe&gt;
 &lt;/td&gt;
 
 &lt;td width=&quot;25%&quot;&gt;
     &lt;table class=&quot;layout&quot;&gt;
         &lt;tr class=&quot;selenium&quot;&gt;
             &lt;th width=&quot;25%&quot; height=&quot;1&quot; class=&quot;header&quot;&gt;
-                &lt;h1&gt;&lt;a href=&quot;http://selenium.thoughtworks.com&quot; title=&quot;The Selenium Project&quot;&gt;Selenium&lt;/a&gt; TestRunner
+                &lt;h1&gt;&lt;a href=&quot;http://selenium.openqa.org&quot; title=&quot;The Selenium Project&quot;&gt;Selenium&lt;/a&gt; TestRunner
                 &lt;/h1&gt;
             &lt;/th&gt;
         &lt;/tr&gt;
@@ -86,16 +88,16 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
 
                     &lt;div id=&quot;imageButtonPanel&quot;&gt;
                         &lt;button type=&quot;button&quot; id=&quot;runSuite&quot; onClick=&quot;htmlTestRunner.startTestSuite();&quot;
-                                title=&quot;Run All tests&quot;&gt;
+                                title=&quot;Run All tests&quot; accesskey=&quot;a&quot;&gt;
                         &lt;/button&gt;
                         &lt;button type=&quot;button&quot; id=&quot;runSeleniumTest&quot; onClick=&quot;htmlTestRunner.runSingleTest();&quot;
-                                title=&quot;Run the Selected test&quot;&gt;
+                                title=&quot;Run the Selected test&quot; accesskey=&quot;r&quot;&gt;
                         &lt;/button&gt;
                         &lt;button type=&quot;button&quot; id=&quot;pauseTest&quot; disabled=&quot;disabled&quot;
-                                title=&quot;Pause/Continue&quot; class=&quot;cssPauseTest&quot;&gt;
+                                title=&quot;Pause/Continue&quot; accesskey=&quot;p&quot; class=&quot;cssPauseTest&quot;&gt;
                         &lt;/button&gt;
                         &lt;button type=&quot;button&quot; id=&quot;stepTest&quot; disabled=&quot;disabled&quot;
-                                title=&quot;Step&quot;&gt;
+                                title=&quot;Step&quot; accesskey=&quot;s&quot;&gt;
                         &lt;/button&gt;
                     &lt;/div&gt;
 
@@ -164,7 +166,7 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
 
 &lt;tr&gt;
     &lt;td colspan=&quot;3&quot; height=&quot;70%&quot;&gt;
-        &lt;iframe name=&quot;myiframe&quot; id=&quot;myiframe&quot; src=&quot;TestRunner-splash.html&quot;&gt;&lt;/iframe&gt;
+        &lt;iframe name=&quot;selenium_myiframe&quot; id=&quot;selenium_myiframe&quot; src=&quot;TestRunner-splash.html&quot;&gt;&lt;/iframe&gt;
     &lt;/td&gt;
 &lt;/tr&gt;
 </diff>
      <filename>selenium-core/TestRunner.hta</filename>
    </modified>
    <modified>
      <diff>@@ -26,13 +26,13 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
 
         &lt;title&gt;Selenium Functional Test Runner&lt;/title&gt;
         &lt;link rel=&quot;stylesheet&quot; type=&quot;text/css&quot; href=&quot;selenium.css&quot;/&gt;
-        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/narcissus-defs.js&quot;&gt;&lt;/script&gt;
-        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/narcissus-parse.js&quot;&gt;&lt;/script&gt;
-        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/narcissus-exec.js&quot;&gt;&lt;/script&gt;
+        &lt;script type=&quot;text/javascript&quot; src=&quot;scripts/xmlextras.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/prototype.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/cssQuery/cssQuery-p.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/snapsie.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/htmlutils.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/ui-element.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/scriptaculous/scriptaculous.js&quot;&gt;&lt;/script&gt;
-        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;lib/cssQuery/cssQuery-p.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-browserdetect.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-browserbot.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/find_matching_child.js&quot;&gt;&lt;/script&gt;
@@ -42,13 +42,15 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-testrunner.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-logging.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/selenium-version.js&quot;&gt;&lt;/script&gt;
-        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/misc.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/util.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/xmltoken.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/dom.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/xpath.js&quot;&gt;&lt;/script&gt;
+        &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;xpath/javascript-xpath-0.1.11.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot; src=&quot;scripts/user-extensions.js&quot;&gt;&lt;/script&gt;
         &lt;script language=&quot;JavaScript&quot; type=&quot;text/javascript&quot;&gt;
             function openDomViewer() {
-                var autFrame = document.getElementById('myiframe');
+                var autFrame = document.getElementById('selenium_myiframe');
                 var autFrameDocument = new SeleniumFrame(autFrame).getDocument();
                 this.rootDocument = autFrameDocument;
                 var domViewer = window.open(getDocumentBase(document) + 'domviewer/domviewer.html');
@@ -68,14 +70,14 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
     &lt;iframe name=&quot;testSuiteFrame&quot; id=&quot;testSuiteFrame&quot; src=&quot;./TestPrompt.html&quot; application=&quot;yes&quot;&gt;&lt;/iframe&gt;
 &lt;/td&gt;
 &lt;td width=&quot;50%&quot; height=&quot;30%&quot;&gt;
-    &lt;iframe name=&quot;testFrame&quot; id=&quot;testFrame&quot; application=&quot;yes&quot;&gt;&lt;/iframe&gt;
+    &lt;iframe name=&quot;testFrame&quot; id=&quot;testFrame&quot; application=&quot;yes&quot; src=&quot;Blank.html&quot;&gt;&lt;/iframe&gt;
 &lt;/td&gt;
 
 &lt;td width=&quot;25%&quot;&gt;
     &lt;table class=&quot;layout&quot;&gt;
         &lt;tr class=&quot;selenium&quot;&gt;
             &lt;th width=&quot;25%&quot; height=&quot;1&quot; class=&quot;header&quot;&gt;
-                &lt;h1&gt;&lt;a href=&quot;http://selenium.thoughtworks.com&quot; title=&quot;The Selenium Project&quot;&gt;Selenium&lt;/a&gt; TestRunner
+                &lt;h1&gt;&lt;a href=&quot;http://selenium.openqa.org&quot; title=&quot;The Selenium Project&quot;&gt;Selenium&lt;/a&gt; TestRunner
                 &lt;/h1&gt;
             &lt;/th&gt;
         &lt;/tr&gt;
@@ -86,16 +88,16 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
 
                     &lt;div id=&quot;imageButtonPanel&quot;&gt;
                         &lt;button type=&quot;button&quot; id=&quot;runSuite&quot; onClick=&quot;htmlTestRunner.startTestSuite();&quot;
-                                title=&quot;Run All tests&quot;&gt;
+                                title=&quot;Run All tests&quot; accesskey=&quot;a&quot;&gt;
                         &lt;/button&gt;
                         &lt;button type=&quot;button&quot; id=&quot;runSeleniumTest&quot; onClick=&quot;htmlTestRunner.runSingleTest();&quot;
-                                title=&quot;Run the Selected test&quot;&gt;
+                                title=&quot;Run the Selected test&quot; accesskey=&quot;r&quot;&gt;
                         &lt;/button&gt;
                         &lt;button type=&quot;button&quot; id=&quot;pauseTest&quot; disabled=&quot;disabled&quot;
-                                title=&quot;Pause/Continue&quot; class=&quot;cssPauseTest&quot;&gt;
+                                title=&quot;Pause/Continue&quot; accesskey=&quot;p&quot; class=&quot;cssPauseTest&quot;&gt;
                         &lt;/button&gt;
                         &lt;button type=&quot;button&quot; id=&quot;stepTest&quot; disabled=&quot;disabled&quot;
-                                title=&quot;Step&quot;&gt;
+                                title=&quot;Step&quot; accesskey=&quot;s&quot;&gt;
                         &lt;/button&gt;
                     &lt;/div&gt;
 
@@ -164,7 +166,7 @@ to work-around a bug in IE on Win2K whereby the HTA application doesn't function
 
 &lt;tr&gt;
     &lt;td colspan=&quot;3&quot; height=&quot;70%&quot;&gt;
-        &lt;iframe name=&quot;myiframe&quot; id=&quot;myiframe&quot; src=&quot;TestRunner-splash.html&quot;&gt;&lt;/iframe&gt;
+        &lt;iframe name=&quot;selenium_myiframe&quot; id=&quot;selenium_myiframe&quot; src=&quot;TestRunner-splash.html&quot;&gt;&lt;/iframe&gt;
     &lt;/td&gt;
 &lt;/tr&gt;
 </diff>
      <filename>selenium-core/TestRunner.html</filename>
    </modified>
    <modified>
      <filename>selenium-core/domviewer/selenium-domviewer.js</filename>
    </modified>
    <modified>
      <diff>@@ -18,14 +18,14 @@ We support the following strategies for locating elements:
 
 &lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;identifier&lt;/strong&gt;=&lt;em&gt;id&lt;/em&gt;: 
-Select the element with the specified &amp;#64;id attribute. If no match is
-found, select the first element whose &amp;#64;name attribute is &lt;em&gt;id&lt;/em&gt;.
+Select the element with the specified &amp;#064;id attribute. If no match is
+found, select the first element whose &amp;#064;name attribute is &lt;em&gt;id&lt;/em&gt;.
 (This is normally the default; see below.)&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;id&lt;/strong&gt;=&lt;em&gt;id&lt;/em&gt;:
-Select the element with the specified &amp;#64;id attribute.&lt;/li&gt;
+Select the element with the specified &amp;#064;id attribute.&lt;/li&gt;
 
 &lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;=&lt;em&gt;name&lt;/em&gt;:
-Select the first element with the specified &amp;#64;name attribute.
+Select the first element with the specified &amp;#064;name attribute.
 &lt;ul class=&quot;first last simple&quot;&gt;
 &lt;li&gt;username&lt;/li&gt;
 &lt;li&gt;name=username&lt;/li&gt;
@@ -52,12 +52,12 @@ Model using JavaScript.  Note that you must not return a value in this string; s
 &lt;li&gt;&lt;strong&gt;xpath&lt;/strong&gt;=&lt;em&gt;xpathExpression&lt;/em&gt;: 
 Locate an element using an XPath expression.
 &lt;ul class=&quot;first last simple&quot;&gt;
-&lt;li&gt;xpath=//img[&amp;#64;alt='The image alt text']&lt;/li&gt;
-&lt;li&gt;xpath=//table[&amp;#64;id='table1']//tr[4]/td[2]&lt;/li&gt;
-&lt;li&gt;xpath=//a[contains(&amp;#64;href,'#id1')]&lt;/li&gt;
-&lt;li&gt;xpath=//a[contains(&amp;#64;href,'#id1')]/&amp;#64;class&lt;/li&gt;
-&lt;li&gt;xpath=(//table[&amp;#64;class='stylee'])//th[text()='theHeaderText']/../td&lt;/li&gt;
-&lt;li&gt;xpath=//input[&amp;#64;name='name2' and &amp;#64;value='yes']&lt;/li&gt;
+&lt;li&gt;xpath=//img[&amp;#064;alt='The image alt text']&lt;/li&gt;
+&lt;li&gt;xpath=//table[&amp;#064;id='table1']//tr[4]/td[2]&lt;/li&gt;
+&lt;li&gt;xpath=//a[contains(&amp;#064;href,'#id1')]&lt;/li&gt;
+&lt;li&gt;xpath=//a[contains(&amp;#064;href,'#id1')]/&amp;#064;class&lt;/li&gt;
+&lt;li&gt;xpath=(//table[&amp;#064;class='stylee'])//th[text()='theHeaderText']/../td&lt;/li&gt;
+&lt;li&gt;xpath=//input[&amp;#064;name='name2' and &amp;#064;value='yes']&lt;/li&gt;
 &lt;li&gt;xpath=//*[text()=&quot;right&quot;]&lt;/li&gt;
 
 &lt;/ul&gt;
@@ -79,6 +79,16 @@ Select the element using css selectors. Please refer to &lt;a href=&quot;http://www.w3.o
 &lt;/ul&gt;
 &lt;p&gt;Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). &lt;/p&gt;
 &lt;/li&gt;
+
+&lt;li&gt;&lt;strong&gt;ui&lt;/strong&gt;=&lt;em&gt;uiSpecifierString&lt;/em&gt;:
+Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the &lt;a href=&quot;http://svn.openqa.org/fisheye/browse/~raw,r=trunk/selenium/trunk/src/main/resources/core/scripts/ui-doc.html&quot;&gt;Selenium UI-Element Reference&lt;/a&gt; for more details.
+&lt;ul class=&quot;first last simple&quot;&gt;
+&lt;li&gt;ui=loginPages::loginButton()&lt;/li&gt;
+&lt;li&gt;ui=settingsPages::toggle(label=Hide Email)&lt;/li&gt;
+&lt;li&gt;ui=forumPages::postBody(index=2)//a[2]&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/li&gt;
+
 &lt;/ul&gt;
 
 &lt;p&gt;
@@ -123,6 +133,8 @@ string.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;regexp:&lt;/strong&gt;&lt;em&gt;regexp&lt;/em&gt;:
 Match a string using a regular-expression. The full power of JavaScript
 regular-expressions is available.&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;regexpi:&lt;/strong&gt;&lt;em&gt;regexpi&lt;/em&gt;:
+Match a string using a case-insensitive regular-expression.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;exact:&lt;/strong&gt;&lt;em&gt;string&lt;/em&gt;:
 
 Match a string exactly, verbatim, without any of that fancy wildcard
@@ -131,6 +143,14 @@ stuff.&lt;/li&gt;
 &lt;p&gt;
 If no pattern prefix is specified, Selenium assumes that it's a &quot;glob&quot;
 pattern.
+&lt;/p&gt;
+&lt;p&gt;
+For commands that return multiple values (such as verifySelectOptions),
+the string being matched is a comma-separated list of the return values,
+where both commas and backslashes in the values are backslash-escaped.
+When providing a pattern, the optional matching syntax (i.e. glob,
+regexp, etc.) is specified once, as usual, at the beginning of the
+pattern.
 &lt;/p&gt;&lt;/top&gt;
 
 &lt;function name=&quot;click&quot;&gt;
@@ -153,6 +173,14 @@ waitForPageToLoad.&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;contextMenu&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an element locator&lt;/param&gt;
+
+&lt;comment&gt;Simulates opening the context menu for the specified element (as might happen if the user &quot;right-clicked&quot; on the element).&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;clickAt&quot;&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an element locator&lt;/param&gt;
@@ -177,6 +205,16 @@ waitForPageToLoad.&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;contextMenuAt&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an element locator&lt;/param&gt;
+
+&lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
+
+&lt;comment&gt;Simulates opening the context menu for the specified element (as might happen if the user &quot;right-clicked&quot; on the element).&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;fireEvent&quot;&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
@@ -188,6 +226,14 @@ handler.&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;focus&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;comment&gt;Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field.&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;keyPress&quot;&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
@@ -286,7 +332,16 @@ handler.&lt;/comment&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
+&lt;comment&gt;Simulates a user pressing the left mouse button (without releasing it yet) on
+the specified element.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseDownRight&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;comment&gt;Simulates a user pressing the right mouse button (without releasing it yet) on
 the specified element.&lt;/comment&gt;
 
 &lt;/function&gt;
@@ -297,8 +352,19 @@ the specified element.&lt;/comment&gt;
 
 &lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
-the specified element.&lt;/comment&gt;
+&lt;comment&gt;Simulates a user pressing the left mouse button (without releasing it yet) at
+the specified location.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseDownRightAt&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
+
+&lt;comment&gt;Simulates a user pressing the right mouse button (without releasing it yet) at
+the specified location.&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -306,8 +372,17 @@ the specified element.&lt;/comment&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
-the specified element.&lt;/comment&gt;
+&lt;comment&gt;Simulates the event that occurs when the user releases the mouse button (i.e., stops
+holding the button down) on the specified element.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseUpRight&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;comment&gt;Simulates the event that occurs when the user releases the right mouse button (i.e., stops
+holding the button down) on the specified element.&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -317,8 +392,19 @@ the specified element.&lt;/comment&gt;
 
 &lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
-the specified element.&lt;/comment&gt;
+&lt;comment&gt;Simulates the event that occurs when the user releases the mouse button (i.e., stops
+holding the button down) at the specified location.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseUpRightAt&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
+
+&lt;comment&gt;Simulates the event that occurs when the user releases the right mouse button (i.e., stops
+holding the button down) at the specified location.&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -386,6 +472,8 @@ the delay is 0 milliseconds.&lt;/comment&gt;
 
 &lt;function name=&quot;getSpeed&quot;&gt;
 
+&lt;return type=&quot;string&quot;&gt;the execution speed in milliseconds.&lt;/return&gt;
+
 &lt;comment&gt;Get execution speed (i.e., get the millisecond length of the delay following each selenium operation).  By default, there is no such delay, i.e.,
 the delay is 0 milliseconds.
 
@@ -538,21 +626,43 @@ an empty (blank) url, like this: openWindow(&quot;&quot;, &quot;myFunnyWindow&quot;).&lt;/p&gt;&lt;/comment&gt;
 
 &lt;param name=&quot;windowID&quot;&gt;the JavaScript window ID of the window to select&lt;/param&gt;
 
-&lt;comment&gt;Selects a popup window; once a popup window has been selected, all
+&lt;comment&gt;Selects a popup window using a window locator; once a popup window has been selected, all
 commands go to that window. To select the main window again, use null
 as the target.
 
-&lt;p&gt;Selenium has several strategies for finding the window object referred to by the &quot;windowID&quot; parameter.&lt;/p&gt;
+&lt;p&gt;
 
-&lt;p&gt;1.) if windowID is null, then it is assumed the user is referring to the original window instantiated by the browser).&lt;/p&gt;
+Window locators provide different ways of specifying the window object:
+by title, by internal JavaScript &quot;name,&quot; or by JavaScript variable.
+&lt;/p&gt;
+&lt;ul&gt;
+&lt;li&gt;&lt;strong&gt;title&lt;/strong&gt;=&lt;em&gt;My Special Window&lt;/em&gt;:
+Finds the window using the text that appears in the title bar.  Be careful;
+two windows can share the same title.  If that happens, this locator will
+just pick one.
+&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;=&lt;em&gt;myWindow&lt;/em&gt;:
+Finds the window using its internal JavaScript &quot;name&quot; property.  This is the second 
+parameter &quot;windowName&quot; passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
+(which Selenium intercepts).
+&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;var&lt;/strong&gt;=&lt;em&gt;variableName&lt;/em&gt;:
+Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current
+application window, e.g. &quot;window.foo = window.open(url);&quot;.  In those cases, you can open the window using
+&quot;var=foo&quot;.
+&lt;/li&gt;
+&lt;/ul&gt;
+&lt;p&gt;
+If no window locator prefix is provided, we'll try to guess what you mean like this:&lt;/p&gt;
+&lt;p&gt;1.) if windowID is null, (or the string &quot;null&quot;) then it is assumed the user is referring to the original window instantiated by the browser).&lt;/p&gt;
 &lt;p&gt;2.) if the value of the &quot;windowID&quot; parameter is a JavaScript variable name in the current application window, then it is assumed
 that this variable contains the return value from a call to the JavaScript window.open() method.&lt;/p&gt;
-&lt;p&gt;3.) Otherwise, selenium looks in a hash it maintains that maps string names to window objects.  Each of these string 
-names matches the second parameter &quot;windowName&quot; past to the JavaScript method  window.open(url, windowName, windowFeatures, replaceFlag)
-(which selenium intercepts).&lt;/p&gt;
+&lt;p&gt;3.) Otherwise, selenium looks in a hash it maintains that maps string names to window &quot;names&quot;.&lt;/p&gt;
+&lt;p&gt;4.) If &lt;em&gt;that&lt;/em&gt; fails, we'll try looping over all of the known windows to try to find the appropriate &quot;title&quot;.
+Since &quot;title&quot; is not necessarily unique, this may have unexpected behavior.&lt;/p&gt;
 
-&lt;p&gt;If you're having trouble figuring out what is the name of a window that you want to manipulate, look at the selenium log messages
-which identify the names of windows created via window.open (and therefore intercepted by selenium).  You will see messages
+&lt;p&gt;If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages
+which identify the names of windows created via window.open (and therefore intercepted by Selenium).  You will see messages
 like the following for each window as it is opened:&lt;/p&gt;
 
 &lt;p&gt;&lt;code&gt;debug: window.open call intercepted; window ID (which you can use with selectWindow()) is &quot;myNewWindow&quot;&lt;/code&gt;&lt;/p&gt;
@@ -570,29 +680,14 @@ an empty (blank) url, like this: openWindow(&quot;&quot;, &quot;myFunnyWindow&quot;).&lt;/p&gt;&lt;/comment&gt;
 &lt;comment&gt;Selects a frame within the current window.  (You may invoke this command
 multiple times to select nested frames.)  To select the parent frame, use
 &quot;relative=parent&quot; as a locator; to select the top frame, use &quot;relative=top&quot;.
+You can also select a frame by its 0-based index number; select the first frame with
+&quot;index=0&quot;, or the third frame with &quot;index=2&quot;.
 
 &lt;p&gt;You may also use a DOM expression to identify the frame you want directly,
 like this: &lt;code&gt;dom=frames[&quot;main&quot;].frames[&quot;subframe&quot;]&lt;/code&gt;&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
-&lt;function name=&quot;getLogMessages&quot;&gt;
-
-&lt;return type=&quot;string&quot;&gt;all log messages seen since the last call to this API&lt;/return&gt;
-
-&lt;comment&gt;Return the contents of the log.
-
-&lt;p&gt;This is a placeholder intended to make the code generator make this API
-available to clients.  The selenium server will intercept this call, however,
-and return its recordkeeping of log messages since the last call to this API.
-Thus this code in JavaScript will never be called.&lt;/p&gt;
-
-&lt;p&gt;The reason I opted for a servercentric solution is to be able to support
-multiple frames served from different domains, which would break a
-centralized JavaScript logging mechanism under some conditions.&lt;/p&gt;&lt;/comment&gt;
-
-&lt;/function&gt;
-
 &lt;function name=&quot;getWhetherThisFrameMatchFrameExpression&quot;&gt;
 
 &lt;return type=&quot;boolean&quot;&gt;true if the new frame is this code's window&lt;/return&gt;
@@ -631,7 +726,7 @@ The selected window will return true, while all others will return false.&lt;/p&gt;&lt;/c
 
 &lt;function name=&quot;waitForPopUp&quot;&gt;
 
-&lt;param name=&quot;windowID&quot;&gt;the JavaScript window ID of the window that will appear&lt;/param&gt;
+&lt;param name=&quot;windowID&quot;&gt;the JavaScript window &quot;name&quot; of the window that will appear (not the text of the title bar)&lt;/param&gt;
 
 &lt;param name=&quot;timeout&quot;&gt;a timeout in milliseconds, after which the action will return with an error&lt;/param&gt;
 
@@ -641,10 +736,40 @@ The selected window will return true, while all others will return false.&lt;/p&gt;&lt;/c
 
 &lt;function name=&quot;chooseCancelOnNextConfirmation&quot;&gt;
 
-&lt;comment&gt;By default, Selenium's overridden window.confirm() function will
-return true, as if the user had manually clicked OK.  After running
+&lt;comment&gt;&lt;p&gt;
+By default, Selenium's overridden window.confirm() function will
+return true, as if the user had manually clicked OK; after running
 this command, the next call to confirm() will return false, as if
-the user had clicked Cancel.&lt;/comment&gt;
+the user had clicked Cancel.  Selenium will then resume using the
+default behavior for future confirmations, automatically returning 
+true (OK) unless/until you explicitly call this command for each
+confirmation.
+&lt;/p&gt;
+&lt;p&gt;
+Take note - every time a confirmation comes up, you must
+consume it with a corresponding getConfirmation, or else
+the next selenium operation will fail.
+&lt;/p&gt;&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;chooseOkOnNextConfirmation&quot;&gt;
+
+&lt;comment&gt;&lt;p&gt;
+Undo the effect of calling chooseCancelOnNextConfirmation.  Note
+that Selenium's overridden window.confirm() function will normally automatically
+return true, as if the user had manually clicked OK, so you shouldn't
+need to use this command unless for some reason you need to change
+your mind prior to the next confirmation.  After any confirmation, Selenium will resume using the
+default behavior for future confirmations, automatically returning 
+true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each
+confirmation.
+&lt;/p&gt;
+&lt;p&gt;
+Take note - every time a confirmation comes up, you must
+consume it with a corresponding getConfirmation, or else
+the next selenium operation will fail.
+&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -719,13 +844,13 @@ This function never throws an exception
 &lt;comment&gt;Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
 
 &lt;p&gt;Getting an alert has the same effect as manually clicking OK. If an
-alert is generated but you do not get/verify it, the next Selenium action
+alert is generated but you do not consume it with getAlert, the next Selenium action
 will fail.&lt;/p&gt;
 
-&lt;p&gt;NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
+&lt;p&gt;Under Selenium, JavaScript alerts will NOT pop up a visible alert
 dialog.&lt;/p&gt;
 
-&lt;p&gt;NOTE: Selenium does NOT support JavaScript alerts that are generated in a
+&lt;p&gt;Selenium does NOT support JavaScript alerts that are generated in a
 page's onload() event handler. In this case a visible dialog WILL be
 generated and Selenium will hang until someone manually clicks OK.&lt;/p&gt;&lt;/comment&gt;
 
@@ -741,8 +866,11 @@ the previous action.
 &lt;p&gt;
 By default, the confirm function will return true, having the same effect
 as manually clicking OK. This can be changed by prior execution of the
-chooseCancelOnNextConfirmation command. If an confirmation is generated
-but you do not get/verify it, the next Selenium action will fail.
+chooseCancelOnNextConfirmation command. 
+&lt;/p&gt;
+&lt;p&gt;
+If an confirmation is generated but you do not consume it with getConfirmation,
+the next Selenium action will fail.
 &lt;/p&gt;
 
 &lt;p&gt;
@@ -846,13 +974,12 @@ text shown to the user.&lt;/comment&gt;
 have multiple lines, but only the result of the last line will be returned.
 
 &lt;p&gt;Note that, by default, the snippet will run in the context of the &quot;selenium&quot;
-object itself, so &lt;code&gt;this&lt;/code&gt; will refer to the Selenium object, and &lt;code&gt;window&lt;/code&gt; will
-refer to the top-level runner test window, not the window of your application.&lt;/p&gt;
+object itself, so &lt;code&gt;this&lt;/code&gt; will refer to the Selenium object.  Use &lt;code&gt;window&lt;/code&gt; to
+refer to the window of your application, e.g. &lt;code&gt;window.document.getElementById('foo')&lt;/code&gt;&lt;/p&gt;
 
-&lt;p&gt;If you need a reference to the window of your application, you can refer
-to &lt;code&gt;this.browserbot.getCurrentWindow()&lt;/code&gt; and if you need to use
+&lt;p&gt;If you need to use
 a locator to refer to a single element in your application page, you can
-use &lt;code&gt;this.browserbot.findElement(&quot;foo&quot;)&lt;/code&gt; where &quot;foo&quot; is your locator.&lt;/p&gt;&lt;/comment&gt;
+use &lt;code&gt;this.browserbot.findElement(&quot;id=foo&quot;)&lt;/code&gt; where &quot;id=foo&quot; is your locator.&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -981,9 +1108,11 @@ tableLocator.row.column, where row and column start at 0.&lt;/comment&gt;
 
 &lt;return type=&quot;string&quot;&gt;the value of the specified attribute&lt;/return&gt;
 
-&lt;param name=&quot;attributeLocator&quot;&gt;an element locator followed by an&lt;/param&gt;
+&lt;param name=&quot;attributeLocator&quot;&gt;an element locator followed by an &amp;#064; sign and then the name of the attribute, e.g. &quot;foo&amp;#064;bar&quot;&lt;/param&gt;
 
-&lt;comment&gt;Gets the value of an element attribute.&lt;/comment&gt;
+&lt;comment&gt;Gets the value of an element attribute. The value of the attribute may
+differ across browsers (this is the case for the &quot;style&quot; attribute, for
+example).&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -1126,17 +1255,13 @@ just send one &quot;mousemove&quot; at the start location and then one final one at the en
 
 &lt;function name=&quot;windowFocus&quot;&gt;
 
-&lt;param name=&quot;windowName&quot;&gt;name of the window to be given focus&lt;/param&gt;
-
-&lt;comment&gt;Gives focus to a window&lt;/comment&gt;
+&lt;comment&gt;Gives focus to the currently selected window&lt;/comment&gt;
 
 &lt;/function&gt;
 
 &lt;function name=&quot;windowMaximize&quot;&gt;
 
-&lt;param name=&quot;windowName&quot;&gt;name of the window to be enlarged&lt;/param&gt;
-
-&lt;comment&gt;Resize window to take up the entire screen&lt;/comment&gt;
+&lt;comment&gt;Resize currently selected window to take up the entire screen&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -1197,13 +1322,13 @@ will be ignored.&lt;/comment&gt;
 
 &lt;function name=&quot;isOrdered&quot;&gt;
 
-&lt;return type=&quot;boolean&quot;&gt;true if two elements are ordered and have same parent, false otherwise&lt;/return&gt;
+&lt;return type=&quot;boolean&quot;&gt;true if element1 is the previous sibling of element2, false otherwise&lt;/return&gt;
 
 &lt;param name=&quot;locator1&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to the first element&lt;/param&gt;
 
 &lt;param name=&quot;locator2&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to the second element&lt;/param&gt;
 
-&lt;comment&gt;Check if these two elements have same parent and are ordered. Two same elements will
+&lt;comment&gt;Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will
 not be considered ordered.&lt;/comment&gt;
 
 &lt;/function&gt;
@@ -1262,33 +1387,68 @@ This method will fail if the specified element isn't an input element or textare
 
 &lt;/function&gt;
 
-&lt;function name=&quot;setContext&quot;&gt;
+&lt;function name=&quot;getExpression&quot;&gt;
+
+&lt;return type=&quot;string&quot;&gt;the value passed in&lt;/return&gt;
+
+&lt;param name=&quot;expression&quot;&gt;the value to return&lt;/param&gt;
+
+&lt;comment&gt;Returns the specified expression.
+
+&lt;p&gt;This is useful because of JavaScript preprocessing.
+It is used to generate commands like assertExpression and waitForExpression.&lt;/p&gt;&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;getXpathCount&quot;&gt;
+
+&lt;return type=&quot;number&quot;&gt;the number of nodes that match the specified xpath&lt;/return&gt;
+
+&lt;param name=&quot;xpath&quot;&gt;the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you.&lt;/param&gt;
 
-&lt;param name=&quot;context&quot;&gt;the message to be sent to the browser&lt;/param&gt;
+&lt;comment&gt;Returns the number of nodes that match the specified xpath, eg. &quot;//table&quot; would give
+the number of tables.&lt;/comment&gt;
 
-&lt;param name=&quot;logLevelThreshold&quot;&gt;one of &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot;, sets the threshold for browser-side logging&lt;/param&gt;
+&lt;/function&gt;
+
+&lt;function name=&quot;assignId&quot;&gt;
 
-&lt;comment&gt;Writes a message to the status bar and adds a note to the browser-side
-log.
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to an element&lt;/param&gt;
 
-&lt;p&gt;If logLevelThreshold is specified, set the threshold for logging
-to that level (debug, info, warn, error).&lt;/p&gt;
+&lt;param name=&quot;identifier&quot;&gt;a string to be used as the ID of the specified element&lt;/param&gt;
 
-&lt;p&gt;(Note that the browser-side logs will &lt;i&gt;not&lt;/i&gt; be sent back to the
-server, and are invisible to the Client Driver.)&lt;/p&gt;&lt;/comment&gt;
+&lt;comment&gt;Temporarily sets the &quot;id&quot; attribute of the specified element, so you can locate it in the future
+using its ID rather than a slow/complicated XPath.  This ID will disappear once the page is
+reloaded.&lt;/comment&gt;
 
 &lt;/function&gt;
 
-&lt;function name=&quot;getExpression&quot;&gt;
+&lt;function name=&quot;allowNativeXpath&quot;&gt;
 
-&lt;return type=&quot;string&quot;&gt;the value passed in&lt;/return&gt;
+&lt;param name=&quot;allow&quot;&gt;boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath&lt;/param&gt;
 
-&lt;param name=&quot;expression&quot;&gt;the value to return&lt;/param&gt;
+&lt;comment&gt;Specifies whether Selenium should use the native in-browser implementation
+of XPath (if any native version is available); if you pass &quot;false&quot; to
+this function, we will always use our pure-JavaScript xpath library.
+Using the pure-JS xpath library can improve the consistency of xpath
+element locators between different browser vendors, but the pure-JS
+version is much slower than the native implementations.&lt;/comment&gt;
 
-&lt;comment&gt;Returns the specified expression.
+&lt;/function&gt;
 
-&lt;p&gt;This is useful because of JavaScript preprocessing.
-It is used to generate commands like assertExpression and waitForExpression.&lt;/p&gt;&lt;/comment&gt;
+&lt;function name=&quot;ignoreAttributesWithoutValue&quot;&gt;
+
+&lt;param name=&quot;ignore&quot;&gt;boolean, true means we'll ignore attributes without value                        at the expense of xpath &quot;correctness&quot;; false means                        we'll sacrifice speed for correctness.&lt;/param&gt;
+
+&lt;comment&gt;Specifies whether Selenium will ignore xpath attributes that have no
+value, i.e. are the empty string, when using the non-native xpath
+evaluation engine. You'd want to do this for performance reasons in IE.
+However, this could break certain xpaths, for example an xpath that looks
+for an attribute whose value is NOT the empty string.
+
+The hope is that such xpaths are relatively rare, but the user should
+have the option of using them. Note that this only influences xpath
+evaluation when using the ajaxslt engine (i.e. not &quot;javascript-xpath&quot;).&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -1336,6 +1496,21 @@ wait immediately after a Selenium command that caused a page-load.&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;waitForFrameToLoad&quot;&gt;
+
+&lt;param name=&quot;frameAddress&quot;&gt;FrameAddress from the server side&lt;/param&gt;
+
+&lt;param name=&quot;timeout&quot;&gt;a timeout in milliseconds, after which this command will return with an error&lt;/param&gt;
+
+&lt;comment&gt;Waits for a new frame to load.
+
+&lt;p&gt;Selenium constantly keeps track of new pages and frames loading, 
+and sets a &quot;newPageLoaded&quot; flag when it first notices a page load.&lt;/p&gt;
+
+See waitForPageToLoad for more information.&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;getCookie&quot;&gt;
 
 &lt;return type=&quot;string&quot;&gt;all cookies of the current page under test&lt;/return&gt;
@@ -1344,11 +1519,31 @@ wait immediately after a Selenium command that caused a page-load.&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;getCookieByName&quot;&gt;
+
+&lt;return type=&quot;string&quot;&gt;the value of the cookie&lt;/return&gt;
+
+&lt;param name=&quot;name&quot;&gt;the name of the cookie&lt;/param&gt;
+
+&lt;comment&gt;Returns the value of the cookie with the specified name, or throws an error if the cookie is not present.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;isCookiePresent&quot;&gt;
+
+&lt;return type=&quot;boolean&quot;&gt;true if a cookie with the specified name is present, or false otherwise.&lt;/return&gt;
+
+&lt;param name=&quot;name&quot;&gt;the name of the cookie&lt;/param&gt;
+
+&lt;comment&gt;Returns true if a cookie with the specified name is present, or false otherwise.&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;createCookie&quot;&gt;
 
 &lt;param name=&quot;nameValuePair&quot;&gt;name and value of the cookie in a format &quot;name=value&quot;&lt;/param&gt;
 
-&lt;param name=&quot;optionsString&quot;&gt;options for the cookie. Currently supported options include 'path' and 'max_age'.      the optionsString's format is &quot;path=/path/, max_age=60&quot;. The order of options are irrelevant, the unit      of the value of 'max_age' is second.&lt;/param&gt;
+&lt;param name=&quot;optionsString&quot;&gt;options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'.      the optionsString's format is &quot;path=/path/, max_age=60, domain=.foo.com&quot;. The order of options are irrelevant, the unit      of the value of 'max_age' is second.  Note that specifying a domain that isn't a subset of the current domain will      usually fail.&lt;/param&gt;
 
 &lt;comment&gt;Create a new cookie whose path and domain are same with those of current page
 under test, unless you specified a path for this cookie explicitly.&lt;/comment&gt;
@@ -1359,9 +1554,142 @@ under test, unless you specified a path for this cookie explicitly.&lt;/comment&gt;
 
 &lt;param name=&quot;name&quot;&gt;the name of the cookie to be deleted&lt;/param&gt;
 
-&lt;param name=&quot;path&quot;&gt;the path property of the cookie to be deleted&lt;/param&gt;
+&lt;param name=&quot;optionsString&quot;&gt;options for the cookie. Currently supported options include 'path', 'domain'      and 'recurse.' The optionsString's format is &quot;path=/path/, domain=.foo.com, recurse=true&quot;.      The order of options are irrelevant. Note that specifying a domain that isn't a subset of      the current domain will usually fail.&lt;/param&gt;
+
+&lt;comment&gt;Delete a named cookie with specified path and domain.  Be careful; to delete a cookie, you
+need to delete it using the exact same path and domain that were used to create the cookie.
+If the path is wrong, or the domain is wrong, the cookie simply won't be deleted.  Also
+note that specifying a domain that isn't a subset of the current domain will usually fail.
+
+Since there's no way to discover at runtime the original path and domain of a given cookie,
+we've added an option called 'recurse' to try all sub-domains of the current domain with
+all paths that are a subset of the current path.  Beware; this option can be slow.  In
+big-O notation, it operates in O(n*m) time, where n is the number of dots in the domain
+name and m is the number of slashes in the path.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;deleteAllVisibleCookies&quot;&gt;
+
+&lt;comment&gt;Calls deleteCookie with recurse=true on all cookies visible to the current page.
+As noted on the documentation for deleteCookie, recurse=true can be much slower
+than simply deleting the cookies using a known domain/path.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;setBrowserLogLevel&quot;&gt;
+
+&lt;param name=&quot;logLevel&quot;&gt;one of the following: &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot; or &quot;off&quot;&lt;/param&gt;
+
+&lt;comment&gt;Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded.
+Valid logLevel strings are: &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot; or &quot;off&quot;.
+To see the browser logs, you need to
+either show the log window in GUI mode, or enable browser-side logging in Selenium RC.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;runScript&quot;&gt;
+
+&lt;param name=&quot;script&quot;&gt;the JavaScript snippet to run&lt;/param&gt;
+
+&lt;comment&gt;Creates a new &quot;script&quot; tag in the body of the current test window, and 
+adds the specified text into the body of the command.  Scripts run in
+this way can often be debugged more easily than scripts executed using
+Selenium's &quot;getEval&quot; command.  Beware that JS exceptions thrown in these script
+tags aren't managed by Selenium, so you should probably wrap your script
+in try/catch blocks if there is any chance that the script will throw
+an exception.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;addLocationStrategy&quot;&gt;
+
+&lt;param name=&quot;strategyName&quot;&gt;the name of the strategy to define; this should use only   letters [a-zA-Z] with no spaces or other punctuation.&lt;/param&gt;
+
+&lt;param name=&quot;functionDefinition&quot;&gt;a string defining the body of a function in JavaScript.   For example: &lt;code&gt;return inDocument.getElementById(locator);&lt;/code&gt;&lt;/param&gt;
+
+&lt;comment&gt;Defines a new function for Selenium to locate elements on the page.
+For example,
+if you define the strategy &quot;foo&quot;, and someone runs click(&quot;foo=blah&quot;), we'll
+run your function, passing you the string &quot;blah&quot;, and click on the element 
+that your function
+returns, or throw an &quot;Element not found&quot; error if your function returns null.
+
+We'll pass three arguments to your function:
+&lt;ul&gt;
+&lt;li&gt;locator: the string the user passed in&lt;/li&gt;
+&lt;li&gt;inWindow: the currently selected window&lt;/li&gt;
+&lt;li&gt;inDocument: the currently selected document&lt;/li&gt;
+&lt;/ul&gt;
+The function must return null if the element can't be found.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;captureEntirePageScreenshot&quot;&gt;
+
+&lt;param name=&quot;filename&quot;&gt;the path to the file to persist the screenshot as. No                  filename extension will be appended by default.                  Directories will not be created if they do not exist,                    and an exception will be thrown, possibly by native                  code.&lt;/param&gt;
+
+&lt;param name=&quot;kwargs&quot;&gt;a kwargs string that modifies the way the screenshot                  is captured. Example: &quot;background=#CCFFDD&quot; .                  Currently valid options:                  &lt;dl&gt;                   &lt;dt&gt;background&lt;/dt&gt;                     &lt;dd&gt;the background CSS for the HTML document. This                     may be useful to set for capturing screenshots of                     less-than-ideal layouts, for example where absolute                     positioning causes the calculation of the canvas                     dimension to fail and a black background is exposed                     (possibly obscuring black text).&lt;/dd&gt;                  &lt;/dl&gt;&lt;/param&gt;
+
+&lt;comment&gt;Saves the entire contents of the current window canvas to a PNG file.
+Contrast this with the captureScreenshot command, which captures the
+contents of the OS viewport (i.e. whatever is currently being displayed
+on the monitor), and is implemented in the RC only. Currently this only
+works in Firefox when running in chrome mode, and in IE non-HTA using
+the EXPERIMENTAL &quot;Snapsie&quot; utility. The Firefox implementation is mostly
+borrowed from the Screengrab! Firefox extension. Please see
+http://www.screengrab.org and http://snapsie.sourceforge.net/ for
+details.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;rollup&quot;&gt;
+
+&lt;param name=&quot;rollupName&quot;&gt;the name of the rollup command&lt;/param&gt;
+
+&lt;param name=&quot;kwargs&quot;&gt;keyword arguments string that influences how the                    rollup expands into commands&lt;/param&gt;
+
+&lt;comment&gt;Executes a command rollup, which is a series of commands with a unique
+name, and optionally arguments that control the generation of the set of
+commands. If any one of the rolled-up commands fails, the rollup is
+considered to have failed. Rollups may also contain nested rollups.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;addScript&quot;&gt;
+
+&lt;param name=&quot;scriptContent&quot;&gt;the Javascript content of the script to add&lt;/param&gt;
+
+&lt;param name=&quot;scriptTagId&quot;&gt;(optional) the id of the new script tag. If                       specified, and an element with this id already                       exists, this operation will fail.&lt;/param&gt;
+
+&lt;comment&gt;Loads script content into a new script tag in the Selenium document. This
+differs from the runScript command in that runScript adds the script tag
+to the document of the AUT, not the Selenium document. The following
+entities in the script content are replaced by the characters they
+represent:
+
+    &amp;lt;
+    &amp;gt;
+    &amp;amp;
+
+The corresponding remove command is removeScript.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;removeScript&quot;&gt;
+
+&lt;param name=&quot;scriptTagId&quot;&gt;the id of the script element to remove.&lt;/param&gt;
+
+&lt;comment&gt;Removes a script tag from the Selenium document identified by the given
+id. Does nothing if the referenced tag doesn't exist.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;useXpathLibrary&quot;&gt;
+
+&lt;param name=&quot;libraryName&quot;&gt;name of the desired library Only the following three can be chosen:   ajaxslt - Google's library   javascript - Cybozu Labs' faster library   default - The default library.  Currently the default library is ajaxslt. If libraryName isn't one of these three, then  no change will be made.&lt;/param&gt;
 
-&lt;comment&gt;Delete a named cookie with specified path.&lt;/comment&gt;
+&lt;comment&gt;Allows choice of one of the available libraries.&lt;/comment&gt;
 
 &lt;/function&gt;
 </diff>
      <filename>selenium-core/iedoc-core.xml</filename>
    </modified>
    <modified>
      <diff>@@ -18,14 +18,14 @@ We support the following strategies for locating elements:
 
 &lt;ul&gt;
 &lt;li&gt;&lt;strong&gt;identifier&lt;/strong&gt;=&lt;em&gt;id&lt;/em&gt;: 
-Select the element with the specified &amp;#64;id attribute. If no match is
-found, select the first element whose &amp;#64;name attribute is &lt;em&gt;id&lt;/em&gt;.
+Select the element with the specified &amp;#064;id attribute. If no match is
+found, select the first element whose &amp;#064;name attribute is &lt;em&gt;id&lt;/em&gt;.
 (This is normally the default; see below.)&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;id&lt;/strong&gt;=&lt;em&gt;id&lt;/em&gt;:
-Select the element with the specified &amp;#64;id attribute.&lt;/li&gt;
+Select the element with the specified &amp;#064;id attribute.&lt;/li&gt;
 
 &lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;=&lt;em&gt;name&lt;/em&gt;:
-Select the first element with the specified &amp;#64;name attribute.
+Select the first element with the specified &amp;#064;name attribute.
 &lt;ul class=&quot;first last simple&quot;&gt;
 &lt;li&gt;username&lt;/li&gt;
 &lt;li&gt;name=username&lt;/li&gt;
@@ -52,12 +52,12 @@ Model using JavaScript.  Note that you must not return a value in this string; s
 &lt;li&gt;&lt;strong&gt;xpath&lt;/strong&gt;=&lt;em&gt;xpathExpression&lt;/em&gt;: 
 Locate an element using an XPath expression.
 &lt;ul class=&quot;first last simple&quot;&gt;
-&lt;li&gt;xpath=//img[&amp;#64;alt='The image alt text']&lt;/li&gt;
-&lt;li&gt;xpath=//table[&amp;#64;id='table1']//tr[4]/td[2]&lt;/li&gt;
-&lt;li&gt;xpath=//a[contains(&amp;#64;href,'#id1')]&lt;/li&gt;
-&lt;li&gt;xpath=//a[contains(&amp;#64;href,'#id1')]/&amp;#64;class&lt;/li&gt;
-&lt;li&gt;xpath=(//table[&amp;#64;class='stylee'])//th[text()='theHeaderText']/../td&lt;/li&gt;
-&lt;li&gt;xpath=//input[&amp;#64;name='name2' and &amp;#64;value='yes']&lt;/li&gt;
+&lt;li&gt;xpath=//img[&amp;#064;alt='The image alt text']&lt;/li&gt;
+&lt;li&gt;xpath=//table[&amp;#064;id='table1']//tr[4]/td[2]&lt;/li&gt;
+&lt;li&gt;xpath=//a[contains(&amp;#064;href,'#id1')]&lt;/li&gt;
+&lt;li&gt;xpath=//a[contains(&amp;#064;href,'#id1')]/&amp;#064;class&lt;/li&gt;
+&lt;li&gt;xpath=(//table[&amp;#064;class='stylee'])//th[text()='theHeaderText']/../td&lt;/li&gt;
+&lt;li&gt;xpath=//input[&amp;#064;name='name2' and &amp;#064;value='yes']&lt;/li&gt;
 &lt;li&gt;xpath=//*[text()=&quot;right&quot;]&lt;/li&gt;
 
 &lt;/ul&gt;
@@ -79,6 +79,16 @@ Select the element using css selectors. Please refer to &lt;a href=&quot;http://www.w3.o
 &lt;/ul&gt;
 &lt;p&gt;Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). &lt;/p&gt;
 &lt;/li&gt;
+
+&lt;li&gt;&lt;strong&gt;ui&lt;/strong&gt;=&lt;em&gt;uiSpecifierString&lt;/em&gt;:
+Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the &lt;a href=&quot;http://svn.openqa.org/fisheye/browse/~raw,r=trunk/selenium/trunk/src/main/resources/core/scripts/ui-doc.html&quot;&gt;Selenium UI-Element Reference&lt;/a&gt; for more details.
+&lt;ul class=&quot;first last simple&quot;&gt;
+&lt;li&gt;ui=loginPages::loginButton()&lt;/li&gt;
+&lt;li&gt;ui=settingsPages::toggle(label=Hide Email)&lt;/li&gt;
+&lt;li&gt;ui=forumPages::postBody(index=2)//a[2]&lt;/li&gt;
+&lt;/ul&gt;
+&lt;/li&gt;
+
 &lt;/ul&gt;
 
 &lt;p&gt;
@@ -123,6 +133,8 @@ string.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;regexp:&lt;/strong&gt;&lt;em&gt;regexp&lt;/em&gt;:
 Match a string using a regular-expression. The full power of JavaScript
 regular-expressions is available.&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;regexpi:&lt;/strong&gt;&lt;em&gt;regexpi&lt;/em&gt;:
+Match a string using a case-insensitive regular-expression.&lt;/li&gt;
 &lt;li&gt;&lt;strong&gt;exact:&lt;/strong&gt;&lt;em&gt;string&lt;/em&gt;:
 
 Match a string exactly, verbatim, without any of that fancy wildcard
@@ -131,6 +143,14 @@ stuff.&lt;/li&gt;
 &lt;p&gt;
 If no pattern prefix is specified, Selenium assumes that it's a &quot;glob&quot;
 pattern.
+&lt;/p&gt;
+&lt;p&gt;
+For commands that return multiple values (such as verifySelectOptions),
+the string being matched is a comma-separated list of the return values,
+where both commas and backslashes in the values are backslash-escaped.
+When providing a pattern, the optional matching syntax (i.e. glob,
+regexp, etc.) is specified once, as usual, at the beginning of the
+pattern.
 &lt;/p&gt;&lt;/top&gt;
 
 &lt;function name=&quot;click&quot;&gt;
@@ -153,6 +173,14 @@ waitForPageToLoad.&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;contextMenu&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an element locator&lt;/param&gt;
+
+&lt;comment&gt;Simulates opening the context menu for the specified element (as might happen if the user &quot;right-clicked&quot; on the element).&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;clickAt&quot;&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an element locator&lt;/param&gt;
@@ -177,6 +205,16 @@ waitForPageToLoad.&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;contextMenuAt&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an element locator&lt;/param&gt;
+
+&lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
+
+&lt;comment&gt;Simulates opening the context menu for the specified element (as might happen if the user &quot;right-clicked&quot; on the element).&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;fireEvent&quot;&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
@@ -188,6 +226,14 @@ handler.&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;focus&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;comment&gt;Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field.&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;keyPress&quot;&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
@@ -286,7 +332,16 @@ handler.&lt;/comment&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
+&lt;comment&gt;Simulates a user pressing the left mouse button (without releasing it yet) on
+the specified element.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseDownRight&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;comment&gt;Simulates a user pressing the right mouse button (without releasing it yet) on
 the specified element.&lt;/comment&gt;
 
 &lt;/function&gt;
@@ -297,8 +352,19 @@ the specified element.&lt;/comment&gt;
 
 &lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
-the specified element.&lt;/comment&gt;
+&lt;comment&gt;Simulates a user pressing the left mouse button (without releasing it yet) at
+the specified location.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseDownRightAt&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
+
+&lt;comment&gt;Simulates a user pressing the right mouse button (without releasing it yet) at
+the specified location.&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -306,8 +372,17 @@ the specified element.&lt;/comment&gt;
 
 &lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
-the specified element.&lt;/comment&gt;
+&lt;comment&gt;Simulates the event that occurs when the user releases the mouse button (i.e., stops
+holding the button down) on the specified element.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseUpRight&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;comment&gt;Simulates the event that occurs when the user releases the right mouse button (i.e., stops
+holding the button down) on the specified element.&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -317,8 +392,19 @@ the specified element.&lt;/comment&gt;
 
 &lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
 
-&lt;comment&gt;Simulates a user pressing the mouse button (without releasing it yet) on
-the specified element.&lt;/comment&gt;
+&lt;comment&gt;Simulates the event that occurs when the user releases the mouse button (i.e., stops
+holding the button down) at the specified location.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;mouseUpRightAt&quot;&gt;
+
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;param name=&quot;coordString&quot;&gt;specifies the x,y position (i.e. - 10,20) of the mouse      event relative to the element returned by the locator.&lt;/param&gt;
+
+&lt;comment&gt;Simulates the event that occurs when the user releases the right mouse button (i.e., stops
+holding the button down) at the specified location.&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -386,6 +472,8 @@ the delay is 0 milliseconds.&lt;/comment&gt;
 
 &lt;function name=&quot;getSpeed&quot;&gt;
 
+&lt;return type=&quot;string&quot;&gt;the execution speed in milliseconds.&lt;/return&gt;
+
 &lt;comment&gt;Get execution speed (i.e., get the millisecond length of the delay following each selenium operation).  By default, there is no such delay, i.e.,
 the delay is 0 milliseconds.
 
@@ -538,21 +626,43 @@ an empty (blank) url, like this: openWindow(&quot;&quot;, &quot;myFunnyWindow&quot;).&lt;/p&gt;&lt;/comment&gt;
 
 &lt;param name=&quot;windowID&quot;&gt;the JavaScript window ID of the window to select&lt;/param&gt;
 
-&lt;comment&gt;Selects a popup window; once a popup window has been selected, all
+&lt;comment&gt;Selects a popup window using a window locator; once a popup window has been selected, all
 commands go to that window. To select the main window again, use null
 as the target.
 
-&lt;p&gt;Selenium has several strategies for finding the window object referred to by the &quot;windowID&quot; parameter.&lt;/p&gt;
+&lt;p&gt;
 
-&lt;p&gt;1.) if windowID is null, then it is assumed the user is referring to the original window instantiated by the browser).&lt;/p&gt;
+Window locators provide different ways of specifying the window object:
+by title, by internal JavaScript &quot;name,&quot; or by JavaScript variable.
+&lt;/p&gt;
+&lt;ul&gt;
+&lt;li&gt;&lt;strong&gt;title&lt;/strong&gt;=&lt;em&gt;My Special Window&lt;/em&gt;:
+Finds the window using the text that appears in the title bar.  Be careful;
+two windows can share the same title.  If that happens, this locator will
+just pick one.
+&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;=&lt;em&gt;myWindow&lt;/em&gt;:
+Finds the window using its internal JavaScript &quot;name&quot; property.  This is the second 
+parameter &quot;windowName&quot; passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
+(which Selenium intercepts).
+&lt;/li&gt;
+&lt;li&gt;&lt;strong&gt;var&lt;/strong&gt;=&lt;em&gt;variableName&lt;/em&gt;:
+Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current
+application window, e.g. &quot;window.foo = window.open(url);&quot;.  In those cases, you can open the window using
+&quot;var=foo&quot;.
+&lt;/li&gt;
+&lt;/ul&gt;
+&lt;p&gt;
+If no window locator prefix is provided, we'll try to guess what you mean like this:&lt;/p&gt;
+&lt;p&gt;1.) if windowID is null, (or the string &quot;null&quot;) then it is assumed the user is referring to the original window instantiated by the browser).&lt;/p&gt;
 &lt;p&gt;2.) if the value of the &quot;windowID&quot; parameter is a JavaScript variable name in the current application window, then it is assumed
 that this variable contains the return value from a call to the JavaScript window.open() method.&lt;/p&gt;
-&lt;p&gt;3.) Otherwise, selenium looks in a hash it maintains that maps string names to window objects.  Each of these string 
-names matches the second parameter &quot;windowName&quot; past to the JavaScript method  window.open(url, windowName, windowFeatures, replaceFlag)
-(which selenium intercepts).&lt;/p&gt;
+&lt;p&gt;3.) Otherwise, selenium looks in a hash it maintains that maps string names to window &quot;names&quot;.&lt;/p&gt;
+&lt;p&gt;4.) If &lt;em&gt;that&lt;/em&gt; fails, we'll try looping over all of the known windows to try to find the appropriate &quot;title&quot;.
+Since &quot;title&quot; is not necessarily unique, this may have unexpected behavior.&lt;/p&gt;
 
-&lt;p&gt;If you're having trouble figuring out what is the name of a window that you want to manipulate, look at the selenium log messages
-which identify the names of windows created via window.open (and therefore intercepted by selenium).  You will see messages
+&lt;p&gt;If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages
+which identify the names of windows created via window.open (and therefore intercepted by Selenium).  You will see messages
 like the following for each window as it is opened:&lt;/p&gt;
 
 &lt;p&gt;&lt;code&gt;debug: window.open call intercepted; window ID (which you can use with selectWindow()) is &quot;myNewWindow&quot;&lt;/code&gt;&lt;/p&gt;
@@ -570,29 +680,14 @@ an empty (blank) url, like this: openWindow(&quot;&quot;, &quot;myFunnyWindow&quot;).&lt;/p&gt;&lt;/comment&gt;
 &lt;comment&gt;Selects a frame within the current window.  (You may invoke this command
 multiple times to select nested frames.)  To select the parent frame, use
 &quot;relative=parent&quot; as a locator; to select the top frame, use &quot;relative=top&quot;.
+You can also select a frame by its 0-based index number; select the first frame with
+&quot;index=0&quot;, or the third frame with &quot;index=2&quot;.
 
 &lt;p&gt;You may also use a DOM expression to identify the frame you want directly,
 like this: &lt;code&gt;dom=frames[&quot;main&quot;].frames[&quot;subframe&quot;]&lt;/code&gt;&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
-&lt;function name=&quot;getLogMessages&quot;&gt;
-
-&lt;return type=&quot;string&quot;&gt;all log messages seen since the last call to this API&lt;/return&gt;
-
-&lt;comment&gt;Return the contents of the log.
-
-&lt;p&gt;This is a placeholder intended to make the code generator make this API
-available to clients.  The selenium server will intercept this call, however,
-and return its recordkeeping of log messages since the last call to this API.
-Thus this code in JavaScript will never be called.&lt;/p&gt;
-
-&lt;p&gt;The reason I opted for a servercentric solution is to be able to support
-multiple frames served from different domains, which would break a
-centralized JavaScript logging mechanism under some conditions.&lt;/p&gt;&lt;/comment&gt;
-
-&lt;/function&gt;
-
 &lt;function name=&quot;getWhetherThisFrameMatchFrameExpression&quot;&gt;
 
 &lt;return type=&quot;boolean&quot;&gt;true if the new frame is this code's window&lt;/return&gt;
@@ -631,7 +726,7 @@ The selected window will return true, while all others will return false.&lt;/p&gt;&lt;/c
 
 &lt;function name=&quot;waitForPopUp&quot;&gt;
 
-&lt;param name=&quot;windowID&quot;&gt;the JavaScript window ID of the window that will appear&lt;/param&gt;
+&lt;param name=&quot;windowID&quot;&gt;the JavaScript window &quot;name&quot; of the window that will appear (not the text of the title bar)&lt;/param&gt;
 
 &lt;param name=&quot;timeout&quot;&gt;a timeout in milliseconds, after which the action will return with an error&lt;/param&gt;
 
@@ -641,10 +736,40 @@ The selected window will return true, while all others will return false.&lt;/p&gt;&lt;/c
 
 &lt;function name=&quot;chooseCancelOnNextConfirmation&quot;&gt;
 
-&lt;comment&gt;By default, Selenium's overridden window.confirm() function will
-return true, as if the user had manually clicked OK.  After running
+&lt;comment&gt;&lt;p&gt;
+By default, Selenium's overridden window.confirm() function will
+return true, as if the user had manually clicked OK; after running
 this command, the next call to confirm() will return false, as if
-the user had clicked Cancel.&lt;/comment&gt;
+the user had clicked Cancel.  Selenium will then resume using the
+default behavior for future confirmations, automatically returning 
+true (OK) unless/until you explicitly call this command for each
+confirmation.
+&lt;/p&gt;
+&lt;p&gt;
+Take note - every time a confirmation comes up, you must
+consume it with a corresponding getConfirmation, or else
+the next selenium operation will fail.
+&lt;/p&gt;&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;chooseOkOnNextConfirmation&quot;&gt;
+
+&lt;comment&gt;&lt;p&gt;
+Undo the effect of calling chooseCancelOnNextConfirmation.  Note
+that Selenium's overridden window.confirm() function will normally automatically
+return true, as if the user had manually clicked OK, so you shouldn't
+need to use this command unless for some reason you need to change
+your mind prior to the next confirmation.  After any confirmation, Selenium will resume using the
+default behavior for future confirmations, automatically returning 
+true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each
+confirmation.
+&lt;/p&gt;
+&lt;p&gt;
+Take note - every time a confirmation comes up, you must
+consume it with a corresponding getConfirmation, or else
+the next selenium operation will fail.
+&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -719,13 +844,13 @@ This function never throws an exception
 &lt;comment&gt;Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
 
 &lt;p&gt;Getting an alert has the same effect as manually clicking OK. If an
-alert is generated but you do not get/verify it, the next Selenium action
+alert is generated but you do not consume it with getAlert, the next Selenium action
 will fail.&lt;/p&gt;
 
-&lt;p&gt;NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
+&lt;p&gt;Under Selenium, JavaScript alerts will NOT pop up a visible alert
 dialog.&lt;/p&gt;
 
-&lt;p&gt;NOTE: Selenium does NOT support JavaScript alerts that are generated in a
+&lt;p&gt;Selenium does NOT support JavaScript alerts that are generated in a
 page's onload() event handler. In this case a visible dialog WILL be
 generated and Selenium will hang until someone manually clicks OK.&lt;/p&gt;&lt;/comment&gt;
 
@@ -741,8 +866,11 @@ the previous action.
 &lt;p&gt;
 By default, the confirm function will return true, having the same effect
 as manually clicking OK. This can be changed by prior execution of the
-chooseCancelOnNextConfirmation command. If an confirmation is generated
-but you do not get/verify it, the next Selenium action will fail.
+chooseCancelOnNextConfirmation command. 
+&lt;/p&gt;
+&lt;p&gt;
+If an confirmation is generated but you do not consume it with getConfirmation,
+the next Selenium action will fail.
 &lt;/p&gt;
 
 &lt;p&gt;
@@ -846,13 +974,12 @@ text shown to the user.&lt;/comment&gt;
 have multiple lines, but only the result of the last line will be returned.
 
 &lt;p&gt;Note that, by default, the snippet will run in the context of the &quot;selenium&quot;
-object itself, so &lt;code&gt;this&lt;/code&gt; will refer to the Selenium object, and &lt;code&gt;window&lt;/code&gt; will
-refer to the top-level runner test window, not the window of your application.&lt;/p&gt;
+object itself, so &lt;code&gt;this&lt;/code&gt; will refer to the Selenium object.  Use &lt;code&gt;window&lt;/code&gt; to
+refer to the window of your application, e.g. &lt;code&gt;window.document.getElementById('foo')&lt;/code&gt;&lt;/p&gt;
 
-&lt;p&gt;If you need a reference to the window of your application, you can refer
-to &lt;code&gt;this.browserbot.getCurrentWindow()&lt;/code&gt; and if you need to use
+&lt;p&gt;If you need to use
 a locator to refer to a single element in your application page, you can
-use &lt;code&gt;this.browserbot.findElement(&quot;foo&quot;)&lt;/code&gt; where &quot;foo&quot; is your locator.&lt;/p&gt;&lt;/comment&gt;
+use &lt;code&gt;this.browserbot.findElement(&quot;id=foo&quot;)&lt;/code&gt; where &quot;id=foo&quot; is your locator.&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -981,9 +1108,11 @@ tableLocator.row.column, where row and column start at 0.&lt;/comment&gt;
 
 &lt;return type=&quot;string&quot;&gt;the value of the specified attribute&lt;/return&gt;
 
-&lt;param name=&quot;attributeLocator&quot;&gt;an element locator followed by an&lt;/param&gt;
+&lt;param name=&quot;attributeLocator&quot;&gt;an element locator followed by an &amp;#064; sign and then the name of the attribute, e.g. &quot;foo&amp;#064;bar&quot;&lt;/param&gt;
 
-&lt;comment&gt;Gets the value of an element attribute.&lt;/comment&gt;
+&lt;comment&gt;Gets the value of an element attribute. The value of the attribute may
+differ across browsers (this is the case for the &quot;style&quot; attribute, for
+example).&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -1126,17 +1255,13 @@ just send one &quot;mousemove&quot; at the start location and then one final one at the en
 
 &lt;function name=&quot;windowFocus&quot;&gt;
 
-&lt;param name=&quot;windowName&quot;&gt;name of the window to be given focus&lt;/param&gt;
-
-&lt;comment&gt;Gives focus to a window&lt;/comment&gt;
+&lt;comment&gt;Gives focus to the currently selected window&lt;/comment&gt;
 
 &lt;/function&gt;
 
 &lt;function name=&quot;windowMaximize&quot;&gt;
 
-&lt;param name=&quot;windowName&quot;&gt;name of the window to be enlarged&lt;/param&gt;
-
-&lt;comment&gt;Resize window to take up the entire screen&lt;/comment&gt;
+&lt;comment&gt;Resize currently selected window to take up the entire screen&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -1197,13 +1322,13 @@ will be ignored.&lt;/comment&gt;
 
 &lt;function name=&quot;isOrdered&quot;&gt;
 
-&lt;return type=&quot;boolean&quot;&gt;true if two elements are ordered and have same parent, false otherwise&lt;/return&gt;
+&lt;return type=&quot;boolean&quot;&gt;true if element1 is the previous sibling of element2, false otherwise&lt;/return&gt;
 
 &lt;param name=&quot;locator1&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to the first element&lt;/param&gt;
 
 &lt;param name=&quot;locator2&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to the second element&lt;/param&gt;
 
-&lt;comment&gt;Check if these two elements have same parent and are ordered. Two same elements will
+&lt;comment&gt;Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will
 not be considered ordered.&lt;/comment&gt;
 
 &lt;/function&gt;
@@ -1262,33 +1387,68 @@ This method will fail if the specified element isn't an input element or textare
 
 &lt;/function&gt;
 
-&lt;function name=&quot;setContext&quot;&gt;
+&lt;function name=&quot;getExpression&quot;&gt;
 
-&lt;param name=&quot;context&quot;&gt;the message to be sent to the browser&lt;/param&gt;
+&lt;return type=&quot;string&quot;&gt;the value passed in&lt;/return&gt;
 
-&lt;param name=&quot;logLevelThreshold&quot;&gt;one of &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot;, sets the threshold for browser-side logging&lt;/param&gt;
+&lt;param name=&quot;expression&quot;&gt;the value to return&lt;/param&gt;
 
-&lt;comment&gt;Writes a message to the status bar and adds a note to the browser-side
-log.
+&lt;comment&gt;Returns the specified expression.
+
+&lt;p&gt;This is useful because of JavaScript preprocessing.
+It is used to generate commands like assertExpression and waitForExpression.&lt;/p&gt;&lt;/comment&gt;
 
-&lt;p&gt;If logLevelThreshold is specified, set the threshold for logging
-to that level (debug, info, warn, error).&lt;/p&gt;
+&lt;/function&gt;
+
+&lt;function name=&quot;getXpathCount&quot;&gt;
+
+&lt;return type=&quot;number&quot;&gt;the number of nodes that match the specified xpath&lt;/return&gt;
+
+&lt;param name=&quot;xpath&quot;&gt;the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you.&lt;/param&gt;
 
-&lt;p&gt;(Note that the browser-side logs will &lt;i&gt;not&lt;/i&gt; be sent back to the
-server, and are invisible to the Client Driver.)&lt;/p&gt;&lt;/comment&gt;
+&lt;comment&gt;Returns the number of nodes that match the specified xpath, eg. &quot;//table&quot; would give
+the number of tables.&lt;/comment&gt;
 
 &lt;/function&gt;
 
-&lt;function name=&quot;getExpression&quot;&gt;
+&lt;function name=&quot;assignId&quot;&gt;
 
-&lt;return type=&quot;string&quot;&gt;the value passed in&lt;/return&gt;
+&lt;param name=&quot;locator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to an element&lt;/param&gt;
 
-&lt;param name=&quot;expression&quot;&gt;the value to return&lt;/param&gt;
+&lt;param name=&quot;identifier&quot;&gt;a string to be used as the ID of the specified element&lt;/param&gt;
 
-&lt;comment&gt;Returns the specified expression.
+&lt;comment&gt;Temporarily sets the &quot;id&quot; attribute of the specified element, so you can locate it in the future
+using its ID rather than a slow/complicated XPath.  This ID will disappear once the page is
+reloaded.&lt;/comment&gt;
 
-&lt;p&gt;This is useful because of JavaScript preprocessing.
-It is used to generate commands like assertExpression and waitForExpression.&lt;/p&gt;&lt;/comment&gt;
+&lt;/function&gt;
+
+&lt;function name=&quot;allowNativeXpath&quot;&gt;
+
+&lt;param name=&quot;allow&quot;&gt;boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath&lt;/param&gt;
+
+&lt;comment&gt;Specifies whether Selenium should use the native in-browser implementation
+of XPath (if any native version is available); if you pass &quot;false&quot; to
+this function, we will always use our pure-JavaScript xpath library.
+Using the pure-JS xpath library can improve the consistency of xpath
+element locators between different browser vendors, but the pure-JS
+version is much slower than the native implementations.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;ignoreAttributesWithoutValue&quot;&gt;
+
+&lt;param name=&quot;ignore&quot;&gt;boolean, true means we'll ignore attributes without value                        at the expense of xpath &quot;correctness&quot;; false means                        we'll sacrifice speed for correctness.&lt;/param&gt;
+
+&lt;comment&gt;Specifies whether Selenium will ignore xpath attributes that have no
+value, i.e. are the empty string, when using the non-native xpath
+evaluation engine. You'd want to do this for performance reasons in IE.
+However, this could break certain xpaths, for example an xpath that looks
+for an attribute whose value is NOT the empty string.
+
+The hope is that such xpaths are relatively rare, but the user should
+have the option of using them. Note that this only influences xpath
+evaluation when using the ajaxslt engine (i.e. not &quot;javascript-xpath&quot;).&lt;/comment&gt;
 
 &lt;/function&gt;
 
@@ -1336,6 +1496,21 @@ wait immediately after a Selenium command that caused a page-load.&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;waitForFrameToLoad&quot;&gt;
+
+&lt;param name=&quot;frameAddress&quot;&gt;FrameAddress from the server side&lt;/param&gt;
+
+&lt;param name=&quot;timeout&quot;&gt;a timeout in milliseconds, after which this command will return with an error&lt;/param&gt;
+
+&lt;comment&gt;Waits for a new frame to load.
+
+&lt;p&gt;Selenium constantly keeps track of new pages and frames loading, 
+and sets a &quot;newPageLoaded&quot; flag when it first notices a page load.&lt;/p&gt;
+
+See waitForPageToLoad for more information.&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;getCookie&quot;&gt;
 
 &lt;return type=&quot;string&quot;&gt;all cookies of the current page under test&lt;/return&gt;
@@ -1344,11 +1519,31 @@ wait immediately after a Selenium command that caused a page-load.&lt;/p&gt;&lt;/comment&gt;
 
 &lt;/function&gt;
 
+&lt;function name=&quot;getCookieByName&quot;&gt;
+
+&lt;return type=&quot;string&quot;&gt;the value of the cookie&lt;/return&gt;
+
+&lt;param name=&quot;name&quot;&gt;the name of the cookie&lt;/param&gt;
+
+&lt;comment&gt;Returns the value of the cookie with the specified name, or throws an error if the cookie is not present.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;isCookiePresent&quot;&gt;
+
+&lt;return type=&quot;boolean&quot;&gt;true if a cookie with the specified name is present, or false otherwise.&lt;/return&gt;
+
+&lt;param name=&quot;name&quot;&gt;the name of the cookie&lt;/param&gt;
+
+&lt;comment&gt;Returns true if a cookie with the specified name is present, or false otherwise.&lt;/comment&gt;
+
+&lt;/function&gt;
+
 &lt;function name=&quot;createCookie&quot;&gt;
 
 &lt;param name=&quot;nameValuePair&quot;&gt;name and value of the cookie in a format &quot;name=value&quot;&lt;/param&gt;
 
-&lt;param name=&quot;optionsString&quot;&gt;options for the cookie. Currently supported options include 'path' and 'max_age'.      the optionsString's format is &quot;path=/path/, max_age=60&quot;. The order of options are irrelevant, the unit      of the value of 'max_age' is second.&lt;/param&gt;
+&lt;param name=&quot;optionsString&quot;&gt;options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'.      the optionsString's format is &quot;path=/path/, max_age=60, domain=.foo.com&quot;. The order of options are irrelevant, the unit      of the value of 'max_age' is second.  Note that specifying a domain that isn't a subset of the current domain will      usually fail.&lt;/param&gt;
 
 &lt;comment&gt;Create a new cookie whose path and domain are same with those of current page
 under test, unless you specified a path for this cookie explicitly.&lt;/comment&gt;
@@ -1359,9 +1554,246 @@ under test, unless you specified a path for this cookie explicitly.&lt;/comment&gt;
 
 &lt;param name=&quot;name&quot;&gt;the name of the cookie to be deleted&lt;/param&gt;
 
-&lt;param name=&quot;path&quot;&gt;the path property of the cookie to be deleted&lt;/param&gt;
+&lt;param name=&quot;optionsString&quot;&gt;options for the cookie. Currently supported options include 'path', 'domain'      and 'recurse.' The optionsString's format is &quot;path=/path/, domain=.foo.com, recurse=true&quot;.      The order of options are irrelevant. Note that specifying a domain that isn't a subset of      the current domain will usually fail.&lt;/param&gt;
+
+&lt;comment&gt;Delete a named cookie with specified path and domain.  Be careful; to delete a cookie, you
+need to delete it using the exact same path and domain that were used to create the cookie.
+If the path is wrong, or the domain is wrong, the cookie simply won't be deleted.  Also
+note that specifying a domain that isn't a subset of the current domain will usually fail.
+
+Since there's no way to discover at runtime the original path and domain of a given cookie,
+we've added an option called 'recurse' to try all sub-domains of the current domain with
+all paths that are a subset of the current path.  Beware; this option can be slow.  In
+big-O notation, it operates in O(n*m) time, where n is the number of dots in the domain
+name and m is the number of slashes in the path.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;deleteAllVisibleCookies&quot;&gt;
+
+&lt;comment&gt;Calls deleteCookie with recurse=true on all cookies visible to the current page.
+As noted on the documentation for deleteCookie, recurse=true can be much slower
+than simply deleting the cookies using a known domain/path.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;setBrowserLogLevel&quot;&gt;
+
+&lt;param name=&quot;logLevel&quot;&gt;one of the following: &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot; or &quot;off&quot;&lt;/param&gt;
+
+&lt;comment&gt;Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded.
+Valid logLevel strings are: &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot; or &quot;off&quot;.
+To see the browser logs, you need to
+either show the log window in GUI mode, or enable browser-side logging in Selenium RC.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;runScript&quot;&gt;
+
+&lt;param name=&quot;script&quot;&gt;the JavaScript snippet to run&lt;/param&gt;
+
+&lt;comment&gt;Creates a new &quot;script&quot; tag in the body of the current test window, and 
+adds the specified text into the body of the command.  Scripts run in
+this way can often be debugged more easily than scripts executed using
+Selenium's &quot;getEval&quot; command.  Beware that JS exceptions thrown in these script
+tags aren't managed by Selenium, so you should probably wrap your script
+in try/catch blocks if there is any chance that the script will throw
+an exception.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;addLocationStrategy&quot;&gt;
+
+&lt;param name=&quot;strategyName&quot;&gt;the name of the strategy to define; this should use only   letters [a-zA-Z] with no spaces or other punctuation.&lt;/param&gt;
+
+&lt;param name=&quot;functionDefinition&quot;&gt;a string defining the body of a function in JavaScript.   For example: &lt;code&gt;return inDocument.getElementById(locator);&lt;/code&gt;&lt;/param&gt;
+
+&lt;comment&gt;Defines a new function for Selenium to locate elements on the page.
+For example,
+if you define the strategy &quot;foo&quot;, and someone runs click(&quot;foo=blah&quot;), we'll
+run your function, passing you the string &quot;blah&quot;, and click on the element 
+that your function
+returns, or throw an &quot;Element not found&quot; error if your function returns null.
+
+We'll pass three arguments to your function:
+&lt;ul&gt;
+&lt;li&gt;locator: the string the user passed in&lt;/li&gt;
+&lt;li&gt;inWindow: the currently selected window&lt;/li&gt;
+&lt;li&gt;inDocument: the currently selected document&lt;/li&gt;
+&lt;/ul&gt;
+The function must return null if the element can't be found.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;captureEntirePageScreenshot&quot;&gt;
+
+&lt;param name=&quot;filename&quot;&gt;the path to the file to persist the screenshot as. No                  filename extension will be appended by default.                  Directories will not be created if they do not exist,                    and an exception will be thrown, possibly by native                  code.&lt;/param&gt;
+
+&lt;param name=&quot;kwargs&quot;&gt;a kwargs string that modifies the way the screenshot                  is captured. Example: &quot;background=#CCFFDD&quot; .                  Currently valid options:                  &lt;dl&gt;                   &lt;dt&gt;background&lt;/dt&gt;                     &lt;dd&gt;the background CSS for the HTML document. This                     may be useful to set for capturing screenshots of                     less-than-ideal layouts, for example where absolute                     positioning causes the calculation of the canvas                     dimension to fail and a black background is exposed                     (possibly obscuring black text).&lt;/dd&gt;                  &lt;/dl&gt;&lt;/param&gt;
+
+&lt;comment&gt;Saves the entire contents of the current window canvas to a PNG file.
+Contrast this with the captureScreenshot command, which captures the
+contents of the OS viewport (i.e. whatever is currently being displayed
+on the monitor), and is implemented in the RC only. Currently this only
+works in Firefox when running in chrome mode, and in IE non-HTA using
+the EXPERIMENTAL &quot;Snapsie&quot; utility. The Firefox implementation is mostly
+borrowed from the Screengrab! Firefox extension. Please see
+http://www.screengrab.org and http://snapsie.sourceforge.net/ for
+details.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;rollup&quot;&gt;
+
+&lt;param name=&quot;rollupName&quot;&gt;the name of the rollup command&lt;/param&gt;
+
+&lt;param name=&quot;kwargs&quot;&gt;keyword arguments string that influences how the                    rollup expands into commands&lt;/param&gt;
+
+&lt;comment&gt;Executes a command rollup, which is a series of commands with a unique
+name, and optionally arguments that control the generation of the set of
+commands. If any one of the rolled-up commands fails, the rollup is
+considered to have failed. Rollups may also contain nested rollups.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;addScript&quot;&gt;
+
+&lt;param name=&quot;scriptContent&quot;&gt;the Javascript content of the script to add&lt;/param&gt;
+
+&lt;param name=&quot;scriptTagId&quot;&gt;(optional) the id of the new script tag. If                       specified, and an element with this id already                       exists, this operation will fail.&lt;/param&gt;
+
+&lt;comment&gt;Loads script content into a new script tag in the Selenium document. This
+differs from the runScript command in that runScript adds the script tag
+to the document of the AUT, not the Selenium document. The following
+entities in the script content are replaced by the characters they
+represent:
+
+    &amp;lt;
+    &amp;gt;
+    &amp;amp;
+
+The corresponding remove command is removeScript.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;removeScript&quot;&gt;
+
+&lt;param name=&quot;scriptTagId&quot;&gt;the id of the script element to remove.&lt;/param&gt;
+
+&lt;comment&gt;Removes a script tag from the Selenium document identified by the given
+id. Does nothing if the referenced tag doesn't exist.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;useXpathLibrary&quot;&gt;
+
+&lt;param name=&quot;libraryName&quot;&gt;name of the desired library Only the following three can be chosen:   ajaxslt - Google's library   javascript - Cybozu Labs' faster library   default - The default library.  Currently the default library is ajaxslt. If libraryName isn't one of these three, then  no change will be made.&lt;/param&gt;
+
+&lt;comment&gt;Allows choice of one of the available libraries.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;setContext&quot;&gt;
+
+&lt;param name=&quot;context&quot;&gt;the message to be sent to the browser&lt;/param&gt;
+
+&lt;comment&gt;Writes a message to the status bar and adds a note to the browser-side
+log.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;attachFile&quot;&gt;
+
+&lt;param name=&quot;fieldLocator&quot;&gt;an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;&lt;/param&gt;
+
+&lt;param name=&quot;fileLocator&quot;&gt;a URL pointing to the specified file. Before the file  can be set in the input field (fieldLocator), Selenium RC may need to transfer the file    to the local machine before attaching the file in a web page form. This is common in selenium  grid configurations where the RC server driving the browser is not the same  machine that started the test.   Supported Browsers: Firefox (&quot;*chrome&quot;) only.&lt;/param&gt;
+
+&lt;comment&gt;Sets a file input (upload) field to the file listed in fileLocator&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;captureScreenshot&quot;&gt;
+
+&lt;param name=&quot;filename&quot;&gt;the absolute path to the file to be written, e.g. &quot;c:\blah\screenshot.png&quot;&lt;/param&gt;
+
+&lt;comment&gt;Captures a PNG screenshot to the specified file.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;captureScreenshotToString&quot;&gt;
+
+&lt;return type=&quot;string&quot;&gt;The base 64 encoded string of the screen shot (PNG file)&lt;/return&gt;
+
+&lt;comment&gt;Capture a PNG screenshot.  It then returns the file as a base 64 encoded string.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;captureEntirePageScreenshotToString&quot;&gt;
+
+&lt;return type=&quot;string&quot;&gt;The base 64 encoded string of the page screenshot (PNG file)&lt;/return&gt;
+
+&lt;param name=&quot;kwargs&quot;&gt;A kwargs string that modifies the way the screenshot is captured. Example: &quot;background=#CCFFDD&quot;. This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed  (possibly obscuring black text).&lt;/param&gt;
+
+&lt;comment&gt;Downloads a screenshot of the browser current window canvas to a 
+based 64 encoded PNG file. The &lt;em&gt;entire&lt;/em&gt; windows canvas is captured,
+including parts rendered outside of the current view port.
+
+Currently this only works in Mozilla and when running in chrome mode.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;shutDownSeleniumServer&quot;&gt;
+
+&lt;comment&gt;Kills the running Selenium Server and all browser sessions.  After you run this command, you will no longer be able to send
+commands to the server; you can't remotely start the server once it has been stopped.  Normally
+you should prefer to run the &quot;stop&quot; command, which terminates the current browser session, rather than 
+shutting down the entire server.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;retrieveLastRemoteControlLogs&quot;&gt;
+
+&lt;return type=&quot;string&quot;&gt;The last N log messages as a multi-line string.&lt;/return&gt;
+
+&lt;comment&gt;Retrieve the last messages logged on a specific remote control. Useful for error reports, especially
+when running multiple remote controls in a distributed environment. The maximum number of log messages
+that can be retrieve is configured on remote control startup.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;keyDownNative&quot;&gt;
+
+&lt;param name=&quot;keycode&quot;&gt;an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!&lt;/param&gt;
+
+&lt;comment&gt;Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke.
+This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
+element, focus on the element first before running this command.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;keyUpNative&quot;&gt;
+
+&lt;param name=&quot;keycode&quot;&gt;an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!&lt;/param&gt;
+
+&lt;comment&gt;Simulates a user releasing a key by sending a native operating system keystroke.
+This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
+element, focus on the element first before running this command.&lt;/comment&gt;
+
+&lt;/function&gt;
+
+&lt;function name=&quot;keyPressNative&quot;&gt;
+
+&lt;param name=&quot;keycode&quot;&gt;an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!&lt;/param&gt;
 
-&lt;comment&gt;Delete a named cookie with specified path.&lt;/comment&gt;
+&lt;comment&gt;Simulates a user pressing and releasing a key by sending a native operating system keystroke.
+This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
+element, focus on the element first before running this command.&lt;/comment&gt;
 
 &lt;/function&gt;
 </diff>
      <filename>selenium-core/iedoc.xml</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/cssQuery/cssQuery-p.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/cssQuery/src/cssQuery-level2.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/cssQuery/src/cssQuery-level3.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/cssQuery/src/cssQuery-standard.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/cssQuery/src/cssQuery.js</filename>
    </modified>
    <modified>
      <diff>@@ -1339,8 +1339,8 @@ Selector.prototype = {
   },
 
   compileMatcher: function() {
-    this.match = new Function('element', 'if (!element.tagName) return false; \
-      return ' + this.buildMatchExpression());
+    this.match = new Function('element', 'if (!element.tagName) return false; \n' +
+    'return ' + this.buildMatchExpression());
   },
 
   findElements: function(scope) {</diff>
      <filename>selenium-core/lib/prototype.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/scriptaculous/builder.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/scriptaculous/controls.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/scriptaculous/dragdrop.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/scriptaculous/effects.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/scriptaculous/scriptaculous.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/scriptaculous/slider.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/lib/scriptaculous/unittest.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/scripts/find_matching_child.js</filename>
    </modified>
    <modified>
      <diff>@@ -31,7 +31,7 @@ function objectExtend(destination, source) {
   return destination;
 }
 
-function $() {
+function sel$() {
   var results = [], element;
   for (var i = 0; i &lt; arguments.length; i++) {
     element = arguments[i];
@@ -42,7 +42,7 @@ function $() {
   return results.length &lt; 2 ? results[0] : results;
 }
 
-function $A(iterable) {
+function sel$A(iterable) {
   if (!iterable) return [];
   if (iterable.toArray) {
     return iterable.toArray();
@@ -55,9 +55,9 @@ function $A(iterable) {
 }
 
 function fnBind() {
-  var args = $A(arguments), __method = args.shift(), object = args.shift();
+  var args = sel$A(arguments), __method = args.shift(), object = args.shift();
   var retval = function() {
-    return __method.apply(object, args.concat($A(arguments)));
+    return __method.apply(object, args.concat(sel$A(arguments)));
   }
   retval.__method = __method;
   return retval;
@@ -121,6 +121,46 @@ String.prototype.startsWith = function(str) {
     return this.indexOf(str) == 0;
 };
 
+/**
+ * Given a string literal that would appear in an XPath, puts it in quotes and
+ * returns it. Special consideration is given to literals who themselves
+ * contain quotes. It's possible for a concat() expression to be returned.
+ */
+String.prototype.quoteForXPath = function()
+{
+    if (/\'/.test(this)) {
+        if (/\&quot;/.test(this)) {
+            // concat scenario
+            var pieces = [];
+            var a = &quot;'&quot;, b = '&quot;', c;
+            for (var i = 0, j = 0; i &lt; this.length;) {
+                if (this.charAt(i) == a) {
+                    // encountered a quote that cannot be contained in current
+                    // quote, so need to flip-flop quoting scheme
+                    if (j &lt; i) {
+                        pieces.push(a + this.substring(j, i) + a);
+                        j = i;
+                    }
+                    c = a;
+                    a = b;
+                    b = c;
+                }
+                else {
+                    ++i;
+                }
+            }
+            pieces.push(a + this.substring(j) + a);
+            return 'concat(' + pieces.join(', ') + ')';
+        }
+        else {
+            // quote with doubles
+            return '&quot;' + this + '&quot;';
+        }
+    }
+    // quote with singles
+    return &quot;'&quot; + this + &quot;'&quot;;
+};
+
 // Returns the text in this element
 function getText(element) {
     var text = &quot;&quot;;
@@ -171,7 +211,7 @@ function getTextContent(element, preformatted) {
 }
 
 /**
- * Convert all newlines to \m
+ * Convert all newlines to \n
  */
 function normalizeNewlines(text)
 {
@@ -246,7 +286,7 @@ function getInputValue(inputElement) {
 /* Fire an event in a browser-compatible manner */
 function triggerEvent(element, eventType, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
     canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
-    if (element.fireEvent) {
+    if (element.fireEvent &amp;&amp; element.ownerDocument &amp;&amp; element.ownerDocument.createEventObject) { // IE
         var evt = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);        
         element.fireEvent('on' + eventType, evt);
     }
@@ -299,7 +339,7 @@ function createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, me
 function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown) {
     var keycode = getKeyCodeFromKeySequence(keySequence);
     canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
-    if (element.fireEvent) {
+    if (element.fireEvent &amp;&amp; element.ownerDocument &amp;&amp; element.ownerDocument.createEventObject) { // IE
         var keyEvent = createEventObject(element, controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown);
         keyEvent.keyCode = keycode;
         element.fireEvent('on' + eventType, keyEvent);
@@ -327,7 +367,7 @@ function triggerKeyEvent(element, eventType, keySequence, canBubble, controlKeyD
 }
 
 function removeLoadListener(element, command) {
-    LOG.info('Removing loadListenter for ' + element + ', ' + command);
+    LOG.debug('Removing loadListenter for ' + element + ', ' + command);
     if (window.removeEventListener)
         element.removeEventListener(&quot;load&quot;, command, true);
     else if (window.detachEvent)
@@ -335,7 +375,7 @@ function removeLoadListener(element, command) {
 }
 
 function addLoadListener(element, command) {
-    LOG.info('Adding loadListenter for ' + element + ', ' + command);
+    LOG.debug('Adding loadListenter for ' + element + ', ' + command);
     var augmentedCommand = function() {
         command.call(this, element);
     }
@@ -373,6 +413,25 @@ function getTagName(element) {
     return tagName;
 }
 
+function selArrayToString(a) {
+    if (isArray(a)) {
+        // DGF copying the array, because the array-like object may be a non-modifiable nodelist
+        var retval = [];
+        for (var i = 0; i &lt; a.length; i++) {
+            var item = a[i];
+            var replaced = new String(item).replace(/([,\\])/g, '\\$1');
+            retval[i] = replaced;
+        }
+        return retval;
+    }
+    return new String(a);
+}
+
+
+function isArray(x) {
+    return ((typeof x) == &quot;object&quot;) &amp;&amp; (x[&quot;length&quot;] != null);
+}
+
 function absolutify(url, baseUrl) {
     /** returns a relative url in its absolute form, given by baseUrl.
     * 
@@ -499,9 +558,30 @@ function reassembleLocation(loc) {
 }
 
 function canonicalize(url) {
+    if(url == &quot;about:blank&quot;)
+    {
+	return url;
+    }
     var tempLink = window.document.createElement(&quot;link&quot;);
-    tempLink.href = url; // this will canonicalize the href
-    return tempLink.href;
+    tempLink.href = url; // this will canonicalize the href on most browsers
+    var loc = parseUrl(tempLink.href)
+    if (!/\/\.\.\//.test(loc.pathname)) {
+    	return tempLink.href;
+    }
+  	// didn't work... let's try it the hard way
+  	var originalParts = loc.pathname.split(&quot;/&quot;);
+  	var newParts = [];
+  	newParts.push(originalParts.shift());
+  	for (var i = 0; i &lt; originalParts.length; i++) {
+  		var part = originalParts[i];
+  		if (&quot;..&quot; == part) {
+  			newParts.pop();
+  			continue;
+  		}
+  		newParts.push(part);
+  	}
+  	loc.pathname = newParts.join(&quot;/&quot;);
+    return reassembleLocation(loc);
 }
 
 function extractExceptionMessage(ex) {
@@ -590,6 +670,20 @@ PatternMatcher.strategies = {
             return this.regexp.test(actual);
         };
     },
+    
+    regexpi: function(regexpString) {
+        this.regexp = new RegExp(regexpString, &quot;i&quot;);
+        this.matches = function(actual) {
+            return this.regexp.test(actual);
+        };
+    },
+
+    regexi: function(regexpString) {
+        this.regexp = new RegExp(regexpString, &quot;i&quot;);
+        this.matches = function(actual) {
+            return this.regexp.test(actual);
+        };
+    },
 
 /**
  * &quot;globContains&quot; (aka &quot;wildmat&quot;) patterns, e.g. &quot;glob:one,two,*&quot;,
@@ -638,52 +732,54 @@ PatternMatcher.regexpFromGlob = function(glob) {
     return &quot;^&quot; + PatternMatcher.convertGlobMetaCharsToRegexpMetaChars(glob) + &quot;$&quot;;
 };
 
-var Assert = {
+if (!this[&quot;Assert&quot;]) Assert = {};
 
-    fail: function(message) {
-        throw new AssertionFailedError(message);
-    },
+
+Assert.fail = function(message) {
+    throw new AssertionFailedError(message);
+};
 
 /*
 * Assert.equals(comment?, expected, actual)
 */
-    equals: function() {
-        var args = new AssertionArguments(arguments);
-        if (args.expected === args.actual) {
-            return;
-        }
-        Assert.fail(args.comment +
-                    &quot;Expected '&quot; + args.expected +
-                    &quot;' but was '&quot; + args.actual + &quot;'&quot;);
-    },
+Assert.equals = function() {
+    var args = new AssertionArguments(arguments);
+    if (args.expected === args.actual) {
+        return;
+    }
+    Assert.fail(args.comment +
+                &quot;Expected '&quot; + args.expected +
+                &quot;' but was '&quot; + args.actual + &quot;'&quot;);
+};
+
+Assert.assertEquals = Assert.equals;
 
 /*
 * Assert.matches(comment?, pattern, actual)
 */
-    matches: function() {
-        var args = new AssertionArguments(arguments);
-        if (PatternMatcher.matches(args.expected, args.actual)) {
-            return;
-        }
-        Assert.fail(args.comment +
-                    &quot;Actual value '&quot; + args.actual +
-                    &quot;' did not match '&quot; + args.expected + &quot;'&quot;);
-    },
+Assert.matches = function() {
+    var args = new AssertionArguments(arguments);
+    if (PatternMatcher.matches(args.expected, args.actual)) {
+        return;
+    }
+    Assert.fail(args.comment +
+                &quot;Actual value '&quot; + args.actual +
+                &quot;' did not match '&quot; + args.expected + &quot;'&quot;);
+}
 
 /*
 * Assert.notMtches(comment?, pattern, actual)
 */
-    notMatches: function() {
-        var args = new AssertionArguments(arguments);
-        if (!PatternMatcher.matches(args.expected, args.actual)) {
-            return;
-        }
-        Assert.fail(args.comment +
-                    &quot;Actual value '&quot; + args.actual +
-                    &quot;' did match '&quot; + args.expected + &quot;'&quot;);
+Assert.notMatches = function() {
+    var args = new AssertionArguments(arguments);
+    if (!PatternMatcher.matches(args.expected, args.actual)) {
+        return;
     }
+    Assert.fail(args.comment +
+                &quot;Actual value '&quot; + args.actual +
+                &quot;' did match '&quot; + args.expected + &quot;'&quot;);
+}
 
-};
 
 // Preprocess the arguments to allow for an optional comment.
 function AssertionArguments(args) {
@@ -707,6 +803,17 @@ function AssertionFailedError(message) {
 
 function SeleniumError(message) {
     var error = new Error(message);
+    if (typeof(arguments.caller) != 'undefined') { // IE, not ECMA
+        var result = '';
+        for (var a = arguments.caller; a != null; a = a.caller) {
+            result += '&gt; ' + a.callee.toString() + '\n';
+            if (a.caller == a) {
+                result += '*';
+                break;
+            }
+        }
+        error.stack = result;
+    }
     error.isSeleniumError = true;
     return error;
 }
@@ -749,6 +856,11 @@ function openSeparateApplicationWindow(url, suppressMozillaWarning) {
     window.moveTo(window.screenX, 0);
 
     var appWindow = window.open(url + '?start=true', 'main');
+    if (appWindow == null) {
+        var errorMessage = &quot;Couldn't open app window; is the pop-up blocker enabled?&quot;
+        LOG.error(errorMessage);
+        throw new Error(&quot;Couldn't open app window; is the pop-up blocker enabled?&quot;);
+    }
     try {
         var windowHeight = 500;
         if (window.outerHeight) {
@@ -840,3 +952,665 @@ function safeScrollIntoView(element) {
     // TODO: work out how to scroll browsers that don't support
     // scrollIntoView (like Konqueror)
 }
+
+/**
+ * Returns the absolute time represented as an offset of the current time.
+ * Throws a SeleniumException if timeout is invalid.
+ *
+ * @param timeout  the number of milliseconds from &quot;now&quot; whose absolute time
+ *                 to return
+ */
+function getTimeoutTime(timeout) {
+    var now = new Date().getTime();
+    var timeoutLength = parseInt(timeout);
+    
+    if (isNaN(timeoutLength)) {
+        throw new SeleniumError(&quot;Timeout is not a number: '&quot; + timeout + &quot;'&quot;);
+    }
+    
+    return now + timeoutLength;
+}
+
+/**
+ * Returns true iff the current environment is the IDE.
+ */
+function is_IDE()
+{
+    return (typeof(SeleniumIDE) != 'undefined');
+}
+
+/**
+ * Logs a message if the Logger exists, and does nothing if it doesn't exist.
+ *
+ * @param level  the level to log at
+ * @param msg    the message to log
+ */
+function safe_log(level, msg)
+{
+    try {
+        LOG[level](msg);
+    }
+    catch (e) {
+        // couldn't log!
+    }
+}
+
+/**
+ * Displays a warning message to the user appropriate to the context under
+ * which the issue is encountered. This is primarily used to avoid popping up
+ * alert dialogs that might pause an automated test suite.
+ *
+ * @param msg  the warning message to display
+ */
+function safe_alert(msg)
+{
+    if (is_IDE()) {
+        alert(msg);
+    }
+}
+
+/**
+ * Returns true iff the given element represents a link with a javascript
+ * href attribute, and does not have an onclick attribute defined.
+ *
+ * @param element  the element to test
+ */
+function hasJavascriptHref(element) {
+    if (getTagName(element) != 'a') {
+        return false;
+    }
+    if (element.onclick) {
+        return false;
+    }
+    if (! element.href) {
+        return false;
+    }
+    if (! /\s*javascript:/i.test(element.href)) {
+        return false;
+    }
+    return true;
+}
+
+/**
+ * Returns the given element, or its nearest ancestor, that satisfies
+ * hasJavascriptHref(). Returns null if none is found.
+ *
+ * @param element  the element whose ancestors to test
+ */
+function getAncestorOrSelfWithJavascriptHref(element) {
+    if (hasJavascriptHref(element)) {
+        return element;
+    }
+    if (element.parentNode == null) {
+        return null;
+    }
+    return getAncestorOrSelfWithJavascriptHref(element.parentNode);
+}
+
+//******************************************************************************
+// Locator evaluation support
+
+/**
+ * Parses a Selenium locator, returning its type and the unprefixed locator
+ * string as an object.
+ *
+ * @param locator  the locator to parse
+ */
+function parse_locator(locator)
+{
+    var result = locator.match(/^([A-Za-z]+)=(.+)/);
+    if (result) {
+        return { type: result[1].toLowerCase(), string: result[2] };
+    }
+    return { type: 'implicit', string: locator };
+}
+
+/**
+ * Evaluates an xpath on a document, and returns a list containing nodes in the
+ * resulting nodeset. The browserbot xpath methods are now backed by this
+ * function. A context node may optionally be provided, and the xpath will be
+ * evaluated from that context.
+ *
+ * @param xpath       the xpath to evaluate
+ * @param inDocument  the document in which to evaluate the xpath.
+ * @param opts        (optional) An object containing various flags that can
+ *                    modify how the xpath is evaluated. Here's a listing of
+ *                    the meaningful keys:
+ *
+ *                     contextNode: 
+ *                       the context node from which to evaluate the xpath. If
+ *                       unspecified, the context will be the root document
+ *                       element.
+ *
+ *                     namespaceResolver:
+ *                       the namespace resolver function. Defaults to null.
+ *
+ *                     xpathLibrary:
+ *                       the javascript library to use for XPath. &quot;ajaxslt&quot; is
+ *                       the default. &quot;javascript-xpath&quot; is newer and faster,
+ *                       but needs more testing.
+ *
+ *                     allowNativeXpath:
+ *                       whether to allow native evaluate(). Defaults to true.
+ *
+ *                     ignoreAttributesWithoutValue:
+ *                       whether it's ok to ignore attributes without value
+ *                       when evaluating the xpath. This can greatly improve
+ *                       performance in IE; however, if your xpaths depend on
+ *                       such attributes, you can't ignore them! Defaults to
+ *                       true.
+ *
+ *                     returnOnFirstMatch:
+ *                       whether to optimize the XPath evaluation to only
+ *                       return the first match. The match, if any, will still
+ *                       be returned in a list. Defaults to false.
+ */
+function eval_xpath(xpath, inDocument, opts)
+{
+    if (!opts) {
+        var opts = {};
+    }
+    var contextNode = opts.contextNode
+        ? opts.contextNode : inDocument;
+    var namespaceResolver = opts.namespaceResolver
+        ? opts.namespaceResolver : null;
+    var xpathLibrary = opts.xpathLibrary
+        ? opts.xpathLibrary : null;
+    var allowNativeXpath = (opts.allowNativeXpath != undefined)
+        ? opts.allowNativeXpath : true;
+    var ignoreAttributesWithoutValue = (opts.ignoreAttributesWithoutValue != undefined)
+        ? opts.ignoreAttributesWithoutValue : true;
+    var returnOnFirstMatch = (opts.returnOnFirstMatch != undefined)
+        ? opts.returnOnFirstMatch : false;
+
+    // Trim any trailing &quot;/&quot;: not valid xpath, and remains from attribute
+    // locator.
+    if (xpath.charAt(xpath.length - 1) == '/') {
+        xpath = xpath.slice(0, -1);
+    }
+    // HUGE hack - remove namespace from xpath for IE
+    if (browserVersion &amp;&amp; browserVersion.isIE) {
+        xpath = xpath.replace(/x:/g, '')
+    }
+    
+    var nativeXpathAvailable = inDocument.evaluate;
+    var useNativeXpath = allowNativeXpath &amp;&amp; nativeXpathAvailable;
+    var useDocumentEvaluate = useNativeXpath;
+
+    // When using the new and faster javascript-xpath library,
+    // we'll use the TestRunner's document object, not the App-Under-Test's document.
+    // The new library only modifies the TestRunner document with the new 
+    // functionality.
+    if (xpathLibrary == 'javascript-xpath' &amp;&amp; !useNativeXpath) {
+        documentForXpath = document;
+        useDocumentEvaluate = true;
+    } else {
+        documentForXpath = inDocument;
+    }
+    var results = [];
+    
+    // this is either native xpath or javascript-xpath via TestRunner.evaluate 
+    if (useDocumentEvaluate) {
+        try {
+            // Regarding use of the second argument to document.evaluate():
+            // http://groups.google.com/group/comp.lang.javascript/browse_thread/thread/a59ce20639c74ba1/a9d9f53e88e5ebb5
+            var xpathResult = documentForXpath
+                .evaluate((contextNode == inDocument ? xpath : '.' + xpath),
+                    contextNode, namespaceResolver, 0, null);
+        }
+        catch (e) {
+            throw new SeleniumError(&quot;Invalid xpath: &quot; + extractExceptionMessage(e));
+        }
+        finally{
+            if (xpathResult == null) {
+                // If the result is null, we should still throw an Error.
+                throw new SeleniumError(&quot;Invalid xpath: &quot; + xpath); 
+            }
+        }
+        var result = xpathResult.iterateNext();
+        while (result) {
+            results.push(result);
+            result = xpathResult.iterateNext();
+        }
+        return results;
+    }
+
+    // If not, fall back to slower JavaScript implementation
+    // DGF set xpathdebug = true (using getEval, if you like) to turn on JS XPath debugging
+    //xpathdebug = true;
+    var context;
+    if (contextNode == inDocument) {
+        context = new ExprContext(inDocument);
+    }
+    else {
+        // provide false values to get the default constructor values
+        context = new ExprContext(contextNode, false, false,
+            contextNode.parentNode);
+    }
+    context.setCaseInsensitive(true);
+    context.setIgnoreAttributesWithoutValue(ignoreAttributesWithoutValue);
+    context.setReturnOnFirstMatch(returnOnFirstMatch);
+    var xpathObj;
+    try {
+        xpathObj = xpathParse(xpath);
+    }
+    catch (e) {
+        throw new SeleniumError(&quot;Invalid xpath: &quot; + extractExceptionMessage(e));
+    }
+    var xpathResult = xpathObj.evaluate(context);
+    if (xpathResult &amp;&amp; xpathResult.value) {
+        for (var i = 0; i &lt; xpathResult.value.length; ++i) {
+            results.push(xpathResult.value[i]);
+        }
+    }
+    return results;
+}
+
+/**
+ * Returns the full resultset of a CSS selector evaluation.
+ */
+function eval_css(locator, inDocument)
+{
+    return cssQuery(locator, inDocument);
+}
+
+/**
+ * This function duplicates part of BrowserBot.findElement() to open up locator
+ * evaluation on arbitrary documents. It returns a plain old array of located
+ * elements found by using a Selenium locator.
+ * 
+ * Multiple results may be generated for xpath and CSS locators. Even though a
+ * list could potentially be generated for other locator types, such as link,
+ * we don't try for them, because they aren't very expressive location
+ * strategies; if you want a list, use xpath or CSS. Furthermore, strategies
+ * for these locators have been optimized to only return the first result. For
+ * these types of locators, performance is more important than ideal behavior.
+ * 
+ * @param locator          a locator string
+ * @param inDocument       the document in which to apply the locator
+ * @param opt_contextNode  the context within which to evaluate the locator
+ *
+ * @return  a list of result elements
+ */
+function eval_locator(locator, inDocument, opt_contextNode)
+{
+    locator = parse_locator(locator);
+    
+    var pageBot;
+    if (typeof(selenium) != 'undefined' &amp;&amp; selenium != undefined) {
+        if (typeof(editor) == 'undefined' || editor.state == 'playing') {
+            safe_log('info', 'Trying [' + locator.type + ']: '
+                + locator.string);
+        }
+        pageBot = selenium.browserbot;
+    }
+    else {
+        if (!UI_GLOBAL.mozillaBrowserBot) {
+            // create a browser bot to evaluate the locator. Hand it the IDE
+            // window as a dummy window, and cache it for future use.
+            UI_GLOBAL.mozillaBrowserBot = new MozillaBrowserBot(window)
+        }
+        pageBot = UI_GLOBAL.mozillaBrowserBot;
+    }
+    
+    var results = [];
+    
+    if (locator.type == 'xpath' || (locator.string.charAt(0) == '/' &amp;&amp;
+        locator.type == 'implicit')) {
+        results = eval_xpath(locator.string, inDocument,
+            { contextNode: opt_contextNode });
+    }
+    else if (locator.type == 'css') {
+        results = eval_css(locator.string, inDocument);
+    }
+    else {
+        var element = pageBot
+            .findElementBy(locator.type, locator.string, inDocument);
+        if (element != null) {
+            results.push(element);
+        }
+    }
+    
+    return results;
+}
+
+//******************************************************************************
+// UI-Element
+
+/**
+ * Escapes the special regular expression characters in a string intended to be
+ * used as a regular expression.
+ *
+ * Based on: http://simonwillison.net/2006/Jan/20/escape/
+ */
+RegExp.escape = (function() {
+    var specials = [
+        '/', '.', '*', '+', '?', '|', '^', '$',
+        '(', ')', '[', ']', '{', '}', '\\'
+    ];
+    
+    var sRE = new RegExp(
+        '(\\' + specials.join('|\\') + ')', 'g'
+    );
+  
+    return function(text) {
+        return text.replace(sRE, '\\$1');
+    }
+})();
+
+/**
+ * Returns true if two arrays are identical, and false otherwise.
+ *
+ * @param a1  the first array, may only contain simple values (strings or
+ *            numbers)
+ * @param a2  the second array, same restricts on data as for a1
+ * @return    true if the arrays are equivalent, false otherwise.
+ */
+function are_equal(a1, a2)
+{
+    if (typeof(a1) != typeof(a2))
+        return false;
+    
+    switch(typeof(a1)) {
+        case 'object':
+            // arrays
+            if (a1.length) {
+                if (a1.length != a2.length)
+                    return false;
+                for (var i = 0; i &lt; a1.length; ++i) {
+                    if (!are_equal(a1[i], a2[i]))
+                        return false
+                }
+            }
+            // associative arrays
+            else {
+                var keys = {};
+                for (var key in a1) {
+                    keys[key] = true;
+                }
+                for (var key in a2) {
+                    keys[key] = true;
+                }
+                for (var key in keys) {
+                    if (!are_equal(a1[key], a2[key]))
+                        return false;
+                }
+            }
+            return true;
+            
+        default:
+            return a1 == a2;
+    }
+}
+
+
+/**
+ * Create a clone of an object and return it. This is a deep copy of everything
+ * but functions, whose references are copied. You shouldn't expect a deep copy
+ * of functions anyway.
+ *
+ * @param orig  the original object to copy
+ * @return      a deep copy of the original object. Any functions attached,
+ *              however, will have their references copied only.
+ */
+function clone(orig) {
+    var copy;
+    switch(typeof(orig)) {
+        case 'object':
+            copy = (orig.length) ? [] : {};
+            for (var attr in orig) {
+                copy[attr] = clone(orig[attr]);
+            }
+            break;
+        default:
+            copy = orig;
+            break;
+    }
+    return copy;
+}
+
+/**
+ * Emulates php's print_r() functionality. Returns a nicely formatted string
+ * representation of an object. Very useful for debugging.
+ *
+ * @param object    the object to dump
+ * @param maxDepth  the maximum depth to recurse into the object. Ellipses will
+ *                  be shown for objects whose depth exceeds the maximum.
+ * @param indent    the string to use for indenting progressively deeper levels
+ *                  of the dump.
+ * @return          a string representing a dump of the object
+ */
+function print_r(object, maxDepth, indent)
+{
+    var parentIndent, attr, str = &quot;&quot;;
+    if (arguments.length == 1) {
+        var maxDepth = Number.MAX_VALUE;
+    } else {
+        maxDepth--;
+    }
+    if (arguments.length &lt; 3) {
+        parentIndent = ''
+        var indent = '    ';
+    } else {
+        parentIndent = indent;
+        indent += '    ';
+    }
+
+    switch(typeof(object)) {
+    case 'object':
+        if (object.length != undefined) {
+            if (object.length == 0) {
+                str += &quot;Array ()\r\n&quot;;
+            }
+            else {
+                str += &quot;Array (\r\n&quot;;
+                for (var i = 0; i &lt; object.length; ++i) {
+                    str += indent + '[' + i + '] =&gt; ';
+                    if (maxDepth == 0)
+                        str += &quot;...\r\n&quot;;
+                    else
+                        str += print_r(object[i], maxDepth, indent);
+                }
+                str += parentIndent + &quot;)\r\n&quot;;
+            }
+        }
+        else {
+            str += &quot;Object (\r\n&quot;;
+            for (attr in object) {
+                str += indent + &quot;[&quot; + attr + &quot;] =&gt; &quot;;
+                if (maxDepth == 0)
+                    str += &quot;...\r\n&quot;;
+                else
+                    str += print_r(object[attr], maxDepth, indent);
+            }
+            str += parentIndent + &quot;)\r\n&quot;;
+        }
+        break;
+    case 'boolean':
+        str += (object ? 'true' : 'false') + &quot;\r\n&quot;;
+        break;
+    case 'function':
+        str += &quot;Function\r\n&quot;;
+        break;
+    default:
+        str += object + &quot;\r\n&quot;;
+        break;
+
+    }
+    return str;
+}
+
+/**
+ * Return an array containing all properties of an object. Perl-style.
+ *
+ * @param object  the object whose keys to return
+ * @return        array of object keys, as strings
+ */
+function keys(object)
+{
+    var keys = [];
+    for (var k in object) {
+        keys.push(k);
+    }
+    return keys;
+}
+
+/**
+ * Emulates python's range() built-in. Returns an array of integers, counting
+ * up (or down) from start to end. Note that the range returned is up to, but
+ * NOT INCLUDING, end.
+ *.
+ * @param start  integer from which to start counting. If the end parameter is
+ *               not provided, this value is considered the end and start will
+ *               be zero.
+ * @param end    integer to which to count. If omitted, the function will count
+ *               up from zero to the value of the start parameter. Note that
+ *               the array returned will count up to but will not include this
+ *               value.
+ * @return       an array of consecutive integers. 
+ */
+function range(start, end)
+{
+    if (arguments.length == 1) {
+        var end = start;
+        start = 0;
+    }
+    
+    var r = [];
+    if (start &lt; end) {
+        while (start != end)
+            r.push(start++);
+    }
+    else {
+        while (start != end)
+            r.push(start--);
+    }
+    return r;
+}
+
+/**
+ * Parses a python-style keyword arguments string and returns the pairs in a
+ * new object.
+ *
+ * @param  kwargs  a string representing a set of keyword arguments. It should
+ *                 look like &lt;tt&gt;keyword1=value1, keyword2=value2, ...&lt;/tt&gt;
+ * @return         an object mapping strings to strings
+ */
+function parse_kwargs(kwargs)
+{
+    var args = new Object();
+    var pairs = kwargs.split(/,/);
+    for (var i = 0; i &lt; pairs.length;) {
+        if (i &gt; 0 &amp;&amp; pairs[i].indexOf('=') == -1) {
+            // the value string contained a comma. Glue the parts back together.
+            pairs[i-1] += ',' + pairs.splice(i, 1)[0];
+        }
+        else {
+            ++i;
+        }
+    }
+    for (var i = 0; i &lt; pairs.length; ++i) {
+        var splits = pairs[i].split(/=/);
+        if (splits.length == 1) {
+            continue;
+        }
+        var key = splits.shift();
+        var value = splits.join('=');
+        args[key.trim()] = value.trim();
+    }
+    return args;
+}
+
+/**
+ * Creates a python-style keyword arguments string from an object.
+ *
+ * @param args        an associative array mapping strings to strings
+ * @param sortedKeys  (optional) a list of keys of the args parameter that
+ *                    specifies the order in which the arguments will appear in
+ *                    the returned kwargs string
+ *
+ * @return            a kwarg string representation of args
+ */
+function to_kwargs(args, sortedKeys)
+{
+    var s = '';
+    if (!sortedKeys) {
+        var sortedKeys = keys(args).sort();
+    }
+    for (var i = 0; i &lt; sortedKeys.length; ++i) {
+        var k = sortedKeys[i];
+        if (args[k] != undefined) {
+            if (s) {
+                s += ', ';
+            }
+            s += k + '=' + args[k];
+        }
+    }
+    return s;
+}
+
+/**
+ * Returns true if a node is an ancestor node of a target node, and false
+ * otherwise.
+ *
+ * @param node    the node being compared to the target node
+ * @param target  the target node
+ * @return        true if node is an ancestor node of target, false otherwise.
+ */
+function is_ancestor(node, target)
+{
+    while (target.parentNode) {
+        target = target.parentNode;
+        if (node == target)
+            return true;
+    }
+    return false;
+}
+
+//******************************************************************************
+// parseUri 1.2.1
+// MIT License
+
+/*
+Copyright (c) 2007 Steven Levithan &lt;stevenlevithan.com&gt;
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the &quot;Software&quot;), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+*/
+
+function parseUri (str) {
+    var o   = parseUri.options,
+        m   = o.parser[o.strictMode ? &quot;strict&quot; : &quot;loose&quot;].exec(str),
+        uri = {},
+        i   = 14;
+
+    while (i--) uri[o.key[i]] = m[i] || &quot;&quot;;
+
+    uri[o.q.name] = {};
+    uri[o.key[12]].replace(o.q.parser, function ($0, $1, $2) {
+        if ($1) uri[o.q.name][$1] = $2;
+    });
+
+    return uri;
+};
+
+parseUri.options = {
+    strictMode: false,
+    key: [&quot;source&quot;,&quot;protocol&quot;,&quot;authority&quot;,&quot;userInfo&quot;,&quot;user&quot;,&quot;password&quot;,&quot;host&quot;,&quot;port&quot;,&quot;relative&quot;,&quot;path&quot;,&quot;directory&quot;,&quot;file&quot;,&quot;query&quot;,&quot;anchor&quot;],
+    q:   {
+        name:   &quot;queryKey&quot;,
+        parser: /(?:^|&amp;)([^&amp;=]*)=?([^&amp;]*)/g
+    },
+    parser: {
+        strict: /^(?:([^:\/?#]+):)?(?:\/\/((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?))?((((?:[^?#\/]*\/)*)([^?#]*))(?:\?([^#]*))?(?:#(.*))?)/,
+        loose:  /^(?:(?![^:@]+:[^:@\/]*@)([^:\/?#.]+):)?(?:\/\/)?((?:(([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)(((\/(?:[^?#](?![^?#\/]*\.[^?#\/.]+(?:[?#]|$)))*\/?)?([^?#\/]*))(?:\?([^#]*))?(?:#(.*))?)/
+    }
+};
+</diff>
      <filename>selenium-core/scripts/htmlutils.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,79 +1,72 @@
-&lt;script language=&quot;JavaScript&quot;&gt;
-    if (window[&quot;selenium_has_been_loaded_into_this_window&quot;]==null)
-    {
-
-        __SELENIUM_JS__
-
-// Some background on the code below: broadly speaking, where we are relative to other windows
-// when running in proxy injection mode depends on whether we are in a frame set file or not.
-//
-// In regular HTML files, the selenium JavaScript is injected into an iframe called &quot;selenium&quot;
-// in order to reduce its impact on the JavaScript environment (through namespace pollution,
-// etc.).  So in regular HTML files, we need to look at the parent of the current window when we want
-// a handle to, e.g., the application window.
-//
-// In frame set files, we can't use an iframe, so we put the JavaScript in the head element and share
-// the window with the frame set.  So in this case, we need to look at the current window, not the
-// parent when looking for, e.g., the application window.  (TODO: Perhaps I should have just
-// assigned a regular frame for selenium?)
-//
-BrowserBot.prototype.getContentWindow = function() {
-    if (window[&quot;seleniumInSameWindow&quot;] != null) return window;
-    return window.parent;
-};
-
-BrowserBot.prototype.getTargetWindow = function(windowName) {
-    if (window[&quot;seleniumInSameWindow&quot;] != null) return window;
-    return window.parent;
-};
-
-BrowserBot.prototype.getCurrentWindow = function() {
-    if (window[&quot;seleniumInSameWindow&quot;] != null) return window;
-    return window.parent;
-};
-
-LOG.openLogWindow = function(message, className) {
-	// disable for now
-};
-
-BrowserBot.prototype.relayToRC = function(name) {
-	var object = eval(name);
-        var s = 'state:' + serializeObject(name, object) + &quot;\n&quot;;
-        sendToRC(s,&quot;state=true&quot;);
-}
-
-BrowserBot.prototype.relayBotToRC = function(s) {
-	this.relayToRC(&quot;selenium.&quot; + s);
-}
-
-function selenium_frameRunTest(oldOnLoadRoutine) {
-	if (oldOnLoadRoutine) {
-		eval(oldOnLoadRoutine);
-	}
-        runSeleniumTest();
-}
-
-function seleniumOnLoad() {
-    	injectedSessionId = @SESSION_ID@;
-        window[&quot;selenium_has_been_loaded_into_this_window&quot;] = true;
-        runSeleniumTest();
-}
-
-function seleniumOnUnload() {
-	sendToRC(&quot;OK&quot;); // just in case some poor PI server thread is waiting for a response
-}
-
-if (window.addEventListener) {
-    	window.addEventListener(&quot;load&quot;, seleniumOnLoad, false);	// firefox
-    	window.addEventListener(&quot;unload&quot;, seleniumOnUnload, false);	// firefox
-} else if (window.attachEvent){
-    	window.attachEvent(&quot;onload&quot;, seleniumOnLoad);	// IE
-    	window.attachEvent(&quot;onunload&quot;, seleniumOnUnload);	// IE
-}
-else {
-    	throw &quot;causing a JavaScript error to tell the world that I did not arrange to be run on load&quot;;
-}
-
-injectedSessionId = @SESSION_ID@;
-}
-&lt;/script&gt;
+&lt;script language=&quot;JavaScript&quot;&gt;
+    if (window[&quot;selenium_has_been_loaded_into_this_window&quot;]==null)
+    {
+
+        __SELENIUM_JS__
+// Some background on the code below: broadly speaking, where we are relative to other windows
+// when running in proxy injection mode depends on whether we are in a frame set file or not.
+//
+// In regular HTML files, the selenium JavaScript is injected into an iframe called &quot;selenium&quot;
+// in order to reduce its impact on the JavaScript environment (through namespace pollution,
+// etc.).  So in regular HTML files, we need to look at the parent of the current window when we want
+// a handle to, e.g., the application window.
+//
+// In frame set files, we can't use an iframe, so we put the JavaScript in the head element and share
+// the window with the frame set.  So in this case, we need to look at the current window, not the
+// parent when looking for, e.g., the application window.  (TODO: Perhaps I should have just
+// assigned a regular frame for selenium?)
+//
+BrowserBot.prototype.getContentWindow = function() {
+    return window;
+};
+
+BrowserBot.prototype.getTargetWindow = function(windowName) {
+    return window;
+};
+
+BrowserBot.prototype.getCurrentWindow = function() {
+    return window;
+};
+
+LOG.openLogWindow = function(message, className) {
+	// disable for now
+};
+
+BrowserBot.prototype.relayToRC = function(name) {
+	var object = eval(name);
+        var s = 'state:' + serializeObject(name, object) + &quot;\n&quot;;
+        sendToRC(s,&quot;state=true&quot;);
+}
+
+function selenium_frameRunTest(oldOnLoadRoutine) {
+	if (oldOnLoadRoutine) {
+		eval(oldOnLoadRoutine);
+	}
+        runSeleniumTest();
+}
+
+function seleniumOnLoad() {
+    injectedSessionId = &quot;@SESSION_ID@&quot;;
+    window[&quot;selenium_has_been_loaded_into_this_window&quot;] = true;
+    runSeleniumTest();
+}
+
+function seleniumOnUnload() {
+	sendToRC(&quot;Current window or frame is closed!&quot;, &quot;closing=true&quot;);
+}
+
+if (window.addEventListener) {
+        window.addEventListener(&quot;load&quot;, seleniumOnLoad, false);	// firefox
+        window.addEventListener(&quot;unload&quot;, seleniumOnUnload, false);	// firefox
+} else if (window.attachEvent){
+    	window.attachEvent(&quot;onload&quot;, seleniumOnLoad);	// IE
+        window.attachEvent(&quot;onunload&quot;, seleniumOnUnload);	// IE
+}
+else {
+    	throw &quot;causing a JavaScript error to tell the world that I did not arrange to be run on load&quot;;
+}
+
+injectedSessionId = &quot;@SESSION_ID@&quot;;
+proxyInjectionMode = true;
+}
+&lt;/script&gt;</diff>
      <filename>selenium-core/scripts/injection.html</filename>
    </modified>
    <modified>
      <diff>@@ -37,14 +37,14 @@ function Selenium(browserbot) {
      * 
      * &lt;ul&gt;
      * &lt;li&gt;&lt;strong&gt;identifier&lt;/strong&gt;=&lt;em&gt;id&lt;/em&gt;: 
-     * Select the element with the specified &amp;#64;id attribute. If no match is
-     * found, select the first element whose &amp;#64;name attribute is &lt;em&gt;id&lt;/em&gt;.
+     * Select the element with the specified &amp;#064;id attribute. If no match is
+     * found, select the first element whose &amp;#064;name attribute is &lt;em&gt;id&lt;/em&gt;.
      * (This is normally the default; see below.)&lt;/li&gt;
      * &lt;li&gt;&lt;strong&gt;id&lt;/strong&gt;=&lt;em&gt;id&lt;/em&gt;:
-     * Select the element with the specified &amp;#64;id attribute.&lt;/li&gt;
+     * Select the element with the specified &amp;#064;id attribute.&lt;/li&gt;
      *
      * &lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;=&lt;em&gt;name&lt;/em&gt;:
-     * Select the first element with the specified &amp;#64;name attribute.
+     * Select the first element with the specified &amp;#064;name attribute.
      * &lt;ul class=&quot;first last simple&quot;&gt;
      * &lt;li&gt;username&lt;/li&gt;
      * &lt;li&gt;name=username&lt;/li&gt;
@@ -71,12 +71,12 @@ function Selenium(browserbot) {
      * &lt;li&gt;&lt;strong&gt;xpath&lt;/strong&gt;=&lt;em&gt;xpathExpression&lt;/em&gt;: 
      * Locate an element using an XPath expression.
      * &lt;ul class=&quot;first last simple&quot;&gt;
-     * &lt;li&gt;xpath=//img[&amp;#64;alt='The image alt text']&lt;/li&gt;
-     * &lt;li&gt;xpath=//table[&amp;#64;id='table1']//tr[4]/td[2]&lt;/li&gt;
-     * &lt;li&gt;xpath=//a[contains(&amp;#64;href,'#id1')]&lt;/li&gt;
-     * &lt;li&gt;xpath=//a[contains(&amp;#64;href,'#id1')]/&amp;#64;class&lt;/li&gt;
-     * &lt;li&gt;xpath=(//table[&amp;#64;class='stylee'])//th[text()='theHeaderText']/../td&lt;/li&gt;
-     * &lt;li&gt;xpath=//input[&amp;#64;name='name2' and &amp;#64;value='yes']&lt;/li&gt;
+     * &lt;li&gt;xpath=//img[&amp;#064;alt='The image alt text']&lt;/li&gt;
+     * &lt;li&gt;xpath=//table[&amp;#064;id='table1']//tr[4]/td[2]&lt;/li&gt;
+     * &lt;li&gt;xpath=//a[contains(&amp;#064;href,'#id1')]&lt;/li&gt;
+     * &lt;li&gt;xpath=//a[contains(&amp;#064;href,'#id1')]/&amp;#064;class&lt;/li&gt;
+     * &lt;li&gt;xpath=(//table[&amp;#064;class='stylee'])//th[text()='theHeaderText']/../td&lt;/li&gt;
+     * &lt;li&gt;xpath=//input[&amp;#064;name='name2' and &amp;#064;value='yes']&lt;/li&gt;
      * &lt;li&gt;xpath=//*[text()=&quot;right&quot;]&lt;/li&gt;
      *
      * &lt;/ul&gt;
@@ -98,8 +98,18 @@ function Selenium(browserbot) {
      * &lt;/ul&gt;
      * &lt;p&gt;Currently the css selector locator supports all css1, css2 and css3 selectors except namespace in css3, some pseudo classes(:nth-of-type, :nth-last-of-type, :first-of-type, :last-of-type, :only-of-type, :visited, :hover, :active, :focus, :indeterminate) and pseudo elements(::first-line, ::first-letter, ::selection, ::before, ::after). &lt;/p&gt;
      * &lt;/li&gt;
-     * &lt;/ul&gt;
      * 
+     * &lt;li&gt;&lt;strong&gt;ui&lt;/strong&gt;=&lt;em&gt;uiSpecifierString&lt;/em&gt;:
+     * Locate an element by resolving the UI specifier string to another locator, and evaluating it. See the &lt;a href=&quot;http://svn.openqa.org/fisheye/browse/~raw,r=trunk/selenium/trunk/src/main/resources/core/scripts/ui-doc.html&quot;&gt;Selenium UI-Element Reference&lt;/a&gt; for more details.
+     * &lt;ul class=&quot;first last simple&quot;&gt;
+     * &lt;li&gt;ui=loginPages::loginButton()&lt;/li&gt;
+     * &lt;li&gt;ui=settingsPages::toggle(label=Hide Email)&lt;/li&gt;
+     * &lt;li&gt;ui=forumPages::postBody(index=2)//a[2]&lt;/li&gt;
+     * &lt;/ul&gt;
+     * &lt;/li&gt;
+     *
+     * &lt;/ul&gt;
+     *
      * &lt;p&gt;
      * Without an explicit locator prefix, Selenium uses the following default
      * strategies:
@@ -142,6 +152,8 @@ function Selenium(browserbot) {
      * &lt;li&gt;&lt;strong&gt;regexp:&lt;/strong&gt;&lt;em&gt;regexp&lt;/em&gt;:
      * Match a string using a regular-expression. The full power of JavaScript
      * regular-expressions is available.&lt;/li&gt;
+     * &lt;li&gt;&lt;strong&gt;regexpi:&lt;/strong&gt;&lt;em&gt;regexpi&lt;/em&gt;:
+     * Match a string using a case-insensitive regular-expression.&lt;/li&gt;
      * &lt;li&gt;&lt;strong&gt;exact:&lt;/strong&gt;&lt;em&gt;string&lt;/em&gt;:
      *
      * Match a string exactly, verbatim, without any of that fancy wildcard
@@ -151,6 +163,14 @@ function Selenium(browserbot) {
      * If no pattern prefix is specified, Selenium assumes that it's a &quot;glob&quot;
      * pattern.
      * &lt;/p&gt;
+     * &lt;p&gt;
+     * For commands that return multiple values (such as verifySelectOptions),
+     * the string being matched is a comma-separated list of the return values,
+     * where both commas and backslashes in the values are backslash-escaped.
+     * When providing a pattern, the optional matching syntax (i.e. glob,
+     * regexp, etc.) is specified once, as usual, at the beginning of the
+     * pattern.
+     * &lt;/p&gt;
      */
     this.browserbot = browserbot;
     this.optionLocatorFactory = new OptionLocatorFactory();
@@ -164,20 +184,18 @@ function Selenium(browserbot) {
 
 Selenium.DEFAULT_TIMEOUT = 30 * 1000;
 Selenium.DEFAULT_MOUSE_SPEED = 10;
+Selenium.RIGHT_MOUSE_CLICK = 2;
 
 Selenium.decorateFunctionWithTimeout = function(f, timeout) {
     if (f == null) {
         return null;
     }
-    var timeoutValue = parseInt(timeout);
-    if (isNaN(timeoutValue)) {
-        throw new SeleniumError(&quot;Timeout is not a number: '&quot; + timeout + &quot;'&quot;);
-    }
-    var now = new Date().getTime();
-    var timeoutTime = now + timeoutValue;
+    
+    var timeoutTime = getTimeoutTime(timeout);
+    
     return function() {
         if (new Date().getTime() &gt; timeoutTime) {
-            throw new SeleniumError(&quot;Timed out after &quot; + timeoutValue + &quot;ms&quot;);
+            throw new SeleniumError(&quot;Timed out after &quot; + timeout + &quot;ms&quot;);
         }
         return f();
     };
@@ -206,8 +224,56 @@ Selenium.prototype.doClick = function(locator) {
    * @param locator an element locator
    *
    */
-   var element = this.browserbot.findElement(locator);
-   this.browserbot.clickElement(element);
+    var element = this.browserbot.findElement(locator);
+    var elementWithHref = getAncestorOrSelfWithJavascriptHref(element);
+   
+    if (browserVersion.isChrome &amp;&amp; elementWithHref != null) {
+        // SEL-621: Firefox chrome: Race condition bug in alert-handling code
+        //
+        // This appears to be because javascript href's are being executed in a
+        // separate thread from the main thread when running in chrome mode.
+        //
+        // This workaround injects a callback into the executing href that
+        // lowers a flag, which is initially raised. Execution of this click
+        // command will wait for the flag to be lowered.
+        
+        var win = elementWithHref.ownerDocument.defaultView;
+        var originalLocation = win.location.href;
+        var originalHref = elementWithHref.href;
+        
+        elementWithHref.href = 'javascript:try { '
+            + originalHref.replace(/^\s*javascript:/i, &quot;&quot;)
+            + '} finally { window._executingJavascriptHref = undefined; }' ;
+        
+        win._executingJavascriptHref = true;
+        
+        this.browserbot.clickElement(element);
+        
+        return Selenium.decorateFunctionWithTimeout(function() {
+            if (win.closed) {
+                return true;
+            }
+            if (win.location.href != originalLocation) {
+                // navigated to some other page ... javascript from previous
+                // page can't still be executing!
+                return true;
+            }
+            if (! win._executingJavascriptHref) {
+                try {
+                    elementWithHref.href = originalHref;
+                }
+                catch (e) {
+                    // maybe the javascript removed the element ... should be
+                    // no danger in not reverting its href attribute
+                }
+                return true;
+            }
+            
+            return false;
+        }, Selenium.DEFAULT_TIMEOUT);
+    }
+    
+    this.browserbot.clickElement(element);
 };
 
 Selenium.prototype.doDoubleClick = function(locator) {
@@ -223,6 +289,17 @@ Selenium.prototype.doDoubleClick = function(locator) {
    this.browserbot.doubleClickElement(element);
 };
 
+Selenium.prototype.doContextMenu = function(locator) {
+    /**
+   * Simulates opening the context menu for the specified element (as might happen if the user &quot;right-clicked&quot; on the element).
+   *
+   * @param locator an element locator
+   *
+   */
+   var element = this.browserbot.findElement(locator);
+   this.browserbot.contextMenuOnElement(element);
+};
+
 Selenium.prototype.doClickAt = function(locator, coordString) {
     /**
    * Clicks on a link, button, checkbox or radio button. If the click action
@@ -236,7 +313,10 @@ Selenium.prototype.doClickAt = function(locator, coordString) {
    */
     var element = this.browserbot.findElement(locator);
     var clientXY = getClientXY(element, coordString)
+    this.doMouseMove(locator);
+    this.doMouseDown(locator);
     this.browserbot.clickElement(element, clientXY[0], clientXY[1]);
+    this.doMouseUp(locator);
 };
 
 Selenium.prototype.doDoubleClickAt = function(locator, coordString) {
@@ -252,7 +332,24 @@ Selenium.prototype.doDoubleClickAt = function(locator, coordString) {
    */
     var element = this.browserbot.findElement(locator);
     var clientXY = getClientXY(element, coordString)
+    this.doMouseMove(locator);
+    this.doMouseDown(locator);
     this.browserbot.doubleClickElement(element, clientXY[0], clientXY[1]);
+    this.doMouseUp(locator);
+};
+
+Selenium.prototype.doContextMenuAt = function(locator, coordString) {
+    /**
+   * Simulates opening the context menu for the specified element (as might happen if the user &quot;right-clicked&quot; on the element).
+   *
+   * @param locator an element locator
+   * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
+   *      event relative to the element returned by the locator.
+   *
+   */
+    var element = this.browserbot.findElement(locator);
+    var clientXY = getClientXY(element, coordString)
+    this.browserbot.contextMenuOnElement(element, clientXY[0], clientXY[1]);
 };
 
 Selenium.prototype.doFireEvent = function(locator, eventName) {
@@ -267,6 +364,19 @@ Selenium.prototype.doFireEvent = function(locator, eventName) {
     triggerEvent(element, eventName, false);
 };
 
+Selenium.prototype.doFocus = function(locator) {
+    /** Move the focus to the specified element; for example, if the element is an input field, move the cursor to that field.
+    *
+    * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
+    */
+    var element = this.browserbot.findElement(locator);
+    if (element.focus) {
+        element.focus();
+    } else {
+         triggerEvent(element, &quot;focus&quot;, false);
+    }
+}
+
 Selenium.prototype.doKeyPress = function(locator, keySequence) {
     /**
    * Simulates a user pressing and releasing a key.
@@ -423,7 +533,7 @@ Selenium.prototype.doMouseOut = function(locator) {
 
 Selenium.prototype.doMouseDown = function(locator) {
     /**
-   * Simulates a user pressing the mouse button (without releasing it yet) on
+   * Simulates a user pressing the left mouse button (without releasing it yet) on
    * the specified element.
    *
    * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
@@ -432,12 +542,23 @@ Selenium.prototype.doMouseDown = function(locator) {
    this.browserbot.triggerMouseEvent(element, 'mousedown', true);
 };
 
-Selenium.prototype.doMouseDownAt = function(locator, coordString) {
+Selenium.prototype.doMouseDownRight = function(locator) {
     /**
-   * Simulates a user pressing the mouse button (without releasing it yet) on
+   * Simulates a user pressing the right mouse button (without releasing it yet) on
    * the specified element.
    *
    * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
+   */
+   var element = this.browserbot.findElement(locator);
+   this.browserbot.triggerMouseEvent(element, 'mousedown', true, undefined, undefined, Selenium.RIGHT_MOUSE_CLICK);
+};
+
+Selenium.prototype.doMouseDownAt = function(locator, coordString) {
+    /**
+   * Simulates a user pressing the left mouse button (without releasing it yet) at
+   * the specified location.
+   *
+   * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
    * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
    *      event relative to the element returned by the locator.
    */
@@ -447,10 +568,25 @@ Selenium.prototype.doMouseDownAt = function(locator, coordString) {
     this.browserbot.triggerMouseEvent(element, 'mousedown', true, clientXY[0], clientXY[1]);
 };
 
+Selenium.prototype.doMouseDownRightAt = function(locator, coordString) {
+    /**
+   * Simulates a user pressing the right mouse button (without releasing it yet) at
+   * the specified location.
+   *
+   * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
+   * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
+   *      event relative to the element returned by the locator.
+   */
+    var element = this.browserbot.findElement(locator);
+    var clientXY = getClientXY(element, coordString)
+
+    this.browserbot.triggerMouseEvent(element, 'mousedown', true, clientXY[0], clientXY[1], Selenium.RIGHT_MOUSE_CLICK);
+};
+
 Selenium.prototype.doMouseUp = function(locator) {
     /**
-   * Simulates a user pressing the mouse button (without releasing it yet) on
-   * the specified element.
+   * Simulates the event that occurs when the user releases the mouse button (i.e., stops
+   * holding the button down) on the specified element.
    *
    * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
    */
@@ -458,10 +594,21 @@ Selenium.prototype.doMouseUp = function(locator) {
    this.browserbot.triggerMouseEvent(element, 'mouseup', true);
 };
 
+Selenium.prototype.doMouseUpRight = function(locator) {
+    /**
+   * Simulates the event that occurs when the user releases the right mouse button (i.e., stops
+   * holding the button down) on the specified element.
+   *
+   * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
+   */
+   var element = this.browserbot.findElement(locator);
+   this.browserbot.triggerMouseEvent(element, 'mouseup', true, undefined, undefined, Selenium.RIGHT_MOUSE_CLICK);
+};
+
 Selenium.prototype.doMouseUpAt = function(locator, coordString) {
     /**
-   * Simulates a user pressing the mouse button (without releasing it yet) on
-   * the specified element.
+   * Simulates the event that occurs when the user releases the mouse button (i.e., stops
+   * holding the button down) at the specified location.
    *
    * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
    * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
@@ -473,6 +620,21 @@ Selenium.prototype.doMouseUpAt = function(locator, coordString) {
     this.browserbot.triggerMouseEvent(element, 'mouseup', true, clientXY[0], clientXY[1]);
 };
 
+Selenium.prototype.doMouseUpRightAt = function(locator, coordString) {
+    /**
+   * Simulates the event that occurs when the user releases the right mouse button (i.e., stops
+   * holding the button down) at the specified location.
+   *
+   * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
+   * @param coordString specifies the x,y position (i.e. - 10,20) of the mouse
+   *      event relative to the element returned by the locator.
+   */
+    var element = this.browserbot.findElement(locator);
+    var clientXY = getClientXY(element, coordString)
+
+    this.browserbot.triggerMouseEvent(element, 'mouseup', true, clientXY[0], clientXY[1], Selenium.RIGHT_MOUSE_CLICK);
+};
+
 Selenium.prototype.doMouseMove = function(locator) {
     /**
    * Simulates a user pressing the mouse button (without releasing it yet) on
@@ -557,12 +719,14 @@ Selenium.prototype.doSetSpeed = function(value) {
    throw new SeleniumError(&quot;this operation is only implemented in selenium-rc, and should never result in a request making it across the wire&quot;);
 };
 
-Selenium.prototype.doGetSpeed = function() {
+Selenium.prototype.getSpeed = function() {
  /**
  * Get execution speed (i.e., get the millisecond length of the delay following each selenium operation).  By default, there is no such delay, i.e.,
  * the delay is 0 milliseconds.
    *
    * See also setSpeed.
+   *
+   * @return string the execution speed in milliseconds.
    */
    throw new SeleniumError(&quot;this operation is only implemented in selenium-rc, and should never result in a request making it across the wire&quot;);
 };
@@ -721,6 +885,10 @@ Selenium.prototype.makePageLoadCondition = function(timeout) {
     if (timeout == null) {
         timeout = this.defaultTimeout;
     }
+    // if the timeout is zero, we won't wait for the page to load before returning
+    if (timeout == 0) {
+    	  return;
+    }
     return Selenium.decorateFunctionWithTimeout(fnBind(this._isNewPageLoaded, this), timeout);
 };
 
@@ -740,7 +908,9 @@ Selenium.prototype.doOpen = function(url) {
    * @param url the URL to open; may be relative or absolute
    */
     this.browserbot.openLocation(url);
-    return this.makePageLoadCondition();
+    if (window[&quot;proxyInjectionMode&quot;] == null || !window[&quot;proxyInjectionMode&quot;]) {
+        return this.makePageLoadCondition();
+    } // in PI mode, just return &quot;OK&quot;; the server will waitForLoad
 };
 
 Selenium.prototype.doOpenWindow = function(url, windowID) {
@@ -761,21 +931,43 @@ Selenium.prototype.doOpenWindow = function(url, windowID) {
 
 Selenium.prototype.doSelectWindow = function(windowID) {
     /**
-   * Selects a popup window; once a popup window has been selected, all
+   * Selects a popup window using a window locator; once a popup window has been selected, all
    * commands go to that window. To select the main window again, use null
    * as the target.
    *
-   * &lt;p&gt;Selenium has several strategies for finding the window object referred to by the &quot;windowID&quot; parameter.&lt;/p&gt;
+   * &lt;p&gt;
    * 
-   * &lt;p&gt;1.) if windowID is null, then it is assumed the user is referring to the original window instantiated by the browser).&lt;/p&gt;
+   * Window locators provide different ways of specifying the window object:
+   * by title, by internal JavaScript &quot;name,&quot; or by JavaScript variable.
+   * &lt;/p&gt;
+   * &lt;ul&gt;
+   * &lt;li&gt;&lt;strong&gt;title&lt;/strong&gt;=&lt;em&gt;My Special Window&lt;/em&gt;:
+   * Finds the window using the text that appears in the title bar.  Be careful;
+   * two windows can share the same title.  If that happens, this locator will
+   * just pick one.
+   * &lt;/li&gt;
+   * &lt;li&gt;&lt;strong&gt;name&lt;/strong&gt;=&lt;em&gt;myWindow&lt;/em&gt;:
+   * Finds the window using its internal JavaScript &quot;name&quot; property.  This is the second 
+   * parameter &quot;windowName&quot; passed to the JavaScript method window.open(url, windowName, windowFeatures, replaceFlag)
+   * (which Selenium intercepts).
+   * &lt;/li&gt;
+   * &lt;li&gt;&lt;strong&gt;var&lt;/strong&gt;=&lt;em&gt;variableName&lt;/em&gt;:
+   * Some pop-up windows are unnamed (anonymous), but are associated with a JavaScript variable name in the current
+   * application window, e.g. &quot;window.foo = window.open(url);&quot;.  In those cases, you can open the window using
+   * &quot;var=foo&quot;.
+   * &lt;/li&gt;
+   * &lt;/ul&gt;
+   * &lt;p&gt;
+   * If no window locator prefix is provided, we'll try to guess what you mean like this:&lt;/p&gt;
+   * &lt;p&gt;1.) if windowID is null, (or the string &quot;null&quot;) then it is assumed the user is referring to the original window instantiated by the browser).&lt;/p&gt;
    * &lt;p&gt;2.) if the value of the &quot;windowID&quot; parameter is a JavaScript variable name in the current application window, then it is assumed
    * that this variable contains the return value from a call to the JavaScript window.open() method.&lt;/p&gt;
-   * &lt;p&gt;3.) Otherwise, selenium looks in a hash it maintains that maps string names to window objects.  Each of these string 
-   * names matches the second parameter &quot;windowName&quot; past to the JavaScript method  window.open(url, windowName, windowFeatures, replaceFlag)
-   * (which selenium intercepts).&lt;/p&gt;
-   *
-   * &lt;p&gt;If you're having trouble figuring out what is the name of a window that you want to manipulate, look at the selenium log messages
-   * which identify the names of windows created via window.open (and therefore intercepted by selenium).  You will see messages
+   * &lt;p&gt;3.) Otherwise, selenium looks in a hash it maintains that maps string names to window &quot;names&quot;.&lt;/p&gt;
+   * &lt;p&gt;4.) If &lt;em&gt;that&lt;/em&gt; fails, we'll try looping over all of the known windows to try to find the appropriate &quot;title&quot;.
+   * Since &quot;title&quot; is not necessarily unique, this may have unexpected behavior.&lt;/p&gt;
+   * 
+   * &lt;p&gt;If you're having trouble figuring out the name of a window that you want to manipulate, look at the Selenium log messages
+   * which identify the names of windows created via window.open (and therefore intercepted by Selenium).  You will see messages
    * like the following for each window as it is opened:&lt;/p&gt;
    * 
    * &lt;p&gt;&lt;code&gt;debug: window.open call intercepted; window ID (which you can use with selectWindow()) is &quot;myNewWindow&quot;&lt;/code&gt;&lt;/p&gt;
@@ -794,6 +986,8 @@ Selenium.prototype.doSelectFrame = function(locator) {
     * Selects a frame within the current window.  (You may invoke this command
     * multiple times to select nested frames.)  To select the parent frame, use
     * &quot;relative=parent&quot; as a locator; to select the top frame, use &quot;relative=top&quot;.
+    * You can also select a frame by its 0-based index number; select the first frame with
+    * &quot;index=0&quot;, or the third frame with &quot;index=2&quot;.
     *
     * &lt;p&gt;You may also use a DOM expression to identify the frame you want directly,
     * like this: &lt;code&gt;dom=frames[&quot;main&quot;].frames[&quot;subframe&quot;]&lt;/code&gt;&lt;/p&gt;
@@ -803,25 +997,6 @@ Selenium.prototype.doSelectFrame = function(locator) {
         this.browserbot.selectFrame(locator);
 };
 
-Selenium.prototype.getLogMessages = function() {
-    /**
-        * Return the contents of the log.
-    *
-        * &lt;p&gt;This is a placeholder intended to make the code generator make this API
-        * available to clients.  The selenium server will intercept this call, however,
-        * and return its recordkeeping of log messages since the last call to this API.
-        * Thus this code in JavaScript will never be called.&lt;/p&gt;
-        *
-        * &lt;p&gt;The reason I opted for a servercentric solution is to be able to support
-        * multiple frames served from different domains, which would break a
-        * centralized JavaScript logging mechanism under some conditions.&lt;/p&gt;
-    *
-        * @return string all log messages seen since the last call to this API
-    */
-        return &quot;getLogMessages should be implemented in the selenium server&quot;;
-};
-
-
 Selenium.prototype.getWhetherThisFrameMatchFrameExpression = function(currentFrameString, target) {
     /**
      * Determine whether current/locator identify the frame containing this running code.
@@ -836,44 +1011,7 @@ Selenium.prototype.getWhetherThisFrameMatchFrameExpression = function(currentFra
      * @param target new frame (which might be relative to the current one)
      * @return boolean true if the new frame is this code's window
      */
-    var isDom = false;
-    if (target.indexOf(&quot;dom=&quot;) == 0) {
-        target = target.substr(4);
-        isDom = true;
-    }
-    var t;
-    try {
-        eval(&quot;t=&quot; + currentFrameString + &quot;.&quot; + target);
-    } catch (e) {
-    }
-    var autWindow = this.browserbot.getCurrentWindow();
-    if (t != null) {
-        if (t.window == autWindow) {
-            return true;
-        }
-        return false;
-    }
-    if (isDom) {
-        return false;
-    }
-    var currentFrame;
-    eval(&quot;currentFrame=&quot; + currentFrameString);
-    if (target == &quot;relative=up&quot;) {
-        if (currentFrame.window.parent == autWindow) {
-            return true;
-        }
-        return false;
-    }
-    if (target == &quot;relative=top&quot;) {
-        if (currentFrame.window.top == autWindow) {
-            return true;
-        }
-        return false;
-    }
-    if (autWindow.name == target &amp;&amp; currentFrame.window == autWindow.parent) {
-        return true;
-    }
-    return false;
+    return this.browserbot.doesThisFrameMatchFrameExpression(currentFrameString, target);
 };
 
 Selenium.prototype.getWhetherThisWindowMatchWindowExpression = function(currentWindowString, target) {
@@ -900,11 +1038,22 @@ Selenium.prototype.doWaitForPopUp = function(windowID, timeout) {
     /**
     * Waits for a popup window to appear and load up.
     *
-    * @param windowID the JavaScript window ID of the window that will appear
+    * @param windowID the JavaScript window &quot;name&quot; of the window that will appear (not the text of the title bar)
     * @param timeout a timeout in milliseconds, after which the action will return with an error
     */
+    var timeoutTime = getTimeoutTime(timeout);
+    
     var popupLoadedPredicate = function () {
-        var targetWindow = selenium.browserbot.getWindowByName(windowID, true);
+        var targetWindow;
+        try {
+            targetWindow = selenium.browserbot.getWindowByName(windowID, true);
+        }
+        catch (e) {
+            if (new Date().getTime() &gt; timeoutTime) {
+                throw e;
+            }
+        }
+        
         if (!targetWindow) return false;
         if (!targetWindow.location) return false;
         if (&quot;about:blank&quot; == targetWindow.location) return false;
@@ -938,15 +1087,45 @@ Selenium.prototype.doWaitForPopUp.dontCheckAlertsAndConfirms = true;
 
 Selenium.prototype.doChooseCancelOnNextConfirmation = function() {
     /**
+   * &lt;p&gt;
    * By default, Selenium's overridden window.confirm() function will
-   * return true, as if the user had manually clicked OK.  After running
+   * return true, as if the user had manually clicked OK; after running
    * this command, the next call to confirm() will return false, as if
-   * the user had clicked Cancel.
-   *
+   * the user had clicked Cancel.  Selenium will then resume using the
+   * default behavior for future confirmations, automatically returning 
+   * true (OK) unless/until you explicitly call this command for each
+   * confirmation.
+   * &lt;/p&gt;
+   * &lt;p&gt;
+   * Take note - every time a confirmation comes up, you must
+   * consume it with a corresponding getConfirmation, or else
+   * the next selenium operation will fail.
+   * &lt;/p&gt;
    */
-    this.browserbot.cancelNextConfirmation();
+    this.browserbot.cancelNextConfirmation(false);
 };
 
+Selenium.prototype.doChooseOkOnNextConfirmation = function() {
+    /**
+   * &lt;p&gt;
+   * Undo the effect of calling chooseCancelOnNextConfirmation.  Note
+   * that Selenium's overridden window.confirm() function will normally automatically
+   * return true, as if the user had manually clicked OK, so you shouldn't
+   * need to use this command unless for some reason you need to change
+   * your mind prior to the next confirmation.  After any confirmation, Selenium will resume using the
+   * default behavior for future confirmations, automatically returning 
+   * true (OK) unless/until you explicitly call chooseCancelOnNextConfirmation for each
+   * confirmation.
+   * &lt;/p&gt;
+   * &lt;p&gt;
+   * Take note - every time a confirmation comes up, you must
+   * consume it with a corresponding getConfirmation, or else
+   * the next selenium operation will fail.
+   * &lt;/p&gt;
+   *
+   */
+    this.browserbot.cancelNextConfirmation(true);
+};
 
 Selenium.prototype.doAnswerOnNextPrompt = function(answer) {
     /**
@@ -1032,16 +1211,17 @@ Selenium.prototype.getAlert = function() {
    * Retrieves the message of a JavaScript alert generated during the previous action, or fail if there were no alerts.
    *
    * &lt;p&gt;Getting an alert has the same effect as manually clicking OK. If an
-   * alert is generated but you do not get/verify it, the next Selenium action
+   * alert is generated but you do not consume it with getAlert, the next Selenium action
    * will fail.&lt;/p&gt;
    *
-   * &lt;p&gt;NOTE: under Selenium, JavaScript alerts will NOT pop up a visible alert
+   * &lt;p&gt;Under Selenium, JavaScript alerts will NOT pop up a visible alert
    * dialog.&lt;/p&gt;
    *
-   * &lt;p&gt;NOTE: Selenium does NOT support JavaScript alerts that are generated in a
+   * &lt;p&gt;Selenium does NOT support JavaScript alerts that are generated in a
    * page's onload() event handler. In this case a visible dialog WILL be
    * generated and Selenium will hang until someone manually clicks OK.&lt;/p&gt;
    * @return string The message of the most recent JavaScript alert
+
    */
     if (!this.browserbot.hasAlerts()) {
         Assert.fail(&quot;There were no alerts&quot;);
@@ -1058,8 +1238,11 @@ Selenium.prototype.getConfirmation = function() {
    * &lt;p&gt;
    * By default, the confirm function will return true, having the same effect
    * as manually clicking OK. This can be changed by prior execution of the
-   * chooseCancelOnNextConfirmation command. If an confirmation is generated
-   * but you do not get/verify it, the next Selenium action will fail.
+   * chooseCancelOnNextConfirmation command. 
+   * &lt;/p&gt;
+   * &lt;p&gt;
+   * If an confirmation is generated but you do not consume it with getConfirmation,
+   * the next Selenium action will fail.
    * &lt;/p&gt;
    *
    * &lt;p&gt;
@@ -1111,7 +1294,7 @@ Selenium.prototype.getLocation = function() {
    *
    * @return string the absolute URL of the current page
    */
-    return this.browserbot.getCurrentWindow().location;
+    return this.browserbot.getCurrentWindow().location.href;
 };
 
 Selenium.prototype.getTitle = function() {
@@ -1174,24 +1357,24 @@ Selenium.prototype.getEval = function(script) {
    * have multiple lines, but only the result of the last line will be returned.
    *
    * &lt;p&gt;Note that, by default, the snippet will run in the context of the &quot;selenium&quot;
-   * object itself, so &lt;code&gt;this&lt;/code&gt; will refer to the Selenium object, and &lt;code&gt;window&lt;/code&gt; will
-   * refer to the top-level runner test window, not the window of your application.&lt;/p&gt;
+   * object itself, so &lt;code&gt;this&lt;/code&gt; will refer to the Selenium object.  Use &lt;code&gt;window&lt;/code&gt; to
+   * refer to the window of your application, e.g. &lt;code&gt;window.document.getElementById('foo')&lt;/code&gt;&lt;/p&gt;
    *
-   * &lt;p&gt;If you need a reference to the window of your application, you can refer
-   * to &lt;code&gt;this.browserbot.getCurrentWindow()&lt;/code&gt; and if you need to use
+   * &lt;p&gt;If you need to use
    * a locator to refer to a single element in your application page, you can
-   * use &lt;code&gt;this.browserbot.findElement(&quot;foo&quot;)&lt;/code&gt; where &quot;foo&quot; is your locator.&lt;/p&gt;
+   * use &lt;code&gt;this.browserbot.findElement(&quot;id=foo&quot;)&lt;/code&gt; where &quot;id=foo&quot; is your locator.&lt;/p&gt;
    *
    * @param script the JavaScript snippet to run
    * @return string the results of evaluating the snippet
    */
     try {
+        var window = this.browserbot.getCurrentWindow();
         var result = eval(script);
         // Selenium RC doesn't allow returning null
         if (null == result) return &quot;null&quot;;
         return result;
     } catch (e) {
-        throw new SeleniumError(&quot;Threw an exception: &quot; + e.message);
+        throw new SeleniumError(&quot;Threw an exception: &quot; + extractExceptionMessage(e));
     }
 };
 
@@ -1250,7 +1433,7 @@ Selenium.prototype.getSelectedLabels = function(selectLocator) {
    * @param selectLocator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; identifying a drop-down menu
    * @return string[] an array of all selected option labels in the specified select drop-down
    */
-    return this.findSelectedOptionProperties(selectLocator, &quot;text&quot;).join(&quot;,&quot;);
+    return this.findSelectedOptionProperties(selectLocator, &quot;text&quot;);
 }
 
 Selenium.prototype.getSelectedLabel = function(selectLocator) {
@@ -1268,7 +1451,7 @@ Selenium.prototype.getSelectedValues = function(selectLocator) {
    * @param selectLocator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; identifying a drop-down menu
    * @return string[] an array of all selected option values in the specified select drop-down
    */
-    return this.findSelectedOptionProperties(selectLocator, &quot;value&quot;).join(&quot;,&quot;);
+    return this.findSelectedOptionProperties(selectLocator, &quot;value&quot;);
 }
 
 Selenium.prototype.getSelectedValue = function(selectLocator) {
@@ -1286,7 +1469,7 @@ Selenium.prototype.getSelectedIndexes = function(selectLocator) {
    * @param selectLocator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; identifying a drop-down menu
    * @return string[] an array of all selected option indexes in the specified select drop-down
    */
-    return this.findSelectedOptionProperties(selectLocator, &quot;index&quot;).join(&quot;,&quot;);
+    return this.findSelectedOptionProperties(selectLocator, &quot;index&quot;);
 }
 
 Selenium.prototype.getSelectedIndex = function(selectLocator) {
@@ -1304,7 +1487,7 @@ Selenium.prototype.getSelectedIds = function(selectLocator) {
    * @param selectLocator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; identifying a drop-down menu
    * @return string[] an array of all selected option IDs in the specified select drop-down
    */
-    return this.findSelectedOptionProperties(selectLocator, &quot;id&quot;).join(&quot;,&quot;);
+    return this.findSelectedOptionProperties(selectLocator, &quot;id&quot;);
 }
 
 Selenium.prototype.getSelectedId = function(selectLocator) {
@@ -1350,9 +1533,6 @@ Selenium.prototype.findSelectedOptionProperties = function(locator, property) {
         if (element.options[i].selected)
         {
             var propVal = element.options[i][property];
-            if (propVal.replace) {
-                propVal.replace(/,/g, &quot;\\,&quot;);
-            }
             selectedOptions.push(propVal);
         }
     }
@@ -1379,19 +1559,21 @@ Selenium.prototype.getSelectOptions = function(selectLocator) {
     var selectOptions = [];
 
     for (var i = 0; i &lt; element.options.length; i++) {
-        var option = element.options[i].text.replace(/,/g, &quot;\\,&quot;);
+        var option = element.options[i].text;
         selectOptions.push(option);
     }
 
-    return selectOptions.join(&quot;,&quot;);
+    return selectOptions;
 };
 
 
 Selenium.prototype.getAttribute = function(attributeLocator) {
     /**
-   * Gets the value of an element attribute.
+   * Gets the value of an element attribute. The value of the attribute may
+   * differ across browsers (this is the case for the &quot;style&quot; attribute, for
+   * example).
    *
-   * @param attributeLocator an element locator followed by an @ sign and then the name of the attribute, e.g. &quot;foo@bar&quot;
+   * @param attributeLocator an element locator followed by an &amp;#064; sign and then the name of the attribute, e.g. &quot;foo&amp;#064;bar&quot;
    * @return string the value of the specified attribute
    */
    var result = this.browserbot.findAttribute(attributeLocator);
@@ -1425,13 +1607,12 @@ Selenium.prototype.isTextPresent = function(pattern) {
 
 Selenium.prototype.isElementPresent = function(locator) {
     /**
-   * Verifies that the specified element is somewhere on the page.
-   * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
-   * @return boolean true if the element is present, false otherwise
-   */
-    try {
-        this.browserbot.findElement(locator);
-    } catch (e) {
+    * Verifies that the specified element is somewhere on the page.
+    * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
+    * @return boolean true if the element is present, false otherwise
+    */
+    var element = this.browserbot.findElementOrNull(locator);
+    if (element == null) {
         return false;
     }
     return true;
@@ -1450,6 +1631,18 @@ Selenium.prototype.isVisible = function(locator) {
    */
     var element;
     element = this.browserbot.findElement(locator);
+    // DGF if it's an input tag of type &quot;hidden&quot; then it's not visible
+    if (element.tagName) {
+        var tagName = new String(element.tagName).toLowerCase();
+        if (tagName == &quot;input&quot;) {
+            if (element.type) {
+                var elementType = new String(element.type).toLowerCase();
+                if (elementType == &quot;hidden&quot;) {
+                    return false;
+                }
+            }
+        }
+    }
     var visibility = this.findEffectiveStyleProperty(element, &quot;visibility&quot;);
     var _isDisplayed = this._isDisplayed(element);
     return (visibility != &quot;hidden&quot; &amp;&amp; _isDisplayed);
@@ -1489,6 +1682,12 @@ Selenium.prototype.findEffectiveStyle = function(element) {
         //   currentStyle is not identical to getComputedStyle()
         //   ... but it's good enough for &quot;visibility&quot;
     }
+
+    if (window.document.defaultView &amp;&amp; window.document.defaultView.getComputedStyle) {
+        return window.document.defaultView.getComputedStyle(element, null);
+    }
+
+
     throw new SeleniumError(&quot;cannot determine effective stylesheet in this browser&quot;);
 };
 
@@ -1504,7 +1703,24 @@ Selenium.prototype.isEditable = function(locator) {
     if (element.value == undefined) {
         Assert.fail(&quot;Element &quot; + locator + &quot; is not an input.&quot;);
     }
-    return !element.disabled;
+    if (element.disabled) {
+        return false;
+    }
+    // DGF &quot;readonly&quot; is a bit goofy... it doesn't necessarily have a value
+    // You can write &lt;input readonly value=&quot;black&quot;&gt;
+    var readOnlyNode = element.getAttributeNode('readonly');
+    if (readOnlyNode) {
+        // DGF on IE, every input element has a readOnly node, but it may be false
+        if (typeof(readOnlyNode.nodeValue) == &quot;boolean&quot;) {
+            var readOnly = readOnlyNode.nodeValue;
+            if (readOnly) {
+                return false;
+            }
+        } else {
+            return false;
+        }
+    }
+    return true;
 };
 
 Selenium.prototype.getAllButtons = function() {
@@ -1569,7 +1785,7 @@ Selenium.prototype.findWindow = function(soughtAfterWindowPropertyValue) {
    }
    else {
        // matching &quot;name&quot;:
-       // If we are not in proxy injection mode, then the top-level test window will be named myiframe.
+       // If we are not in proxy injection mode, then the top-level test window will be named selenium_myiframe.
         // But as far as the interface goes, we are expected to match a blank string to this window, if
         // we are searching with respect to the widow name.
         // So make a special case so that this logic will work:
@@ -1688,25 +1904,22 @@ Selenium.prototype.doDragAndDropToObject = function(locatorOfObjectToBeDragged,
    this.doDragAndDrop(locatorOfObjectToBeDragged, movementsString);
 };
 
-Selenium.prototype.doWindowFocus = function(windowName) {
-/** Gives focus to a window
+Selenium.prototype.doWindowFocus = function() {
+/** Gives focus to the currently selected window
    *
-   * @param windowName name of the window to be given focus
    */
-   this.findWindow(windowName).focus();
+   this.browserbot.getCurrentWindow().focus();
 };
 
 
-Selenium.prototype.doWindowMaximize = function(windowName) {
-/** Resize window to take up the entire screen
+Selenium.prototype.doWindowMaximize = function() {
+/** Resize currently selected window to take up the entire screen
    *
-   * @param windowName name of the window to be enlarged
    */
-   var window = this.findWindow(windowName);
+   var window = this.browserbot.getCurrentWindow();
    if (window!=null &amp;&amp; window.screen) {
        window.moveTo(0,0);
-        window.outerHeight = screen.availHeight;
-        window.outerWidth = screen.availWidth;
+       window.resizeTo(screen.availWidth, screen.availHeight);
    }
 };
 
@@ -1795,12 +2008,12 @@ Selenium.prototype.getElementIndex = function(locator) {
 
 Selenium.prototype.isOrdered = function(locator1, locator2) {
     /**
-     * Check if these two elements have same parent and are ordered. Two same elements will
+     * Check if these two elements have same parent and are ordered siblings in the DOM. Two same elements will
      * not be considered ordered.
      *
      * @param locator1 an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to the first element
      * @param locator2 an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to the second element
-     * @return boolean true if two elements are ordered and have same parent, false otherwise
+     * @return boolean true if element1 is the previous sibling of element2, false otherwise
      */
     var element1 = this.browserbot.findElement(locator1);
     var element2 = this.browserbot.findElement(locator2);
@@ -1997,27 +2210,6 @@ Selenium.prototype.getCursorPosition = function(locator) {
 }
 
 
-Selenium.prototype.doSetContext = function(context, logLevelThreshold) {
-    /**
-   * Writes a message to the status bar and adds a note to the browser-side
-   * log.
-   *
-   * &lt;p&gt;If logLevelThreshold is specified, set the threshold for logging
-   * to that level (debug, info, warn, error).&lt;/p&gt;
-   *
-   * &lt;p&gt;(Note that the browser-side logs will &lt;i&gt;not&lt;/i&gt; be sent back to the
-   * server, and are invisible to the Client Driver.)&lt;/p&gt;
-   *
-   * @param context
-   *            the message to be sent to the browser
-   * @param logLevelThreshold one of &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot;, sets the threshold for browser-side logging
-   */
-    if  (logLevelThreshold==null || logLevelThreshold==&quot;&quot;) {
-        return this.browserbot.setContext(context);
-    }
-    return this.browserbot.setContext(context, logLevelThreshold);
-};
-
 Selenium.prototype.getExpression = function(expression) {
     /**
      * Returns the specified expression.
@@ -2031,6 +2223,68 @@ Selenium.prototype.getExpression = function(expression) {
     return expression;
 }
 
+Selenium.prototype.getXpathCount = function(xpath) {
+    /**
+    * Returns the number of nodes that match the specified xpath, eg. &quot;//table&quot; would give
+    * the number of tables.
+    * 
+    * @param xpath the xpath expression to evaluate. do NOT wrap this expression in a 'count()' function; we will do that for you.
+    * @return number the number of nodes that match the specified xpath
+    */
+    var result = this.browserbot.evaluateXPathCount(xpath, this.browserbot.getDocument());
+    return result;
+}
+
+Selenium.prototype.doAssignId = function(locator, identifier) {
+    /**
+    * Temporarily sets the &quot;id&quot; attribute of the specified element, so you can locate it in the future
+    * using its ID rather than a slow/complicated XPath.  This ID will disappear once the page is
+    * reloaded.
+    * @param locator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt; pointing to an element
+    * @param identifier a string to be used as the ID of the specified element
+    */
+    var element = this.browserbot.findElement(locator);
+    element.id = identifier;
+}
+
+Selenium.prototype.doAllowNativeXpath = function(allow) {
+    /**
+    * Specifies whether Selenium should use the native in-browser implementation
+    * of XPath (if any native version is available); if you pass &quot;false&quot; to
+    * this function, we will always use our pure-JavaScript xpath library.
+    * Using the pure-JS xpath library can improve the consistency of xpath
+    * element locators between different browser vendors, but the pure-JS
+    * version is much slower than the native implementations.
+    * @param allow boolean, true means we'll prefer to use native XPath; false means we'll only use JS XPath
+    */
+    if (&quot;false&quot; == allow || &quot;0&quot; == allow) { // The strings &quot;false&quot; and &quot;0&quot; are true values in JS
+        allow = false;
+    }
+    this.browserbot.allowNativeXpath = allow;
+}
+
+Selenium.prototype.doIgnoreAttributesWithoutValue = function(ignore) {
+    /**
+    * Specifies whether Selenium will ignore xpath attributes that have no
+    * value, i.e. are the empty string, when using the non-native xpath
+    * evaluation engine. You'd want to do this for performance reasons in IE.
+    * However, this could break certain xpaths, for example an xpath that looks
+    * for an attribute whose value is NOT the empty string.
+    *
+    * The hope is that such xpaths are relatively rare, but the user should
+    * have the option of using them. Note that this only influences xpath
+    * evaluation when using the ajaxslt engine (i.e. not &quot;javascript-xpath&quot;).
+    *
+    * @param ignore boolean, true means we'll ignore attributes without value
+    *                        at the expense of xpath &quot;correctness&quot;; false means
+    *                        we'll sacrifice speed for correctness.
+    */
+    if ('false' == ignore || '0' == ignore) {
+        ignore = false;
+    }
+    this.browserbot.ignoreAttributesWithoutValue = ignore;
+}
+
 Selenium.prototype.doWaitForCondition = function(script, timeout) {
     /**
    * Runs the specified JavaScript snippet repeatedly until it evaluates to &quot;true&quot;.
@@ -2044,7 +2298,9 @@ Selenium.prototype.doWaitForCondition = function(script, timeout) {
    * @param script the JavaScript snippet to run
    * @param timeout a timeout in milliseconds, after which this command will return with an error
    */
+   
     return Selenium.decorateFunctionWithTimeout(function () {
+        var window = selenium.browserbot.getCurrentWindow();
         return eval(script);
     }, timeout);
 };
@@ -2084,6 +2340,24 @@ Selenium.prototype.doWaitForPageToLoad = function(timeout) {
     }
 };
 
+Selenium.prototype.doWaitForFrameToLoad = function(frameAddress, timeout) {
+    /**
+     * Waits for a new frame to load.
+     *
+     * &lt;p&gt;Selenium constantly keeps track of new pages and frames loading, 
+     * and sets a &quot;newPageLoaded&quot; flag when it first notices a page load.&lt;/p&gt;
+     * 
+     * See waitForPageToLoad for more information.
+     * 
+     * @param frameAddress FrameAddress from the server side
+     * @param timeout a timeout in milliseconds, after which this command will return with an error
+     */
+    // in pi-mode, the test and the harness share the window; thus if we are executing this code, then we have loaded
+    if (window[&quot;proxyInjectionMode&quot;] == null || !window[&quot;proxyInjectionMode&quot;]) {
+        return this.makePageLoadCondition(timeout);
+    }
+};
+
 Selenium.prototype._isNewPageLoaded = function() {
     return this.browserbot.isNewPageLoaded();
 };
@@ -2137,15 +2411,40 @@ Selenium.prototype.getCookie = function() {
     return doc.cookie;
 };
 
+Selenium.prototype.getCookieByName = function(name) {
+    /**
+     * Returns the value of the cookie with the specified name, or throws an error if the cookie is not present.
+     * @param name the name of the cookie
+     * @return string the value of the cookie
+     */
+    var v = this.browserbot.getCookieByName(name);
+    if (v === null) {
+        throw new SeleniumError(&quot;Cookie '&quot;+name+&quot;' was not found&quot;);
+    }
+    return v;
+};
+
+Selenium.prototype.isCookiePresent = function(name) {
+    /**
+     * Returns true if a cookie with the specified name is present, or false otherwise.
+     * @param name the name of the cookie
+     * @return boolean true if a cookie with the specified name is present, or false otherwise.
+     */
+    var v = this.browserbot.getCookieByName(name);
+    var absent = (v === null);
+    return !absent;
+}   
+
 Selenium.prototype.doCreateCookie = function(nameValuePair, optionsString) {
     /**
      * Create a new cookie whose path and domain are same with those of current page
      * under test, unless you specified a path for this cookie explicitly.
      *
      * @param nameValuePair name and value of the cookie in a format &quot;name=value&quot;
-     * @param optionsString options for the cookie. Currently supported options include 'path' and 'max_age'.
-     *      the optionsString's format is &quot;path=/path/, max_age=60&quot;. The order of options are irrelevant, the unit
-     *      of the value of 'max_age' is second.
+     * @param optionsString options for the cookie. Currently supported options include 'path', 'max_age' and 'domain'.
+     *      the optionsString's format is &quot;path=/path/, max_age=60, domain=.foo.com&quot;. The order of options are irrelevant, the unit
+     *      of the value of 'max_age' is second.  Note that specifying a domain that isn't a subset of the current domain will
+     *      usually fail.
      */
     var results = /[^\s=\[\]\(\),&quot;\/\?@:;]+=[^\s=\[\]\(\),&quot;\/\?@:;]*/.test(nameValuePair);
     if (!results) {
@@ -2168,31 +2467,586 @@ Selenium.prototype.doCreateCookie = function(nameValuePair, optionsString) {
         }
         cookie += &quot;; path=&quot; + path;
     }
+    results = /domain=([^\s,]+)[,]?/.exec(optionsString);
+    if (results) {
+        var domain = results[1];
+        cookie += &quot;; domain=&quot; + domain;
+    }
     LOG.debug(&quot;Setting cookie to: &quot; + cookie);
     this.browserbot.getDocument().cookie = cookie;
 }
 
-Selenium.prototype.doDeleteCookie = function(name,path) {
+Selenium.prototype.doDeleteCookie = function(name,optionsString) {
     /**
-     * Delete a named cookie with specified path.
+     * Delete a named cookie with specified path and domain.  Be careful; to delete a cookie, you
+     * need to delete it using the exact same path and domain that were used to create the cookie.
+     * If the path is wrong, or the domain is wrong, the cookie simply won't be deleted.  Also
+     * note that specifying a domain that isn't a subset of the current domain will usually fail.
+     *
+     * Since there's no way to discover at runtime the original path and domain of a given cookie,
+     * we've added an option called 'recurse' to try all sub-domains of the current domain with
+     * all paths that are a subset of the current path.  Beware; this option can be slow.  In
+     * big-O notation, it operates in O(n*m) time, where n is the number of dots in the domain
+     * name and m is the number of slashes in the path.
      *
      * @param name the name of the cookie to be deleted
-     * @param path the path property of the cookie to be deleted
+     * @param optionsString options for the cookie. Currently supported options include 'path', 'domain'
+     *      and 'recurse.' The optionsString's format is &quot;path=/path/, domain=.foo.com, recurse=true&quot;.
+     *      The order of options are irrelevant. Note that specifying a domain that isn't a subset of
+     *      the current domain will usually fail.
      */
     // set the expire time of the cookie to be deleted to one minute before now.
-    path = path.trim();
+    var path = &quot;&quot;;
+    var domain = &quot;&quot;;
+    var recurse = false;
+    var matched = false;
+    results = /path=([^\s,]+)[,]?/.exec(optionsString);
+    if (results) {
+        matched = true;
+        path = results[1];
+    }
+    results = /domain=([^\s,]+)[,]?/.exec(optionsString);
+    if (results) {
+        matched = true;
+        domain = results[1];
+    }
+    results = /recurse=([^\s,]+)[,]?/.exec(optionsString);
+    if (results) {
+        matched = true;
+        recurse = results[1];
+        if (&quot;false&quot; == recurse) {
+            recurse = false;
+        }
+    }
+    // Treat the entire optionsString as a path (for backwards compatibility)
+    if (optionsString &amp;&amp; !matched) {
+        LOG.warn(&quot;Using entire optionsString as a path; please change the argument to deleteCookie to use path=&quot;+optionsString);
+        path = optionsString;
+    }
     if (browserVersion.khtml) {
         // Safari and conquerer don't like paths with / at the end
         if (&quot;/&quot; != path) {
             path = path.replace(/\/$/, &quot;&quot;);
         }
+    }    
+    path = path.trim();
+    domain = domain.trim();
+    var cookieName = name.trim();
+    if (recurse) {
+        this.browserbot.recursivelyDeleteCookie(cookieName, domain, path);
+    } else {
+        this.browserbot.deleteCookie(cookieName, domain, path);
+    }
+}
+
+Selenium.prototype.doDeleteAllVisibleCookies = function() {
+    /** Calls deleteCookie with recurse=true on all cookies visible to the current page.
+    * As noted on the documentation for deleteCookie, recurse=true can be much slower
+    * than simply deleting the cookies using a known domain/path.
+    */
+    var win = this.browserbot.getCurrentWindow();
+    var doc = win.document;
+    var cookieNames = this.browserbot.getAllCookieNames(doc);
+    var domain = doc.domain;
+    var path = win.location.pathname;
+    for (var i = 0; i &lt; cookieNames.length; i++) {
+        this.browserbot.recursivelyDeleteCookie(cookieNames[i], domain, path, win);
     }
-    var expireDateInMilliseconds = (new Date()).getTime() + (-1 * 1000);
-    var cookie = name.trim() + &quot;=deleted; path=&quot; + path + &quot;; expires=&quot; + new Date(expireDateInMilliseconds).toGMTString();
-    LOG.debug(&quot;Setting cookie to: &quot; + cookie);
-    this.browserbot.getDocument().cookie = cookie;
 }
 
+Selenium.prototype.doSetBrowserLogLevel = function(logLevel) {
+    /**
+    * Sets the threshold for browser-side logging messages; log messages beneath this threshold will be discarded.
+    * Valid logLevel strings are: &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot; or &quot;off&quot;.
+    * To see the browser logs, you need to
+    * either show the log window in GUI mode, or enable browser-side logging in Selenium RC.
+    *
+    * @param logLevel one of the following: &quot;debug&quot;, &quot;info&quot;, &quot;warn&quot;, &quot;error&quot; or &quot;off&quot;
+    */
+    if (logLevel == null || logLevel == &quot;&quot;) {
+        throw new SeleniumError(&quot;You must specify a log level&quot;);
+    }
+    logLevel = logLevel.toLowerCase();
+    if (LOG.logLevels[logLevel] == null) {
+        throw new SeleniumError(&quot;Invalid log level: &quot; + logLevel);
+    }
+    LOG.setLogLevelThreshold(logLevel);
+}
+
+Selenium.prototype.doRunScript = function(script) {
+    /**
+    * Creates a new &quot;script&quot; tag in the body of the current test window, and 
+    * adds the specified text into the body of the command.  Scripts run in
+    * this way can often be debugged more easily than scripts executed using
+    * Selenium's &quot;getEval&quot; command.  Beware that JS exceptions thrown in these script
+    * tags aren't managed by Selenium, so you should probably wrap your script
+    * in try/catch blocks if there is any chance that the script will throw
+    * an exception.
+    * @param script the JavaScript snippet to run
+    */
+    var win = this.browserbot.getCurrentWindow();
+    var doc = win.document;
+    var scriptTag = doc.createElement(&quot;script&quot;);
+    scriptTag.type = &quot;text/javascript&quot;
+    scriptTag.text = script;
+    doc.body.appendChild(scriptTag);
+}
+
+Selenium.prototype.doAddLocationStrategy = function(strategyName, functionDefinition) {
+    /**
+    * Defines a new function for Selenium to locate elements on the page.
+    * For example,
+    * if you define the strategy &quot;foo&quot;, and someone runs click(&quot;foo=blah&quot;), we'll
+    * run your function, passing you the string &quot;blah&quot;, and click on the element 
+    * that your function
+    * returns, or throw an &quot;Element not found&quot; error if your function returns null.
+    *
+    * We'll pass three arguments to your function:
+    * &lt;ul&gt;
+    * &lt;li&gt;locator: the string the user passed in&lt;/li&gt;
+    * &lt;li&gt;inWindow: the currently selected window&lt;/li&gt;
+    * &lt;li&gt;inDocument: the currently selected document&lt;/li&gt;
+    * &lt;/ul&gt;
+    * The function must return null if the element can't be found.
+    * 
+    * @param strategyName the name of the strategy to define; this should use only
+    *   letters [a-zA-Z] with no spaces or other punctuation.
+    * @param functionDefinition a string defining the body of a function in JavaScript.
+    *   For example: &lt;code&gt;return inDocument.getElementById(locator);&lt;/code&gt;
+    */
+    if (!/^[a-zA-Z]+$/.test(strategyName)) {
+        throw new SeleniumError(&quot;Invalid strategy name: &quot; + strategyName);
+    }
+    var strategyFunction;
+    try {
+        strategyFunction = new Function(&quot;locator&quot;, &quot;inDocument&quot;, &quot;inWindow&quot;, functionDefinition);
+    } catch (ex) {
+        throw new SeleniumError(&quot;Error evaluating function definition: &quot; + extractExceptionMessage(ex));
+    }
+    var safeStrategyFunction = function() {
+        try {
+            return strategyFunction.apply(this, arguments);
+        } catch (ex) {
+            throw new SeleniumError(&quot;Error executing strategy function &quot; + strategyName + &quot;: &quot; + extractExceptionMessage(ex));
+        }
+    }
+    this.browserbot.locationStrategies[strategyName] = safeStrategyFunction;
+}
+
+Selenium.prototype.doCaptureEntirePageScreenshot = function(filename, kwargs) {
+    /**
+     * Saves the entire contents of the current window canvas to a PNG file.
+     * Contrast this with the captureScreenshot command, which captures the
+     * contents of the OS viewport (i.e. whatever is currently being displayed
+     * on the monitor), and is implemented in the RC only. Currently this only
+     * works in Firefox when running in chrome mode, and in IE non-HTA using
+     * the EXPERIMENTAL &quot;Snapsie&quot; utility. The Firefox implementation is mostly
+     * borrowed from the Screengrab! Firefox extension. Please see
+     * http://www.screengrab.org and http://snapsie.sourceforge.net/ for
+     * details.
+     *
+     * @param filename  the path to the file to persist the screenshot as. No
+     *                  filename extension will be appended by default.
+     *                  Directories will not be created if they do not exist,  
+     *                  and an exception will be thrown, possibly by native
+     *                  code.
+     * @param kwargs    a kwargs string that modifies the way the screenshot
+     *                  is captured. Example: &quot;background=#CCFFDD&quot; .
+     *                  Currently valid options:
+     *                  &lt;dl&gt;
+     *                   &lt;dt&gt;background&lt;/dt&gt;
+     *                     &lt;dd&gt;the background CSS for the HTML document. This
+     *                     may be useful to set for capturing screenshots of
+     *                     less-than-ideal layouts, for example where absolute
+     *                     positioning causes the calculation of the canvas
+     *                     dimension to fail and a black background is exposed
+     *                     (possibly obscuring black text).&lt;/dd&gt;
+     *                  &lt;/dl&gt;
+     */
+    if (! browserVersion.isChrome &amp;&amp;
+        ! (browserVersion.isIE &amp;&amp; ! browserVersion.isHTA)) {
+        throw new SeleniumError('captureEntirePageScreenshot is only '
+            + 'implemented for Firefox (&quot;firefox&quot; or &quot;chrome&quot;, NOT '
+            + '&quot;firefoxproxy&quot;) and IE non-HTA (&quot;iexploreproxy&quot;, NOT &quot;iexplore&quot; '
+            + 'or &quot;iehta&quot;). The current browser isn\'t one of them!');
+    }
+    
+    // do or do not ... there is no try
+    
+    if (browserVersion.isIE) {
+        // targeting snapsIE &gt;= 0.2
+        function getFailureMessage(exceptionMessage) {
+            var msg = 'Snapsie failed: ';
+            if (exceptionMessage) {
+                if (exceptionMessage ==
+                    &quot;Automation server can't create object&quot;) {
+                    msg += 'Is it installed? Does it have permission to run '
+                        'as an add-on? See http://snapsie.sourceforge.net/';
+                }
+                else {
+                    msg += exceptionMessage;
+                }
+            }
+            else {
+                msg += 'Undocumented error';
+            }
+            return msg;
+        }
+    
+        if (typeof(runOptions) != 'undefined' &amp;&amp;
+            runOptions.isMultiWindowMode() == false) {
+            // framed mode
+            try {
+                new Snapsie().saveSnapshot(filename, 'selenium_myiframe');
+            }
+            catch (e) {
+                throw new SeleniumError(getFailureMessage(e.message));
+            }
+        }
+        else {
+            // multi-window mode
+            if (!this.snapsieSrc) {
+                // XXX - cache snapsie, and capture the screenshot as a
+                // callback. Definitely a hack, because we may be late taking
+                // the first screenshot, but saves us from polluting other code
+                // for now. I wish there were an easier way to get at the
+                // contents of a referenced script!
+                var snapsieUrl = (this.browserbot.buttonWindow.location.href)
+                    .replace(/(Test|Remote)Runner\.html/, 'lib/snapsie.js');
+                var self = this;
+                new Ajax.Request(snapsieUrl, {
+                    method: 'get'
+                    , onSuccess: function(transport) {
+                        self.snapsieSrc = transport.responseText;
+                        self.doCaptureEntirePageScreenshot(filename, kwargs);
+                    }
+                });
+                return;
+            }
+
+            // it's going into a string, so escape the backslashes
+            filename = filename.replace(/\\/g, '\\\\');
+            
+            // this is sort of hackish. We insert a script into the document,
+            // and remove it before anyone notices.
+            var doc = selenium.browserbot.getDocument();
+            var script = doc.createElement('script'); 
+            var scriptContent = this.snapsieSrc 
+                + 'try {'
+                + '    new Snapsie().saveSnapshot(&quot;' + filename + '&quot;);'
+                + '}'
+                + 'catch (e) {'
+                + '    document.getElementById(&quot;takeScreenshot&quot;).failure ='
+                + '        e.message;'
+                + '}';
+            script.id = 'takeScreenshot';
+            script.language = 'javascript';
+            script.text = scriptContent;
+            doc.body.appendChild(script);
+            script.parentNode.removeChild(script);
+            if (script.failure) {
+                throw new SeleniumError(getFailureMessage(script.failure));
+            }
+        }
+        return;
+    }
+    
+    var grabber = {
+        prepareCanvas: function(width, height) {
+            var styleWidth = width + 'px';
+            var styleHeight = height + 'px';
+            
+            var grabCanvas = document.getElementById('screenshot_canvas');
+            if (!grabCanvas) {
+                // create the canvas
+                var ns = 'http://www.w3.org/1999/xhtml';
+                grabCanvas = document.createElementNS(ns, 'html:canvas');
+                grabCanvas.id = 'screenshot_canvas';
+                grabCanvas.style.display = 'none';
+                document.documentElement.appendChild(grabCanvas);
+            }
+            
+            grabCanvas.width = width;
+            grabCanvas.style.width = styleWidth;
+            grabCanvas.style.maxWidth = styleWidth;
+            grabCanvas.height = height;
+            grabCanvas.style.height = styleHeight;
+            grabCanvas.style.maxHeight = styleHeight;
+        
+            return grabCanvas;
+        },
+        
+        prepareContext: function(canvas, box) {
+            var context = canvas.getContext('2d');
+            context.clearRect(box.x, box.y, box.width, box.height);
+            context.save();
+            return context;
+        }
+    };
+    
+    var SGNsUtils = {
+        dataUrlToBinaryInputStream: function(dataUrl) {
+            var nsIoService = Components.classes[&quot;@mozilla.org/network/io-service;1&quot;]
+                .getService(Components.interfaces.nsIIOService);
+            var channel = nsIoService
+                .newChannelFromURI(nsIoService.newURI(dataUrl, null, null));
+            var binaryInputStream = Components.classes[&quot;@mozilla.org/binaryinputstream;1&quot;]
+                .createInstance(Components.interfaces.nsIBinaryInputStream);
+            
+            binaryInputStream.setInputStream(channel.open());
+            return binaryInputStream;
+        },
+        
+        newFileOutputStream: function(nsFile) {
+            var writeFlag = 0x02; // write only
+            var createFlag = 0x08; // create
+            var truncateFlag = 0x20; // truncate
+            var fileOutputStream = Components.classes[&quot;@mozilla.org/network/file-output-stream;1&quot;]
+                .createInstance(Components.interfaces.nsIFileOutputStream);
+                
+            fileOutputStream.init(nsFile,
+                writeFlag | createFlag | truncateFlag, 0664, null);
+            return fileOutputStream;
+        },
+        
+        writeBinaryInputStreamToFileOutputStream:
+        function(binaryInputStream, fileOutputStream) {
+            var numBytes = binaryInputStream.available();
+            var bytes = binaryInputStream.readBytes(numBytes);
+            fileOutputStream.write(bytes, numBytes);
+        }
+    };
+    
+    // compute dimensions
+    var window = this.browserbot.getCurrentWindow();
+    var doc = window.document.documentElement;
+    var box = {
+        x: 0,
+        y: 0,
+        width: doc.scrollWidth,
+        height: doc.scrollHeight
+    };
+    LOG.debug('computed dimensions');
+    
+    var originalBackground = doc.style.background;
+    
+    if (kwargs) {
+        var args = parse_kwargs(kwargs);
+        if (args.background) {
+            doc.style.background = args.background;
+        }
+    }
+    
+    // grab
+    var format = 'png';
+    var canvas = grabber.prepareCanvas(box.width, box.height);
+    var context = grabber.prepareContext(canvas, box);
+    context.drawWindow(window, box.x, box.y, box.width, box.height,
+        'rgb(0, 0, 0)');
+    context.restore();
+    var dataUrl = canvas.toDataURL(&quot;image/&quot; + format);
+    LOG.debug('grabbed to canvas');
+    
+    doc.style.background = originalBackground;
+    
+    // save to file
+    var nsFile = Components.classes[&quot;@mozilla.org/file/local;1&quot;]
+        .createInstance(Components.interfaces.nsILocalFile);
+    try {
+        nsFile.initWithPath(filename);
+    }
+    catch (e) {
+        if (/NS_ERROR_FILE_UNRECOGNIZED_PATH/.test(e.message)) {
+            // try using the opposite file separator
+            if (filename.indexOf('/') != -1) {
+                filename = filename.replace(/\//g, '\\');
+            }
+            else {
+                filename = filename.replace(/\\/g, '/');
+            }
+            nsFile.initWithPath(filename);
+        }
+        else {
+            throw e;
+        }
+    }
+    var binaryInputStream = SGNsUtils.dataUrlToBinaryInputStream(dataUrl);
+    var fileOutputStream = SGNsUtils.newFileOutputStream(nsFile);
+    SGNsUtils.writeBinaryInputStreamToFileOutputStream(binaryInputStream,
+        fileOutputStream);
+    fileOutputStream.close();
+    LOG.debug('saved to file');
+};
+
+Selenium.prototype.doRollup = function(rollupName, kwargs) {
+    /**
+     * Executes a command rollup, which is a series of commands with a unique
+     * name, and optionally arguments that control the generation of the set of
+     * commands. If any one of the rolled-up commands fails, the rollup is
+     * considered to have failed. Rollups may also contain nested rollups.
+     *
+     * @param rollupName  the name of the rollup command
+     * @param kwargs      keyword arguments string that influences how the
+     *                    rollup expands into commands
+     */
+    // we have to temporarily hijack the commandStarted, nextCommand(),
+    // commandComplete(), and commandError() methods of the TestLoop object.
+    // When the expanded rollup commands are done executing (or an error has
+    // occurred), we'll restore them to their original values.
+    var loop = currentTest || htmlTestRunner.currentTest;
+    var backupManager = {
+        backup: function() {
+            for (var item in this.data) {
+                this.data[item] = loop[item];
+            }
+        }
+        , restore: function() {
+            for (var item in this.data) {
+                loop[item] = this.data[item];
+            }
+        }
+        , data: {
+            requiresCallBack: null
+            , commandStarted: null
+            , nextCommand: null
+            , commandComplete: null
+            , commandError: null
+            , pendingRollupCommands: null
+            , rollupFailed: null
+            , rollupFailedMessage: null
+        }
+    };
+    
+    var rule = RollupManager.getInstance().getRollupRule(rollupName);
+    var expandedCommands = rule.getExpandedCommands(kwargs);
+    
+    // hold your breath ...
+    try {
+        backupManager.backup();
+        loop.requiresCallBack = false;
+        loop.commandStarted = function() {};
+        loop.nextCommand = function() {
+            if (this.pendingRollupCommands.length == 0) {
+                return null;
+            }
+            var command = this.pendingRollupCommands.shift();
+            return command;
+        };
+        loop.commandComplete = function(result) {
+            if (result.failed) {
+                this.rollupFailed = true;
+                this.rollupFailureMessages.push(result.failureMessage);
+            }
+            
+            if (this.pendingRollupCommands.length == 0) {
+                result = {
+                    failed: this.rollupFailed
+                    , failureMessage: this.rollupFailureMessages.join('; ')
+                };
+                LOG.info('Rollup execution complete: ' + (result.failed
+                    ? 'failed! (see error messages below)' : 'ok'));
+                backupManager.restore();
+                this.commandComplete(result);
+            }
+        };
+        loop.commandError = function(errorMessage) {
+            LOG.info('Rollup execution complete: bombed!');
+            backupManager.restore();
+            this.commandError(errorMessage);
+        };
+        
+        loop.pendingRollupCommands = expandedCommands;
+        loop.rollupFailed = false;
+        loop.rollupFailureMessages = [];
+    }
+    catch (e) {
+        LOG.error('Rollup error: ' + e);
+        backupManager.restore();
+    }
+};
+
+Selenium.prototype.doAddScript = function(scriptContent, scriptTagId) {
+    /**
+    * Loads script content into a new script tag in the Selenium document. This
+    * differs from the runScript command in that runScript adds the script tag
+    * to the document of the AUT, not the Selenium document. The following
+    * entities in the script content are replaced by the characters they
+    * represent:
+    *
+    *     &amp;lt;
+    *     &amp;gt;
+    *     &amp;amp;
+    *
+    * The corresponding remove command is removeScript.
+    *
+    * @param scriptContent  the Javascript content of the script to add
+    * @param scriptTagId    (optional) the id of the new script tag. If
+    *                       specified, and an element with this id already
+    *                       exists, this operation will fail.
+    */
+    if (scriptTagId &amp;&amp; document.getElementById(scriptTagId)) {
+        var msg = &quot;Element with id '&quot; + scriptTagId + &quot;' already exists!&quot;;
+        throw new SeleniumError(msg);
+    }
+    
+    var head = document.getElementsByTagName('head')[0];
+    var script = document.createElement('script');
+    
+    script.type = 'text/javascript';
+    
+    if (scriptTagId) {
+        script.id = scriptTagId;
+    }
+    
+    // replace some entities
+    scriptContent = scriptContent
+        .replace(/&amp;lt;/g, '&lt;')
+        .replace(/&amp;gt;/g, '&gt;')
+        .replace(/&amp;amp;/g, '&amp;');
+    
+    script.text = scriptContent;
+    head.appendChild(script);
+};
+
+Selenium.prototype.doRemoveScript = function(scriptTagId) {
+    /**
+    * Removes a script tag from the Selenium document identified by the given
+    * id. Does nothing if the referenced tag doesn't exist.
+    *
+    * @param scriptTagId  the id of the script element to remove.
+    */
+    var script = document.getElementById(scriptTagId);
+    
+    if (script &amp;&amp; getTagName(script) == 'script') {
+        script.parentNode.removeChild(script);
+    }
+};
+
+Selenium.prototype.doUseXpathLibrary = function(libraryName) {
+    /**
+	* Allows choice of one of the available libraries.
+    * @param libraryName name of the desired library
+    * Only the following three can be chosen:
+    *   ajaxslt - Google's library
+    *   javascript - Cybozu Labs' faster library
+    *   default - The default library.  Currently the default library is ajaxslt.
+    * If libraryName isn't one of these three, then 
+    * no change will be made.
+    *   
+    */
+
+    if (libraryName == &quot;default&quot;) {
+        this.browserbot.xpathLibrary = this.browserbot.defaultXpathLibrary;
+        return;
+    }
+
+	if ((libraryName != 'ajaxslt') &amp;&amp; (libraryName != 'javascript-xpath')) {
+		return;
+	}
+	
+	this.browserbot.xpathLibrary = libraryName;	
+	
+};
 
 /**
  *  Factory for creating &quot;Option Locators&quot;.
@@ -2224,7 +3078,7 @@ OptionLocatorFactory.prototype.fromLocatorString = function(locatorString) {
     if (this.optionLocators[locatorType]) {
         return new this.optionLocators[locatorType](locatorValue);
     }
-    throw new SeleniumError(&quot;Unkown option locator type: &quot; + locatorType);
+    throw new SeleniumError(&quot;Unknown option locator type: &quot; + locatorType);
 };
 
 /**
@@ -2327,3 +3181,4 @@ OptionLocatorFactory.prototype.OptionLocatorById = function(id) {
         Assert.matches(this.id, selectedId)
     };
 };
+</diff>
      <filename>selenium-core/scripts/selenium-api.js</filename>
    </modified>
    <modified>
      <diff>@@ -36,10 +36,12 @@ var BrowserBot = function(topLevelApplicationWindow) {
     this.buttonWindow = window;
     this.currentWindow = this.topWindow;
     this.currentWindowName = null;
-    
+    this.allowNativeXpath = true;
+    this.xpathLibrary = this.defaultXpathLibrary = 'ajaxslt' // change to &quot;javascript-xpath&quot; for the newer, faster engine
+
     // We need to know this in advance, in case the frame closes unexpectedly
     this.isSubFrameSelected = false;
-    
+
     this.altKeyDown = false;
     this.controlKeyDown = false;
     this.shiftKeyDown = false;
@@ -54,10 +56,10 @@ var BrowserBot = function(topLevelApplicationWindow) {
     this.nextPromptResult = '';
     this.newPageLoaded = false;
     this.pageLoadError = null;
-    
+
     this.shouldHighlightLocatedElement = false;
 
-    this.uniqueId = new Date().getTime();
+    this.uniqueId = &quot;seleniumMarker&quot; + new Date().getTime();
     this.pollingForLoad = new Object();
     this.permDeniedCount = new Object();
     this.windowPollers = new Array();
@@ -65,10 +67,10 @@ var BrowserBot = function(topLevelApplicationWindow) {
     this.browserbot = this;
 
     var self = this;
-    
+
     objectExtend(this, PageBot.prototype);
     this._registerAllLocatorFunctions();
-    
+
     this.recordPageLoad = function(elementOrWindow) {
         LOG.debug(&quot;Page load detected&quot;);
         try {
@@ -126,7 +128,7 @@ BrowserBot.createForWindow = function(window, proxyInjectionMode) {
     }
     // getCurrentWindow has the side effect of modifying it to handle page loads etc
     browserbot.proxyInjectionMode = proxyInjectionMode;
-    browserbot.getCurrentWindow();	// for modifyWindow side effect.  This is not a transparent style
+    browserbot.getCurrentWindow();    // for modifyWindow side effect.  This is not a transparent style
     return browserbot;
 };
 
@@ -135,8 +137,8 @@ BrowserBot.prototype.doModalDialogTest = function(test) {
     this.modalDialogTest = test;
 };
 
-BrowserBot.prototype.cancelNextConfirmation = function() {
-    this.nextConfirmResult = false;
+BrowserBot.prototype.cancelNextConfirmation = function(result) {
+    this.nextConfirmResult = result;
 };
 
 BrowserBot.prototype.setNextPromptResult = function(result) {
@@ -147,9 +149,25 @@ BrowserBot.prototype.hasAlerts = function() {
     return (this.recordedAlerts.length &gt; 0);
 };
 
-BrowserBot.prototype.relayBotToRC = function() {
+BrowserBot.prototype.relayBotToRC = function(s) {
+    // DGF need to do this funny trick to see if we're in PI mode, because
+    // &quot;this&quot; might be the window, rather than the browserbot (e.g. during window.alert) 
+    var piMode = this.proxyInjectionMode;
+    if (!piMode) {
+        if (typeof(selenium) != &quot;undefined&quot;) {
+            piMode = selenium.browserbot &amp;&amp; selenium.browserbot.proxyInjectionMode;
+        }
+    }
+    if (piMode) {
+        this.relayToRC(&quot;selenium.&quot; + s);
+    }
 };
-// override in injection.html
+
+BrowserBot.prototype.relayToRC = function(name) {
+        var object = eval(name);
+        var s = 'state:' + serializeObject(name, object) + &quot;\n&quot;;
+        sendToRC(s,&quot;state=true&quot;);
+}
 
 BrowserBot.prototype.resetPopups = function() {
     this.recordedAlerts = [];
@@ -159,6 +177,9 @@ BrowserBot.prototype.resetPopups = function() {
 
 BrowserBot.prototype.getNextAlert = function() {
     var t = this.recordedAlerts.shift();
+    if (t) { 
+        t = t.replace(/\n/g, &quot; &quot;);  // because Selenese loses \n's when retrieving text from HTML table
+    }
     this.relayBotToRC(&quot;browserbot.recordedAlerts&quot;);
     return t;
 };
@@ -185,20 +206,19 @@ BrowserBot.prototype.getNextPrompt = function() {
 
 /* Fire a mouse event in a browser-compatible manner */
 
-BrowserBot.prototype.triggerMouseEvent = function(element, eventType, canBubble, clientX, clientY) {
+BrowserBot.prototype.triggerMouseEvent = function(element, eventType, canBubble, clientX, clientY, button) {
     clientX = clientX ? clientX : 0;
     clientY = clientY ? clientY : 0;
 
-    LOG.warn(&quot;triggerMouseEvent assumes setting screenX and screenY to 0 is ok&quot;);
+    LOG.debug(&quot;triggerMouseEvent assumes setting screenX and screenY to 0 is ok&quot;);
     var screenX = 0;
     var screenY = 0;
 
     canBubble = (typeof(canBubble) == undefined) ? true : canBubble;
-    if (element.fireEvent) {
-        LOG.info(&quot;element has fireEvent&quot;);
+    if (element.fireEvent &amp;&amp; element.ownerDocument &amp;&amp; element.ownerDocument.createEventObject) { //IE
         var evt = createEventObject(element, this.controlKeyDown, this.altKeyDown, this.shiftKeyDown, this.metaKeyDown);
         evt.detail = 0;
-        evt.button = 1;
+        evt.button = button ? button : 1; // default will be the left mouse click ( http://www.javascriptkit.com/jsref/event.shtml )
         evt.relatedTarget = null;
         if (!screenX &amp;&amp; !screenY &amp;&amp; !clientX &amp;&amp; !clientY &amp;&amp; !this.controlKeyDown &amp;&amp; !this.altKeyDown &amp;&amp; !this.shiftKeyDown &amp;&amp; !this.metaKeyDown) {
             element.fireEvent('on' + eventType);
@@ -228,24 +248,27 @@ BrowserBot.prototype.triggerMouseEvent = function(element, eventType, canBubble,
         }
     }
     else {
-        LOG.info(&quot;element doesn't have fireEvent&quot;);
         var evt = document.createEvent('MouseEvents');
         if (evt.initMouseEvent)
         {
-            LOG.info(&quot;element has initMouseEvent&quot;);
+            // see http://developer.mozilla.org/en/docs/DOM:event.button and
+            // http://developer.mozilla.org/en/docs/DOM:event.initMouseEvent for button ternary logic logic
             //Safari
-            evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, screenX, screenY, clientX, clientY, 
-            	this.controlKeyDown, this.altKeyDown, this.shiftKeyDown, this.metaKeyDown, 0, null);
+            evt.initMouseEvent(eventType, canBubble, true, document.defaultView, 1, screenX, screenY, clientX, clientY,
+                this.controlKeyDown, this.altKeyDown, this.shiftKeyDown, this.metaKeyDown, button ? button : 0, null);
         }
         else {
-        	LOG.warn(&quot;element doesn't have initMouseEvent; firing an event which should -- but doesn't -- have other mouse-event related attributes here, as well as controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown&quot;);
+            LOG.warn(&quot;element doesn't have initMouseEvent; firing an event which should -- but doesn't -- have other mouse-event related attributes here, as well as controlKeyDown, altKeyDown, shiftKeyDown, metaKeyDown&quot;);
             evt.initEvent(eventType, canBubble, true);
-            
+
             evt.shiftKey = this.shiftKeyDown;
             evt.metaKey = this.metaKeyDown;
             evt.altKey = this.altKeyDown;
             evt.ctrlKey = this.controlKeyDown;
-
+            if(button)
+            {
+              evt.button = button;
+            }
         }
         element.dispatchEvent(evt);
     }
@@ -269,7 +292,7 @@ BrowserBot.prototype._modifyWindow = function(win) {
         LOG.debug('modifyWindow ' + this.uniqueId + &quot;:&quot; + win[this.uniqueId]);
     }
     if (!win[this.uniqueId]) {
-        win[this.uniqueId] = true;
+        win[this.uniqueId] = 1;
         this.modifyWindowToRecordPopUpDialogs(win, this);
     }
     // In proxyInjection mode, we have our own mechanism for detecting page loads
@@ -287,10 +310,32 @@ BrowserBot.prototype._modifyWindow = function(win) {
 };
 
 BrowserBot.prototype.selectWindow = function(target) {
-    if (target &amp;&amp; target != &quot;null&quot;) {
-        this._selectWindowByName(target);
-    } else {
+    if (!target || target == &quot;null&quot;) {
         this._selectTopWindow();
+        return;
+    }
+    var result = target.match(/^([a-zA-Z]+)=(.*)/);
+    if (!result) {
+        try {
+            this._selectWindowByName(target);
+        }
+        catch (e) {
+            this._selectWindowByTitle(target);
+        }
+        return;
+    }
+    locatorType = result[1];
+    locatorValue = result[2];
+    if (locatorType == &quot;title&quot;) {
+        this._selectWindowByTitle(locatorValue);
+    }
+    // TODO separate name and var into separate functions
+    else if (locatorType == &quot;name&quot;) {
+        this._selectWindowByName(locatorValue);
+    } else if (locatorType == &quot;var&quot;) {
+        this._selectWindowByName(locatorValue);
+    } else {
+        throw new SeleniumError(&quot;Window locator not recognized: &quot; + locatorType);
     }
 };
 
@@ -308,8 +353,29 @@ BrowserBot.prototype._selectWindowByName = function(target) {
     this.isSubFrameSelected = false;
 }
 
+BrowserBot.prototype._selectWindowByTitle = function(target) {
+    var windowName = this.getWindowNameByTitle(target);
+    if (!windowName) {
+        this._selectTopWindow();
+    } else {
+        this._selectWindowByName(windowName);
+    }
+}
+
 BrowserBot.prototype.selectFrame = function(target) {
-    if (target == &quot;relative=up&quot;) {
+    if (target.indexOf(&quot;index=&quot;) == 0) {
+        target = target.substr(6);
+        var frame = this.getCurrentWindow().frames[target];
+        if (frame == null) {
+            throw new SeleniumError(&quot;Not found: frames[&quot;+index+&quot;]&quot;);
+        }
+        if (!frame.document) {
+            throw new SeleniumError(&quot;frames[&quot;+index+&quot;] is not a frame&quot;);
+        }
+        this.currentWindow = frame;
+        this.isSubFrameSelected = true;
+    }
+    else if (target == &quot;relative=up&quot; || target == &quot;relative=parent&quot;) {
         this.currentWindow = this.getCurrentWindow().parent;
         this.isSubFrameSelected = (this._getFrameElement(this.currentWindow) != null);
     } else if (target == &quot;relative=top&quot;) {
@@ -338,11 +404,11 @@ BrowserBot.prototype.selectFrame = function(target) {
             this.isSubFrameSelected = true;
             match = true;
         }
-        
+
         if (!match) {
             // neither, let's loop through the frame names
             var win = this.getCurrentWindow();
-            
+
             if (win &amp;&amp; win.frames &amp;&amp; win.frames.length) {
                 for (var i = 0; i &lt; win.frames.length; i++) {
                     if (win.frames[i].name == target) {
@@ -362,6 +428,65 @@ BrowserBot.prototype.selectFrame = function(target) {
     this.getCurrentWindow();
 };
 
+BrowserBot.prototype.doesThisFrameMatchFrameExpression = function(currentFrameString, target) {
+    var isDom = false;
+    if (target.indexOf(&quot;dom=&quot;) == 0) {
+        target = target.substr(4);
+        isDom = true;
+    } else if (target.indexOf(&quot;index=&quot;) == 0) {
+        target = &quot;frames[&quot; + target.substr(6) + &quot;]&quot;;
+        isDom = true;
+    }
+    var t;
+    try {
+        eval(&quot;t=&quot; + currentFrameString + &quot;.&quot; + target);
+    } catch (e) {
+    }
+    var autWindow = this.browserbot.getCurrentWindow();
+    if (t != null) {
+        try {
+            if (t.window == autWindow) {
+                return true;
+            }
+            if (t.window.uniqueId == autWindow.uniqueId) {
+                return true;
+               }
+            return false;
+        } catch (permDenied) {
+            // DGF if the windows are incomparable, they're probably not the same...
+        }
+    }
+    if (isDom) {
+        return false;
+    }
+    var currentFrame;
+    eval(&quot;currentFrame=&quot; + currentFrameString);
+    if (target == &quot;relative=up&quot;) {
+        if (currentFrame.window.parent == autWindow) {
+            return true;
+        }
+        return false;
+    }
+    if (target == &quot;relative=top&quot;) {
+        if (currentFrame.window.top == autWindow) {
+            return true;
+        }
+        return false;
+    }
+    if (currentFrame.window == autWindow.parent) {
+        if (autWindow.name == target) {
+            return true;
+        }
+        try {
+            var element = this.findElement(target, currentFrame.window);
+            if (element.contentWindow == autWindow) {
+                return true;
+            }
+        } catch (e) {}
+    }
+    return false;
+};
+
 BrowserBot.prototype.openLocation = function(target) {
     // We're moving to a new page - clear the current one
     var win = this.getCurrentWindow();
@@ -413,16 +538,18 @@ BrowserBot.prototype.getCurrentPage = function() {
 BrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify, browserBot) {
     var self = this;
 
+    windowToModify.seleniumAlert = windowToModify.alert;
+
     windowToModify.alert = function(alert) {
         browserBot.recordedAlerts.push(alert);
-        self.relayBotToRC(&quot;browserbot.recordedAlerts&quot;);
+        self.relayBotToRC.call(self, &quot;browserbot.recordedAlerts&quot;);
     };
 
     windowToModify.confirm = function(message) {
         browserBot.recordedConfirmations.push(message);
         var result = browserBot.nextConfirmResult;
         browserBot.nextConfirmResult = true;
-        self.relayBotToRC(&quot;browserbot.recordedConfirmations&quot;);
+        self.relayBotToRC.call(self, &quot;browserbot.recordedConfirmations&quot;);
         return result;
     };
 
@@ -431,7 +558,7 @@ BrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify,
         var result = !browserBot.nextConfirmResult ? null : browserBot.nextPromptResult;
         browserBot.nextConfirmResult = true;
         browserBot.nextPromptResult = '';
-        self.relayBotToRC(&quot;browserbot.recordedPrompts&quot;);
+        self.relayBotToRC.call(self, &quot;browserbot.recordedPrompts&quot;);
         return result;
     };
 
@@ -443,28 +570,32 @@ BrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModify,
         originalOpenReference = 'selenium_originalOpen' + new Date().getTime();
         windowToModify[originalOpenReference] = windowToModify.open;
     }
-    
+
     var isHTA = browserVersion.isHTA;
-    
+
     var newOpen = function(url, windowName, windowFeatures, replaceFlag) {
         var myOriginalOpen = originalOpen;
         if (isHTA) {
             myOriginalOpen = this[originalOpenReference];
         }
+        if (windowName == &quot;&quot; || windowName == &quot;_blank&quot;) {
+            windowName = &quot;selenium_blank&quot; + Math.round(100000 * Math.random());
+            LOG.warn(&quot;Opening window '_blank', which is not a real window name.  Randomizing target to be: &quot; + windowName);
+        }
         var openedWindow = myOriginalOpen(url, windowName, windowFeatures, replaceFlag);
         LOG.debug(&quot;window.open call intercepted; window ID (which you can use with selectWindow()) is \&quot;&quot; +  windowName + &quot;\&quot;&quot;);
         if (windowName!=null) {
-        	openedWindow[&quot;seleniumWindowName&quot;] = windowName;
+            openedWindow[&quot;seleniumWindowName&quot;] = windowName;
         }
         selenium.browserbot.openedWindows[windowName] = openedWindow;
         return openedWindow;
     };
-    
+
     if (browserVersion.isHTA) {
         originalOpenReference = 'selenium_originalOpen' + new Date().getTime();
         newOpenReference = 'selenium_newOpen' + new Date().getTime();
         var setOriginalRef = &quot;this['&quot; + originalOpenReference + &quot;'] = this.open;&quot;;
-        
+
         if (windowToModify.eval) {
             windowToModify.eval(setOriginalRef);
             windowToModify.open = newOpen;
@@ -510,7 +641,12 @@ BrowserBot.prototype.modifySeparateTestWindowToDetectPageLoads = function(window
         LOG.debug(&quot;modifySeparateTestWindowToDetectPageLoads: this window is a frame; attaching a load listener&quot;);
         addLoadListener(frameElement, this.recordPageLoad);
         frameElement[marker] = true;
-        frameElement[this.uniqueId] = marker;
+        frameElement[&quot;frame&quot;+this.uniqueId] = marker;
+	LOG.debug(&quot;dgf this.uniqueId=&quot;+this.uniqueId);
+	LOG.debug(&quot;dgf marker=&quot;+marker);
+	LOG.debug(&quot;dgf frameElement['frame'+this.uniqueId]=&quot;+frameElement['frame'+this.uniqueId]);
+frameElement[this.uniqueId] = marker;
+LOG.debug(&quot;dgf frameElement[this.uniqueId]=&quot;+frameElement[this.uniqueId]);
     } else {
         windowObject.location[marker] = true;
         windowObject[this.uniqueId] = marker;
@@ -539,15 +675,51 @@ BrowserBot.prototype._getFrameElement = function(win) {
         try {
             parentContainsIdenticallyNamedFrame = win.parent.frames[win.name];
         } catch (e) {} // this may fail if access is denied to the parent; in that case, assume it's not a pop-up
-        
+
         if (parentContainsIdenticallyNamedFrame) {
             // it can't be a coincidence that the parent has a frame with the same name as myself!
-            return BrowserBot.prototype.locateElementByName(win.name, win.parent.document, win.parent);
+            var result;
+            try {
+                result = parentContainsIdenticallyNamedFrame.frameElement;
+                if (result) {
+                    return result;
+                }
+            } catch (e) {} // it was worth a try! _getFrameElementsByName is often slow
+            result = this._getFrameElementByName(win.name, win.parent.document, win);
+            return result;
         }
-    } 
+    }
+    LOG.debug(&quot;_getFrameElement: frameElement=&quot;+frameElement); 
+    if (frameElement) {
+        LOG.debug(&quot;frameElement.name=&quot;+frameElement.name);
+    }
     return frameElement;
 }
 
+BrowserBot.prototype._getFrameElementByName = function(name, doc, win) {
+    var frames;
+    var frame;
+    var i;
+    frames = doc.getElementsByTagName(&quot;iframe&quot;);
+    for (i = 0; i &lt; frames.length; i++) {
+        frame = frames[i];        
+        if (frame.name === name) {
+            return frame;
+        }
+    }
+    frames = doc.getElementsByTagName(&quot;frame&quot;);
+    for (i = 0; i &lt; frames.length; i++) {
+        frame = frames[i];        
+        if (frame.name === name) {
+            return frame;
+        }
+    }
+    // DGF weird; we only call this function when we know the doc contains the frame
+    LOG.warn(&quot;_getFrameElementByName couldn't find a frame or iframe; checking every element for the name &quot; + name);
+    return BrowserBot.prototype.locateElementByName(win.name, win.parent.document);
+}
+    
+
 /**
  * Set up a polling timer that will keep checking the readyState of the document until it's complete.
  * Since we might call this before the original page is unloaded, we first check to see that the current location
@@ -594,8 +766,9 @@ BrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, original
         LOG.debug(&quot;pollForLoad continue (&quot; + marker + &quot;): &quot; + currentHref);
         this.reschedulePoller(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker);
     } catch (e) {
-        LOG.error(&quot;Exception during pollForLoad; this should get noticed soon (&quot; + e.message + &quot;)!&quot;);
-        LOG.exception(e);
+        LOG.debug(&quot;Exception during pollForLoad; this should get noticed soon (&quot; + e.message + &quot;)!&quot;);
+        //DGF this is supposed to get logged later; log it at debug just in case
+        //LOG.exception(e);
         this.pageLoadError = e;
     }
 };
@@ -735,7 +908,7 @@ BrowserBot.prototype.isPollingForLoad = function(win) {
     var frameElement = this._getFrameElement(win);
     var htaSubFrame = this._isHTASubFrame(win);
     if (frameElement &amp;&amp; !htaSubFrame) {
-        marker = frameElement[this.uniqueId];
+	marker = frameElement[&quot;frame&quot;+this.uniqueId];
     } else {
         marker = win[this.uniqueId];
     }
@@ -773,7 +946,7 @@ BrowserBot.prototype.getWindowByName = function(windowName, doNotModify) {
         }
     }
     if (!targetWindow) {
-        throw new SeleniumError(&quot;Window does not exist&quot;);
+        throw new SeleniumError(&quot;Window does not exist. If this looks like a Selenium bug, make sure to read http://selenium-core.openqa.org/reference.html#openWindow for potential workarounds.&quot;);
     }
     if (browserVersion.isHTA) {
         try {
@@ -789,25 +962,73 @@ BrowserBot.prototype.getWindowByName = function(windowName, doNotModify) {
     return targetWindow;
 };
 
+/**
+ * Find a window name from the window title.
+ */
+BrowserBot.prototype.getWindowNameByTitle = function(windowTitle) {
+    LOG.debug(&quot;getWindowNameByTitle(&quot; + windowTitle + &quot;)&quot;);
+
+    // First look in the map of opened windows and iterate them
+    for (var windowName in this.openedWindows) {
+        var targetWindow = this.openedWindows[windowName];
+
+        // If the target window's title is our title
+        try {
+            // TODO implement Pattern Matching here
+            if (!this._windowClosed(targetWindow) &amp;&amp;
+                targetWindow.document.title == windowTitle) {
+                return windowName;
+            }
+        } catch (e) {
+            // You'll often get Permission Denied errors here in IE
+            // eh, if we can't read this window's title,
+            // it's probably not available to us right now anyway
+        }
+    }
+    
+    try {
+        if (this.topWindow.document.title == windowTitle) {
+            return &quot;&quot;;
+        }
+    } catch (e) {} // IE Perm denied
+
+    throw new SeleniumError(&quot;Could not find window with title &quot; + windowTitle);
+};
+
 BrowserBot.prototype.getCurrentWindow = function(doNotModify) {
+    if (this.proxyInjectionMode) {
+        return window;
+    }
     var testWindow = this.currentWindow;
     if (!doNotModify) {
         this._modifyWindow(testWindow);
-        if (!this.proxyInjectionMode) {
-            // In proxy injection mode, have to avoid logging during getCurrentWindow to avoid an infinite loop
-            LOG.debug(&quot;getCurrentWindow newPageLoaded = false&quot;);
-        }
+        LOG.debug(&quot;getCurrentWindow newPageLoaded = false&quot;);
         this.newPageLoaded = false;
     }
     testWindow = this._handleClosedSubFrame(testWindow, doNotModify);
     return testWindow;
 };
 
+/**
+ * Offer a method the end-user can reliably use to retrieve the current window.
+ * This should work even for windows with an XPCNativeWrapper. Returns the
+ * current window object.
+ */
+BrowserBot.prototype.getUserWindow = function() {
+    var userWindow = this.getCurrentWindow(true);
+    
+    if (userWindow.wrappedJSObject) {
+        userWindow = userWindow.wrappedJSObject;
+    }
+    
+    return userWindow;
+};
+
 BrowserBot.prototype._handleClosedSubFrame = function(testWindow, doNotModify) {
     if (this.proxyInjectionMode) {
         return testWindow;
     }
-    
+
     if (this.isSubFrameSelected) {
         var missing = true;
         if (testWindow.parent &amp;&amp; testWindow.parent.frames &amp;&amp; testWindow.parent.frames.length) {
@@ -902,6 +1123,103 @@ BrowserBot.prototype.getTitle = function() {
     return t;
 }
 
+BrowserBot.prototype.getCookieByName = function(cookieName, doc) {
+    if (!doc) doc = this.getDocument();
+    var ck = doc.cookie;
+    if (!ck) return null;
+    var ckPairs = ck.split(/;/);
+    for (var i = 0; i &lt; ckPairs.length; i++) {
+        var ckPair = ckPairs[i].trim();
+        var ckNameValue = ckPair.split(/=/);
+        var ckName = decodeURIComponent(ckNameValue[0]);
+        if (ckName === cookieName) {
+            return decodeURIComponent(ckNameValue[1]);
+        }
+    }
+    return null;
+}
+
+BrowserBot.prototype.getAllCookieNames = function(doc) {
+    if (!doc) doc = this.getDocument();
+    var ck = doc.cookie;
+    if (!ck) return [];
+    var cookieNames = [];
+    var ckPairs = ck.split(/;/);
+    for (var i = 0; i &lt; ckPairs.length; i++) {
+        var ckPair = ckPairs[i].trim();
+        var ckNameValue = ckPair.split(/=/);
+        var ckName = decodeURIComponent(ckNameValue[0]);
+        cookieNames.push(ckName);
+    }
+    return cookieNames;
+}
+
+BrowserBot.prototype.deleteCookie = function(cookieName, domain, path, doc) {
+    if (!doc) doc = this.getDocument();
+    var expireDateInMilliseconds = (new Date()).getTime() + (-1 * 1000);
+    var cookie = cookieName + &quot;=deleted; &quot;;
+    if (path) {
+        cookie += &quot;path=&quot; + path + &quot;; &quot;;
+    }
+    if (domain) {
+        cookie += &quot;domain=&quot; + domain + &quot;; &quot;;
+    }
+    cookie += &quot;expires=&quot; + new Date(expireDateInMilliseconds).toGMTString();
+    LOG.debug(&quot;Setting cookie to: &quot; + cookie);
+    doc.cookie = cookie;
+}
+
+/** Try to delete cookie, return false if it didn't work */
+BrowserBot.prototype._maybeDeleteCookie = function(cookieName, domain, path, doc) {
+    this.deleteCookie(cookieName, domain, path, doc);
+    return (!this.getCookieByName(cookieName, doc));
+}
+    
+
+BrowserBot.prototype._recursivelyDeleteCookieDomains = function(cookieName, domain, path, doc) {
+    var deleted = this._maybeDeleteCookie(cookieName, domain, path, doc);
+    if (deleted) return true;
+    var dotIndex = domain.indexOf(&quot;.&quot;);
+    if (dotIndex == 0) {
+        return this._recursivelyDeleteCookieDomains(cookieName, domain.substring(1), path, doc);
+    } else if (dotIndex != -1) {
+        return this._recursivelyDeleteCookieDomains(cookieName, domain.substring(dotIndex), path, doc);
+    } else {
+        // No more dots; try just not passing in a domain at all
+        return this._maybeDeleteCookie(cookieName, null, path, doc);
+    }
+}
+
+BrowserBot.prototype._recursivelyDeleteCookie = function(cookieName, domain, path, doc) {
+    var slashIndex = path.lastIndexOf(&quot;/&quot;);
+    var finalIndex = path.length-1;
+    if (slashIndex == finalIndex) {
+        slashIndex--;
+    }
+    if (slashIndex != -1) {
+        deleted = this._recursivelyDeleteCookie(cookieName, domain, path.substring(0, slashIndex+1), doc);
+        if (deleted) return true;
+    }
+    return this._recursivelyDeleteCookieDomains(cookieName, domain, path, doc);
+}
+
+BrowserBot.prototype.recursivelyDeleteCookie = function(cookieName, domain, path, win) {
+    if (!win) win = this.getCurrentWindow();
+    var doc = win.document;
+    if (!domain) {
+        domain = doc.domain;
+    }
+    if (!path) {
+        path = win.location.pathname;
+    }
+    var deleted = this._recursivelyDeleteCookie(cookieName, &quot;.&quot; + domain, path, doc);
+    if (deleted) return;
+    // Finally try a null path (Try it last because it's uncommon)
+    deleted = this._recursivelyDeleteCookieDomains(cookieName, &quot;.&quot; + domain, null, doc);
+    if (deleted) return;
+    throw new SeleniumError(&quot;Couldn't delete cookie &quot; + cookieName);
+}
+
 /*
  * Finds an element recursively in frames and nested frames
  * in the specified document, using various lookup protocols
@@ -912,12 +1230,16 @@ BrowserBot.prototype.findElementRecursive = function(locatorType, locatorString,
     if (element != null) {
         return element;
     }
-    
-    for (var i = 0; i &lt; inWindow.frames.length; i++) {        
-        element = this.findElementRecursive(locatorType, locatorString, inWindow.frames[i].document, inWindow.frames[i]);
-        
-        if (element != null) {
-        	return element;
+
+    for (var i = 0; i &lt; inWindow.frames.length; i++) {
+        // On some browsers, the document object is undefined for third-party
+        // frames.  Make sure the document is valid before continuing.
+        if (inWindow.frames[i].document) {
+            element = this.findElementRecursive(locatorType, locatorString, inWindow.frames[i].document, inWindow.frames[i]);
+
+            if (element != null) {
+                return element;
+            }
         }
     }
 };
@@ -925,27 +1247,28 @@ BrowserBot.prototype.findElementRecursive = function(locatorType, locatorString,
 /*
 * Finds an element on the current page, using various lookup protocols
 */
-BrowserBot.prototype.findElement = function(locator) {
-    var locatorType = 'implicit';
-    var locatorString = locator;
+BrowserBot.prototype.findElementOrNull = function(locator, win) {
+    locator = parse_locator(locator);
 
-    // If there is a locator prefix, use the specified strategy
-    var result = locator.match(/^([A-Za-z]+)=(.+)/);
-    if (result) {
-        locatorType = result[1].toLowerCase();
-        locatorString = result[2];
+    if (win == null) {
+        win = this.getCurrentWindow();
     }
-    
-    var element = this.findElementRecursive(locatorType, locatorString, this.getDocument(), this.getCurrentWindow())
-    
+    var element = this.findElementRecursive(locator.type, locator.string, win.document, win);
+
     if (element != null) {
-    	return this.browserbot.highlight(element);
+        return this.browserbot.highlight(element);
     }
 
     // Element was not found by any locator function.
-    throw new SeleniumError(&quot;Element &quot; + locator + &quot; not found&quot;);
+    return null;
 };
 
+BrowserBot.prototype.findElement = function(locator, win) {
+    var element = this.findElementOrNull(locator, win);
+    if (element == null) throw new SeleniumError(&quot;Element &quot; + locator + &quot; not found&quot;);
+    return element;
+}
+
 /**
  * In non-IE browsers, getElementById() does not search by name.  Instead, we
  * we search separately by id and name.
@@ -961,9 +1284,15 @@ BrowserBot.prototype.locateElementByIdentifier = function(identifier, inDocument
  */
 BrowserBot.prototype.locateElementById = function(identifier, inDocument, inWindow) {
     var element = inDocument.getElementById(identifier);
-    if (element &amp;&amp; element.id === identifier) {
+    if (element &amp;&amp; element.getAttribute('id') === identifier) {
         return element;
     }
+    else if (browserVersion.isIE || browserVersion.isOpera) {
+        // SEL-484
+        var xpath = '/descendant::*[@id=' + identifier.quoteForXPath() + ']';
+        return BrowserBot.prototype
+            .locateElementByXPath(xpath, inDocument, inWindow);
+    }
     else {
         return null;
     }
@@ -1000,8 +1329,7 @@ BrowserBot.prototype.locateElementByDomTraversal = function(domTraversal, docume
     try {
         element = eval(domTraversal);
     } catch (e) {
-        e.isSeleniumError = true;
-        throw e;
+        return null;
     }
 
     if (!element) {
@@ -1017,80 +1345,14 @@ BrowserBot.prototype.locateElementByDomTraversal.prefix = &quot;dom&quot;;
  * begin with &quot;//&quot;.
  */
 BrowserBot.prototype.locateElementByXPath = function(xpath, inDocument, inWindow) {
-
-    // Trim any trailing &quot;/&quot;: not valid xpath, and remains from attribute
-    // locator.
-    if (xpath.charAt(xpath.length - 1) == '/') {
-        xpath = xpath.slice(0, -1);
-    }
-
-    // Handle //tag
-    var match = xpath.match(/^\/\/(\w+|\*)$/);
-    if (match) {
-        var elements = inDocument.getElementsByTagName(match[1].toUpperCase());
-        if (elements == null) return null;
-        return elements[0];
-    }
-
-    // Handle //tag[@attr='value']
-    var match = xpath.match(/^\/\/(\w+|\*)\[@(\w+)=('([^\']+)'|&quot;([^\&quot;]+)&quot;)\]$/);
-    if (match) {
-        // We don't return the value without checking if it is null first.
-        // This is beacuse in some rare cases, this shortcut actually WONT work
-        // but that the full XPath WILL. A known case, for example, is in IE
-        // when the attribute is onclick/onblur/onsubmit/etc. Due to a bug in IE
-        // this shortcut won't work because the actual function is returned
-        // by getAttribute() rather than the text of the attribute.
-        var val = this._findElementByTagNameAndAttributeValue(
-                inDocument,
-                match[1].toUpperCase(),
-                match[2].toLowerCase(),
-                match[3].slice(1, -1)
-                );
-        if (val) {
-            return val;
-        }
-    }
-
-    // Handle //tag[text()='value']
-    var match = xpath.match(/^\/\/(\w+|\*)\[text\(\)=('([^\']+)'|&quot;([^\&quot;]+)&quot;)\]$/);
-    if (match) {
-        return this._findElementByTagNameAndText(
-                inDocument,
-                match[1].toUpperCase(),
-                match[2].slice(1, -1)
-                );
-    }
-
-    return this._findElementUsingFullXPath(xpath, inDocument);
-};
-
-BrowserBot.prototype._findElementByTagNameAndAttributeValue = function(
-        inDocument, tagName, attributeName, attributeValue
-        ) {
-    if (browserVersion.isIE &amp;&amp; attributeName == &quot;class&quot;) {
-        attributeName = &quot;className&quot;;
-    }
-    var elements = inDocument.getElementsByTagName(tagName);
-    for (var i = 0; i &lt; elements.length; i++) {
-        var elementAttr = elements[i].getAttribute(attributeName);
-        if (elementAttr == attributeValue) {
-            return elements[i];
-        }
-    }
-    return null;
-};
-
-BrowserBot.prototype._findElementByTagNameAndText = function(
-        inDocument, tagName, text
-        ) {
-    var elements = inDocument.getElementsByTagName(tagName);
-    for (var i = 0; i &lt; elements.length; i++) {
-        if (getText(elements[i]) == text) {
-            return elements[i];
-        }
-    }
-    return null;
+    var results = eval_xpath(xpath, inDocument, {
+        returnOnFirstMatch          : true,
+        ignoreAttributesWithoutValue: this.ignoreAttributesWithoutValue,
+        allowNativeXpath            : this.allowNativeXpath,
+        xpathLibrary                : this.xpathLibrary,
+        namespaceResolver           : this._namespaceResolver
+    });
+    return (results.length &gt; 0) ? results[0] : null;
 };
 
 BrowserBot.prototype._namespaceResolver = function(prefix) {
@@ -1103,25 +1365,17 @@ BrowserBot.prototype._namespaceResolver = function(prefix) {
     }
 }
 
-BrowserBot.prototype._findElementUsingFullXPath = function(xpath, inDocument, inWindow) {
-    // HUGE hack - remove namespace from xpath for IE
-    if (browserVersion.isIE) {
-        xpath = xpath.replace(/x:/g, '')
-    }
-
-    // Use document.evaluate() if it's available
-    if (inDocument.evaluate) {
-        return inDocument.evaluate(xpath, inDocument, this._namespaceResolver, 0, null).iterateNext();
-    }
-
-    // If not, fall back to slower JavaScript implementation
-    var context = new ExprContext(inDocument);
-    var xpathObj = xpathParse(xpath);
-    var xpathResult = xpathObj.evaluate(context);
-    if (xpathResult &amp;&amp; xpathResult.value) {
-        return xpathResult.value[0];
-    }
-    return null;
+/**
+ * Returns the number of xpath results.
+ */
+BrowserBot.prototype.evaluateXPathCount = function(xpath, inDocument) {
+    var results = eval_xpath(xpath, inDocument, {
+        ignoreAttributesWithoutValue: this.ignoreAttributesWithoutValue,
+        allowNativeXpath            : this.allowNativeXpath,
+        xpathLibrary                : this.xpathLibrary,
+        namespaceResolver           : this._namespaceResolver
+    });
+    return results.length;
 };
 
 /**
@@ -1160,6 +1414,11 @@ BrowserBot.prototype.findAttribute = function(locator) {
 
     // Get the attribute value.
     var attributeValue = element.getAttribute(attributeName);
+    
+    // IE returns an object for the &quot;style&quot; attribute
+    if (attributeName == 'style' &amp;&amp; typeof(attributeValue) != 'string') {
+        attributeValue = attributeValue.cssText;
+    }
 
     return attributeValue ? attributeValue.toString() : null;
 };
@@ -1227,12 +1486,10 @@ BrowserBot.prototype.replaceText = function(element, stringValue) {
     if (maxLengthAttr != null) {
         var maxLength = parseInt(maxLengthAttr);
         if (stringValue.length &gt; maxLength) {
-            LOG.warn(&quot;BEFORE&quot;)
             actualValue = stringValue.substr(0, maxLength);
-            LOG.warn(&quot;AFTER&quot;)
         }
     }
-    
+
     if (getTagName(element) == &quot;body&quot;) {
         if (element.ownerDocument &amp;&amp; element.ownerDocument.designMode) {
             var designMode = new String(element.ownerDocument.designMode).toLowerCase();
@@ -1290,11 +1547,16 @@ BrowserBot.prototype.submit = function(formElement) {
 BrowserBot.prototype.clickElement = function(element, clientX, clientY) {
        this._fireEventOnElement(&quot;click&quot;, element, clientX, clientY);
 };
-    
+
 BrowserBot.prototype.doubleClickElement = function(element, clientX, clientY) {
        this._fireEventOnElement(&quot;dblclick&quot;, element, clientX, clientY);
 };
 
+// The contextmenu event is fired when the user right-clicks to open the context menu
+BrowserBot.prototype.contextMenuOnElement = function(element, clientX, clientY) {
+       this._fireEventOnElement(&quot;contextmenu&quot;, element, clientX, clientY);
+};
+
 BrowserBot.prototype._modifyElementTarget = function(element) {
     if (element.target) {
         if (element.target == &quot;_blank&quot; || /^selenium_blank/.test(element.target) ) {
@@ -1311,8 +1573,13 @@ BrowserBot.prototype._modifyElementTarget = function(element) {
 
 
 BrowserBot.prototype._handleClickingImagesInsideLinks = function(targetWindow, element) {
-    if (element.parentNode &amp;&amp; element.parentNode.href) {
-        targetWindow.location.href = element.parentNode.href;
+    var itrElement = element;
+    while (itrElement != null) {
+        if (itrElement.href) {
+            targetWindow.location.href = itrElement.href;
+            break;
+        }
+        itrElement = itrElement.parentNode;
     }
 }
 
@@ -1325,7 +1592,10 @@ BrowserBot.prototype._getTargetWindow = function(element) {
 }
 
 BrowserBot.prototype._getFrameFromGlobal = function(target) {
-    
+
+    if (target == &quot;_self&quot;) {
+        return this.getCurrentWindow();
+    }
     if (target == &quot;_top&quot;) {
         return this.topFrame;
     } else if (target == &quot;_parent&quot;) {
@@ -1345,18 +1615,19 @@ BrowserBot.prototype._getFrameFromGlobal = function(target) {
 
 
 BrowserBot.prototype.bodyText = function() {
+    if (!this.getDocument().body) {
+        throw new SeleniumError(&quot;Couldn't access document.body.  Is this HTML page fully loaded?&quot;);
+    }
     return getText(this.getDocument().body);
 };
 
 BrowserBot.prototype.getAllButtons = function() {
     var elements = this.getDocument().getElementsByTagName('input');
-    var result = '';
+    var result = [];
 
     for (var i = 0; i &lt; elements.length; i++) {
         if (elements[i].type == 'button' || elements[i].type == 'submit' || elements[i].type == 'reset') {
-            result += elements[i].id;
-
-            result += ',';
+            result.push(elements[i].id);
         }
     }
 
@@ -1366,13 +1637,11 @@ BrowserBot.prototype.getAllButtons = function() {
 
 BrowserBot.prototype.getAllFields = function() {
     var elements = this.getDocument().getElementsByTagName('input');
-    var result = '';
+    var result = [];
 
     for (var i = 0; i &lt; elements.length; i++) {
         if (elements[i].type == 'text') {
-            result += elements[i].id;
-
-            result += ',';
+            result.push(elements[i].id);
         }
     }
 
@@ -1381,29 +1650,15 @@ BrowserBot.prototype.getAllFields = function() {
 
 BrowserBot.prototype.getAllLinks = function() {
     var elements = this.getDocument().getElementsByTagName('a');
-    var result = '';
+    var result = [];
 
     for (var i = 0; i &lt; elements.length; i++) {
-        result += elements[i].id;
-
-        result += ',';
+        result.push(elements[i].id);
     }
 
     return result;
 };
 
-BrowserBot.prototype.setContext = function(strContext, logLevel) {
-
-    //set the current test title
-    var ctx = document.getElementById(&quot;context&quot;);
-    if (ctx != null) {
-        ctx.innerHTML = strContext;
-    }
-    if (logLevel != null) {
-        LOG.setLogLevelThreshold(logLevel);
-    }
-};
-
 function isDefined(value) {
     return typeof(value) != undefined;
 }
@@ -1417,10 +1672,21 @@ BrowserBot.prototype.goForward = function() {
 };
 
 BrowserBot.prototype.close = function() {
+    if (browserVersion.isIE) {
+        // fix &quot;do you want to close this window&quot; warning in IE
+        // You can only close windows that you have opened.
+        // So, let's &quot;open&quot; it.
+        try {
+            this.topFrame.name=new Date().getTime();
+            window.open(&quot;&quot;, this.topFrame.name, &quot;&quot;);
+            this.topFrame.close();
+            return;
+        } catch (e) {}
+    }
     if (browserVersion.isChrome || browserVersion.isSafari || browserVersion.isOpera) {
-        this.getCurrentWindow().close();
+        this.topFrame.close();
     } else {
-        this.getCurrentWindow().eval(&quot;window.close();&quot;);
+        this.getCurrentWindow().eval(&quot;window.top.close();&quot;);
     }
 };
 
@@ -1513,12 +1779,75 @@ BrowserBot.prototype.locateElementByAlt = function(locator, document) {
  * Find an element by css selector
  */
 BrowserBot.prototype.locateElementByCss = function(locator, document) {
-    var elements = cssQuery(locator, document);
+    var elements = eval_css(locator, document);
     if (elements.length != 0)
         return elements[0];
     return null;
 }
 
+/**
+ * This function is responsible for mapping a UI specifier string to an element
+ * on the page, and returning it. If no element is found, null is returned.
+ * Returning null on failure to locate the element is part of the undocumented
+ * API for locator strategies.
+ */
+BrowserBot.prototype.locateElementByUIElement = function(locator, inDocument) {
+    // offset locators are delimited by &quot;-&gt;&quot;, which is much simpler than the
+    // previous scheme involving detecting the close-paren.
+    var locators = locator.split(/-&gt;/, 2);
+    
+    var locatedElement = null;
+    var pageElements = UIMap.getInstance()
+        .getPageElements(locators[0], inDocument);
+    
+    if (locators.length &gt; 1) {
+        for (var i = 0; i &lt; pageElements.length; ++i) {
+            var locatedElements = eval_locator(locators[1], inDocument,
+                pageElements[i]);
+            if (locatedElements.length) {
+                locatedElement = locatedElements[0];
+                break;
+            }
+        }
+    }
+    else if (pageElements.length) {
+        locatedElement = pageElements[0];
+    }
+    
+    return locatedElement;
+}
+
+BrowserBot.prototype.locateElementByUIElement.prefix = 'ui';
+
+// define a function used to compare the result of a close UI element
+// match with the actual interacted element. If they are close enough
+// according to the heuristic, consider them a match.
+/**
+ * A heuristic function for comparing a node with a target node. Typically the
+ * node is specified in a UI element definition, while the target node is
+ * returned by the recorder as the leaf element which had some event enacted
+ * upon it. This particular heuristic covers the case where the anchor element
+ * contains other inline tags, such as &quot;em&quot; or &quot;img&quot;.
+ *
+ * @param node    the node being compared to the target node
+ * @param target  the target node
+ * @return        true if node equals target, or if node is a link
+ *                element and target is its descendant, or if node has
+ *                an onclick attribute and target is its descendant.
+ *                False otherwise.
+ */
+BrowserBot.prototype.locateElementByUIElement.is_fuzzy_match = function(node, target) {
+    try {
+        var isMatch = (
+            (node == target) ||
+            ((node.nodeName == 'A' || node.onclick) &amp;&amp; is_ancestor(node, target))
+        );
+        return isMatch;
+    }
+    catch (e) {
+        return false;
+    }
+};
 
 /*****************************************************************/
 /* BROWSER-SPECIFIC FUNCTIONS ONLY AFTER THIS LINE */
@@ -1545,9 +1874,12 @@ KonquerorBrowserBot.prototype.setOpenLocation = function(win, loc) {
     // so we just refresh in that case instead.
     loc = absolutify(loc, this.baseUrl);
     loc = canonicalize(loc);
-    var startLoc = parseUrl(win.location.href);
-    startLoc.hash = null;
-    var startUrl = reassembleLocation(startLoc);
+    var startUrl = win.location.href;
+    if (&quot;about:blank&quot; != win.location.href) {
+        var startLoc = parseUrl(win.location.href);
+        startLoc.hash = null;
+        var startUrl = reassembleLocation(startLoc);
+    }
     LOG.debug(&quot;startUrl=&quot;+startUrl);
     LOG.debug(&quot;win.location.href=&quot;+win.location.href);
     LOG.debug(&quot;loc=&quot;+loc);
@@ -1606,7 +1938,7 @@ IEBrowserBot.prototype._handleClosedSubFrame = function(testWindow, doNotModify)
     if (this.proxyInjectionMode) {
         return testWindow;
     }
-    
+
     try {
         testWindow.location.href;
         this.permDenied = 0;
@@ -1639,10 +1971,32 @@ IEBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToModif
         var doc_location = document.location.toString();
         var end_of_base_ref = doc_location.indexOf('TestRunner.html');
         var base_ref = doc_location.substring(0, end_of_base_ref);
-
-        var fullURL = base_ref + &quot;TestRunner.html?singletest=&quot; + escape(browserBot.modalDialogTest) + &quot;&amp;autoURL=&quot; + escape(url) + &quot;&amp;runInterval=&quot; + runOptions.runInterval;
+        var runInterval = '';
+        
+        // Only set run interval if options is defined
+        if (typeof(window.runOptions) != 'undefined') {
+            runInterval = &quot;&amp;runInterval=&quot; + runOptions.runInterval;
+        }
+            
+        var testRunnerURL = &quot;TestRunner.html?auto=true&amp;singletest=&quot; 
+            + escape(browserBot.modalDialogTest)
+            + &quot;&amp;autoURL=&quot; 
+            + escape(url) 
+            + runInterval;
+        var fullURL = base_ref + testRunnerURL;
         browserBot.modalDialogTest = null;
 
+        // If using proxy injection mode
+        if (this.proxyInjectionMode) {
+            var sessionId = runOptions.getSessionId();
+            if (sessionId == undefined) {
+                sessionId = injectedSessionId;
+            }
+            if (sessionId != undefined) {
+                LOG.debug(&quot;Invoking showModalDialog and injecting URL &quot; + fullURL);
+            }
+            fullURL = url;
+        }
         var returnValue = oldShowModalDialog(fullURL, args, features);
         return returnValue;
     };
@@ -1665,7 +2019,7 @@ IEBrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, origin
     if (this.pageLoadError) {
         if (this.pageUnloading) {
             var self = this;
-            LOG.warn(&quot;pollForLoad UNLOADING (&quot; + marker + &quot;): caught exception while firing events on unloading page: &quot; + this.pageLoadError.message);
+            LOG.debug(&quot;pollForLoad UNLOADING (&quot; + marker + &quot;): caught exception while firing events on unloading page: &quot; + this.pageLoadError.message);
             this.reschedulePoller(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker);
             this.pageLoadError = null;
             return;
@@ -1683,15 +2037,15 @@ IEBrowserBot.prototype.pollForLoad = function(loadFunction, windowObject, origin
                     canAccessCurrentlySelectedWindow = true;
                 } catch (e) {}
                 if (canAccessCurrentlySelectedWindow &amp; !canAccessThisWindow) {
-                    LOG.warn(&quot;pollForLoad (&quot; + marker + &quot;) ABORTING: &quot; + this.pageLoadError.message + &quot; (&quot; + this.permDeniedCount[marker] + &quot;), but the currently selected window is fine&quot;);
+                    LOG.debug(&quot;pollForLoad (&quot; + marker + &quot;) ABORTING: &quot; + this.pageLoadError.message + &quot; (&quot; + this.permDeniedCount[marker] + &quot;), but the currently selected window is fine&quot;);
                     // returning without rescheduling
                     this.pageLoadError = null;
                     return;
                 }
             }
-            
+
             var self = this;
-            LOG.warn(&quot;pollForLoad (&quot; + marker + &quot;): &quot; + this.pageLoadError.message + &quot; (&quot; + this.permDeniedCount[marker] + &quot;), waiting to see if it goes away&quot;);
+            LOG.debug(&quot;pollForLoad (&quot; + marker + &quot;): &quot; + this.pageLoadError.message + &quot; (&quot; + this.permDeniedCount[marker] + &quot;), waiting to see if it goes away&quot;);
             this.reschedulePoller(loadFunction, windowObject, originalDocument, originalLocation, originalHref, marker);
             this.pageLoadError = null;
             return;
@@ -1732,7 +2086,7 @@ IEBrowserBot.prototype._windowClosed = function(win) {
         return c;
     } catch (e) {
         LOG.debug(&quot;IEBrowserBot._windowClosed: Got an exception trying to read win.closed; we'll have to take a guess!&quot;);
-            
+
         if (browserVersion.isHTA) {
             if (e.message == &quot;Permission denied&quot;) {
                 // the window is probably unloading, which means it's not closed yet
@@ -1782,7 +2136,7 @@ SafariBrowserBot.prototype.modifyWindowToRecordPopUpDialogs = function(windowToM
         var openedWindow = originalOpen(newUrl, windowName, windowFeatures, replaceFlag);
         LOG.debug(&quot;window.open call intercepted; window ID (which you can use with selectWindow()) is \&quot;&quot; +  windowName + &quot;\&quot;&quot;);
         if (windowName!=null) {
-        	openedWindow[&quot;seleniumWindowName&quot;] = windowName;
+            openedWindow[&quot;seleniumWindowName&quot;] = windowName;
         }
         return openedWindow;
     };
@@ -1802,7 +2156,7 @@ MozillaBrowserBot.prototype._fireEventOnElement = function(eventType, element, c
     element.addEventListener(eventType, function(evt) {
         savedEvent = evt;
     }, false);
-    
+
     this._modifyElementTarget(element);
 
     // Trigger the event.
@@ -1811,7 +2165,7 @@ MozillaBrowserBot.prototype._fireEventOnElement = function(eventType, element, c
     if (this._windowClosed(win)) {
         return;
     }
-    
+
     // Perform the link action if preventDefault was set.
     // In chrome URL, the link action is already executed by triggerMouseEvent.
     if (!browserVersion.isChrome &amp;&amp; savedEvent != null &amp;&amp; !savedEvent.getPreventDefault()) {
@@ -1829,7 +2183,7 @@ MozillaBrowserBot.prototype._fireEventOnElement = function(eventType, element, c
 OperaBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) {
     var win = this.getCurrentWindow();
     triggerEvent(element, 'focus', false);
-    
+
     this._modifyElementTarget(element);
 
     // Trigger the click event.
@@ -1845,11 +2199,11 @@ OperaBrowserBot.prototype._fireEventOnElement = function(eventType, element, cli
 KonquerorBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) {
     var win = this.getCurrentWindow();
     triggerEvent(element, 'focus', false);
-    
+
     this._modifyElementTarget(element);
 
     if (element[eventType]) {
-    	element[eventType]();
+        element[eventType]();
     }
     else {
         this.browserbot.triggerMouseEvent(element, eventType, true, clientX, clientY);
@@ -1864,12 +2218,12 @@ KonquerorBrowserBot.prototype._fireEventOnElement = function(eventType, element,
 SafariBrowserBot.prototype._fireEventOnElement = function(eventType, element, clientX, clientY) {
     triggerEvent(element, 'focus', false);
     var wasChecked = element.checked;
-    
+
     this._modifyElementTarget(element);
 
     // For form element it is simple.
     if (element[eventType]) {
-    	element[eventType]();
+        element[eventType]();
     }
     // For links and other elements, event emulation is required.
     else {
@@ -1910,7 +2264,7 @@ IEBrowserBot.prototype._fireEventOnElement = function(eventType, element, client
     win.attachEvent(&quot;onbeforeunload&quot;, pageUnloadDetector);
     this._modifyElementTarget(element);
     if (element[eventType]) {
-    	element[eventType]();
+        element[eventType]();
     }
     else {
         this.browserbot.triggerMouseEvent(element, eventType, true, clientX, clientY);</diff>
      <filename>selenium-core/scripts/selenium-browserbot.js</filename>
    </modified>
    <modified>
      <diff>@@ -24,6 +24,14 @@
 var BrowserVersion = function() {
     this.name = navigator.appName;
 
+    if (navigator.userAgent.indexOf('Mac OS X') != -1) {
+        this.isOSX = true;
+    }
+
+    if (navigator.userAgent.indexOf('Windows NT 6') != -1) {
+        this.isVista = true;
+    }
+
     if (window.opera != null) {
         this.browser = BrowserVersion.OPERA;
         this.isOpera = true;
@@ -85,9 +93,12 @@ var BrowserVersion = function() {
                 self.isHTA = false;
             }
         }
+        if (navigator.appVersion.match(/MSIE 6.0/)) {
+        	this.isIE6 = true;
+        }
         if (&quot;0&quot; == navigator.appMinorVersion) {
             this.preSV1 = true;
-            if (navigator.appVersion.match(/MSIE 6.0/)) {
+            if (this.isIE6) {
             	this.appearsToBeBrokenInitialIE6 = true;
             }
         }</diff>
      <filename>selenium-core/scripts/selenium-browserdetect.js</filename>
    </modified>
    <modified>
      <diff>@@ -136,6 +136,7 @@ objectExtend(CommandHandlerFactory.prototype, {
         // is true when the value returned by the accessor matches the specified value.
         return function(target, value) {
             var accessorResult = accessBlock(target);
+            accessorResult = selArrayToString(accessorResult);
             if (PatternMatcher.matches(value, accessorResult)) {
                 return new PredicateResult(true, &quot;Actual value '&quot; + accessorResult + &quot;' did match '&quot; + value + &quot;'&quot;);
             } else {
@@ -150,6 +151,7 @@ objectExtend(CommandHandlerFactory.prototype, {
         // is true when the value returned by the accessor matches the specified value.
         return function(value) {
             var accessorResult = accessBlock();
+            accessorResult = selArrayToString(accessorResult);
             if (PatternMatcher.matches(value, accessorResult)) {
                 return new PredicateResult(true, &quot;Actual value '&quot; + accessorResult + &quot;' did match '&quot; + value + &quot;'&quot;);
             } else {</diff>
      <filename>selenium-core/scripts/selenium-commandhandlers.js</filename>
    </modified>
    <modified>
      <diff>@@ -79,7 +79,7 @@ TestLoop.prototype = {
             this.continueTestWhenConditionIsTrue();
         } catch (e) {
             if (!this._handleCommandError(e)) {
-                this._testComplete();
+                this.testComplete();
             } else {
                 this.continueTest();
             }
@@ -119,10 +119,8 @@ TestLoop.prototype = {
     _handleCommandError : function(e) {
         if (!e.isSeleniumError) {
             LOG.exception(e);
-            var msg = &quot;Selenium failure. Please report to selenium-dev@openqa.org, with error details from the log window.&quot;;
-            if (e.message) {
-                msg += &quot;  The error message is: &quot; + e.message;
-            }
+            var msg = &quot;Command execution failure. Please search the forum at http://clearspace.openqa.org for error details from the log window.&quot;;
+            msg += &quot;  The error message is: &quot; + extractExceptionMessage(e);
             return this.commandError(msg);
         } else {
             LOG.error(e.message);</diff>
      <filename>selenium-core/scripts/selenium-executionloop.js</filename>
    </modified>
    <modified>
      <diff>@@ -19,11 +19,24 @@ var Logger = function() {
 }
 Logger.prototype = {
 
+    logLevels: {
+        debug: 0,
+        info: 1,
+        warn: 2,
+        error: 3,
+        off: 999
+    },
+
     pendingMessages: new Array(),
+    
+    threshold: &quot;info&quot;,
 
     setLogLevelThreshold: function(logLevel) {
-        this.pendingLogLevelThreshold = logLevel;
-        this.show();
+        this.threshold = logLevel;
+        var logWindow = this.getLogWindow()
+        if (logWindow &amp;&amp; logWindow.setThresholdLevel) {
+            logWindow.setThresholdLevel(logLevel);
+        }
         // NOTE: log messages will be discarded until the log window is
         // fully loaded.
     },
@@ -32,23 +45,12 @@ Logger.prototype = {
         if (this.logWindow &amp;&amp; this.logWindow.closed) {
             this.logWindow = null;
         }
-        if (this.logWindow &amp;&amp; this.pendingLogLevelThreshold &amp;&amp; this.logWindow.setThresholdLevel) {
-            this.logWindow.setThresholdLevel(this.pendingLogLevelThreshold);
-            
-            // can't just directly log because that action would loop back
-            // to this code infinitely
-            var pendingMessage = new LogMessage(&quot;info&quot;, &quot;Log level programmatically set to &quot; + this.pendingLogLevelThreshold + &quot; (presumably by driven-mode test code)&quot;);
-            this.pendingMessages.push(pendingMessage);
-            
-            this.pendingLogLevelThreshold = null;    // let's only go this way one time
-        }
-
         return this.logWindow;
     },
     
     openLogWindow: function() {
         this.logWindow = window.open(
-            getDocumentBase(document) + &quot;SeleniumLog.html&quot;, &quot;SeleniumLog&quot;,
+            getDocumentBase(document) + &quot;SeleniumLog.html?startingThreshold=&quot;+this.threshold, &quot;SeleniumLog&quot;,
             &quot;width=600,height=1000,bottom=0,right=0,status,scrollbars,resizable&quot;
         );
         this.logWindow.moveTo(window.screenX + 1210, window.screenY + window.outerHeight - 1400);
@@ -66,33 +68,39 @@ Logger.prototype = {
         if (! this.getLogWindow()) {
             this.openLogWindow();
         }
-        setTimeout(function(){LOG.info(&quot;Log window displayed&quot;);}, 500);
+        setTimeout(function(){LOG.error(&quot;Log window displayed.  Logging events will now be recorded to this window.&quot;);}, 500);
     },
 
-    logHook: function(className, message) {
+    logHook: function(logLevel, message) {
     },
 
-    log: function(className, message) {
+    log: function(logLevel, message) {
+        if (this.logLevels[logLevel] &lt; this.logLevels[this.threshold]) {
+            return;
+        }
+        this.logHook(logLevel, message);
         var logWindow = this.getLogWindow();
-        this.logHook(className, message);
         if (logWindow) {
             if (logWindow.append) {
+                if (logWindow.disabled) {
+                    logWindow.callBack = fnBind(this.setLogLevelThreshold, this);
+                    logWindow.enableButtons();
+                }
                 if (this.pendingMessages.length &gt; 0) {
-                    logWindow.append(&quot;info: Appending missed logging messages&quot;, &quot;info&quot;);
+                    logWindow.append(&quot;info(&quot;+(new Date().getTime())+&quot;): Appending missed logging messages&quot;, &quot;info&quot;);
                     while (this.pendingMessages.length &gt; 0) {
                         var msg = this.pendingMessages.shift();
-                        logWindow.append(msg.type + &quot;: &quot; + msg.msg, msg.type);
+                        logWindow.append(msg.type + &quot;(&quot;+msg.timestamp+&quot;): &quot; + msg.msg, msg.type);
                     }
-                    logWindow.append(&quot;info: Done appending missed logging messages&quot;, &quot;info&quot;);
+                    logWindow.append(&quot;info(&quot;+(new Date().getTime())+&quot;): Done appending missed logging messages&quot;, &quot;info&quot;);
                 }
-                logWindow.append(className + &quot;: &quot; + message, className);
+                logWindow.append(logLevel + &quot;(&quot;+(new Date().getTime())+&quot;): &quot; + message, logLevel);
             }
         } else {
-            // uncomment this to turn on background logging
-            /* these logging messages are never flushed, which creates 
-               an enormous array of strings that never stops growing.  Only
-               turn this on if you need it for debugging! */
-            //this.pendingMessages.push(new LogMessage(className, message));
+            // TODO these logging messages are never flushed, which creates 
+            //   an enormous array of strings that never stops growing.
+            //   there should at least be a way to clear the messages!
+            this.pendingMessages.push(new LogMessage(logLevel, message));
         }
     },
 
@@ -136,4 +144,5 @@ var LOG = new Logger();
 var LogMessage = function(type, msg) {
     this.type = type;
     this.msg = msg;
+    this.timestamp = (new Date().getTime());
 }</diff>
      <filename>selenium-core/scripts/selenium-logging.js</filename>
    </modified>
    <modified>
      <diff>@@ -22,16 +22,23 @@ workingColor = &quot;#DEE7EC&quot;;
 doneColor = &quot;#FFFFCC&quot;;
 
 var injectedSessionId;
-var cmd1 = document.createElement(&quot;div&quot;);
-var cmd2 = document.createElement(&quot;div&quot;);
-var cmd3 = document.createElement(&quot;div&quot;);
-var cmd4 = document.createElement(&quot;div&quot;);
 
 var postResult = &quot;START&quot;;
 var debugMode = false;
 var relayToRC = null;
 var proxyInjectionMode = false;
 var uniqueId = 'sel_' + Math.round(100000 * Math.random());
+var seleniumSequenceNumber = 0;
+var cmd8 = &quot;&quot;;
+var cmd7 = &quot;&quot;;
+var cmd6 = &quot;&quot;;
+var cmd5 = &quot;&quot;;
+var cmd4 = &quot;&quot;;
+var cmd3 = &quot;&quot;;
+var cmd2 = &quot;&quot;;
+var cmd1 = &quot;&quot;;
+var lastCmd = &quot;&quot;;
+var lastCmdTime = new Date();
 
 var RemoteRunnerOptions = classCreate();
 objectExtend(RemoteRunnerOptions.prototype, URLConfiguration.prototype);
@@ -51,8 +58,12 @@ objectExtend(RemoteRunnerOptions.prototype, {
         return this._getQueryParameter(&quot;driverUrl&quot;);
     },
 
+    // requires per-session extension Javascript as soon as this Selenium
+    // instance becomes aware of the session identifier
     getSessionId: function() {
-        return this._getQueryParameter(&quot;sessionId&quot;);
+        var sessionId = this._getQueryParameter(&quot;sessionId&quot;);
+        requireExtensionJs(sessionId);
+        return sessionId;
     },
 
     _acquireQueryString: function () {
@@ -62,7 +73,7 @@ objectExtend(RemoteRunnerOptions.prototype, {
             if (args.length &lt; 2) return null;
             this.queryString = args[1];
         } else if (proxyInjectionMode) {
-            this.queryString = selenium.browserbot.getCurrentWindow().location.search.substr(1);
+            this.queryString = window.location.search.substr(1);
         } else {
             this.queryString = top.location.search.substr(1);
         }
@@ -77,8 +88,8 @@ function runSeleniumTest() {
 
     if (runOptions.isMultiWindowMode()) {
         testAppWindow = openSeparateApplicationWindow('Blank.html', true);
-    } else if ($('myiframe') != null) {
-        var myiframe = $('myiframe');
+    } else if (sel$('selenium_myiframe') != null) {
+        var myiframe = sel$('selenium_myiframe');
         if (myiframe) {
             testAppWindow = myiframe.contentWindow;
         }
@@ -95,7 +106,7 @@ function runSeleniumTest() {
         debugMode = runOptions.isDebugMode();
     }
     if (proxyInjectionMode) {
-        LOG.log = logToRc;
+        LOG.logHook = logToRc;
         selenium.browserbot._modifyWindow(testAppWindow);
     }
     else if (debugMode) {
@@ -108,13 +119,6 @@ function runSeleniumTest() {
 
     currentTest = new RemoteRunner(commandFactory);
 
-    if (document.getElementById(&quot;commandList&quot;) != null) {
-        document.getElementById(&quot;commandList&quot;).appendChild(cmd4);
-        document.getElementById(&quot;commandList&quot;).appendChild(cmd3);
-        document.getElementById(&quot;commandList&quot;).appendChild(cmd2);
-        document.getElementById(&quot;commandList&quot;).appendChild(cmd1);
-    }
-
     var doContinue = runOptions.getContinue();
     if (doContinue != null) postResult = &quot;OK&quot;;
 
@@ -130,21 +134,18 @@ function buildDriverUrl() {
     var slashPairOffset = s.indexOf(&quot;//&quot;) + &quot;//&quot;.length
     var pathSlashOffset = s.substring(slashPairOffset).indexOf(&quot;/&quot;)
     return s.substring(0, slashPairOffset + pathSlashOffset) + &quot;/selenium-server/driver/&quot;;
+    //return &quot;http://localhost&quot; + uniqueId + &quot;/selenium-server/driver/&quot;;
 }
 
 function logToRc(logLevel, message) {
-    if (logLevel == null) {
-        logLevel = &quot;debug&quot;;
-    }
     if (debugMode) {
-        sendToRC(&quot;logLevel=&quot; + logLevel + &quot;:&quot; + message.replace(/[\n\r\015]/g, &quot; &quot;) + &quot;\n&quot;, &quot;logging=true&quot;);
+        if (logLevel == null) {
+            logLevel = &quot;debug&quot;;
+        }
+        sendToRCAndForget(&quot;logLevel=&quot; + logLevel + &quot;:&quot; + message.replace(/[\n\r\015]/g, &quot; &quot;) + &quot;\n&quot;, &quot;logging=true&quot;);
     }
 }
 
-function isArray(x) {
-    return ((typeof x) == &quot;object&quot;) &amp;&amp; (x[&quot;length&quot;] != null);
-}
-
 function serializeString(name, s) {
     return name + &quot;=unescape(\&quot;&quot; + escape(s) + &quot;\&quot;);&quot;;
 }
@@ -176,7 +177,7 @@ function serializeObject(name, x)
 function relayBotToRC(s) {
 }
 
-// seems like no one uses this, but in fact it is called using eval from server-side PI mode code; however, 
+// seems like no one uses this, but in fact it is called using eval from server-side PI mode code; however,
 // because multiple names can map to the same popup, assigning a single name confuses matters sometimes;
 // thus, I'm disabling this for now.  -Nelson 10/21/06
 function setSeleniumWindowName(seleniumWindowName) {
@@ -204,33 +205,44 @@ objectExtend(RemoteRunner.prototype, {
 
     commandStarted : function(command) {
         this.commandNode = document.createElement(&quot;div&quot;);
-        var innerHTML = command.command + '(';
+        var cmdText = command.command + '(';
         if (command.target != null &amp;&amp; command.target != &quot;&quot;) {
-            innerHTML += command.target;
+            cmdText += command.target;
             if (command.value != null &amp;&amp; command.value != &quot;&quot;) {
-                innerHTML += ', ' + command.value;
+                cmdText += ', ' + command.value;
             }
         }
-        innerHTML += &quot;)&quot;;
-        if (innerHTML.length &gt;40) {
-            innerHTML = innerHTML.substring(0,40);
-            innerHTML += &quot;...&quot;;
+        if (cmdText.length &gt; 70) {
+            cmdText = cmdText.substring(0, 70) + &quot;...\n&quot;;
+        } else {
+            cmdText += &quot;)\n&quot;;
         }
-        this.commandNode.innerHTML = innerHTML;
-        this.commandNode.style.backgroundColor = workingColor;
-        if (document.getElementById(&quot;commandList&quot;) != null) {
-            document.getElementById(&quot;commandList&quot;).removeChild(cmd1);
-            document.getElementById(&quot;commandList&quot;).removeChild(cmd2);
-            document.getElementById(&quot;commandList&quot;).removeChild(cmd3);
-            document.getElementById(&quot;commandList&quot;).removeChild(cmd4);
+
+        if (cmdText == lastCmd) {
+	        var rightNow = new Date();
+	        var msSinceStart = rightNow.getTime() - lastCmdTime.getTime();
+	        var sinceStart = msSinceStart + &quot;ms&quot;;
+	        if (msSinceStart &gt; 1000) {
+		        sinceStart = Math.round(msSinceStart / 1000) + &quot;s&quot;;
+		    }
+            cmd1 = &quot;Same command (&quot; + sinceStart + &quot;): &quot; + lastCmd;
+        } else {
+	        lastCmdTime = new Date();
+            cmd8 = cmd7;
+            cmd7 = cmd6;
+            cmd6 = cmd5;
+            cmd5 = cmd4;
             cmd4 = cmd3;
             cmd3 = cmd2;
             cmd2 = cmd1;
-            cmd1 = this.commandNode;
-            document.getElementById(&quot;commandList&quot;).appendChild(cmd4);
-            document.getElementById(&quot;commandList&quot;).appendChild(cmd3);
-            document.getElementById(&quot;commandList&quot;).appendChild(cmd2);
-            document.getElementById(&quot;commandList&quot;).appendChild(cmd1);
+            cmd1 = cmdText;
+        }
+        lastCmd = cmdText;
+        
+        if (! proxyInjectionMode) {
+            var commandList = document.commands.commandList;
+            commandList.value = cmd8 + cmd7 + cmd6 + cmd5 + cmd4 + cmd3 + cmd2 + cmd1;
+            commandList.scrollTop = commandList.scrollHeight;
         }
     },
 
@@ -250,7 +262,9 @@ objectExtend(RemoteRunner.prototype, {
             if (result.result == null) {
                 postResult = &quot;OK&quot;;
             } else {
-                postResult = &quot;OK,&quot; + result.result;
+                var actualResult = result.result;
+                actualResult = selArrayToString(actualResult);
+                postResult = &quot;OK,&quot; + actualResult;
             }
             this.commandNode.style.backgroundColor = doneColor;
         }
@@ -259,7 +273,7 @@ objectExtend(RemoteRunner.prototype, {
     commandError : function(message) {
         postResult = &quot;ERROR: &quot; + message;
         this.commandNode.style.backgroundColor = errorColor;
-        this.commandNode.title = message;
+        this.commandNode.titcle = message;
     },
 
     testComplete : function() {
@@ -270,16 +284,26 @@ objectExtend(RemoteRunner.prototype, {
     },
 
     _HandleHttpResponse : function() {
+        // When request is completed
         if (this.xmlHttpForCommandsAndResults.readyState == 4) {
+            // OK
             if (this.xmlHttpForCommandsAndResults.status == 200) {
             	if (this.xmlHttpForCommandsAndResults.responseText==&quot;&quot;) {
                     LOG.error(&quot;saw blank string xmlHttpForCommandsAndResults.responseText&quot;);
                     return;
                 }
                 var command = this._extractCommand(this.xmlHttpForCommandsAndResults);
-                this.currentCommand = command;
-                this.continueTestAtCurrentCommand();
-            } else {
+                if (command.command == 'retryLast') {
+                    setTimeout(fnBind(function() {
+                        sendToRC(&quot;RETRY&quot;, &quot;retry=true&quot;, fnBind(this._HandleHttpResponse, this), this.xmlHttpForCommandsAndResults, true);
+                    }, this), 1000);
+                } else {
+                    this.currentCommand = command;
+                    this.continueTestAtCurrentCommand();
+                }
+            }
+            // Not OK 
+            else {
                 var s = 'xmlHttp returned: ' + this.xmlHttpForCommandsAndResults.status + &quot;: &quot; + this.xmlHttpForCommandsAndResults.statusText;
                 LOG.error(s);
                 this.currentCommand = null;
@@ -290,7 +314,15 @@ objectExtend(RemoteRunner.prototype, {
     },
 
     _extractCommand : function(xmlHttp) {
-        var command;
+        var command, text, json;
+        text = command = xmlHttp.responseText;
+        if (/^json=/.test(text)) {
+            eval(text);
+            if (json.rest) {
+                eval(json.rest);
+            }
+            return json;
+        }
         try {
             var re = new RegExp(&quot;^(.*?)\n((.|[\r\n])*)&quot;);
             if (re.exec(xmlHttp.responseText)) {
@@ -364,21 +396,68 @@ function sendToRC(dataToBePosted, urlParms, callback, xmlHttpObject, async) {
     if (urlParms) {
         url += urlParms;
     }
-    url += &quot;&amp;localFrameAddress=&quot; + (proxyInjectionMode ? makeAddressToAUTFrame() : &quot;top&quot;);
-    url += getSeleniumWindowNameURLparameters();
-    url += &quot;&amp;uniqueId=&quot; + uniqueId;
+    url = addUrlParams(url);
+    url += &quot;&amp;sequenceNumber=&quot; + seleniumSequenceNumber++;
+    
+    var postedData = &quot;postedData=&quot; + encodeURIComponent(dataToBePosted);
 
-    if (callback == null) {
-        callback = function() {
-        };
-    }
-    url += buildDriverParams() + preventBrowserCaching();
+    //xmlHttpObject.setRequestHeader(&quot;Content-Type&quot;, &quot;application/x-www-form-urlencoded&quot;);
     xmlHttpObject.open(&quot;POST&quot;, url, async);
-    xmlHttpObject.onreadystatechange = callback;
-    xmlHttpObject.send(dataToBePosted);
+    if (callback) xmlHttpObject.onreadystatechange = callback;
+    xmlHttpObject.send(postedData);
     return null;
 }
 
+function addUrlParams(url) {
+    return url + &quot;&amp;localFrameAddress=&quot; + (proxyInjectionMode ? makeAddressToAUTFrame() : &quot;top&quot;)
+    + getSeleniumWindowNameURLparameters()
+    + &quot;&amp;uniqueId=&quot; + uniqueId
+    + buildDriverParams() + preventBrowserCaching()
+}
+
+function sendToRCAndForget(dataToBePosted, urlParams) {
+    var url;
+    if (!(browserVersion.isChrome || browserVersion.isHTA)) { 
+        // DGF we're behind a proxy, so we can send our logging message to literally any host, to avoid 2-connection limit
+        var protocol = &quot;http:&quot;;
+        if (window.location.protocol == &quot;https:&quot;) {
+            // DGF if we're in HTTPS, use another HTTPS url to avoid security warning
+            protocol = &quot;https:&quot;;
+        }
+        // we don't choose a super large random value, but rather 1 - 16, because this matches with the pre-computed
+        // tunnels waiting on the Selenium Server side. This gives us higher throughput than the two-connection-per-host
+        // limitation, but doesn't require we generate an extremely large ammount of fake SSL certs either.
+        url = protocol + &quot;//&quot; + Math.floor(Math.random()* 16 + 1) + &quot;.selenium.doesnotexist/selenium-server/driver/?&quot; + urlParams;
+    } else {
+        url = buildDriverUrl() + &quot;?&quot; + urlParams;
+    }
+    url = addUrlParams(url);
+    
+    var method = &quot;GET&quot;;
+    if (method == &quot;POST&quot;) {
+        // DGF submit a request using an iframe; we can't see the response, but we don't need to
+        // TODO not using this mechanism because it screws up back-button
+        var loggingForm = document.createElement(&quot;form&quot;);
+        loggingForm.method = &quot;POST&quot;;
+        loggingForm.action = url;
+        loggingForm.target = &quot;seleniumLoggingFrame&quot;;
+        var postedDataInput = document.createElement(&quot;input&quot;);
+        postedDataInput.type = &quot;hidden&quot;;
+        postedDataInput.name = &quot;postedData&quot;;
+        postedDataInput.value = dataToBePosted;
+        loggingForm.appendChild(postedDataInput);
+        document.body.appendChild(loggingForm);
+        loggingForm.submit();
+        document.body.removeChild(loggingForm);
+    } else {
+        var postedData = &quot;&amp;postedData=&quot; + encodeURIComponent(dataToBePosted);
+        var scriptTag = document.createElement(&quot;script&quot;);
+        scriptTag.src = url + postedData;
+        document.body.appendChild(scriptTag);
+        document.body.removeChild(scriptTag);
+    }
+}
+
 function buildDriverParams() {
     var params = &quot;&quot;;
 
@@ -402,7 +481,7 @@ function preventBrowserCaching() {
 //
 // In selenium, the main (i.e., first) window's name is a blank string.
 //
-// Additional pop-ups are associated with either 1.) the name given by the 2nd parameter to window.open, or 2.) the name of a 
+// Additional pop-ups are associated with either 1.) the name given by the 2nd parameter to window.open, or 2.) the name of a
 // property on the opening window which points at the window.
 //
 // An example of #2: if window X contains JavaScript as follows:
@@ -420,11 +499,13 @@ function getSeleniumWindowNameURLparameters() {
         return s;
     }
     if (w[&quot;seleniumWindowName&quot;] == null) {
-    	s +=  'generatedSeleniumWindowName_' + Math.round(100000 * Math.random());
-    }
-    else {
-    	s += w[&quot;seleniumWindowName&quot;];
+        if (w.name) {
+            w[&quot;seleniumWindowName&quot;] = w.name;
+        } else {
+    	    w[&quot;seleniumWindowName&quot;] = 'generatedSeleniumWindowName_' + Math.round(100000 * Math.random());
+    	}
     }
+    s += w[&quot;seleniumWindowName&quot;];
     var windowOpener = w.opener;
     for (key in windowOpener) {
         var val = null;
@@ -432,7 +513,7 @@ function getSeleniumWindowNameURLparameters() {
     	    val = windowOpener[key];
         }
         catch(e) {
-        }        
+        }
         if (val==w) {
 	    s += &quot;&amp;jsWindowNameVar=&quot; + key;			// found a js variable in the opener referring to this window
         }
@@ -464,3 +545,151 @@ function makeAddressToAUTFrame(w, frameNavigationalJSexpression)
     }
     return null;
 }
+
+Selenium.prototype.doSetContext = function(context) {
+    /**
+   * Writes a message to the status bar and adds a note to the browser-side
+   * log.
+   *
+   * @param context
+   *            the message to be sent to the browser
+   */
+    //set the current test title
+    var ctx = document.getElementById(&quot;context&quot;);
+    if (ctx != null) {
+        ctx.innerHTML = context;
+    }
+};
+
+/**
+ * Adds a script tag referencing a specially-named user extensions &quot;file&quot;. The
+ * resource handler for this special file (which won't actually exist) will use
+ * the session ID embedded in its name to retrieve per-session specified user
+ * extension javascript.
+ *
+ * @param sessionId
+ */
+function requireExtensionJs(sessionId) {
+    var src = 'scripts/user-extensions.js[' + sessionId + ']';
+    if (document.getElementById(src) == null) {
+        var scriptTag = document.createElement('script');
+        scriptTag.language = 'JavaScript';
+        scriptTag.type = 'text/javascript';
+        scriptTag.src = src;
+        scriptTag.id = src;
+        var headTag = document.getElementsByTagName('head')[0];
+        headTag.appendChild(scriptTag);
+    }
+}
+
+Selenium.prototype.doAttachFile = function(fieldLocator,fileLocator) {
+   /**
+   * Sets a file input (upload) field to the file listed in fileLocator
+   *
+   *  @param fieldLocator an &lt;a href=&quot;#locators&quot;&gt;element locator&lt;/a&gt;
+   *  @param fileLocator a URL pointing to the specified file. Before the file
+   *  can be set in the input field (fieldLocator), Selenium RC may need to transfer the file  
+   *  to the local machine before attaching the file in a web page form. This is common in selenium
+   *  grid configurations where the RC server driving the browser is not the same
+   *  machine that started the test.
+   *
+   *  Supported Browsers: Firefox (&quot;*chrome&quot;) only.
+   *   
+   */
+   // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us! 
+};
+
+Selenium.prototype.doCaptureScreenshot = function(filename) {
+    /**
+    * Captures a PNG screenshot to the specified file.
+    *
+    * @param filename the absolute path to the file to be written, e.g. &quot;c:\blah\screenshot.png&quot;
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+
+Selenium.prototype.doCaptureScreenshotToString = function() {
+    /**
+    * Capture a PNG screenshot.  It then returns the file as a base 64 encoded string. 
+    * 
+    * @return string The base 64 encoded string of the screen shot (PNG file)
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+
+Selenium.prototype.doCaptureEntirePageScreenshotToString = function(kwargs) {
+    /**
+    * Downloads a screenshot of the browser current window canvas to a 
+    * based 64 encoded PNG file. The &lt;em&gt;entire&lt;/em&gt; windows canvas is captured,
+    * including parts rendered outside of the current view port.
+    *
+	* Currently this only works in Mozilla and when running in chrome mode. 
+    * 
+    * @param kwargs  A kwargs string that modifies the way the screenshot is captured. Example: &quot;background=#CCFFDD&quot;. This may be useful to set for capturing screenshots of less-than-ideal layouts, for example where absolute positioning causes the calculation of the canvas dimension to fail and a black background is exposed  (possibly obscuring black text).
+    *
+    * @return string The base 64 encoded string of the page screenshot (PNG file)
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+
+Selenium.prototype.doShutDownSeleniumServer = function(keycode) {
+    /**
+    * Kills the running Selenium Server and all browser sessions.  After you run this command, you will no longer be able to send
+    * commands to the server; you can't remotely start the server once it has been stopped.  Normally
+    * you should prefer to run the &quot;stop&quot; command, which terminates the current browser session, rather than 
+    * shutting down the entire server.
+    *
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+
+Selenium.prototype.doRetrieveLastRemoteControlLogs = function() {
+    /**
+    * Retrieve the last messages logged on a specific remote control. Useful for error reports, especially
+    * when running multiple remote controls in a distributed environment. The maximum number of log messages
+    * that can be retrieve is configured on remote control startup.
+    *
+    * @return string The last N log messages as a multi-line string.
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+
+Selenium.prototype.doKeyDownNative = function(keycode) {
+    /**
+    * Simulates a user pressing a key (without releasing it yet) by sending a native operating system keystroke.
+    * This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+    * a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+    * metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
+    * element, focus on the element first before running this command.
+    *
+    * @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+
+Selenium.prototype.doKeyUpNative = function(keycode) {
+    /**
+    * Simulates a user releasing a key by sending a native operating system keystroke.
+    * This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+    * a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+    * metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
+    * element, focus on the element first before running this command.
+    *
+    * @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+
+Selenium.prototype.doKeyPressNative = function(keycode) {
+    /**
+    * Simulates a user pressing and releasing a key by sending a native operating system keystroke.
+    * This function uses the java.awt.Robot class to send a keystroke; this more accurately simulates typing
+    * a key on the keyboard.  It does not honor settings from the shiftKeyDown, controlKeyDown, altKeyDown and
+    * metaKeyDown commands, and does not target any particular HTML element.  To send a keystroke to a particular
+    * element, focus on the element first before running this command.
+    *
+    * @param keycode an integer keycode number corresponding to a java.awt.event.KeyEvent; note that Java keycodes are NOT the same thing as JavaScript keycodes!
+    */
+    // This doesn't really do anything on the JS side; we let the Selenium Server take care of this for us!
+};
+</diff>
      <filename>selenium-core/scripts/selenium-remoterunner.js</filename>
    </modified>
    <modified>
      <diff>@@ -45,6 +45,10 @@ objectExtend(HtmlTestRunner.prototype, {
     },
 
     loadSuiteFrame: function() {
+        var logLevel = this.controlPanel.getDefaultLogLevel();
+        if (logLevel) {
+            LOG.setLogLevelThreshold(logLevel);
+        }
         if (selenium == null) {
             var appWindow = this._getApplicationWindow();
             try { appWindow.location; }
@@ -76,7 +80,7 @@ objectExtend(HtmlTestRunner.prototype, {
         if (this.controlPanel.isMultiWindowMode()) {
             return this._getSeparateApplicationWindow();
         }
-        return $('myiframe').contentWindow;
+        return sel$('selenium_myiframe').contentWindow;
     },
 
     _getSeparateApplicationWindow: function () {
@@ -87,6 +91,7 @@ objectExtend(HtmlTestRunner.prototype, {
     },
 
     _onloadTestSuite:function () {
+        suiteFrame = new HtmlTestSuiteFrame(getSuiteFrame());
         if (! this.getTestSuite().isAvailable()) {
             return;
         }
@@ -97,7 +102,12 @@ objectExtend(HtmlTestRunner.prototype, {
             addLoadListener(this._getApplicationWindow(), fnBind(this._startSingleTest, this));
             this._getApplicationWindow().src = this.controlPanel.getAutoUrl();
         } else {
-            this.getTestSuite().getSuiteRows()[0].loadTestCase();
+            var testCaseLoaded = fnBind(function(){this.testCaseLoaded=true;},this);
+            var testNumber = 0;
+            if (this.controlPanel.getTestNumber() != null){
+                var testNumber = this.controlPanel.getTestNumber() - 1; 
+            }
+            this.getTestSuite().getSuiteRows()[testNumber].loadTestCase(testCaseLoaded);
         }
     },
 
@@ -134,6 +144,8 @@ objectExtend(HtmlTestRunner.prototype, {
         //todo: move testFailed and storedVars to TestCase
         this.testFailed = false;
         storedVars = new Object();
+        storedVars.nbsp = String.fromCharCode(160);
+        storedVars.space = ' ';
         this.currentTest = new HtmlRunnerTestLoop(testFrame.getCurrentTestCase(), this.metrics, this.commandFactory);
         currentTest = this.currentTest;
         this.currentTest.start();
@@ -157,6 +169,10 @@ objectExtend(SeleniumFrame.prototype, {
         addLoadListener(this.frame, fnBind(this._handleLoad, this));
     },
 
+    getWindow : function() {
+        return this.frame.contentWindow;
+    },
+
     getDocument : function() {
         return this.frame.contentWindow.document;
     },
@@ -166,7 +182,6 @@ objectExtend(SeleniumFrame.prototype, {
         this._onLoad();
         if (this.loadCallback) {
             this.loadCallback();
-            this.loadCallback = null;
         }
     },
 
@@ -187,10 +202,12 @@ objectExtend(SeleniumFrame.prototype, {
             // this works in every browser (except Firefox in chrome mode)
             var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, &quot;selenium-test.css&quot;);
             if (browserVersion.isIE &amp;&amp; window.location.protocol == &quot;file:&quot;) {
-                styleSheetPath = &quot;file://&quot; + styleSheetPath;
+                styleSheetPath = &quot;file:///&quot; + styleSheetPath;
             }
             styleLink.href = styleSheetPath;
         }
+        // DGF You're only going to see this log message if you set defaultLogLevel=debug
+        LOG.debug(&quot;styleLink.href=&quot;+styleLink.href);
         head.appendChild(styleLink);
     },
 
@@ -205,7 +222,8 @@ objectExtend(SeleniumFrame.prototype, {
         var isChrome = browserVersion.isChrome || false;
         var isHTA = browserVersion.isHTA || false;
         // DGF TODO multiWindow
-        location += &quot;?thisIsChrome=&quot; + isChrome + &quot;&amp;thisIsHTA=&quot; + isHTA;
+        location += (location.indexOf(&quot;?&quot;) == -1 ? &quot;?&quot; : &quot;&amp;&quot;);
+        location += &quot;thisIsChrome=&quot; + isChrome + &quot;&amp;thisIsHTA=&quot; + isHTA; 
         if (browserVersion.isSafari) {
             // safari doesn't reload the page when the location equals to current location.
             // hence, set the location to blank so that the page will reload automatically.
@@ -246,7 +264,7 @@ objectExtend(HtmlTestFrame.prototype, SeleniumFrame.prototype);
 objectExtend(HtmlTestFrame.prototype, {
 
     _onLoad: function() {
-        this.currentTestCase = new HtmlTestCase(this.getDocument(), htmlTestRunner.getTestSuite().getCurrentRow());
+        this.currentTestCase = new HtmlTestCase(this.getWindow(), htmlTestRunner.getTestSuite().getCurrentRow());
     },
 
     getCurrentTestCase: function() {
@@ -265,19 +283,19 @@ var suiteFrame;
 var testFrame;
 
 function getSuiteFrame() {
-    var f = $('testSuiteFrame');
+    var f = sel$('testSuiteFrame');
     if (f == null) {
         f = top;
-        // proxyInjection mode does not set myiframe
+        // proxyInjection mode does not set selenium_myiframe
     }
     return f;
 }
 
 function getTestFrame() {
-    var f = $('testFrame');
+    var f = sel$('testFrame');
     if (f == null) {
         f = top;
-        // proxyInjection mode does not set myiframe
+        // proxyInjection mode does not set selenium_myiframe
     }
     return f;
 }
@@ -290,9 +308,9 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
 
         this.runInterval = 0;
 
-        this.highlightOption = $('highlightOption');
-        this.pauseButton = $('pauseTest');
-        this.stepButton = $('stepTest');
+        this.highlightOption = sel$('highlightOption');
+        this.pauseButton = sel$('pauseTest');
+        this.stepButton = sel$('stepTest');
 
         this.highlightOption.onclick = fnBindAsEventListener((function() {
             this.setHighlightOption();
@@ -342,7 +360,7 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
     },
 
     reset: function() {
-        // this.runInterval = this.speedController.value;
+        this.runInterval = this.speedController.value;
         this._switchContinueButtonToPause();
     },
 
@@ -352,7 +370,7 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
     },
 
     _switchPauseButtonToContinue: function() {
-        $('stepTest').disabled = false;
+        sel$('stepTest').disabled = false;
         this.pauseButton.className = &quot;cssContinueTest&quot;;
         this.pauseButton.onclick = fnBindAsEventListener(this.continueCurrentTest, this);
     },
@@ -378,6 +396,10 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
         return this._getQueryParameter(&quot;test&quot;);
     },
 
+    getTestNumber: function() {
+        return this._getQueryParameter(&quot;testNumber&quot;);
+    },
+
     getSingleTestName: function() {
         return this._getQueryParameter(&quot;singletest&quot;);
     },
@@ -385,6 +407,10 @@ objectExtend(HtmlTestRunnerControlPanel.prototype, {
     getAutoUrl: function() {
         return this._getQueryParameter(&quot;autoURL&quot;);
     },
+    
+    getDefaultLogLevel: function() {
+        return this._getQueryParameter(&quot;defaultLogLevel&quot;);
+    },
 
     getResultsUrl: function() {
         return this._getQueryParameter(&quot;resultsUrl&quot;);
@@ -574,7 +600,9 @@ objectExtend(HtmlTestSuite.prototype, {
     initialize: function(suiteDocument) {
         this.suiteDocument = suiteDocument;
         this.suiteRows = this._collectSuiteRows();
-        this.titleRow = new TitleRow(this.getTestTable().rows[0]);
+        var testTable = this.getTestTable();
+        if (!testTable) return;
+        this.titleRow = new TitleRow(testTable.rows[0]);
         this.reset();
     },
 
@@ -593,7 +621,7 @@ objectExtend(HtmlTestSuite.prototype, {
     },
 
     getTestTable: function() {
-        var tables = $A(this.suiteDocument.getElementsByTagName(&quot;table&quot;));
+        var tables = sel$A(this.suiteDocument.getElementsByTagName(&quot;table&quot;));
         return tables[0];
     },
 
@@ -603,15 +631,16 @@ objectExtend(HtmlTestSuite.prototype, {
 
     _collectSuiteRows: function () {
         var result = [];
-        var tables = $A(this.suiteDocument.getElementsByTagName(&quot;table&quot;));
+        var tables = sel$A(this.suiteDocument.getElementsByTagName(&quot;table&quot;));
         var testTable = tables[0];
+        if (!testTable) return;
         for (rowNum = 1; rowNum &lt; testTable.rows.length; rowNum++) {
             var rowElement = testTable.rows[rowNum];
             result.push(new HtmlTestSuiteRow(rowElement, testFrame, this));
         }
         
         // process the unsuited rows as well
-        for (var tableNum = 1; tableNum &lt; $A(this.suiteDocument.getElementsByTagName(&quot;table&quot;)).length; tableNum++) {
+        for (var tableNum = 1; tableNum &lt; sel$A(this.suiteDocument.getElementsByTagName(&quot;table&quot;)).length; tableNum++) {
             testTable = tables[tableNum];
             for (rowNum = 1; rowNum &lt; testTable.rows.length; rowNum++) {
                 var rowElement = testTable.rows[rowNum];
@@ -652,7 +681,7 @@ objectExtend(HtmlTestSuite.prototype, {
 
     _onTestSuiteComplete: function() {
         this.markDone();
-        new TestResult(this.failed, this.getTestTable()).post();
+        new SeleniumTestResult(this.failed, this.getTestTable()).post();
     },
 
     updateSuiteWithResultOfPreviousTest: function() {
@@ -676,8 +705,8 @@ objectExtend(HtmlTestSuite.prototype, {
 
 });
 
-var TestResult = classCreate();
-objectExtend(TestResult.prototype, {
+var SeleniumTestResult = classCreate();
+objectExtend(SeleniumTestResult.prototype, {
 
 // Post the results to a servlet, CGI-script, etc.  The URL of the
 // results-handler defaults to &quot;/postResults&quot;, but an alternative location
@@ -713,7 +742,7 @@ objectExtend(TestResult.prototype, {
 
         form.id = &quot;resultsForm&quot;;
         form.method = &quot;post&quot;;
-        form.target = &quot;myiframe&quot;;
+        form.target = &quot;selenium_myiframe&quot;;
 
         var resultsUrl = this.controlPanel.getResultsUrl();
         if (!resultsUrl) {
@@ -766,11 +795,22 @@ objectExtend(TestResult.prototype, {
             }
         }
 
-        form.createHiddenField(&quot;numTestTotal&quot;, rowNum);
+        form.createHiddenField(&quot;numTestTotal&quot;, rowNum-1);
 
         // Add HTML for the suite itself
         form.createHiddenField(&quot;suite&quot;, this.suiteTable.parentNode.innerHTML);
 
+        var logMessages = [];
+        while (LOG.pendingMessages.length &gt; 0) {
+            var msg = LOG.pendingMessages.shift();
+            logMessages.push(msg.type);
+            logMessages.push(&quot;: &quot;);
+            logMessages.push(msg.msg);
+            logMessages.push('\n');
+        }
+        var logOutput = logMessages.join(&quot;&quot;);
+        form.createHiddenField(&quot;log&quot;, logOutput);
+
         if (this.controlPanel.shouldSaveResultsToFile()) {
             this._saveToFile(resultsUrl, form);
         } else {
@@ -788,22 +828,50 @@ objectExtend(TestResult.prototype, {
         for (var i = 0; i &lt; form.elements.length; i++) {
             inputs[form.elements[i].name] = form.elements[i].value;
         }
+        
         var objFSO = new ActiveXObject(&quot;Scripting.FileSystemObject&quot;)
+        
+        // DGF get CSS
+        var styles = &quot;&quot;;
+        try {
+            var styleSheetPath = window.location.pathname.replace(/[^\/\\]+$/, &quot;selenium-test.css&quot;);
+            if (window.location.protocol == &quot;file:&quot;) {
+                var stylesFile = objFSO.OpenTextFile(styleSheetPath, 1);
+                styles = stylesFile.ReadAll();
+            } else {
+                var xhr = XmlHttp.create();
+                xhr.open(&quot;GET&quot;, styleSheetPath, false);
+                xhr.send(&quot;&quot;);
+                styles = xhr.responseText;
+            }
+        } catch (e) {}
+        
         var scriptFile = objFSO.CreateTextFile(fileName);
-        scriptFile.WriteLine(&quot;&lt;html&gt;&lt;body&gt;\n&lt;h1&gt;Test suite results &lt;/h1&gt;&quot; +
-                             &quot;\n\n&lt;table&gt;\n&lt;tr&gt;\n&lt;td&gt;result:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;result&quot;] + &quot;&lt;/td&gt;\n&quot; +
-                             &quot;&lt;/tr&gt;\n&lt;tr&gt;\n&lt;td&gt;totalTime:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;totalTime&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
-                             &quot;&lt;tr&gt;\n&lt;td&gt;numTestPasses:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numTestPasses&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
-                             &quot;&lt;tr&gt;\n&lt;td&gt;numTestFailures:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numTestFailures&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
-                             &quot;&lt;tr&gt;\n&lt;td&gt;numCommandPasses:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numCommandPasses&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
-                             &quot;&lt;tr&gt;\n&lt;td&gt;numCommandFailures:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numCommandFailures&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
-                             &quot;&lt;tr&gt;\n&lt;td&gt;numCommandErrors:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numCommandErrors&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
-                             &quot;&lt;tr&gt;\n&lt;td&gt;&quot; + inputs[&quot;suite&quot;] + &quot;&lt;/td&gt;\n&lt;td&gt;&amp;nbsp;&lt;/td&gt;\n&lt;/tr&gt;&quot;);
+        
+        
+        scriptFile.WriteLine(&quot;&lt;html&gt;&lt;head&gt;&lt;title&gt;Test suite results&lt;/title&gt;&lt;style&gt;&quot;);
+        scriptFile.WriteLine(styles);
+        scriptFile.WriteLine(&quot;&lt;/style&gt;&quot;);
+        scriptFile.WriteLine(&quot;&lt;body&gt;\n&lt;h1&gt;Test suite results&lt;/h1&gt;&quot; +
+             &quot;\n\n&lt;table&gt;\n&lt;tr&gt;\n&lt;td&gt;result:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;result&quot;] + &quot;&lt;/td&gt;\n&quot; +
+             &quot;&lt;/tr&gt;\n&lt;tr&gt;\n&lt;td&gt;totalTime:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;totalTime&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
+             &quot;&lt;tr&gt;\n&lt;td&gt;numTestTotal:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numTestTotal&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
+             &quot;&lt;tr&gt;\n&lt;td&gt;numTestPasses:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numTestPasses&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
+             &quot;&lt;tr&gt;\n&lt;td&gt;numTestFailures:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numTestFailures&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
+             &quot;&lt;tr&gt;\n&lt;td&gt;numCommandPasses:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numCommandPasses&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
+             &quot;&lt;tr&gt;\n&lt;td&gt;numCommandFailures:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numCommandFailures&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
+             &quot;&lt;tr&gt;\n&lt;td&gt;numCommandErrors:&lt;/td&gt;\n&lt;td&gt;&quot; + inputs[&quot;numCommandErrors&quot;] + &quot;&lt;/td&gt;\n&lt;/tr&gt;\n&quot; +
+             &quot;&lt;tr&gt;\n&lt;td&gt;&quot; + inputs[&quot;suite&quot;] + &quot;&lt;/td&gt;\n&lt;td&gt;&amp;nbsp;&lt;/td&gt;\n&lt;/tr&gt;&lt;/table&gt;&lt;table&gt;&quot;);
         var testNum = inputs[&quot;numTestTotal&quot;];
-        for (var rowNum = 1; rowNum &lt; testNum; rowNum++) {
+        
+        for (var rowNum = 1; rowNum &lt;= testNum; rowNum++) {
             scriptFile.WriteLine(&quot;&lt;tr&gt;\n&lt;td&gt;&quot; + inputs[&quot;testTable.&quot; + rowNum] + &quot;&lt;/td&gt;\n&lt;td&gt;&amp;nbsp;&lt;/td&gt;\n&lt;/tr&gt;&quot;);
         }
-        scriptFile.WriteLine(&quot;&lt;/table&gt;&lt;/body&gt;&lt;/html&gt;&quot;);
+        scriptFile.WriteLine(&quot;&lt;/table&gt;&lt;pre&gt;&quot;);
+        var log = inputs[&quot;log&quot;];
+        log=log.replace(/&amp;/gm,&quot;&amp;amp;&quot;).replace(/&lt;/gm,&quot;&amp;lt;&quot;).replace(/&gt;/gm,&quot;&amp;gt;&quot;).replace(/&quot;/gm,&quot;&amp;quot;&quot;).replace(/'/gm,&quot;&amp;apos;&quot;);
+        scriptFile.WriteLine(log);
+        scriptFile.WriteLine(&quot;&lt;/pre&gt;&lt;/body&gt;&lt;/html&gt;&quot;);
         scriptFile.Close();
     }
 });
@@ -812,14 +880,22 @@ objectExtend(TestResult.prototype, {
 var HtmlTestCase = classCreate();
 objectExtend(HtmlTestCase.prototype, {
 
-    initialize: function(testDocument, htmlTestSuiteRow) {
-        if (testDocument == null) {
-            throw &quot;testDocument should not be null&quot;;
+    initialize: function(testWindow, htmlTestSuiteRow) {
+        if (testWindow == null) {
+            throw &quot;testWindow should not be null&quot;;
         }
         if (htmlTestSuiteRow == null) {
             throw &quot;htmlTestSuiteRow should not be null&quot;;
         }
-        this.testDocument = testDocument;
+        this.testWindow = testWindow;
+        this.testDocument = testWindow.document;
+        this.pathname = &quot;'unknown'&quot;;
+        try {
+            if (this.testWindow.location) {
+                this.pathname = this.testWindow.location.pathname;
+            }
+        } catch (e) {}
+            
         this.htmlTestSuiteRow = htmlTestSuiteRow;
         this.headerRow = new TitleRow(this.testDocument.getElementsByTagName(&quot;tr&quot;)[0]);
         this.commandRows = this._collectCommandRows();
@@ -829,11 +905,11 @@ objectExtend(HtmlTestCase.prototype, {
 
     _collectCommandRows: function () {
         var commandRows = [];
-        var tables = $A(this.testDocument.getElementsByTagName(&quot;table&quot;));
+        var tables = sel$A(this.testDocument.getElementsByTagName(&quot;table&quot;));
         var self = this;
         for (var i = 0; i &lt; tables.length; i++) {
             var table = tables[i];
-            var tableRows = $A(table.rows);
+            var tableRows = sel$A(table.rows);
             for (var j = 0; j &lt; tableRows.length; j++) {
                 var candidateRow = tableRows[j];
                 if (self.isCommandRow(candidateRow)) {
@@ -959,11 +1035,11 @@ objectExtend(Metrics.prototype, {
     },
 
     printMetrics: function() {
-        setText($('commandPasses'), this.numCommandPasses);
-        setText($('commandFailures'), this.numCommandFailures);
-        setText($('commandErrors'), this.numCommandErrors);
-        setText($('testRuns'), this.numTestPasses + this.numTestFailures);
-        setText($('testFailures'), this.numTestFailures);
+        setText(sel$('commandPasses'), this.numCommandPasses);
+        setText(sel$('commandFailures'), this.numCommandFailures);
+        setText(sel$('commandErrors'), this.numCommandErrors);
+        setText(sel$('testRuns'), this.numTestPasses + this.numTestFailures);
+        setText(sel$('testFailures'), this.numTestFailures);
 
         this.currentTime = new Date().getTime();
 
@@ -973,7 +1049,7 @@ objectExtend(Metrics.prototype, {
         var minutes = Math.floor(totalSecs / 60);
         var seconds = totalSecs % 60;
 
-        setText($('elapsedTime'), this._pad(minutes) + &quot;:&quot; + this._pad(seconds));
+        setText(sel$('elapsedTime'), this._pad(minutes) + &quot;:&quot; + this._pad(seconds));
     },
 
 // Puts a leading 0 on num if it is less than 10
@@ -1020,9 +1096,7 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
         this.metrics = metrics;
 
         this.htmlTestCase = htmlTestCase;
-
-        se = selenium;
-        global.se = selenium;
+        LOG.info(&quot;Starting test &quot; + htmlTestCase.pathname);
 
         this.currentRow = null;
         this.currentRowIndex = 0;
@@ -1034,17 +1108,6 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
         this.expectedFailureType = null;
 
         this.htmlTestCase.reset();
-
-        this.sejsElement = this.htmlTestCase.testDocument.getElementById('sejs');
-        if (this.sejsElement) {
-            var fname = 'Selenium JavaScript';
-            parse_result = parse(this.sejsElement.innerHTML, fname, 0);
-
-            var x2 = new ExecutionContext(GLOBAL_CODE);
-            ExecutionContext.current = x2;
-
-            execute(parse_result, x2)
-        }
     },
 
     _advanceToNextRow: function() {
@@ -1069,7 +1132,7 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
     },
 
     commandStarted : function() {
-        $('pauseTest').disabled = false;
+        sel$('pauseTest').disabled = false;
         this.currentRow.select();
         this.metrics.printMetrics();
     },
@@ -1141,8 +1204,8 @@ objectExtend(HtmlRunnerTestLoop.prototype, {
     },
 
     testComplete : function() {
-        $('pauseTest').disabled = true;
-        $('stepTest').disabled = true;
+        sel$('pauseTest').disabled = true;
+        sel$('stepTest').disabled = true;
         if (htmlTestRunner.testFailed) {
             this.htmlTestCase.markFailed();
             this.metrics.numTestFailures += 1;
@@ -1231,6 +1294,24 @@ Selenium.prototype.doEcho = function(message) {
     currentTest.currentRow.setMessage(message);
 }
 
+/*
+ * doSetSpeed and getSpeed are already defined in selenium-api.js,
+ * so we're defining these functions in a tricky way so that doc.js doesn't
+ * try to read API doc from the function definitions here.
+ */
+Selenium.prototype._doSetSpeed = function(value) {
+    var milliseconds = parseInt(value);
+    if (milliseconds &lt; 0) milliseconds = 0;
+    htmlTestRunner.controlPanel.speedController.setValue(milliseconds);
+    htmlTestRunner.controlPanel.setRunInterval(milliseconds);
+}
+Selenium.prototype.doSetSpeed = Selenium.prototype._doSetSpeed;
+
+Selenium.prototype._getSpeed = function() {
+    return htmlTestRunner.controlPanel.runInterval;
+}
+Selenium.prototype.getSpeed = Selenium.prototype._getSpeed;
+
 Selenium.prototype.assertSelected = function(selectLocator, optionLocator) {
     /**
      * Verifies that the selected option of a drop-down satisfies the optionSpecifier.  &lt;i&gt;Note that this command is deprecated; you should use assertSelectedLabel, assertSelectedValue, assertSelectedIndex, or assertSelectedId instead.&lt;/i&gt;</diff>
      <filename>selenium-core/scripts/selenium-testrunner.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,5 +1,5 @@
-Selenium.version = &quot;0.8.2&quot;;
-Selenium.revision = &quot;1727&quot;;
+Selenium.version = &quot;1.0-beta-2&quot;;
+Selenium.revision = &quot;2330&quot;;
 
 window.top.document.title += &quot; v&quot; + Selenium.version + &quot; [&quot; + Selenium.revision + &quot;]&quot;;
 </diff>
      <filename>selenium-core/scripts/selenium-version.js</filename>
    </modified>
    <modified>
      <filename>selenium-core/scripts/xmlextras.js</filename>
    </modified>
    <modified>
      <diff></diff>
      <filename>selenium-core/selenium-logo.png</filename>
    </modified>
    <modified>
      <diff>@@ -48,7 +48,11 @@ iframe {
     width: 100%;
     height: 100%;
     background: white;
-    overflow: auto;
+    /*  HBC - this particular property seems to be causing an issue in
+        conjunction with the native Draw() method in Snapsie. I don't really
+        see a visual difference from commenting this out, so I'm going ahead
+        with it.
+    overflow: auto; */
 }
 
 /*---( Style )---*/
@@ -67,6 +71,19 @@ body, html {
     font-size: 90%;
 }
 
+.remoterunner {
+	font-size: 10pt;
+}
+
+.remoterunner fieldset {
+	padding: 0.25em;
+}
+
+.remoterunner button, .remoterunner label {
+	margin-right: 1em;
+}
+
+
 #controlPanel {
     padding: 0.5ex;
     background: #eee;</diff>
      <filename>selenium-core/selenium.css</filename>
    </modified>
    <modified>
      <diff>@@ -1,428 +1,566 @@
-// Copyright 2005 Google Inc.
-// All Rights Reserved
-//
-// An XML parse and a minimal DOM implementation that just supportes
-// the subset of the W3C DOM that is used in the XSLT implementation.
-//
-// References: 
-//
-// [DOM] W3C DOM Level 3 Core Specification
-//       &lt;http://www.w3.org/TR/2004/REC-DOM-Level-3-Core-20040407/&gt;.
-//
-// 
-// Author: Steffen Meschkat &lt;mesch@google.com&gt;
-
-// NOTE: The split() method in IE omits empty result strings. This is
-// utterly annoying. So we don't use it here.
-
-// Resolve entities in XML text fragments. According to the DOM
-// specification, the DOM is supposed to resolve entity references at
-// the API level. I.e. no entity references are passed through the
-// API. See &quot;Entities and the DOM core&quot;, p.12, DOM 2 Core
-// Spec. However, different browsers actually pass very different
-// values at the API.
-//
-function xmlResolveEntities(s) {
-
-  var parts = stringSplit(s, '&amp;');
-
-  var ret = parts[0];
-  for (var i = 1; i &lt; parts.length; ++i) {
-    var rp = stringSplit(parts[i], ';');
-    if (rp.length == 1) {
-      // no entity reference: just a &amp; but no ;
-      ret += parts[i];
-      continue;
-    }
-    
-    var ch;
-    switch (rp[0]) {
-      case 'lt': 
-        ch = '&lt;';
-        break;
-      case 'gt': 
-        ch = '&gt;';
-        break;
-      case 'amp': 
-        ch = '&amp;';
-        break;
-      case 'quot': 
-        ch = '&quot;';
-        break;
-      case 'apos': 
-        ch = '\'';
-        break;
-      case 'nbsp': 
-        ch = String.fromCharCode(160);
-        break;
-      default:
-        // Cool trick: let the DOM do the entity decoding. We assign
-        // the entity text through non-W3C DOM properties and read it
-        // through the W3C DOM. W3C DOM access is specified to resolve
-        // entities. 
-        var span = window.document.createElement('span');
-        span.innerHTML = '&amp;' + rp[0] + '; ';
-        ch = span.childNodes[0].nodeValue.charAt(0);
-    }
-    ret += ch + rp[1];
-  }
-
-  return ret;
-}
-
-
-// Parses the given XML string with our custom, JavaScript XML parser. Written
-// by Steffen Meschkat (mesch@google.com).
-function xmlParse(xml) {
-  Timer.start('xmlparse');
-  var regex_empty = /\/$/;
-
-  // See also &lt;http://www.w3.org/TR/REC-xml/#sec-common-syn&gt; for
-  // allowed chars in a tag and attribute name. TODO(mesch): the
-  // following is still not completely correct.
-
-  var regex_tagname = /^([\w:-]*)/;
-  var regex_attribute = /([\w:-]+)\s?=\s?('([^\']*)'|&quot;([^\&quot;]*)&quot;)/g;
-
-  var xmldoc = new XDocument();
-  var root = xmldoc;
-
-  // For the record: in Safari, we would create native DOM nodes, but
-  // in Opera that is not possible, because the DOM only allows HTML
-  // element nodes to be created, so we have to do our own DOM nodes.
-
-  // xmldoc = document.implementation.createDocument('','',null);
-  // root = xmldoc; // .createDocumentFragment();
-  // NOTE(mesch): using the DocumentFragment instead of the Document
-  // crashes my Safari 1.2.4 (v125.12).
-  var stack = [];
-
-  var parent = root;
-  stack.push(parent);
-
-  var x = stringSplit(xml, '&lt;');
-  for (var i = 1; i &lt; x.length; ++i) {
-    var xx = stringSplit(x[i], '&gt;');
-    var tag = xx[0];
-    var text = xmlResolveEntities(xx[1] || '');
-
-    if (tag.charAt(0) == '/') {
-      stack.pop();
-      parent = stack[stack.length-1];
-
-    } else if (tag.charAt(0) == '?') {
-      // Ignore XML declaration and processing instructions
-    } else if (tag.charAt(0) == '!') {
-      // Ignore notation and comments
-    } else {
-      var empty = tag.match(regex_empty);
-      var tagname = regex_tagname.exec(tag)[1];
-      var node = xmldoc.createElement(tagname);
-
-      var att;
-      while (att = regex_attribute.exec(tag)) {
-        var val = xmlResolveEntities(att[3] || att[4] || '');
-        node.setAttribute(att[1], val);
-      }
-      
-      if (empty) {
-        parent.appendChild(node);
-      } else {
-        parent.appendChild(node);
-        parent = node;
-        stack.push(node);
-      }
-    }
-
-    if (text &amp;&amp; parent != root) {
-      parent.appendChild(xmldoc.createTextNode(text));
-    }
-  }
-
-  Timer.end('xmlparse');
-  return root;
-}
-
-
-// Our W3C DOM Node implementation. Note we call it XNode because we
-// can't define the identifier Node. We do this mostly for Opera,
-// where we can't reuse the HTML DOM for parsing our own XML, and for
-// Safari, where it is too expensive to have the template processor
-// operate on native DOM nodes.
-function XNode(type, name, value, owner) {
-  this.attributes = [];
-  this.childNodes = [];
-
-  XNode.init.call(this, type, name, value, owner);
-}
-
-// Don't call as method, use apply() or call().
-XNode.init = function(type, name, value, owner) {
-  this.nodeType = type - 0;
-  this.nodeName = '' + name;
-  this.nodeValue = '' + value;
-  this.ownerDocument = owner;
-
-  this.firstChild = null;
-  this.lastChild = null;
-  this.nextSibling = null;
-  this.previousSibling = null;
-  this.parentNode = null;
-}
-
-XNode.unused_ = [];
-
-XNode.recycle = function(node) {
-  if (!node) {
-    return;
-  }
-
-  if (node.constructor == XDocument) {
-    XNode.recycle(node.documentElement);
-    return;
-  }
-
-  if (node.constructor != this) {
-    return;
-  }
-
-  XNode.unused_.push(node);
-  for (var a = 0; a &lt; node.attributes.length; ++a) {
-    XNode.recycle(node.attributes[a]);
-  }
-  for (var c = 0; c &lt; node.childNodes.length; ++c) {
-    XNode.recycle(node.childNodes[c]);
-  }
-  node.attributes.length = 0;
-  node.childNodes.length = 0;
-  XNode.init.call(node, 0, '', '', null);
-}
-
-XNode.create = function(type, name, value, owner) {
-  if (XNode.unused_.length &gt; 0) {
-    var node = XNode.unused_.pop();
-    XNode.init.call(node, type, name, value, owner);
-    return node;
-  } else {
-    return new XNode(type, name, value, owner);
-  }
-}
-
-XNode.prototype.appendChild = function(node) {
-  // firstChild
-  if (this.childNodes.length == 0) {
-    this.firstChild = node;
-  }
-
-  // previousSibling
-  node.previousSibling = this.lastChild;
-
-  // nextSibling
-  node.nextSibling = null;
-  if (this.lastChild) {
-    this.lastChild.nextSibling = node;
-  }
-
-  // parentNode
-  node.parentNode = this;
-
-  // lastChild
-  this.lastChild = node;
-
-  // childNodes
-  this.childNodes.push(node);
-}
-
-
-XNode.prototype.replaceChild = function(newNode, oldNode) {
-  if (oldNode == newNode) {
-    return;
-  }
-
-  for (var i = 0; i &lt; this.childNodes.length; ++i) {
-    if (this.childNodes[i] == oldNode) {
-      this.childNodes[i] = newNode;
-      
-      var p = oldNode.parentNode;
-      oldNode.parentNode = null;
-      newNode.parentNode = p;
-      
-      p = oldNode.previousSibling;
-      oldNode.previousSibling = null;
-      newNode.previousSibling = p;
-      if (newNode.previousSibling) {
-        newNode.previousSibling.nextSibling = newNode;
-      }
-      
-      p = oldNode.nextSibling;
-      oldNode.nextSibling = null;
-      newNode.nextSibling = p;
-      if (newNode.nextSibling) {
-        newNode.nextSibling.previousSibling = newNode;
-      }
-
-      if (this.firstChild == oldNode) {
-        this.firstChild = newNode;
-      }
-
-      if (this.lastChild == oldNode) {
-        this.lastChild = newNode;
-      }
-
-      break;
-    }
-  }
-}
-
-XNode.prototype.insertBefore = function(newNode, oldNode) {
-  if (oldNode == newNode) {
-    return;
-  }
-
-  if (oldNode.parentNode != this) {
-    return;
-  }
-
-  if (newNode.parentNode) {
-    newNode.parentNode.removeChild(newNode);
-  }
-
-  var newChildren = [];
-  for (var i = 0; i &lt; this.childNodes.length; ++i) {
-    var c = this.childNodes[i];
-    if (c == oldNode) {
-      newChildren.push(newNode);
-
-      newNode.parentNode = this;
-
-      newNode.previousSibling = oldNode.previousSibling;
-      oldNode.previousSibling = newNode;
-      if (newNode.previousSibling) {
-        newNode.previousSibling.nextSibling = newNode;
-      }
-      
-      newNode.nextSibling = oldNode;
-
-      if (this.firstChild == oldNode) {
-        this.firstChild = newNode;
-      }
-    }
-    newChildren.push(c);
-  }
-  this.childNodes = newChildren;
-}
-
-XNode.prototype.removeChild = function(node) {
-  var newChildren = [];
-  for (var i = 0; i &lt; this.childNodes.length; ++i) {
-    var c = this.childNodes[i];
-    if (c != node) {
-      newChildren.push(c);
-    } else {
-      if (c.previousSibling) {
-        c.previousSibling.nextSibling = c.nextSibling;
-      }
-      if (c.nextSibling) {
-        c.nextSibling.previousSibling = c.previousSibling;
-      }
-      if (this.firstChild == c) {
-        this.firstChild = c.nextSibling;
-      }
-      if (this.lastChild == c) {
-        this.lastChild = c.previousSibling;
-      }
-    }
-  }
-  this.childNodes = newChildren;
-}
-
-
-XNode.prototype.hasAttributes = function() {
-  return this.attributes.length &gt; 0;
-}
-
-
-XNode.prototype.setAttribute = function(name, value) {
-  for (var i = 0; i &lt; this.attributes.length; ++i) {
-    if (this.attributes[i].nodeName == name) {
-      this.attributes[i].nodeValue = '' + value;
-      return;
-    }
-  }
-  this.attributes.push(new XNode(DOM_ATTRIBUTE_NODE, name, value));
-}
-
-
-XNode.prototype.getAttribute = function(name) {
-  for (var i = 0; i &lt; this.attributes.length; ++i) {
-    if (this.attributes[i].nodeName == name) {
-      return this.attributes[i].nodeValue;
-    }
-  }
-  return null;
-}
-
-XNode.prototype.removeAttribute = function(name) {
-  var a = [];
-  for (var i = 0; i &lt; this.attributes.length; ++i) {
-    if (this.attributes[i].nodeName != name) {
-      a.push(this.attributes[i]);
-    }
-  }
-  this.attributes = a;
-}
-
-
-function XDocument() {
-  XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, this);
-  this.documentElement = null;
-}
-
-XDocument.prototype = new XNode(DOM_DOCUMENT_NODE, '#document');
-
-XDocument.prototype.clear = function() {
-  XNode.recycle(this.documentElement);
-  this.documentElement = null;
-}
-
-XDocument.prototype.appendChild = function(node) {
-  XNode.prototype.appendChild.call(this, node);
-  this.documentElement = this.childNodes[0];
-}
-
-XDocument.prototype.createElement = function(name) {
-  return XNode.create(DOM_ELEMENT_NODE, name, null, this);
-}
-
-XDocument.prototype.createDocumentFragment = function() {
-  return XNode.create(DOM_DOCUMENT_FRAGMENT_NODE, '#document-fragment',
-                    null, this);
-}
-
-XDocument.prototype.createTextNode = function(value) {
-  return XNode.create(DOM_TEXT_NODE, '#text', value, this);
-}
-
-XDocument.prototype.createAttribute = function(name) {
-  return XNode.create(DOM_ATTRIBUTE_NODE, name, null, this);
-}
-
-XDocument.prototype.createComment = function(data) {
-  return XNode.create(DOM_COMMENT_NODE, '#comment', data, this);
-}
-
-XNode.prototype.getElementsByTagName = function(name, list) {
-  if (!list) {
-    list = [];
-  }
-
-  if (this.nodeName == name) {
-    list.push(this);
-  }
-
-  for (var i = 0; i &lt; this.childNodes.length; ++i) {
-    this.childNodes[i].getElementsByTagName(name, list);
-  }
-
-  return list;
-}
+// Copyright 2005 Google Inc.
+// All Rights Reserved
+//
+// Author: Steffen Meschkat &lt;mesch@google.com&gt;
+//
+// An XML parse and a minimal DOM implementation that just supportes
+// the subset of the W3C DOM that is used in the XSLT implementation.
+
+// NOTE: The split() method in IE omits empty result strings. This is
+// utterly annoying. So we don't use it here.
+
+// Resolve entities in XML text fragments. According to the DOM
+// specification, the DOM is supposed to resolve entity references at
+// the API level. I.e. no entity references are passed through the
+// API. See &quot;Entities and the DOM core&quot;, p.12, DOM 2 Core
+// Spec. However, different browsers actually pass very different
+// values at the API. See &lt;http://mesch.nyc/test-xml-quote&gt;.
+function xmlResolveEntities(s) {
+
+  var parts = stringSplit(s, '&amp;');
+
+  var ret = parts[0];
+  for (var i = 1; i &lt; parts.length; ++i) {
+    var rp = parts[i].indexOf(';');
+    if (rp == -1) {
+      // no entity reference: just a &amp; but no ;
+      ret += parts[i];
+      continue;
+    }
+
+    var entityName = parts[i].substring(0, rp);
+    var remainderText = parts[i].substring(rp + 1);
+
+    var ch;
+    switch (entityName) {
+      case 'lt':
+        ch = '&lt;';
+        break;
+      case 'gt':
+        ch = '&gt;';
+        break;
+      case 'amp':
+        ch = '&amp;';
+        break;
+      case 'quot':
+        ch = '&quot;';
+        break;
+      case 'apos':
+        ch = '\'';
+        break;
+      case 'nbsp':
+        ch = String.fromCharCode(160);
+        break;
+      default:
+        // Cool trick: let the DOM do the entity decoding. We assign
+        // the entity text through non-W3C DOM properties and read it
+        // through the W3C DOM. W3C DOM access is specified to resolve
+        // entities.
+        var span = domCreateElement(window.document, 'span');
+        span.innerHTML = '&amp;' + entityName + '; ';
+        ch = span.childNodes[0].nodeValue.charAt(0);
+    }
+    ret += ch + remainderText;
+  }
+
+  return ret;
+}
+
+var XML10_TAGNAME_REGEXP = new RegExp('^(' + XML10_NAME + ')');
+var XML10_ATTRIBUTE_REGEXP = new RegExp(XML10_ATTRIBUTE, 'g');
+
+var XML11_TAGNAME_REGEXP = new RegExp('^(' + XML11_NAME + ')');
+var XML11_ATTRIBUTE_REGEXP = new RegExp(XML11_ATTRIBUTE, 'g');
+
+// Parses the given XML string with our custom, JavaScript XML parser. Written
+// by Steffen Meschkat (mesch@google.com).
+function xmlParse(xml) {
+  var regex_empty = /\/$/;
+
+  var regex_tagname;
+  var regex_attribute;
+  if (xml.match(/^&lt;\?xml/)) {
+    // When an XML document begins with an XML declaration
+    // VersionInfo must appear.
+    if (xml.search(new RegExp(XML10_VERSION_INFO)) == 5) {
+      regex_tagname = XML10_TAGNAME_REGEXP;
+      regex_attribute = XML10_ATTRIBUTE_REGEXP;
+    } else if (xml.search(new RegExp(XML11_VERSION_INFO)) == 5) {
+      regex_tagname = XML11_TAGNAME_REGEXP;
+      regex_attribute = XML11_ATTRIBUTE_REGEXP;
+    } else {
+      // VersionInfo is missing, or unknown version number.
+      // TODO : Fallback to XML 1.0 or XML 1.1, or just return null?
+      alert('VersionInfo is missing, or unknown version number.');
+    }
+  } else {
+    // When an XML declaration is missing it's an XML 1.0 document.
+    regex_tagname = XML10_TAGNAME_REGEXP;
+    regex_attribute = XML10_ATTRIBUTE_REGEXP;
+  }
+
+  var xmldoc = new XDocument();
+  var root = xmldoc;
+
+  // For the record: in Safari, we would create native DOM nodes, but
+  // in Opera that is not possible, because the DOM only allows HTML
+  // element nodes to be created, so we have to do our own DOM nodes.
+
+  // xmldoc = document.implementation.createDocument('','',null);
+  // root = xmldoc; // .createDocumentFragment();
+  // NOTE(mesch): using the DocumentFragment instead of the Document
+  // crashes my Safari 1.2.4 (v125.12).
+  var stack = [];
+
+  var parent = root;
+  stack.push(parent);
+
+  // The token that delimits a section that contains markup as
+  // content: CDATA or comments.
+  var slurp = '';
+
+  var x = stringSplit(xml, '&lt;');
+  for (var i = 1; i &lt; x.length; ++i) {
+    var xx = stringSplit(x[i], '&gt;');
+    var tag = xx[0];
+    var text = xmlResolveEntities(xx[1] || '');
+
+    if (slurp) {
+      // In a &quot;slurp&quot; section (CDATA or comment): only check for the
+      // end of the section, otherwise append the whole text.
+      var end = x[i].indexOf(slurp);
+      if (end != -1) {
+        var data = x[i].substring(0, end);
+        parent.nodeValue += '&lt;' + data;
+        stack.pop();
+        parent = stack[stack.length-1];
+        text = x[i].substring(end + slurp.length);
+        slurp = '';
+      } else {
+        parent.nodeValue += '&lt;' + x[i];
+        text = null;
+      }
+
+    } else if (tag.indexOf('![CDATA[') == 0) {
+      var start = '![CDATA['.length;
+      var end = x[i].indexOf(']]&gt;');
+      if (end != -1) {
+        var data = x[i].substring(start, end);
+        var node = domCreateCDATASection(xmldoc, data);
+        domAppendChild(parent, node);
+      } else {
+        var data = x[i].substring(start);
+        text = null;
+        var node = domCreateCDATASection(xmldoc, data);
+        domAppendChild(parent, node);
+        parent = node;
+        stack.push(node);
+        slurp = ']]&gt;';
+      }
+
+    } else if (tag.indexOf('!--') == 0) {
+      var start = '!--'.length;
+      var end = x[i].indexOf('--&gt;');
+      if (end != -1) {
+        var data = x[i].substring(start, end);
+        var node = domCreateComment(xmldoc, data);
+        domAppendChild(parent, node);
+      } else {
+        var data = x[i].substring(start);
+        text = null;
+        var node = domCreateComment(xmldoc, data);
+        domAppendChild(parent, node);
+        parent = node;
+        stack.push(node);
+        slurp = '--&gt;';
+      }
+
+    } else if (tag.charAt(0) == '/') {
+      stack.pop();
+      parent = stack[stack.length-1];
+
+    } else if (tag.charAt(0) == '?') {
+      // Ignore XML declaration and processing instructions
+    } else if (tag.charAt(0) == '!') {
+      // Ignore notation and comments
+    } else {
+      var empty = tag.match(regex_empty);
+      var tagname = regex_tagname.exec(tag)[1];
+      var node = domCreateElement(xmldoc, tagname);
+
+      var att;
+      while (att = regex_attribute.exec(tag)) {
+        var val = xmlResolveEntities(att[5] || att[7] || '');
+        domSetAttribute(node, att[1], val);
+      }
+
+      domAppendChild(parent, node);
+      if (!empty) {
+        parent = node;
+        stack.push(node);
+      }
+    }
+
+    if (text &amp;&amp; parent != root) {
+      domAppendChild(parent, domCreateTextNode(xmldoc, text));
+    }
+  }
+
+  return root;
+}
+
+// Based on &lt;http://www.w3.org/TR/2000/REC-DOM-Level-2-Core-20001113/
+// core.html#ID-1950641247&gt;
+var DOM_ELEMENT_NODE = 1;
+var DOM_ATTRIBUTE_NODE = 2;
+var DOM_TEXT_NODE = 3;
+var DOM_CDATA_SECTION_NODE = 4;
+var DOM_ENTITY_REFERENCE_NODE = 5;
+var DOM_ENTITY_NODE = 6;
+var DOM_PROCESSING_INSTRUCTION_NODE = 7;
+var DOM_COMMENT_NODE = 8;
+var DOM_DOCUMENT_NODE = 9;
+var DOM_DOCUMENT_TYPE_NODE = 10;
+var DOM_DOCUMENT_FRAGMENT_NODE = 11;
+var DOM_NOTATION_NODE = 12;
+
+// Traverses the element nodes in the DOM section underneath the given
+// node and invokes the given callbacks as methods on every element
+// node encountered. Function opt_pre is invoked before a node's
+// children are traversed; opt_post is invoked after they are
+// traversed. Traversal will not be continued if a callback function
+// returns boolean false. NOTE(mesch): copied from
+// &lt;//google3/maps/webmaps/javascript/dom.js&gt;.
+function domTraverseElements(node, opt_pre, opt_post) {
+  var ret;
+  if (opt_pre) {
+    ret = opt_pre.call(null, node);
+    if (typeof ret == 'boolean' &amp;&amp; !ret) {
+      return false;
+    }
+  }
+
+  for (var c = node.firstChild; c; c = c.nextSibling) {
+    if (c.nodeType == DOM_ELEMENT_NODE) {
+      ret = arguments.callee.call(this, c, opt_pre, opt_post);
+      if (typeof ret == 'boolean' &amp;&amp; !ret) {
+        return false;
+      }
+    }
+  }
+
+  if (opt_post) {
+    ret = opt_post.call(null, node);
+    if (typeof ret == 'boolean' &amp;&amp; !ret) {
+      return false;
+    }
+  }
+}
+
+// Our W3C DOM Node implementation. Note we call it XNode because we
+// can't define the identifier Node. We do this mostly for Opera,
+// where we can't reuse the HTML DOM for parsing our own XML, and for
+// Safari, where it is too expensive to have the template processor
+// operate on native DOM nodes.
+function XNode(type, name, opt_value, opt_owner) {
+  this.attributes = [];
+  this.childNodes = [];
+
+  XNode.init.call(this, type, name, opt_value, opt_owner);
+}
+
+// Don't call as method, use apply() or call().
+XNode.init = function(type, name, value, owner) {
+  this.nodeType = type - 0;
+  this.nodeName = '' + name;
+  this.nodeValue = '' + value;
+  this.ownerDocument = owner;
+
+  this.firstChild = null;
+  this.lastChild = null;
+  this.nextSibling = null;
+  this.previousSibling = null;
+  this.parentNode = null;
+}
+
+XNode.unused_ = [];
+
+XNode.recycle = function(node) {
+  if (!node) {
+    return;
+  }
+
+  if (node.constructor == XDocument) {
+    XNode.recycle(node.documentElement);
+    return;
+  }
+
+  if (node.constructor != this) {
+    return;
+  }
+
+  XNode.unused_.push(node);
+  for (var a = 0; a &lt; node.attributes.length; ++a) {
+    XNode.recycle(node.attributes[a]);
+  }
+  for (var c = 0; c &lt; node.childNodes.length; ++c) {
+    XNode.recycle(node.childNodes[c]);
+  }
+  node.attributes.length = 0;
+  node.childNodes.length = 0;
+  XNode.init.call(node, 0, '', '', null);
+}
+
+XNode.create = function(type, name, value, owner) {
+  if (XNode.unused_.length &gt; 0) {
+    var node = XNode.unused_.pop();
+    XNode.init.call(node, type, name, value, owner);
+    return node;
+  } else {
+    return new XNode(type, name, value, owner);
+  }
+}
+
+XNode.prototype.appendChild = function(node) {
+  // firstChild
+  if (this.childNodes.length == 0) {
+    this.firstChild = node;
+  }
+
+  // previousSibling
+  node.previousSibling = this.lastChild;
+
+  // nextSibling
+  node.nextSibling = null;
+  if (this.lastChild) {
+    this.lastChild.nextSibling = node;
+  }
+
+  // parentNode
+  node.parentNode = this;
+
+  // lastChild
+  this.lastChild = node;
+
+  // childNodes
+  this.childNodes.push(node);
+}
+
+
+XNode.prototype.replaceChild = function(newNode, oldNode) {
+  if (oldNode == newNode) {
+    return;
+  }
+
+  for (var i = 0; i &lt; this.childNodes.length; ++i) {
+    if (this.childNodes[i] == oldNode) {
+      this.childNodes[i] = newNode;
+
+      var p = oldNode.parentNode;
+      oldNode.parentNode = null;
+      newNode.parentNode = p;
+
+      p = oldNode.previousSibling;
+      oldNode.previousSibling = null;
+      newNode.previousSibling = p;
+      if (newNode.previousSibling) {
+        newNode.previousSibling.nextSibling = newNode;
+      }
+
+      p = oldNode.nextSibling;
+      oldNode.nextSibling = null;
+      newNode.nextSibling = p;
+      if (newNode.nextSibling) {
+        newNode.nextSibling.previousSibling = newNode;
+      }
+
+      if (this.firstChild == oldNode) {
+        this.firstChild = newNode;
+      }
+
+      if (this.lastChild == oldNode) {
+        this.lastChild = newNode;
+      }
+
+      break;
+    }
+  }
+}
+
+
+XNode.prototype.insertBefore = function(newNode, oldNode) {
+  if (oldNode == newNode) {
+    return;
+  }
+
+  if (oldNode.parentNode != this) {
+    return;
+  }
+
+  if (newNode.parentNode) {
+    newNode.parentNode.removeChild(newNode);
+  }
+
+  var newChildren = [];
+  for (var i = 0; i &lt; this.childNodes.length; ++i) {
+    var c = this.childNodes[i];
+    if (c == oldNode) {
+      newChildren.push(newNode);
+
+      newNode.parentNode = this;
+
+      newNode.previousSibling = oldNode.previousSibling;
+      oldNode.previousSibling = newNode;
+      if (newNode.previousSibling) {
+        newNode.previousSibling.nextSibling = newNode;
+      }
+
+      newNode.nextSibling = oldNode;
+
+      if (this.firstChild == oldNode) {
+        this.firstChild = newNode;
+      }
+    }
+    newChildren.push(c);
+  }
+  this.childNodes = newChildren;
+}
+
+
+XNode.prototype.removeChild = function(node) {
+  var newChildren = [];
+  for (var i = 0; i &lt; this.childNodes.length; ++i) {
+    var c = this.childNodes[i];
+    if (c != node) {
+      newChildren.push(c);
+    } else {
+      if (c.previousSibling) {
+        c.previousSibling.nextSibling = c.nextSibling;
+      }
+      if (c.nextSibling) {
+        c.nextSibling.previousSibling = c.previousSibling;
+      }
+      if (this.firstChild == c) {
+        this.firstChild = c.nextSibling;
+      }
+      if (this.lastChild == c) {
+        this.lastChild = c.previousSibling;
+      }
+    }
+  }
+  this.childNodes = newChildren;
+}
+
+
+XNode.prototype.hasAttributes = function() {
+  return this.attributes.length &gt; 0;
+}
+
+
+XNode.prototype.setAttribute = function(name, value) {
+  for (var i = 0; i &lt; this.attributes.length; ++i) {
+    if (this.attributes[i].nodeName == name) {
+      this.attributes[i].nodeValue = '' + value;
+      return;
+    }
+  }
+  this.attributes.push(XNode.create(DOM_ATTRIBUTE_NODE, name, value, this));
+}
+
+
+XNode.prototype.getAttribute = function(name) {
+  for (var i = 0; i &lt; this.attributes.length; ++i) {
+    if (this.attributes[i].nodeName == name) {
+      return this.attributes[i].nodeValue;
+    }
+  }
+  return null;
+}
+
+
+XNode.prototype.removeAttribute = function(name) {
+  var a = [];
+  for (var i = 0; i &lt; this.attributes.length; ++i) {
+    if (this.attributes[i].nodeName != name) {
+      a.push(this.attributes[i]);
+    }
+  }
+  this.attributes = a;
+}
+
+
+XNode.prototype.getElementsByTagName = function(name) {
+  var ret = [];
+  var self = this;
+  if (&quot;*&quot; == name) {
+    domTraverseElements(this, function(node) {
+      if (self == node) return;
+      ret.push(node);
+    }, null);
+  } else {
+    domTraverseElements(this, function(node) {
+      if (self == node) return;
+      if (node.nodeName == name) {
+        ret.push(node);
+      }
+    }, null);
+  }
+  return ret;
+}
+
+
+XNode.prototype.getElementById = function(id) {
+  var ret = null;
+  domTraverseElements(this, function(node) {
+    if (node.getAttribute('id') == id) {
+      ret = node;
+      return false;
+    }
+  }, null);
+  return ret;
+}
+
+
+function XDocument() {
+  // NOTE(mesch): Acocording to the DOM Spec, ownerDocument of a
+  // document node is null.
+  XNode.call(this, DOM_DOCUMENT_NODE, '#document', null, null);
+  this.documentElement = null;
+}
+
+XDocument.prototype = new XNode(DOM_DOCUMENT_NODE, '#document');
+
+XDocument.prototype.clear = function() {
+  XNode.recycle(this.documentElement);
+  this.documentElement = null;
+}
+
+XDocument.prototype.appendChild = function(node) {
+  XNode.prototype.appendChild.call(this, node);
+  this.documentElement = this.childNodes[0];
+}
+
+XDocument.prototype.createElement = function(name) {
+  return XNode.create(DOM_ELEMENT_NODE, name, null, this);
+}
+
+XDocument.prototype.createDocumentFragment = function() {
+  return XNode.create(DOM_DOCUMENT_FRAGMENT_NODE, '#document-fragment',
+                    null, this);
+}
+
+XDocument.prototype.createTextNode = function(value) {
+  return XNode.create(DOM_TEXT_NODE, '#text', value, this);
+}
+
+XDocument.prototype.createAttribute = function(name) {
+  return XNode.create(DOM_ATTRIBUTE_NODE, name, null, this);
+}
+
+XDocument.prototype.createComment = function(data) {
+  return XNode.create(DOM_COMMENT_NODE, '#comment', data, this);
+}
+
+XDocument.prototype.createCDATASection = function(data) {
+  return XNode.create(DOM_CDATA_SECTION_NODE, '#cdata-section', data, this);
+}</diff>
      <filename>selenium-core/xpath/dom.js</filename>
    </modified>
    <modified>
      <diff>@@ -1,2182 +1,2450 @@
-// Copyright 2005 Google Inc.
-// All Rights Reserved
-//
-// An XPath parser and evaluator written in JavaScript. The
-// implementation is complete except for functions handling
-// namespaces.
-//
-// Reference: [XPATH] XPath Specification
-// &lt;http://www.w3.org/TR/1999/REC-xpath-19991116&gt;.
-//
-//
-// The API of the parser has several parts:
-//
-// 1. The parser function xpathParse() that takes a string and returns
-// an expession object.
-//
-// 2. The expression object that has an evaluate() method to evaluate the
-// XPath expression it represents. (It is actually a hierarchy of
-// objects that resembles the parse tree, but an application will call
-// evaluate() only on the top node of this hierarchy.)
-//
-// 3. The context object that is passed as an argument to the evaluate()
-// method, which represents the DOM context in which the expression is
-// evaluated.
-//
-// 4. The value object that is returned from evaluate() and represents
-// values of the different types that are defined by XPath (number,
-// string, boolean, and node-set), and allows to convert between them.
-//
-// These parts are near the top of the file, the functions and data
-// that are used internally follow after them.
-//
-//
-// TODO(mesch): add jsdoc comments. Use more coherent naming.
-//
-//
-// Author: Steffen Meschkat &lt;mesch@google.com&gt;
-
-
-// The entry point for the parser.
-//
-// @param expr a string that contains an XPath expression.
-// @return an expression object that can be evaluated with an
-// expression context.
-
-function xpathParse(expr) {
-  if (xpathdebug) {
-    Log.write('XPath parse ' + expr);
-  }
-  xpathParseInit();
-
-  var cached = xpathCacheLookup(expr);
-  if (cached) {
-    if (xpathdebug) {
-      Log.write(' ... cached');
-    }
-    return cached;
-  }
-
-  // Optimize for a few common cases: simple attribute node tests
-  // (@id), simple element node tests (page), variable references
-  // ($address), numbers (4), multi-step path expressions where each
-  // step is a plain element node test
-  // (page/overlay/locations/location).
-  
-  if (expr.match(/^(\$|@)?\w+$/i)) {
-    var ret = makeSimpleExpr(expr);
-    xpathParseCache[expr] = ret;
-    if (xpathdebug) {
-      Log.write(' ... simple');
-    }
-    return ret;
-  }
-
-  if (expr.match(/^\w+(\/\w+)*$/i)) {
-    var ret = makeSimpleExpr2(expr);
-    xpathParseCache[expr] = ret;
-    if (xpathdebug) {
-      Log.write(' ... simple 2');
-    }
-    return ret;
-  }
-
-  var cachekey = expr; // expr is modified during parse
-  if (xpathdebug) {
-    Timer.start('XPath parse', cachekey);
-  }
-
-  var stack = [];
-  var ahead = null;
-  var previous = null;
-  var done = false;
-
-  var parse_count = 0;
-  var lexer_count = 0;
-  var reduce_count = 0;
-  
-  while (!done) {
-    parse_count++;
-    expr = expr.replace(/^\s*/, '');
-    previous = ahead;
-    ahead = null;
-
-    var rule = null;
-    var match = '';
-    for (var i = 0; i &lt; xpathTokenRules.length; ++i) {
-      var result = xpathTokenRules[i].re.exec(expr);
-      lexer_count++;
-      if (result &amp;&amp; result.length &gt; 0 &amp;&amp; result[0].length &gt; match.length) {
-        rule = xpathTokenRules[i];
-        match = result[0];
-        break;
-      }
-    }
-
-    // Special case: allow operator keywords to be element and
-    // variable names.
-
-    // NOTE(mesch): The parser resolves conflicts by looking ahead,
-    // and this is the only case where we look back to
-    // disambiguate. So this is indeed something different, and
-    // looking back is usually done in the lexer (via states in the
-    // general case, called &quot;start conditions&quot; in flex(1)). Also,the
-    // conflict resolution in the parser is not as robust as it could
-    // be, so I'd like to keep as much off the parser as possible (all
-    // these precedence values should be computed from the grammar
-    // rules and possibly associativity declarations, as in bison(1),
-    // and not explicitly set.
-
-    if (rule &amp;&amp;
-        (rule == TOK_DIV || 
-         rule == TOK_MOD ||
-         rule == TOK_AND || 
-         rule == TOK_OR) &amp;&amp;
-        (!previous || 
-         previous.tag == TOK_AT || 
-         previous.tag == TOK_DSLASH || 
-         previous.tag == TOK_SLASH ||
-         previous.tag == TOK_AXIS || 
-         previous.tag == TOK_DOLLAR)) {
-      rule = TOK_QNAME;
-    }
-
-    if (rule) {
-      expr = expr.substr(match.length);
-      if (xpathdebug) {
-        Log.write('token: ' + match + ' -- ' + rule.label);
-      }
-      ahead = {
-        tag: rule,
-        match: match,
-        prec: rule.prec ?  rule.prec : 0, // || 0 is removed by the compiler
-        expr: makeTokenExpr(match)
-      };
-
-    } else {
-      if (xpathdebug) {
-        Log.write('DONE');
-      }
-      done = true;
-    }
-
-    while (xpathReduce(stack, ahead)) {
-      reduce_count++;
-      if (xpathdebug) {
-        Log.write('stack: ' + stackToString(stack));
-      }
-    }
-  }
-
-  if (xpathdebug) {
-    Log.write(stackToString(stack));
-  }
-
-  if (stack.length != 1) {
-    throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);
-  }
-
-  var result = stack[0].expr;
-  xpathParseCache[cachekey] = result;
-
-  if (xpathdebug) {
-    Timer.end('XPath parse', cachekey);
-  }
-
-  if (xpathdebug) {
-    Log.write('XPath parse: ' + parse_count + ' / ' + 
-              lexer_count + ' / ' + reduce_count);
-  }
-
-  return result;
-}
-
-var xpathParseCache = {};
-
-function xpathCacheLookup(expr) {
-  return xpathParseCache[expr];
-}
-
-function xpathReduce(stack, ahead) {
-  var cand = null;
-
-  if (stack.length &gt; 0) {
-    var top = stack[stack.length-1];
-    var ruleset = xpathRules[top.tag.key];
-
-    if (ruleset) {
-      for (var i = 0; i &lt; ruleset.length; ++i) {
-        var rule = ruleset[i];
-        var match = xpathMatchStack(stack, rule[1]);
-        if (match.length) {
-          cand = {
-            tag: rule[0],
-            rule: rule,
-            match: match
-          };
-          cand.prec = xpathGrammarPrecedence(cand);
-          break;
-        }
-      }
-    }
-  }
-
-  var ret;
-  if (cand &amp;&amp; (!ahead || cand.prec &gt; ahead.prec || 
-               (ahead.tag.left &amp;&amp; cand.prec &gt;= ahead.prec))) {
-    for (var i = 0; i &lt; cand.match.matchlength; ++i) {
-      stack.pop();
-    }
-
-    if (xpathdebug) {
-      Log.write('reduce ' + cand.tag.label + ' ' + cand.prec +
-                ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec + 
-                             (ahead.tag.left ? ' left' : '')
-                             : ' none '));
-    }
-
-    var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
-    cand.expr = cand.rule[3].apply(null, matchexpr);
-
-    stack.push(cand);
-    ret = true;
-
-  } else {
-    if (ahead) {
-      if (xpathdebug) {
-        Log.write('shift ' + ahead.tag.label + ' ' + ahead.prec + 
-                  (ahead.tag.left ? ' left' : '') +
-                  ' over ' + (cand ? cand.tag.label + ' ' + 
-                              cand.prec : ' none'));
-      }
-      stack.push(ahead);
-    }
-    ret = false;
-  }
-  return ret;
-}
-
-function xpathMatchStack(stack, pattern) {
-
-  // NOTE(mesch): The stack matches for variable cardinality are
-  // greedy but don't do backtracking. This would be an issue only
-  // with rules of the form A* A, i.e. with an element with variable
-  // cardinality followed by the same element. Since that doesn't
-  // occur in the grammar at hand, all matches on the stack are
-  // unambiguous.
-
-  var S = stack.length;
-  var P = pattern.length;
-  var p, s;
-  var match = [];
-  match.matchlength = 0;
-  var ds = 0;
-  for (p = P - 1, s = S - 1; p &gt;= 0 &amp;&amp; s &gt;= 0; --p, s -= ds) {
-    ds = 0;
-    var qmatch = [];
-    if (pattern[p] == Q_MM) {
-      p -= 1;
-      match.push(qmatch);
-      while (s - ds &gt;= 0 &amp;&amp; stack[s - ds].tag == pattern[p]) {
-        qmatch.push(stack[s - ds]);
-        ds += 1;
-        match.matchlength += 1;
-      }
-
-    } else if (pattern[p] == Q_01) {
-      p -= 1;
-      match.push(qmatch);
-      while (s - ds &gt;= 0 &amp;&amp; ds &lt; 2 &amp;&amp; stack[s - ds].tag == pattern[p]) {
-        qmatch.push(stack[s - ds]);
-        ds += 1;
-        match.matchlength += 1;
-      }
-
-    } else if (pattern[p] == Q_1M) {
-      p -= 1;
-      match.push(qmatch);
-      if (stack[s].tag == pattern[p]) {
-        while (s - ds &gt;= 0 &amp;&amp; stack[s - ds].tag == pattern[p]) {
-          qmatch.push(stack[s - ds]);
-          ds += 1;
-          match.matchlength += 1;
-        }
-      } else {
-        return [];
-      }
-
-    } else if (stack[s].tag == pattern[p]) {
-      match.push(stack[s]);
-      ds += 1;
-      match.matchlength += 1;
-
-    } else {
-      return [];
-    }
-
-    reverseInplace(qmatch);
-    qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });
-  }
-
-  reverseInplace(match);
-
-  if (p == -1) {
-    return match;
-
-  } else {
-    return [];
-  }
-}
-
-function xpathTokenPrecedence(tag) {
-  return tag.prec || 2;
-}
-
-function xpathGrammarPrecedence(frame) {
-  var ret = 0;
-
-  if (frame.rule) { /* normal reduce */
-    if (frame.rule.length &gt;= 3 &amp;&amp; frame.rule[2] &gt;= 0) {
-      ret = frame.rule[2];
-
-    } else {
-      for (var i = 0; i &lt; frame.rule[1].length; ++i) {
-        var p = xpathTokenPrecedence(frame.rule[1][i]);
-        ret = Math.max(ret, p);
-      }
-    }
-  } else if (frame.tag) { /* TOKEN match */
-    ret = xpathTokenPrecedence(frame.tag);
-
-  } else if (frame.length) { /* Q_ match */
-    for (var j = 0; j &lt; frame.length; ++j) {
-      var p = xpathGrammarPrecedence(frame[j]);
-      ret = Math.max(ret, p);
-    }
-  }
-
-  return ret;
-}
-
-function stackToString(stack) {
-  var ret = '';
-  for (var i = 0; i &lt; stack.length; ++i) {
-    if (ret) {
-      ret += '\n';
-    }
-    ret += stack[i].tag.label;
-  }
-  return ret;
-}
-
-
-// XPath expression evaluation context. An XPath context consists of a
-// DOM node, a list of DOM nodes that contains this node, a number
-// that represents the position of the single node in the list, and a
-// current set of variable bindings. (See XPath spec.)
-//
-// The interface of the expression context:
-//
-//   Constructor -- gets the node, its position, the node set it
-//   belongs to, and a parent context as arguments. The parent context
-//   is used to implement scoping rules for variables: if a variable
-//   is not found in the current context, it is looked for in the
-//   parent context, recursively. Except for node, all arguments have
-//   default values: default position is 0, default node set is the
-//   set that contains only the node, and the default parent is null.
-//
-//     Notice that position starts at 0 at the outside interface;
-//     inside XPath expressions this shows up as position()=1.
-//
-//   clone() -- creates a new context with the current context as
-//   parent. If passed as argument to clone(), the new context has a
-//   different node, position, or node set. What is not passed is
-//   inherited from the cloned context.
-//
-//   setVariable(name, expr) -- binds given XPath expression to the
-//   name.
-//
-//   getVariable(name) -- what the name says.
-//
-//   setNode(node, position) -- sets the context to the new node and
-//   its corresponding position. Needed to implement scoping rules for
-//   variables in XPath. (A variable is visible to all subsequent
-//   siblings, not only to its children.)
-
-function ExprContext(node, position, nodelist, parent) {
-  this.node = node;
-  this.position = position || 0;
-  this.nodelist = nodelist || [ node ];
-  this.variables = {};
-  this.parent = parent || null;
-  this.root = parent ? parent.root : node.ownerDocument;
-}
-
-ExprContext.prototype.clone = function(node, position, nodelist) {
-  return new
-  ExprContext(node || this.node,
-              typeof position != 'undefined' ? position : this.position,
-              nodelist || this.nodelist, this);
-};
-
-ExprContext.prototype.setVariable = function(name, value) {
-  this.variables[name] = value;
-};
-
-ExprContext.prototype.getVariable = function(name) {
-  if (typeof this.variables[name] != 'undefined') {
-    return this.variables[name];
-
-  } else if (this.parent) {
-    return this.parent.getVariable(name);
-
-  } else {
-    return null;
-  }
-}
-
-ExprContext.prototype.setNode = function(node, position) {
-  this.node = node;
-  this.position = position;
-}
-
-
-// XPath expression values. They are what XPath expressions evaluate
-// to. Strangely, the different value types are not specified in the
-// XPath syntax, but only in the semantics, so they don't show up as
-// nonterminals in the grammar. Yet, some expressions are required to
-// evaluate to particular types, and not every type can be coerced
-// into every other type. Although the types of XPath values are
-// similar to the types present in JavaScript, the type coercion rules
-// are a bit peculiar, so we explicitly model XPath types instead of
-// mapping them onto JavaScript types. (See XPath spec.)
-//
-// The four types are:
-//
-//   StringValue
-//
-//   NumberValue
-//
-//   BooleanValue
-//
-//   NodeSetValue
-//
-// The common interface of the value classes consists of methods that
-// implement the XPath type coercion rules:
-//
-//   stringValue() -- returns the value as a JavaScript String,
-//
-//   numberValue() -- returns the value as a JavaScript Number,
-//
-//   booleanValue() -- returns the value as a JavaScript Boolean,
-//
-//   nodeSetValue() -- returns the value as a JavaScript Array of DOM
-//   Node objects.
-//
-
-function StringValue(value) {
-  this.value = value;
-  this.type = 'string';
-}
-
-StringValue.prototype.stringValue = function() {
-  return this.value;
-}
-
-StringValue.prototype.booleanValue = function() {
-  return this.value.length &gt; 0;
-}
-
-StringValue.prototype.numberValue = function() {
-  return this.value - 0;
-}
-
-StringValue.prototype.nodeSetValue = function() {
-  throw this + ' ' + Error().stack;
-}
-
-function BooleanValue(value) {
-  this.value = value;
-  this.type = 'boolean';
-}
-
-BooleanValue.prototype.stringValue = function() {
-  return '' + this.value;
-}
-
-BooleanValue.prototype.booleanValue = function() {
-  return this.value;
-}
-
-BooleanValue.prototype.numberValue = function() {
-  return this.value ? 1 : 0;
-}
-
-BooleanValue.prototype.nodeSetValue = function() {
-  throw this + ' ' + Error().stack;
-}
-
-function NumberValue(value) {
-  this.value = value;
-  this.type = 'number';
-}
-
-NumberValue.prototype.stringValue = function() {
-  return '' + this.value;
-}
-
-NumberValue.prototype.booleanValue = function() {
-  return !!this.value;
-}
-
-NumberValue.prototype.numberValue = function() {
-  return this.value - 0;
-}
-
-NumberValue.prototype.nodeSetValue = function() {
-  throw this + ' ' + Error().stack;
-}
-
-function NodeSetValue(value) {
-  this.value = value;
-  this.type = 'node-set';
-}
-
-NodeSetValue.prototype.stringValue = function() {
-  if (this.value.length == 0) {
-    return '';
-  } else {
-    return xmlValue(this.value[0]);
-  }
-}
-
-NodeSetValue.prototype.booleanValue = function() {
-  return this.value.length &gt; 0;
-}
-
-NodeSetValue.prototype.numberValue = function() {
-  return this.stringValue() - 0;
-}
-
-NodeSetValue.prototype.nodeSetValue = function() {
-  return this.value;
-};
-
-// XPath expressions. They are used as nodes in the parse tree and
-// possess an evaluate() method to compute an XPath value given an XPath
-// context. Expressions are returned from the parser. Teh set of
-// expression classes closely mirrors the set of non terminal symbols
-// in the grammar. Every non trivial nonterminal symbol has a
-// corresponding expression class.
-//
-// The common expression interface consists of the following methods:
-//
-// evaluate(context) -- evaluates the expression, returns a value.
-//
-// toString() -- returns the XPath text representation of the
-// expression (defined in xsltdebug.js).
-//
-// parseTree(indent) -- returns a parse tree representation of the
-// expression (defined in xsltdebug.js).
-
-function TokenExpr(m) {
-  this.value = m;
-}
-
-TokenExpr.prototype.evaluate = function() {
-  return new StringValue(this.value);
-};
-
-function LocationExpr() {
-  this.absolute = false;
-  this.steps = [];
-}
-
-LocationExpr.prototype.appendStep = function(s) {
-  this.steps.push(s);
-}
-
-LocationExpr.prototype.prependStep = function(s) {
-  var steps0 = this.steps;
-  this.steps = [ s ];
-  for (var i = 0; i &lt; steps0.length; ++i) {
-    this.steps.push(steps0[i]);
-  }
-};
-
-LocationExpr.prototype.evaluate = function(ctx) {
-  var start;
-  if (this.absolute) {
-    start = ctx.root;
-
-  } else {
-    start = ctx.node;
-  }
-
-  var nodes = [];
-  xPathStep(nodes, this.steps, 0, start, ctx);
-  return new NodeSetValue(nodes);
-};
-
-function xPathStep(nodes, steps, step, input, ctx) {
-  var s = steps[step];
-  var ctx2 = ctx.clone(input);
-  var nodelist = s.evaluate(ctx2).nodeSetValue();
-
-  for (var i = 0; i &lt; nodelist.length; ++i) {
-    if (step == steps.length - 1) {
-      nodes.push(nodelist[i]);
-    } else {
-      xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
-    }
-  }
-}
-
-function StepExpr(axis, nodetest, predicate) {
-  this.axis = axis;
-  this.nodetest = nodetest;
-  this.predicate = predicate || [];
-}
-
-StepExpr.prototype.appendPredicate = function(p) {
-  this.predicate.push(p);
-}
-
-StepExpr.prototype.evaluate = function(ctx) {
-  var input = ctx.node;
-  var nodelist = [];
-
-  // NOTE(mesch): When this was a switch() statement, it didn't work
-  // in Safari/2.0. Not sure why though; it resulted in the JavaScript
-  // console output &quot;undefined&quot; (without any line number or so).
-
-  if (this.axis ==  xpathAxis.ANCESTOR_OR_SELF) {
-    nodelist.push(input);
-    for (var n = input.parentNode; n; n = input.parentNode) {
-      nodelist.push(n);
-    }
-
-  } else if (this.axis == xpathAxis.ANCESTOR) {
-    for (var n = input.parentNode; n; n = input.parentNode) {
-      nodelist.push(n);
-    }
-
-  } else if (this.axis == xpathAxis.ATTRIBUTE) {
-    copyArray(nodelist, input.attributes);
-
-  } else if (this.axis == xpathAxis.CHILD) {
-    copyArray(nodelist, input.childNodes);
-
-  } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
-    nodelist.push(input);
-    xpathCollectDescendants(nodelist, input);
-
-  } else if (this.axis == xpathAxis.DESCENDANT) {
-    xpathCollectDescendants(nodelist, input);
-
-  } else if (this.axis == xpathAxis.FOLLOWING) {
-    for (var n = input.parentNode; n; n = n.parentNode) {
-      for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
-        nodelist.push(nn);
-        xpathCollectDescendants(nodelist, nn);
-      }
-    }
-
-  } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {
-    for (var n = input.nextSibling; n; n = input.nextSibling) {
-      nodelist.push(n);
-    }
-
-  } else if (this.axis == xpathAxis.NAMESPACE) {
-    alert('not implemented: axis namespace');
-
-  } else if (this.axis == xpathAxis.PARENT) {
-    if (input.parentNode) {
-      nodelist.push(input.parentNode);
-    }
-
-  } else if (this.axis == xpathAxis.PRECEDING) {
-    for (var n = input.parentNode; n; n = n.parentNode) {
-      for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
-        nodelist.push(nn);
-        xpathCollectDescendantsReverse(nodelist, nn);
-      }
-    }
-
-  } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {
-    for (var n = input.previousSibling; n; n = input.previousSibling) {
-      nodelist.push(n);
-    }
-
-  } else if (this.axis == xpathAxis.SELF) {
-    nodelist.push(input);
-
-  } else {
-    throw 'ERROR -- NO SUCH AXIS: ' + this.axis;
-  }
-
-  // process node test
-  var nodelist0 = nodelist;
-  nodelist = [];
-  for (var i = 0; i &lt; nodelist0.length; ++i) {
-    var n = nodelist0[i];
-    if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {
-      nodelist.push(n);
-    }
-  }
-
-  // process predicates
-  for (var i = 0; i &lt; this.predicate.length; ++i) {
-    var nodelist0 = nodelist;
-    nodelist = [];
-    for (var ii = 0; ii &lt; nodelist0.length; ++ii) {
-      var n = nodelist0[ii];
-      if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {
-        nodelist.push(n);
-      }
-    }
-  }
-
-  return new NodeSetValue(nodelist);
-};
-
-function NodeTestAny() {
-  this.value = new BooleanValue(true);
-}
-
-NodeTestAny.prototype.evaluate = function(ctx) {
-  return this.value;
-};
-
-function NodeTestElement() {}
-
-NodeTestElement.prototype.evaluate = function(ctx) {
-  return new BooleanValue(ctx.node.nodeType == DOM_ELEMENT_NODE);
-}
-
-function NodeTestText() {}
-
-NodeTestText.prototype.evaluate = function(ctx) {
-  return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);
-}
-
-function NodeTestComment() {}
-
-NodeTestComment.prototype.evaluate = function(ctx) {
-  return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);
-}
-
-function NodeTestPI(target) {
-  this.target = target;
-}
-
-NodeTestPI.prototype.evaluate = function(ctx) {
-  return new
-  BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &amp;&amp;
-               (!this.target || ctx.node.nodeName == this.target));
-}
-
-function NodeTestNC(nsprefix) {
-  this.regex = new RegExp(&quot;^&quot; + nsprefix + &quot;:&quot;);
-  this.nsprefix = nsprefix;
-}
-
-NodeTestNC.prototype.evaluate = function(ctx) {
-  var n = ctx.node;
-  return new BooleanValue(this.regex.match(n.nodeName));
-}
-
-function NodeTestName(name) {
-  this.name = name;
-}
-
-NodeTestName.prototype.evaluate = function(ctx) {
-  var n = ctx.node;
-  // NOTE (Patrick Lightbody): this change allows node selection to be case-insensitive
-  return new BooleanValue(n.nodeName.toUpperCase() == this.name.toUpperCase());
-}
-
-function PredicateExpr(expr) {
-  this.expr = expr;
-}
-
-PredicateExpr.prototype.evaluate = function(ctx) {
-  var v = this.expr.evaluate(ctx);
-  if (v.type == 'number') {
-    // NOTE(mesch): Internally, position is represented starting with
-    // 0, however in XPath position starts with 1. See functions
-    // position() and last().
-    return new BooleanValue(ctx.position == v.numberValue() - 1);
-  } else {
-    return new BooleanValue(v.booleanValue());
-  }
-};
-
-function FunctionCallExpr(name) {
-  this.name = name;
-  this.args = [];
-}
-
-FunctionCallExpr.prototype.appendArg = function(arg) {
-  this.args.push(arg);
-};
-
-FunctionCallExpr.prototype.evaluate = function(ctx) {
-  var fn = '' + this.name.value;
-  var f = this.xpathfunctions[fn];
-  if (f) {
-    return f.call(this, ctx);
-  } else {
-    Log.write('XPath NO SUCH FUNCTION ' + fn);
-    return new BooleanValue(false);
-  }
-};
-
-FunctionCallExpr.prototype.xpathfunctions = {
-  'last': function(ctx) {
-    assert(this.args.length == 0);
-    // NOTE(mesch): XPath position starts at 1.
-    return new NumberValue(ctx.nodelist.length);
-  },
-
-  'position': function(ctx) {
-    assert(this.args.length == 0);
-    // NOTE(mesch): XPath position starts at 1.
-    return new NumberValue(ctx.position + 1);
-  },
-
-  'count': function(ctx) {
-    assert(this.args.length == 1);
-    var v = this.args[0].evaluate(ctx);
-    return new NumberValue(v.nodeSetValue().length);
-  },
-
-  'id': function(ctx) {
-    assert(this.args.length == 1);
-    var e = this.args.evaluate(ctx);
-    var ret = [];
-    var ids;
-    if (e.type == 'node-set') {
-      ids = [];
-      for (var i = 0; i &lt; e.length; ++i) {
-        var v = xmlValue(e[i]).split(/\s+/);
-        for (var ii = 0; ii &lt; v.length; ++ii) {
-          ids.push(v[ii]);
-        }
-      }
-    } else {
-      ids = e.split(/\s+/);
-    }
-    var d = ctx.node.ownerDocument;
-    for (var i = 0; i &lt; ids.length; ++i) {
-      var n = d.getElementById(ids[i]);
-      if (n) {
-        ret.push(n);
-      }
-    }
-    return new NodeSetValue(ret);
-  },
-
-  'local-name': function(ctx) {
-    alert('not implmented yet: XPath function local-name()');
-  },
-
-  'namespace-uri': function(ctx) {
-    alert('not implmented yet: XPath function namespace-uri()');
-  },
-
-  'name': function(ctx) {
-    assert(this.args.length == 1 || this.args.length == 0);
-    var n;
-    if (this.args.length == 0) {
-      n = [ ctx.node ];
-    } else {
-      n = this.args[0].evaluate(ctx).nodeSetValue();
-    }
-
-    if (n.length == 0) {
-      return new StringValue('');
-    } else {
-      return new StringValue(n[0].nodeName);
-    }
-  },
-
-  'string':  function(ctx) {
-    assert(this.args.length == 1 || this.args.length == 0);
-    if (this.args.length == 0) {
-      return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());
-    } else {
-      return new StringValue(this.args[0].evaluate(ctx).stringValue());
-    }
-  },
-
-  'concat': function(ctx) {
-    var ret = '';
-    for (var i = 0; i &lt; this.args.length; ++i) {
-      ret += this.args[i].evaluate(ctx).stringValue();
-    }
-    return new StringValue(ret);
-  },
-
-  'starts-with': function(ctx) {
-    assert(this.args.length == 2);
-    var s0 = this.args[0].evaluate(ctx).stringValue();
-    var s1 = this.args[1].evaluate(ctx).stringValue();
-    return new BooleanValue(s0.indexOf(s1) == 0);
-  },
-
-  'contains': function(ctx) {
-    assert(this.args.length == 2);
-    var s0 = this.args[0].evaluate(ctx).stringValue();
-    var s1 = this.args[1].evaluate(ctx).stringValue();
-    return new BooleanValue(s0.indexOf(s1) != -1);
-  },
-
-  'substring-before': function(ctx) {
-    assert(this.args.length == 2);
-    var s0 = this.args[0].evaluate(ctx).stringValue();
-    var s1 = this.args[1].evaluate(ctx).stringValue();
-    var i = s0.indexOf(s1);
-    var ret;
-    if (i == -1) {
-      ret = '';
-    } else {
-      ret = s0.substr(0,i);
-    }
-    return new StringValue(ret);
-  },
-
-  'substring-after': function(ctx) {
-    assert(this.args.length == 2);
-    var s0 = this.args[0].evaluate(ctx).stringValue();
-    var s1 = this.args[1].evaluate(ctx).stringValue();
-    var i = s0.indexOf(s1);
-    var ret;
-    if (i == -1) {
-      ret = '';
-    } else {
-      ret = s0.substr(i + s1.length);
-    }
-    return new StringValue(ret);
-  },
-
-  'substring': function(ctx) {
-    // NOTE: XPath defines the position of the first character in a
-    // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).
-    assert(this.args.length == 2 || this.args.length == 3);
-    var s0 = this.args[0].evaluate(ctx).stringValue();
-    var s1 = this.args[1].evaluate(ctx).numberValue();
-    var ret;
-    if (this.args.length == 2) {
-      var i1 = Math.max(0, Math.round(s1) - 1);
-      ret = s0.substr(i1);
-
-    } else {
-      var s2 = this.args[2].evaluate(ctx).numberValue();
-      var i0 = Math.round(s1) - 1;
-      var i1 = Math.max(0, i0);
-      var i2 = Math.round(s2) - Math.max(0, -i0);
-      ret = s0.substr(i1, i2);
-    }
-    return new StringValue(ret);
-  },
-
-  'string-length': function(ctx) {
-    var s;
-    if (this.args.length &gt; 0) {
-      s = this.args[0].evaluate(ctx).stringValue();
-    } else {
-      s = new NodeSetValue([ ctx.node ]).stringValue();
-    }
-    return new NumberValue(s.length);
-  },
-
-  'normalize-space': function(ctx) {
-    var s;
-    if (this.args.length &gt; 0) {
-      s = this.args[0].evaluate(ctx).stringValue();
-    } else {
-      s = new NodeSetValue([ ctx.node ]).stringValue();
-    }
-    s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');
-    return new StringValue(s);
-  },
-
-  'translate': function(ctx) {
-    assert(this.args.length == 3);
-    var s0 = this.args[0].evaluate(ctx).stringValue();
-    var s1 = this.args[1].evaluate(ctx).stringValue();
-    var s2 = this.args[2].evaluate(ctx).stringValue();
-
-    for (var i = 0; i &lt; s1.length; ++i) {
-      s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));
-    }
-    return new StringValue(s0);
-  },
-
-  'boolean': function(ctx) {
-    assert(this.args.length == 1);
-    return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
-  },
-
-  'not': function(ctx) {
-    assert(this.args.length == 1);
-    var ret = !this.args[0].evaluate(ctx).booleanValue();
-    return new BooleanValue(ret);
-  },
-
-  'true': function(ctx) {
-    assert(this.args.length == 0);
-    return new BooleanValue(true);
-  },
-
-  'false': function(ctx) {
-    assert(this.args.length == 0);
-    return new BooleanValue(false);
-  },
-
-  'lang': function(ctx) {
-    assert(this.args.length == 1);
-    var lang = this.args[0].evaluate(ctx).stringValue();
-    var xmllang;
-    var n = ctx.node;
-    while (n &amp;&amp; n != n.parentNode /* just in case ... */) {
-      xmllang = n.getAttribute('xml:lang');
-      if (xmllang) {
-        break;
-      }
-      n = n.parentNode;
-    }
-    if (!xmllang) {
-      return new BooleanValue(false);
-    } else {
-      var re = new RegExp('^' + lang + '$', 'i');
-      return new BooleanValue(xmllang.match(re) ||
-                              xmllang.replace(/_.*$/,'').match(re));
-    }
-  },
-
-  'number': function(ctx) {
-    assert(this.args.length == 1 || this.args.length == 0);
-
-    if (this.args.length == 1) {
-      return new NumberValue(this.args[0].evaluate(ctx).numberValue());
-    } else {
-      return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());
-    }
-  },
-
-  'sum': function(ctx) {
-    assert(this.args.length == 1);
-    var n = this.args[0].evaluate(ctx).nodeSetValue();
-    var sum = 0;
-    for (var i = 0; i &lt; n.length; ++i) {
-      sum += xmlValue(n[i]) - 0;
-    }
-    return new NumberValue(sum);
-  },
-
-  'floor': function(ctx) {
-    assert(this.args.length == 1);
-    var num = this.args[0].evaluate(ctx).numberValue();
-    return new NumberValue(Math.floor(num));
-  },
-
-  'ceiling': function(ctx) {
-    assert(this.args.length == 1);
-    var num = this.args[0].evaluate(ctx).numberValue();
-    return new NumberValue(Math.ceil(num));
-  },
-
-  'round': function(ctx) {
-    assert(this.args.length == 1);
-    var num = this.args[0].evaluate(ctx).numberValue();
-    return new NumberValue(Math.round(num));
-  },
-
-  // TODO(mesch): The following functions are custom. There is a
-  // standard that defines how to add functions, which should be
-  // applied here.
-
-  'ext-join': function(ctx) {
-    assert(this.args.length == 2);
-    var nodes = this.args[0].evaluate(ctx).nodeSetValue();
-    var delim = this.args[1].evaluate(ctx).stringValue();
-    var ret = '';
-    for (var i = 0; i &lt; nodes.length; ++i) {
-      if (ret) {
-        ret += delim;
-      }
-      ret += xmlValue(nodes[i]);
-    }
-    return new StringValue(ret);
-  },
-
-  // ext-if() evaluates and returns its second argument, if the
-  // boolean value of its first argument is true, otherwise it
-  // evaluates and returns its third argument.
-
-  'ext-if': function(ctx) {
-    assert(this.args.length == 3);
-    if (this.args[0].evaluate(ctx).booleanValue()) {
-      return this.args[1].evaluate(ctx);
-    } else {
-      return this.args[2].evaluate(ctx);
-    }
-  },
-
-  'ext-sprintf': function(ctx) {
-    assert(this.args.length &gt;= 1);
-    var args = [];
-    for (var i = 0; i &lt; this.args.length; ++i) {
-      args.push(this.args[i].evaluate(ctx).stringValue());
-    }
-    return new StringValue(sprintf.apply(null, args));
-  },
-
-  // ext-cardinal() evaluates its single argument as a number, and
-  // returns the current node that many times. It can be used in the
-  // select attribute to iterate over an integer range.
-  
-  'ext-cardinal': function(ctx) {
-    assert(this.args.length &gt;= 1);
-    var c = this.args[0].evaluate(ctx).numberValue();
-    var ret = [];
-    for (var i = 0; i &lt; c; ++i) {
-      ret.push(ctx.node);
-    }
-    return new NodeSetValue(ret);
-  }
-};
-
-function UnionExpr(expr1, expr2) {
-  this.expr1 = expr1;
-  this.expr2 = expr2;
-}
-
-UnionExpr.prototype.evaluate = function(ctx) {
-  var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();
-  var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();
-  var I1 = nodes1.length;
-  for (var i2 = 0; i2 &lt; nodes2.length; ++i2) {
-    for (var i1 = 0; i1 &lt; I1; ++i1) {
-      if (nodes1[i1] == nodes2[i2]) {
-        // break inner loop and continue outer loop, labels confuse
-        // the js compiler, so we don't use them here.
-        i1 = I1;
-      }
-    }
-    nodes1.push(nodes2[i2]);
-  }
-  return new NodeSetValue(nodes2);
-};
-
-function PathExpr(filter, rel) {
-  this.filter = filter;
-  this.rel = rel;
-}
-
-PathExpr.prototype.evaluate = function(ctx) {
-  var nodes = this.filter.evaluate(ctx).nodeSetValue();
-  var nodes1 = [];
-  for (var i = 0; i &lt; nodes.length; ++i) {
-    var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
-    for (var ii = 0; ii &lt; nodes0.length; ++ii) {
-      nodes1.push(nodes0[ii]);
-    }
-  }
-  return new NodeSetValue(nodes1);
-};
-
-function FilterExpr(expr, predicate) {
-  this.expr = expr;
-  this.predicate = predicate;
-}
-
-FilterExpr.prototype.evaluate = function(ctx) {
-  var nodes = this.expr.evaluate(ctx).nodeSetValue();
-  for (var i = 0; i &lt; this.predicate.length; ++i) {
-    var nodes0 = nodes;
-    nodes = [];
-    for (var j = 0; j &lt; nodes0.length; ++j) {
-      var n = nodes0[j];
-      if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {
-        nodes.push(n);
-      }
-    }
-  }
-
-  return new NodeSetValue(nodes);
-}
-
-function UnaryMinusExpr(expr) {
-  this.expr = expr;
-}
-
-UnaryMinusExpr.prototype.evaluate = function(ctx) {
-  return new NumberValue(-this.expr.evaluate(ctx).numberValue());
-};
-
-function BinaryExpr(expr1, op, expr2) {
-  this.expr1 = expr1;
-  this.expr2 = expr2;
-  this.op = op;
-}
-
-BinaryExpr.prototype.evaluate = function(ctx) {
-  var ret;
-  switch (this.op.value) {
-    case 'or':
-      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||
-                             this.expr2.evaluate(ctx).booleanValue());
-      break;
-
-    case 'and':
-      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &amp;&amp;
-                             this.expr2.evaluate(ctx).booleanValue());
-      break;
-
-    case '+':
-      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +
-                            this.expr2.evaluate(ctx).numberValue());
-      break;
-
-    case '-':
-      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -
-                            this.expr2.evaluate(ctx).numberValue());
-      break;
-
-    case '*':
-      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *
-                            this.expr2.evaluate(ctx).numberValue());
-      break;
-
-    case 'mod':
-      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %
-                            this.expr2.evaluate(ctx).numberValue());
-      break;
-
-    case 'div':
-      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /
-                            this.expr2.evaluate(ctx).numberValue());
-      break;
-
-    case '=':
-      ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });
-      break;
-
-    case '!=':
-      ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });
-      break;
-
-    case '&lt;':
-      ret = this.compare(ctx, function(x1, x2) { return x1 &lt; x2; });
-      break;
-
-    case '&lt;=':
-      ret = this.compare(ctx, function(x1, x2) { return x1 &lt;= x2; });
-      break;
-
-    case '&gt;':
-      ret = this.compare(ctx, function(x1, x2) { return x1 &gt; x2; });
-      break;
-
-    case '&gt;=':
-      ret = this.compare(ctx, function(x1, x2) { return x1 &gt;= x2; });
-      break;
-
-    default:
-      alert('BinaryExpr.evaluate: ' + this.op.value);
-  }
-  return ret;
-};
-
-BinaryExpr.prototype.compare = function(ctx, cmp) {
-  var v1 = this.expr1.evaluate(ctx);
-  var v2 = this.expr2.evaluate(ctx);
-
-  var ret;
-  if (v1.type == 'node-set' &amp;&amp; v2.type == 'node-set') {
-    var n1 = v1.nodeSetValue();
-    var n2 = v2.nodeSetValue();
-    ret = false;
-    for (var i1 = 0; i1 &lt; n1.length; ++i1) {
-      for (var i2 = 0; i2 &lt; n2.length; ++i2) {
-        if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {
-          ret = true;
-          // Break outer loop. Labels confuse the jscompiler and we
-          // don't use them.
-          i2 = n2.length;
-          i1 = n1.length;
-        }
-      }
-    }
-
-  } else if (v1.type == 'node-set' || v2.type == 'node-set') {
-
-    if (v1.type == 'number') {
-      var s = v1.numberValue();
-      var n = v2.nodeSetValue();
-
-      ret = false;
-      for (var i = 0;  i &lt; n.length; ++i) {
-        var nn = xmlValue(n[i]) - 0;
-        if (cmp(s, nn)) {
-          ret = true;
-          break;
-        }
-      }
-
-    } else if (v2.type == 'number') {
-      var n = v1.nodeSetValue();
-      var s = v2.numberValue();
-
-      ret = false;
-      for (var i = 0;  i &lt; n.length; ++i) {
-        var nn = xmlValue(n[i]) - 0;
-        if (cmp(nn, s)) {
-          ret = true;
-          break;
-        }
-      }
-
-    } else if (v1.type == 'string') {
-      var s = v1.stringValue();
-      var n = v2.nodeSetValue();
-
-      ret = false;
-      for (var i = 0;  i &lt; n.length; ++i) {
-        var nn = xmlValue(n[i]);
-        if (cmp(s, nn)) {
-          ret = true;
-          break;
-        }
-      }
-
-    } else if (v2.type == 'string') {
-      var n = v1.nodeSetValue();
-      var s = v2.stringValue();
-
-      ret = false;
-      for (var i = 0;  i &lt; n.length; ++i) {
-        var nn = xmlValue(n[i]);
-        if (cmp(nn, s)) {
-          ret = true;
-          break;
-        }
-      }
-
-    } else {
-      ret = cmp(v1.booleanValue(), v2.booleanValue());
-    }
-
-  } else if (v1.type == 'boolean' || v2.type == 'boolean') {
-    ret = cmp(v1.booleanValue(), v2.booleanValue());
-
-  } else if (v1.type == 'number' || v2.type == 'number') {
-    ret = cmp(v1.numberValue(), v2.numberValue());
-
-  } else {
-    ret = cmp(v1.stringValue(), v2.stringValue());
-  }
-
-  return new BooleanValue(ret);
-}
-
-function LiteralExpr(value) {
-  this.value = value;
-}
-
-LiteralExpr.prototype.evaluate = function(ctx) {
-  return new StringValue(this.value);
-};
-
-function NumberExpr(value) {
-  this.value = value;
-}
-
-NumberExpr.prototype.evaluate = function(ctx) {
-  return new NumberValue(this.value);
-};
-
-function VariableExpr(name) {
-  this.name = name;
-}
-
-VariableExpr.prototype.evaluate = function(ctx) {
-  return ctx.getVariable(this.name);
-}
-
-// Factory functions for semantic values (i.e. Expressions) of the
-// productions in the grammar. When a production is matched to reduce
-// the current parse state stack, the function is called with the
-// semantic values of the matched elements as arguments, and returns
-// another semantic value. The semantic value is a node of the parse
-// tree, an expression object with an evaluate() method that evaluates the
-// expression in an actual context. These factory functions are used
-// in the specification of the grammar rules, below.
-
-function makeTokenExpr(m) {
-  return new TokenExpr(m);
-}
-
-function passExpr(e) {
-  return e;
-}
-
-function makeLocationExpr1(slash, rel) {
-  rel.absolute = true;
-  return rel;
-}
-
-function makeLocationExpr2(dslash, rel) {
-  rel.absolute = true;
-  rel.prependStep(makeAbbrevStep(dslash.value));
-  return rel;
-}
-
-function makeLocationExpr3(slash) {
-  var ret = new LocationExpr();
-  ret.appendStep(makeAbbrevStep('.'));
-  ret.absolute = true;
-  return ret;
-}
-
-function makeLocationExpr4(dslash) {
-  var ret = new LocationExpr();
-  ret.absolute = true;
-  ret.appendStep(makeAbbrevStep(dslash.value));
-  return ret;
-}
-
-function makeLocationExpr5(step) {
-  var ret = new LocationExpr();
-  ret.appendStep(step);
-  return ret;
-}
-
-function makeLocationExpr6(rel, slash, step) {
-  rel.appendStep(step);
-  return rel;
-}
-
-function makeLocationExpr7(rel, dslash, step) {
-  rel.appendStep(makeAbbrevStep(dslash.value));
-  return rel;
-}
-
-function makeStepExpr1(dot) {
-  return makeAbbrevStep(dot.value);
-}
-
-function makeStepExpr2(ddot) {
-  return makeAbbrevStep(ddot.value);
-}
-
-function makeStepExpr3(axisname, axis, nodetest) {
-  return new StepExpr(axisname.value, nodetest);
-}
-
-function makeStepExpr4(at, nodetest) {
-  return new StepExpr('attribute', nodetest);
-}
-
-function makeStepExpr5(nodetest) {
-  return new StepExpr('child', nodetest);
-}
-
-function makeStepExpr6(step, predicate) {
-  step.appendPredicate(predicate);
-  return step;
-}
-
-function makeAbbrevStep(abbrev) {
-  switch (abbrev) {
-  case '//':
-    return new StepExpr('descendant-or-self', new NodeTestAny);
-
-  case '.':
-    return new StepExpr('self', new NodeTestAny);
-
-  case '..':
-    return new StepExpr('parent', new NodeTestAny);
-  }
-}
-
-function makeNodeTestExpr1(asterisk) {
-  return new NodeTestElement;
-}
-
-function makeNodeTestExpr2(ncname, colon, asterisk) {
-  return new NodeTestNC(ncname.value);
-}
-
-function makeNodeTestExpr3(qname) {
-  return new NodeTestName(qname.value);
-}
-
-function makeNodeTestExpr4(typeo, parenc) {
-  var type = typeo.value.replace(/\s*\($/, '');
-  switch(type) {
-  case 'node':
-    return new NodeTestAny;
-
-  case 'text':
-    return new NodeTestText;
-
-  case 'comment':
-    return new NodeTestComment;
-
-  case 'processing-instruction':
-    return new NodeTestPI;
-  }
-}
-
-function makeNodeTestExpr5(typeo, target, parenc) {
-  var type = typeo.replace(/\s*\($/, '');
-  if (type != 'processing-instruction') {
-    throw type + ' ' + Error().stack;
-  }
-  return new NodeTestPI(target.value);
-}
-
-function makePredicateExpr(pareno, expr, parenc) {
-  return new PredicateExpr(expr);
-}
-
-function makePrimaryExpr(pareno, expr, parenc) {
-  return expr;
-}
-
-function makeFunctionCallExpr1(name, pareno, parenc) {
-  return new FunctionCallExpr(name);
-}
-
-function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {
-  var ret = new FunctionCallExpr(name);
-  ret.appendArg(arg1);
-  for (var i = 0; i &lt; args.length; ++i) {
-    ret.appendArg(args[i]);
-  }
-  return ret;
-}
-
-function makeArgumentExpr(comma, expr) {
-  return expr;
-}
-
-function makeUnionExpr(expr1, pipe, expr2) {
-  return new UnionExpr(expr1, expr2);
-}
-
-function makePathExpr1(filter, slash, rel) {
-  return new PathExpr(filter, rel);
-}
-
-function makePathExpr2(filter, dslash, rel) {
-  rel.prependStep(makeAbbrevStep(dslash.value));
-  return new PathExpr(filter, rel);
-}
-
-function makeFilterExpr(expr, predicates) {
-  if (predicates.length &gt; 0) {
-    return new FilterExpr(expr, predicates);
-  } else {
-    return expr;
-  }
-}
-
-function makeUnaryMinusExpr(minus, expr) {
-  return new UnaryMinusExpr(expr);
-}
-
-function makeBinaryExpr(expr1, op, expr2) {
-  return new BinaryExpr(expr1, op, expr2);
-}
-
-function makeLiteralExpr(token) {
-  // remove quotes from the parsed value:
-  var value = token.value.substring(1, token.value.length - 1);
-  return new LiteralExpr(value);
-}
-
-function makeNumberExpr(token) {
-  return new NumberExpr(token.value);
-}
-
-function makeVariableReference(dollar, name) {
-  return new VariableExpr(name.value);
-}
-
-// Used before parsing for optimization of common simple cases. See
-// the begin of xpathParse() for which they are.
-function makeSimpleExpr(expr) {
-  if (expr.charAt(0) == '$') {
-    return new VariableExpr(expr.substr(1));
-  } else if (expr.charAt(0) == '@') {
-    var a = new NodeTestName(expr.substr(1));
-    var b = new StepExpr('attribute', a);
-    var c = new LocationExpr();
-    c.appendStep(b);
-    return c;
-  } else if (expr.match(/^[0-9]+$/)) {
-    return new NumberExpr(expr);
-  } else {
-    var a = new NodeTestName(expr);
-    var b = new StepExpr('child', a);
-    var c = new LocationExpr();
-    c.appendStep(b);
-    return c;
-  }
-}
-
-function makeSimpleExpr2(expr) {
-  var steps = expr.split('/');
-  var c = new LocationExpr();
-  for (var i in steps) {
-    var a = new NodeTestName(steps[i]);
-    var b = new StepExpr('child', a);
-    c.appendStep(b);
-  }
-  return c;
-}
-
-// The axes of XPath expressions.
-
-var xpathAxis = {
-  ANCESTOR_OR_SELF: 'ancestor-or-self',
-  ANCESTOR: 'ancestor',
-  ATTRIBUTE: 'attribute',
-  CHILD: 'child',
-  DESCENDANT_OR_SELF: 'descendant-or-self',
-  DESCENDANT: 'descendant',
-  FOLLOWING_SIBLING: 'following-sibling',
-  FOLLOWING: 'following',
-  NAMESPACE: 'namespace',
-  PARENT: 'parent',
-  PRECEDING_SIBLING: 'preceding-sibling',
-  PRECEDING: 'preceding',
-  SELF: 'self'
-};
-
-var xpathAxesRe = [
-    xpathAxis.ANCESTOR_OR_SELF,
-    xpathAxis.ANCESTOR,
-    xpathAxis.ATTRIBUTE,
-    xpathAxis.CHILD,
-    xpathAxis.DESCENDANT_OR_SELF,
-    xpathAxis.DESCENDANT,
-    xpathAxis.FOLLOWING_SIBLING,
-    xpathAxis.FOLLOWING,
-    xpathAxis.NAMESPACE,
-    xpathAxis.PARENT,
-    xpathAxis.PRECEDING_SIBLING,
-    xpathAxis.PRECEDING,
-    xpathAxis.SELF
-].join('|');
-
-
-// The tokens of the language. The label property is just used for
-// generating debug output. The prec property is the precedence used
-// for shift/reduce resolution. Default precedence is 0 as a lookahead
-// token and 2 on the stack. TODO(mesch): this is certainly not
-// necessary and too complicated. Simplify this!
-
-// NOTE: tabular formatting is the big exception, but here it should
-// be OK.
-
-var TOK_PIPE =   { label: &quot;|&quot;,   prec:   17, re: new RegExp(&quot;^\\|&quot;) };
-var TOK_DSLASH = { label: &quot;//&quot;,  prec:   19, re: new RegExp(&quot;^//&quot;)  };
-var TOK_SLASH =  { label: &quot;/&quot;,   prec:   30, re: new RegExp(&quot;^/&quot;)   };
-var TOK_AXIS =   { label: &quot;::&quot;,  prec:   20, re: new RegExp(&quot;^::&quot;)  };
-var TOK_COLON =  { label: &quot;:&quot;,   prec: 1000, re: new RegExp(&quot;^:&quot;)  };
-var TOK_AXISNAME = { label: &quot;[axis]&quot;, re: new RegExp('^(' + xpathAxesRe + ')') };
-var TOK_PARENO = { label: &quot;(&quot;,   prec:   34, re: new RegExp(&quot;^\\(&quot;) };
-var TOK_PARENC = { label: &quot;)&quot;,               re: new RegExp(&quot;^\\)&quot;) };
-var TOK_DDOT =   { label: &quot;..&quot;,  prec:   34, re: new RegExp(&quot;^\\.\\.&quot;) };
-var TOK_DOT =    { label: &quot;.&quot;,   prec:   34, re: new RegExp(&quot;^\\.&quot;) };
-var TOK_AT =     { label: &quot;@&quot;,   prec:   34, re: new RegExp(&quot;^@&quot;)   };
-
-var TOK_COMMA =  { label: &quot;,&quot;,               re: new RegExp(&quot;^,&quot;) };
-
-var TOK_OR =     { label: &quot;or&quot;,  prec:   10, re: new RegExp(&quot;^or\\b&quot;) };
-var TOK_AND =    { label: &quot;and&quot;, prec:   11, re: new RegExp(&quot;^and\\b&quot;) };
-var TOK_EQ =     { label: &quot;=&quot;,   prec:   12, re: new RegExp(&quot;^=&quot;)   };
-var TOK_NEQ =    { label: &quot;!=&quot;,  prec:   12, re: new RegExp(&quot;^!=&quot;)  };
-var TOK_GE =     { label: &quot;&gt;=&quot;,  prec:   13, re: new RegExp(&quot;^&gt;=&quot;)  };
-var TOK_GT =     { label: &quot;&gt;&quot;,   prec:   13, re: new RegExp(&quot;^&gt;&quot;)   };
-var TOK_LE =     { label: &quot;&lt;=&quot;,  prec:   13, re: new RegExp(&quot;^&lt;=&quot;)  };
-var TOK_LT =     { label: &quot;&lt;&quot;,   prec:   13, re: new RegExp(&quot;^&lt;&quot;)   };
-var TOK_PLUS =   { label: &quot;+&quot;,   prec:   14, re: new RegExp(&quot;^\\+&quot;), left: true };
-var TOK_MINUS =  { label: &quot;-&quot;,   prec:   14, re: new RegExp(&quot;^\\-&quot;), left: true };
-var TOK_DIV =    { label: &quot;div&quot;, prec:   15, re: new RegExp(&quot;^div\\b&quot;), left: true };
-var TOK_MOD =    { label: &quot;mod&quot;, prec:   15, re: new RegExp(&quot;^mod\\b&quot;), left: true };
-
-var TOK_BRACKO = { label: &quot;[&quot;,   prec:   32, re: new RegExp(&quot;^\\[&quot;) };
-var TOK_BRACKC = { label: &quot;]&quot;,               re: new RegExp(&quot;^\\]&quot;) };
-var TOK_DOLLAR = { label: &quot;$&quot;,               re: new RegExp(&quot;^\\$&quot;) };
-
-var TOK_NCNAME = { label: &quot;[ncname]&quot;, re: new RegExp('^[a-z][-\\w]*','i') };
-
-var TOK_ASTERISK = { label: &quot;*&quot;, prec: 15, re: new RegExp(&quot;^\\*&quot;), left: true };
-var TOK_LITERALQ = { label: &quot;[litq]&quot;, prec: 20, re: new RegExp(&quot;^'[^\\']*'&quot;) };
-var TOK_LITERALQQ = {
-  label: &quot;[litqq]&quot;,
-  prec: 20,
-  re: new RegExp('^&quot;[^\\&quot;]*&quot;')
-};
-
-var TOK_NUMBER  = {
-  label: &quot;[number]&quot;,
-  prec: 35,
-  re: new RegExp('^\\d+(\\.\\d*)?') };
-
-var TOK_QNAME = {
-  label: &quot;[qname]&quot;,
-  re: new RegExp('^([a-z][-\\w]*:)?[a-z][-\\w]*','i')
-};
-
-var TOK_NODEO = {
-  label: &quot;[nodetest-start]&quot;,
-  re: new RegExp('^(processing-instruction|comment|text|node)\\(')
-};
-
-// The table of the tokens of our grammar, used by the lexer: first
-// column the tag, second column a regexp to recognize it in the
-// input, third column the precedence of the token, fourth column a
-// factory function for the semantic value of the token.
-//
-// NOTE: order of this list is important, because the first match
-// counts. Cf. DDOT and DOT, and AXIS and COLON.
-
-var xpathTokenRules = [
-    TOK_DSLASH,
-    TOK_SLASH,
-    TOK_DDOT,
-    TOK_DOT,
-    TOK_AXIS,
-    TOK_COLON,
-    TOK_AXISNAME,
-    TOK_NODEO,
-    TOK_PARENO,
-    TOK_PARENC,
-    TOK_BRACKO,
-    TOK_BRACKC,
-    TOK_AT,
-    TOK_COMMA,
-    TOK_OR,
-    TOK_AND,
-    TOK_NEQ,
-    TOK_EQ,
-    TOK_GE,
-    TOK_GT,
-    TOK_LE,
-    TOK_LT,
-    TOK_PLUS,
-    TOK_MINUS,
-    TOK_ASTERISK,
-    TOK_PIPE,
-    TOK_MOD,
-    TOK_DIV,
-    TOK_LITERALQ,
-    TOK_LITERALQQ,
-    TOK_NUMBER,
-    TOK_QNAME,
-    TOK_NCNAME,
-    TOK_DOLLAR
-];
-
-// All the nonterminals of the grammar. The nonterminal objects are
-// identified by object identity; the labels are used in the debug
-// output only.
-var XPathLocationPath = { label: &quot;LocationPath&quot; };
-var XPathRelativeLocationPath = { label: &quot;RelativeLocationPath&quot; };
-var XPathAbsoluteLocationPath = { label: &quot;AbsoluteLocationPath&quot; };
-var XPathStep = { label: &quot;Step&quot; };
-var XPathNodeTest = { label: &quot;NodeTest&quot; };
-var XPathPredicate = { label: &quot;Predicate&quot; };
-var XPathLiteral = { label: &quot;Literal&quot; };
-var XPathExpr = { label: &quot;Expr&quot; };
-var XPathPrimaryExpr = { label: &quot;PrimaryExpr&quot; };
-var XPathVariableReference = { label: &quot;Variablereference&quot; };
-var XPathNumber = { label: &quot;Number&quot; };
-var XPathFunctionCall = { label: &quot;FunctionCall&quot; };
-var XPathArgumentRemainder = { label: &quot;ArgumentRemainder&quot; };
-var XPathPathExpr = { label: &quot;PathExpr&quot; };
-var XPathUnionExpr = { label: &quot;UnionExpr&quot; };
-var XPathFilterExpr = { label: &quot;FilterExpr&quot; };
-var XPathDigits = { label: &quot;Digits&quot; };
-
-var xpathNonTerminals = [
-    XPathLocationPath,
-    XPathRelativeLocationPath,
-    XPathAbsoluteLocationPath,
-    XPathStep,
-    XPathNodeTest,
-    XPathPredicate,
-    XPathLiteral,
-    XPathExpr,
-    XPathPrimaryExpr,
-    XPathVariableReference,
-    XPathNumber,
-    XPathFunctionCall,
-    XPathArgumentRemainder,
-    XPathPathExpr,
-    XPathUnionExpr,
-    XPathFilterExpr,
-    XPathDigits
-];
-
-// Quantifiers that are used in the productions of the grammar.
-var Q_01 = { label: &quot;?&quot; };
-var Q_MM = { label: &quot;*&quot; };
-var Q_1M = { label: &quot;+&quot; };
-
-// Tag for left associativity (right assoc is implied by undefined).
-var ASSOC_LEFT = true;
-
-// The productions of the grammar. Columns of the table:
-//
-// - target nonterminal,
-// - pattern,
-// - precedence,
-// - semantic value factory
-//
-// The semantic value factory is a function that receives parse tree
-// nodes from the stack frames of the matched symbols as arguments and
-// returns an a node of the parse tree. The node is stored in the top
-// stack frame along with the target object of the rule. The node in
-// the parse tree is an expression object that has an evaluate() method
-// and thus evaluates XPath expressions.
-//
-// The precedence is used to decide between reducing and shifting by
-// comparing the precendence of the rule that is candidate for
-// reducing with the precedence of the look ahead token. Precedence of
-// -1 means that the precedence of the tokens in the pattern is used
-// instead. TODO: It shouldn't be necessary to explicitly assign
-// precedences to rules.
-
-var xpathGrammarRules =
-  [
-   [ XPathLocationPath, [ XPathRelativeLocationPath ], 18,
-     passExpr ],
-   [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18,
-     passExpr ],
-
-   [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18, 
-     makeLocationExpr1 ],
-   [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18,
-     makeLocationExpr2 ],
-
-   [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0,
-     makeLocationExpr3 ],
-   [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0,
-     makeLocationExpr4 ],
-
-   [ XPathRelativeLocationPath, [ XPathStep ], 31,
-     makeLocationExpr5 ],
-   [ XPathRelativeLocationPath,
-     [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31,
-     makeLocationExpr6 ],
-   [ XPathRelativeLocationPath,
-     [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31,
-     makeLocationExpr7 ],
-
-   [ XPathStep, [ TOK_DOT ], 33,
-     makeStepExpr1 ],
-   [ XPathStep, [ TOK_DDOT ], 33,
-     makeStepExpr2 ],
-   [ XPathStep,
-     [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33,
-     makeStepExpr3 ],
-   [ XPathStep, [ TOK_AT, XPathNodeTest ], 33,
-     makeStepExpr4 ],
-   [ XPathStep, [ XPathNodeTest ], 33,
-     makeStepExpr5 ],
-   [ XPathStep, [ XPathStep, XPathPredicate ], 33,
-     makeStepExpr6 ],
-
-   [ XPathNodeTest, [ TOK_ASTERISK ], 33,
-     makeNodeTestExpr1 ],
-   [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33,
-     makeNodeTestExpr2 ],
-   [ XPathNodeTest, [ TOK_QNAME ], 33,
-     makeNodeTestExpr3 ],
-   [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33,
-     makeNodeTestExpr4 ],
-   [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33,
-     makeNodeTestExpr5 ],
-
-   [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33,
-     makePredicateExpr ],
-
-   [ XPathPrimaryExpr, [ XPathVariableReference ], 33,
-     passExpr ],
-   [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33,
-     makePrimaryExpr ],
-   [ XPathPrimaryExpr, [ XPathLiteral ], 30,
-     passExpr ],
-   [ XPathPrimaryExpr, [ XPathNumber ], 30,
-     passExpr ],
-   [ XPathPrimaryExpr, [ XPathFunctionCall ], 30,
-     passExpr ],
-
-   [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,
-     makeFunctionCallExpr1 ],
-   [ XPathFunctionCall,
-     [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM,
-       TOK_PARENC ], -1,
-     makeFunctionCallExpr2 ],
-   [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1,
-     makeArgumentExpr ],
-
-   [ XPathUnionExpr, [ XPathPathExpr ], 20,
-     passExpr ],
-   [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20,
-     makeUnionExpr ],
-
-   [ XPathPathExpr, [ XPathLocationPath ], 20, 
-     passExpr ], 
-   [ XPathPathExpr, [ XPathFilterExpr ], 19, 
-     passExpr ], 
-   [ XPathPathExpr, 
-     [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 20,
-     makePathExpr1 ],
-   [ XPathPathExpr,
-     [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 20,
-     makePathExpr2 ],
-
-   [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 20,
-     makeFilterExpr ], 
-
-   [ XPathExpr, [ XPathPrimaryExpr ], 16,
-     passExpr ],
-   [ XPathExpr, [ XPathUnionExpr ], 16,
-     passExpr ],
-
-   [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1,
-     makeUnaryMinusExpr ],
-
-   [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1,
-     makeBinaryExpr ],
-   [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1,
-     makeBinaryExpr ],
-
-   [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1,
-     makeBinaryExpr ],
-   [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1,
-     makeBinaryExpr ],
-
-   [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1,
-     makeBinaryExpr ],
-   [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1,
-     makeBinaryExpr ],
-   [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1,
-     makeBinaryExpr ],
-   [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1,
-     makeBinaryExpr ],
-
-   [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1,
-     makeBinaryExpr, ASSOC_LEFT ],
-   [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1,
-     makeBinaryExpr, ASSOC_LEFT ],
-
-   [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1,
-     makeBinaryExpr, ASSOC_LEFT ],
-   [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1,
-     makeBinaryExpr, ASSOC_LEFT ],
-   [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1,
-     makeBinaryExpr, ASSOC_LEFT ],
-
-   [ XPathLiteral, [ TOK_LITERALQ ], -1,
-     makeLiteralExpr ],
-   [ XPathLiteral, [ TOK_LITERALQQ ], -1,
-     makeLiteralExpr ],
-
-   [ XPathNumber, [ TOK_NUMBER ], -1,
-     makeNumberExpr ],
-
-   [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200,
-     makeVariableReference ]
-   ];
-
-// That function computes some optimizations of the above data
-// structures and will be called right here. It merely takes the
-// counter variables out of the global scope.
-
-var xpathRules = [];
-
-function xpathParseInit() {
-  if (xpathRules.length) {
-    return;
-  }
-
-  // Some simple optimizations for the xpath expression parser: sort
-  // grammar rules descending by length, so that the longest match is
-  // first found.
-
-  xpathGrammarRules.sort(function(a,b) {
-    var la = a[1].length;
-    var lb = b[1].length;
-    if (la &lt; lb) {
-      return 1;
-    } else if (la &gt; lb) {
-      return -1;
-    } else {
-      return 0;
-    }
-  });
-
-  var k = 1;
-  for (var i = 0; i &lt; xpathNonTerminals.length; ++i) {
-    xpathNonTerminals[i].key = k++;
-  }
-
-  for (i = 0; i &lt; xpathTokenRules.length; ++i) {
-    xpathTokenRules[i].key = k++;
-  }
-
-  Log.write('XPath parse INIT: ' + k + ' rules');
-
-  // Another slight optimization: sort the rules into bins according
-  // to the last element (observing quantifiers), so we can restrict
-  // the match against the stack to the subest of rules that match the
-  // top of the stack.
-  //
-  // TODO(mesch): What we actually want is to compute states as in
-  // bison, so that we don't have to do any explicit and iterated
-  // match against the stack.
-
-  function push_(array, position, element) {
-    if (!array[position]) {
-      array[position] = [];
-    }
-    array[position].push(element);
-  }
-
-  for (i = 0; i &lt; xpathGrammarRules.length; ++i) {
-    var rule = xpathGrammarRules[i];
-    var pattern = rule[1];
-
-    for (var j = pattern.length - 1; j &gt;= 0; --j) {
-      if (pattern[j] == Q_1M) {
-        push_(xpathRules, pattern[j-1].key, rule);
-        break;
-        
-      } else if (pattern[j] == Q_MM || pattern[j] == Q_01) {
-        push_(xpathRules, pattern[j-1].key, rule);
-        --j;
-
-      } else {
-        push_(xpathRules, pattern[j].key, rule);
-        break;
-      }
-    }
-  }
-
-  Log.write('XPath parse INIT: ' + xpathRules.length + ' rule bins');
-  
-  var sum = 0;
-  mapExec(xpathRules, function(i) {
-    if (i) {
-      sum += i.length;
-    }
-  });
-  
-  Log.write('XPath parse INIT: ' + (sum / xpathRules.length) + ' average bin size');
-}
-
-// Local utility functions that are used by the lexer or parser.
-
-function xpathCollectDescendants(nodelist, node) {
-  for (var n = node.firstChild; n; n = n.nextSibling) {
-    nodelist.push(n);
-    arguments.callee(nodelist, n);
-  }
-}
-
-function xpathCollectDescendantsReverse(nodelist, node) {
-  for (var n = node.lastChild; n; n = n.previousSibling) {
-    nodelist.push(n);
-    arguments.callee(nodelist, n);
-  }
-}
-
-
-// The entry point for the library: match an expression against a DOM
-// node. Returns an XPath value.
-function xpathDomEval(expr, node) {
-  var expr1 = xpathParse(expr);
-  var ret = expr1.evaluate(new ExprContext(node));
-  return ret;
-}
-
-// Utility function to sort a list of nodes. Used by xsltSort() and
-// nxslSelect().
-function xpathSort(input, sort) {
-  if (sort.length == 0) {
-    return;
-  }
-
-  var sortlist = [];
-
-  for (var i = 0; i &lt; input.nodelist.length; ++i) {
-    var node = input.nodelist[i];
-    var sortitem = { node: node, key: [] };
-    var context = input.clone(node, 0, [ node ]);
-    
-    for (var j = 0; j &lt; sort.length; ++j) {
-      var s = sort[j];
-      var value = s.expr.evaluate(context);
-
-      var evalue;
-      if (s.type == 'text') {
-        evalue = value.stringValue();
-      } else if (s.type == 'number') {
-        evalue = value.numberValue();
-      }
-      sortitem.key.push({ value: evalue, order: s.order });
-    }
-
-    // Make the sort stable by adding a lowest priority sort by
-    // id. This is very convenient and furthermore required by the
-    // spec ([XSLT] - Section 10 Sorting).
-    sortitem.key.push({ value: i, order: 'ascending' });
-
-    sortlist.push(sortitem);
-  }
-
-  sortlist.sort(xpathSortByKey);
-
-  var nodes = [];
-  for (var i = 0; i &lt; sortlist.length; ++i) {
-    nodes.push(sortlist[i].node);
-  }
-  input.nodelist = nodes;
-  input.setNode(nodes[0], 0);
-}
-
-
-// Sorts by all order criteria defined. According to the JavaScript
-// spec ([ECMA] Section 11.8.5), the compare operators compare strings
-// as strings and numbers as numbers.
-//
-// NOTE: In browsers which do not follow the spec, this breaks only in
-// the case that numbers should be sorted as strings, which is very
-// uncommon.
-
-function xpathSortByKey(v1, v2) {
-  // NOTE: Sort key vectors of different length never occur in
-  // xsltSort.
-
-  for (var i = 0; i &lt; v1.key.length; ++i) {
-    var o = v1.key[i].order == 'descending' ? -1 : 1;
-    if (v1.key[i].value &gt; v2.key[i].value) {
-      return +1 * o;
-    } else if (v1.key[i].value &lt; v2.key[i].value) {
-      return -1 * o;
-    }
-  }
-
-  return 0;
-}
+// Copyright 2005 Google Inc.
+// All Rights Reserved
+//
+// An XPath parser and evaluator written in JavaScript. The
+// implementation is complete except for functions handling
+// namespaces.
+//
+// Reference: [XPATH] XPath Specification
+// &lt;http://www.w3.org/TR/1999/REC-xpath-19991116&gt;.
+//
+//
+// The API of the parser has several parts:
+//
+// 1. The parser function xpathParse() that takes a string and returns
+// an expession object.
+//
+// 2. The expression object that has an evaluate() method to evaluate the
+// XPath expression it represents. (It is actually a hierarchy of
+// objects that resembles the parse tree, but an application will call
+// evaluate() only on the top node of this hierarchy.)
+//
+// 3. The context object that is passed as an argument to the evaluate()
+// method, which represents the DOM context in which the expression is
+// evaluated.
+//
+// 4. The value object that is returned from evaluate() and represents
+// values of the different types that are defined by XPath (number,
+// string, boolean, and node-set), and allows to convert between them.
+//
+// These parts are near the top of the file, the functions and data
+// that are used internally follow after them.
+//
+//
+// Author: Steffen Meschkat &lt;mesch@google.com&gt;
+
+
+// The entry point for the parser.
+//
+// @param expr a string that contains an XPath expression.
+// @return an expression object that can be evaluated with an
+// expression context.
+
+function xpathParse(expr) {
+  xpathLog('parse ' + expr);
+  xpathParseInit();
+
+  var cached = xpathCacheLookup(expr);
+  if (cached) {
+    xpathLog(' ... cached');
+    return cached;
+  }
+
+  // Optimize for a few common cases: simple attribute node tests
+  // (@id), simple element node tests (page), variable references
+  // ($address), numbers (4), multi-step path expressions where each
+  // step is a plain element node test
+  // (page/overlay/locations/location).
+
+  if (expr.match(/^(\$|@)?\w+$/i)) {
+    var ret = makeSimpleExpr(expr);
+    xpathParseCache[expr] = ret;
+    xpathLog(' ... simple');
+    return ret;
+  }
+
+  if (expr.match(/^\w+(\/\w+)*$/i)) {
+    var ret = makeSimpleExpr2(expr);
+    xpathParseCache[expr] = ret;
+    xpathLog(' ... simple 2');
+    return ret;
+  }
+
+  var cachekey = expr; // expr is modified during parse
+
+  var stack = [];
+  var ahead = null;
+  var previous = null;
+  var done = false;
+
+  var parse_count = 0;
+  var lexer_count = 0;
+  var reduce_count = 0;
+
+  while (!done) {
+    parse_count++;
+    expr = expr.replace(/^\s*/, '');
+    previous = ahead;
+    ahead = null;
+
+    var rule = null;
+    var match = '';
+    for (var i = 0; i &lt; xpathTokenRules.length; ++i) {
+      var result = xpathTokenRules[i].re.exec(expr);
+      lexer_count++;
+      if (result &amp;&amp; result.length &gt; 0 &amp;&amp; result[0].length &gt; match.length) {
+        rule = xpathTokenRules[i];
+        match = result[0];
+        break;
+      }
+    }
+
+    // Special case: allow operator keywords to be element and
+    // variable names.
+
+    // NOTE(mesch): The parser resolves conflicts by looking ahead,
+    // and this is the only case where we look back to
+    // disambiguate. So this is indeed something different, and
+    // looking back is usually done in the lexer (via states in the
+    // general case, called &quot;start conditions&quot; in flex(1)). Also,the
+    // conflict resolution in the parser is not as robust as it could
+    // be, so I'd like to keep as much off the parser as possible (all
+    // these precedence values should be computed from the grammar
+    // rules and possibly associativity declarations, as in bison(1),
+    // and not explicitly set.
+
+    if (rule &amp;&amp;
+        (rule == TOK_DIV ||
+         rule == TOK_MOD ||
+         rule == TOK_AND ||
+         rule == TOK_OR) &amp;&amp;
+        (!previous ||
+         previous.tag == TOK_AT ||
+         previous.tag == TOK_DSLASH ||
+         previous.tag == TOK_SLASH ||
+         previous.tag == TOK_AXIS ||
+         previous.tag == TOK_DOLLAR)) {
+      rule = TOK_QNAME;
+    }
+
+    if (rule) {
+      expr = expr.substr(match.length);
+      xpathLog('token: ' + match + ' -- ' + rule.label);
+      ahead = {
+        tag: rule,
+        match: match,
+        prec: rule.prec ?  rule.prec : 0, // || 0 is removed by the compiler
+        expr: makeTokenExpr(match)
+      };
+
+    } else {
+      xpathLog('DONE');
+      done = true;
+    }
+
+    while (xpathReduce(stack, ahead)) {
+      reduce_count++;
+      xpathLog('stack: ' + stackToString(stack));
+    }
+  }
+
+  xpathLog('stack: ' + stackToString(stack));
+
+  // DGF any valid XPath should &quot;reduce&quot; to a single Expr token
+  if (stack.length != 1) {
+    throw 'XPath parse error ' + cachekey + ':\n' + stackToString(stack);
+  }
+
+  var result = stack[0].expr;
+  xpathParseCache[cachekey] = result;
+
+  xpathLog('XPath parse: ' + parse_count + ' / ' +
+           lexer_count + ' / ' + reduce_count);
+
+  return result;
+}
+
+var xpathParseCache = {};
+
+function xpathCacheLookup(expr) {
+  return xpathParseCache[expr];
+}
+
+/*DGF xpathReduce is where the magic happens in this parser.
+Skim down to the bottom of this file to find the table of 
+grammatical rules and precedence numbers, &quot;The productions of the grammar&quot;.
+
+The idea here
+is that we want to take a stack of tokens and apply
+grammatical rules to them, &quot;reducing&quot; them to higher-level
+tokens.  Ultimately, any valid XPath should reduce to exactly one
+&quot;Expr&quot; token.
+
+Reduce too early or too late and you'll have two tokens that can't reduce
+to single Expr.  For example, you may hastily reduce a qname that
+should name a function, incorrectly treating it as a tag name.
+Or you may reduce too late, accidentally reducing the last part of the
+XPath into a top-level &quot;Expr&quot; that won't reduce with earlier parts of
+the XPath.
+
+A &quot;cand&quot; is a grammatical rule candidate, with a given precedence
+number.  &quot;ahead&quot; is the upcoming token, which also has a precedence
+number.  If the token has a higher precedence number than
+the rule candidate, we'll &quot;shift&quot; the token onto the token stack,
+instead of immediately applying the rule candidate.
+
+Some tokens have left associativity, in which case we shift when they
+have LOWER precedence than the candidate.
+*/
+function xpathReduce(stack, ahead) {
+  var cand = null;
+
+  if (stack.length &gt; 0) {
+    var top = stack[stack.length-1];
+    var ruleset = xpathRules[top.tag.key];
+
+    if (ruleset) {
+      for (var i = 0; i &lt; ruleset.length; ++i) {
+        var rule = ruleset[i];
+        var match = xpathMatchStack(stack, rule[1]);
+        if (match.length) {
+          cand = {
+            tag: rule[0],
+            rule: rule,
+            match: match
+          };
+          cand.prec = xpathGrammarPrecedence(cand);
+          break;
+        }
+      }
+    }
+  }
+
+  var ret;
+  if (cand &amp;&amp; (!ahead || cand.prec &gt; ahead.prec ||
+               (ahead.tag.left &amp;&amp; cand.prec &gt;= ahead.prec))) {
+    for (var i = 0; i &lt; cand.match.matchlength; ++i) {
+      stack.pop();
+    }
+
+    xpathLog('reduce ' + cand.tag.label + ' ' + cand.prec +
+             ' ahead ' + (ahead ? ahead.tag.label + ' ' + ahead.prec +
+                          (ahead.tag.left ? ' left' : '')
+                          : ' none '));
+
+    var matchexpr = mapExpr(cand.match, function(m) { return m.expr; });
+    xpathLog('going to apply ' + cand.rule[3].toString());
+    cand.expr = cand.rule[3].apply(null, matchexpr);
+
+    stack.push(cand);
+    ret = true;
+
+  } else {
+    if (ahead) {
+      xpathLog('shift ' + ahead.tag.label + ' ' + ahead.prec +
+               (ahead.tag.left ? ' left' : '') +
+               ' over ' + (cand ? cand.tag.label + ' ' +
+                           cand.prec : ' none'));
+      stack.push(ahead);
+    }
+    ret = false;
+  }
+  return ret;
+}
+
+function xpathMatchStack(stack, pattern) {
+
+  // NOTE(mesch): The stack matches for variable cardinality are
+  // greedy but don't do backtracking. This would be an issue only
+  // with rules of the form A* A, i.e. with an element with variable
+  // cardinality followed by the same element. Since that doesn't
+  // occur in the grammar at hand, all matches on the stack are
+  // unambiguous.
+
+  var S = stack.length;
+  var P = pattern.length;
+  var p, s;
+  var match = [];
+  match.matchlength = 0;
+  var ds = 0;
+  for (p = P - 1, s = S - 1; p &gt;= 0 &amp;&amp; s &gt;= 0; --p, s -= ds) {
+    ds = 0;
+    var qmatch = [];
+    if (pattern[p] == Q_MM) {
+      p -= 1;
+      match.push(qmatch);
+      while (s - ds &gt;= 0 &amp;&amp; stack[s - ds].tag == pattern[p]) {
+        qmatch.push(stack[s - ds]);
+        ds += 1;
+        match.matchlength += 1;
+      }
+
+    } else if (pattern[p] == Q_01) {
+      p -= 1;
+      match.push(qmatch);
+      while (s - ds &gt;= 0 &amp;&amp; ds &lt; 2 &amp;&amp; stack[s - ds].tag == pattern[p]) {
+        qmatch.push(stack[s - ds]);
+        ds += 1;
+        match.matchlength += 1;
+      }
+
+    } else if (pattern[p] == Q_1M) {
+      p -= 1;
+      match.push(qmatch);
+      if (stack[s].tag == pattern[p]) {
+        while (s - ds &gt;= 0 &amp;&amp; stack[s - ds].tag == pattern[p]) {
+          qmatch.push(stack[s - ds]);
+          ds += 1;
+          match.matchlength += 1;
+        }
+      } else {
+        return [];
+      }
+
+    } else if (stack[s].tag == pattern[p]) {
+      match.push(stack[s]);
+      ds += 1;
+      match.matchlength += 1;
+
+    } else {
+      return [];
+    }
+
+    reverseInplace(qmatch);
+    qmatch.expr = mapExpr(qmatch, function(m) { return m.expr; });
+  }
+
+  reverseInplace(match);
+
+  if (p == -1) {
+    return match;
+
+  } else {
+    return [];
+  }
+}
+
+function xpathTokenPrecedence(tag) {
+  return tag.prec || 2;
+}
+
+function xpathGrammarPrecedence(frame) {
+  var ret = 0;
+
+  if (frame.rule) { /* normal reduce */
+    if (frame.rule.length &gt;= 3 &amp;&amp; frame.rule[2] &gt;= 0) {
+      ret = frame.rule[2];
+
+    } else {
+      for (var i = 0; i &lt; frame.rule[1].length; ++i) {
+        var p = xpathTokenPrecedence(frame.rule[1][i]);
+        ret = Math.max(ret, p);
+      }
+    }
+  } else if (frame.tag) { /* TOKEN match */
+    ret = xpathTokenPrecedence(frame.tag);
+
+  } else if (frame.length) { /* Q_ match */
+    for (var j = 0; j &lt; frame.length; ++j) {
+      var p = xpathGrammarPrecedence(frame[j]);
+      ret = Math.max(ret, p);
+    }
+  }
+
+  return ret;
+}
+
+function stackToString(stack) {
+  var ret = '';
+  for (var i = 0; i &lt; stack.length; ++i) {
+    if (ret) {
+      ret += '\n';
+    }
+    ret += stack[i].tag.label;
+  }
+  return ret;
+}
+
+
+// XPath expression evaluation context. An XPath context consists of a
+// DOM node, a list of DOM nodes that contains this node, a number
+// that represents the position of the single node in the list, and a
+// current set of variable bindings. (See XPath spec.)
+//
+// The interface of the expression context:
+//
+//   Constructor -- gets the node, its position, the node set it
+//   belongs to, and a parent context as arguments. The parent context
+//   is used to implement scoping rules for variables: if a variable
+//   is not found in the current context, it is looked for in the
+//   parent context, recursively. Except for node, all arguments have
+//   default values: default position is 0, default node set is the
+//   set that contains only the node, and the default parent is null.
+//
+//     Notice that position starts at 0 at the outside interface;
+//     inside XPath expressions this shows up as position()=1.
+//
+//   clone() -- creates a new context with the current context as
+//   parent. If passed as argument to clone(), the new context has a
+//   different node, position, or node set. What is not passed is
+//   inherited from the cloned context.
+//
+//   setVariable(name, expr) -- binds given XPath expression to the
+//   name.
+//
+//   getVariable(name) -- what the name says.
+//
+//   setNode(position) -- sets the context to the node at the given
+//   position. Needed to implement scoping rules for variables in
+//   XPath. (A variable is visible to all subsequent siblings, not
+//   only to its children.)
+//
+//   set/isCaseInsensitive -- specifies whether node name tests should
+//   be case sensitive.  If you're executing xpaths against a regular
+//   HTML DOM, you probably don't want case-sensitivity, because
+//   browsers tend to disagree about whether elements &amp; attributes
+//   should be upper/lower case.  If you're running xpaths in an
+//   XSLT instance, you probably DO want case sensitivity, as per the
+//   XSL spec.
+
+function ExprContext(node, opt_position, opt_nodelist, opt_parent,
+  opt_caseInsensitive, opt_ignoreAttributesWithoutValue,
+  opt_returnOnFirstMatch)
+{
+  this.node = node;
+  this.position = opt_position || 0;
+  this.nodelist = opt_nodelist || [ node ];
+  this.variables = {};
+  this.parent = opt_parent || null;
+  this.caseInsensitive = opt_caseInsensitive || false;
+  this.ignoreAttributesWithoutValue = opt_ignoreAttributesWithoutValue || false;
+  this.returnOnFirstMatch = opt_returnOnFirstMatch || false;
+  if (opt_parent) {
+    this.root = opt_parent.root;
+  } else if (this.node.nodeType == DOM_DOCUMENT_NODE) {
+    // NOTE(mesch): DOM Spec stipulates that the ownerDocument of a
+    // document is null. Our root, however is the document that we are
+    // processing, so the initial context is created from its document
+    // node, which case we must handle here explcitly.
+    this.root = node;
+  } else {
+    this.root = node.ownerDocument;
+  }
+}
+
+ExprContext.prototype.clone = function(opt_node, opt_position, opt_nodelist) {
+  return new ExprContext(
+      opt_node || this.node,
+      typeof opt_position != 'undefined' ? opt_position : this.position,
+      opt_nodelist || this.nodelist, this, this.caseInsensitive,
+      this.ignoreAttributesWithoutValue, this.returnOnFirstMatch);
+};
+
+ExprContext.prototype.setVariable = function(name, value) {
+  if (value instanceof StringValue || value instanceof BooleanValue || 
+    value instanceof NumberValue || value instanceof NodeSetValue) {
+    this.variables[name] = value;
+    return;
+  }
+  if ('true' === value) {
+    this.variables[name] = new BooleanValue(true);
+  } else if ('false' === value) {
+    this.variables[name] = new BooleanValue(false);
+  } else if (TOK_NUMBER.re.test(value)) {
+    this.variables[name] = new NumberValue(value);
+  } else {
+    // DGF What if it's null?
+    this.variables[name] = new StringValue(value);
+  }
+};
+
+ExprContext.prototype.getVariable = function(name) {
+  if (typeof this.variables[name] != 'undefined') {
+    return this.variables[name];
+
+  } else if (this.parent) {
+    return this.parent.getVariable(name);
+
+  } else {
+    return null;
+  }
+};
+
+ExprContext.prototype.setNode = function(position) {
+  this.node = this.nodelist[position];
+  this.position = position;
+};
+
+ExprContext.prototype.contextSize = function() {
+  return this.nodelist.length;
+};
+
+ExprContext.prototype.isCaseInsensitive = function() {
+  return this.caseInsensitive;
+};
+
+ExprContext.prototype.setCaseInsensitive = function(caseInsensitive) {
+  return this.caseInsensitive = caseInsensitive;
+};
+
+ExprContext.prototype.isIgnoreAttributesWithoutValue = function() {
+  return this.ignoreAttributesWithoutValue;
+};
+
+ExprContext.prototype.setIgnoreAttributesWithoutValue = function(ignore) {
+  return this.ignoreAttributesWithoutValue = ignore;
+};
+
+ExprContext.prototype.isReturnOnFirstMatch = function() {
+  return this.returnOnFirstMatch;
+};
+
+ExprContext.prototype.setReturnOnFirstMatch = function(returnOnFirstMatch) {
+  return this.returnOnFirstMatch = returnOnFirstMatch;
+};
+
+// XPath expression values. They are what XPath expressions evaluate
+// to. Strangely, the different value types are not specified in the
+// XPath syntax, but only in the semantics, so they don't show up as
+// nonterminals in the grammar. Yet, some expressions are required to
+// evaluate to particular types, and not every type can be coerced
+// into every other type. Although the types of XPath values are
+// similar to the types present in JavaScript, the type coercion rules
+// are a bit peculiar, so we explicitly model XPath types instead of
+// mapping them onto JavaScript types. (See XPath spec.)
+//
+// The four types are:
+//
+//   StringValue
+//
+//   NumberValue
+//
+//   BooleanValue
+//
+//   NodeSetValue
+//
+// The common interface of the value classes consists of methods that
+// implement the XPath type coercion rules:
+//
+//   stringValue() -- returns the value as a JavaScript String,
+//
+//   numberValue() -- returns the value as a JavaScript Number,
+//
+//   booleanValue() -- returns the value as a JavaScript Boolean,
+//
+//   nodeSetValue() -- returns the value as a JavaScript Array of DOM
+//   Node objects.
+//
+
+function StringValue(value) {
+  this.value = value;
+  this.type = 'string';
+}
+
+StringValue.prototype.stringValue = function() {
+  return this.value;
+}
+
+StringValue.prototype.booleanValue = function() {
+  return this.value.length &gt; 0;
+}
+
+StringValue.prototype.numberValue = function() {
+  return this.value - 0;
+}
+
+StringValue.prototype.nodeSetValue = function() {
+  throw this;
+}
+
+function BooleanValue(value) {
+  this.value = value;
+  this.type = 'boolean';
+}
+
+BooleanValue.prototype.stringValue = function() {
+  return '' + this.value;
+}
+
+BooleanValue.prototype.booleanValue = function() {
+  return this.value;
+}
+
+BooleanValue.prototype.numberValue = function() {
+  return this.value ? 1 : 0;
+}
+
+BooleanValue.prototype.nodeSetValue = function() {
+  throw this;
+}
+
+function NumberValue(value) {
+  this.value = value;
+  this.type = 'number';
+}
+
+NumberValue.prototype.stringValue = function() {
+  return '' + this.value;
+}
+
+NumberValue.prototype.booleanValue = function() {
+  return !!this.value;
+}
+
+NumberValue.prototype.numberValue = function() {
+  return this.value - 0;
+}
+
+NumberValue.prototype.nodeSetValue = function() {
+  throw this;
+}
+
+function NodeSetValue(value) {
+  this.value = value;
+  this.type = 'node-set';
+}
+
+NodeSetValue.prototype.stringValue = function() {
+  if (this.value.length == 0) {
+    return '';
+  } else {
+    return xmlValue(this.value[0]);
+  }
+}
+
+NodeSetValue.prototype.booleanValue = function() {
+  return this.value.length &gt; 0;
+}
+
+NodeSetValue.prototype.numberValue = function() {
+  return this.stringValue() - 0;
+}
+
+NodeSetValue.prototype.nodeSetValue = function() {
+  return this.value;
+};
+
+// XPath expressions. They are used as nodes in the parse tree and
+// possess an evaluate() method to compute an XPath value given an XPath
+// context. Expressions are returned from the parser. Teh set of
+// expression classes closely mirrors the set of non terminal symbols
+// in the grammar. Every non trivial nonterminal symbol has a
+// corresponding expression class.
+//
+// The common expression interface consists of the following methods:
+//
+// evaluate(context) -- evaluates the expression, returns a value.
+//
+// toString() -- returns the XPath text representation of the
+// expression (defined in xsltdebug.js).
+//
+// parseTree(indent) -- returns a parse tree representation of the
+// expression (defined in xsltdebug.js).
+
+function TokenExpr(m) {
+  this.value = m;
+}
+
+TokenExpr.prototype.evaluate = function() {
+  return new StringValue(this.value);
+};
+
+function LocationExpr() {
+  this.absolute = false;
+  this.steps = [];
+}
+
+LocationExpr.prototype.appendStep = function(s) {
+  var combinedStep = this._combineSteps(this.steps[this.steps.length-1], s);
+  if (combinedStep) {
+    this.steps[this.steps.length-1] = combinedStep;
+  } else {
+    this.steps.push(s);
+  }
+}
+
+LocationExpr.prototype.prependStep = function(s) {
+  var combinedStep = this._combineSteps(s, this.steps[0]);
+  if (combinedStep) {
+    this.steps[0] = combinedStep;
+  } else {
+    this.steps.unshift(s);
+  }
+};
+
+// DGF try to combine two steps into one step (perf enhancement)
+LocationExpr.prototype._combineSteps = function(prevStep, nextStep) {
+  if (!prevStep) return null;
+  if (!nextStep) return null;
+  var hasPredicates = (prevStep.predicates &amp;&amp; prevStep.predicates.length &gt; 0);
+  if (prevStep.nodetest instanceof NodeTestAny &amp;&amp; !hasPredicates) {
+    // maybe suitable to be combined
+    if (prevStep.axis == xpathAxis.DESCENDANT_OR_SELF) {
+      if (nextStep.axis == xpathAxis.CHILD) {
+        // HBC - commenting out, because this is not a valid reduction
+        //nextStep.axis = xpathAxis.DESCENDANT;
+        //return nextStep;
+      } else if (nextStep.axis == xpathAxis.SELF) {
+        nextStep.axis = xpathAxis.DESCENDANT_OR_SELF;
+        return nextStep;
+      }
+    } else if (prevStep.axis == xpathAxis.DESCENDANT) {
+      if (nextStep.axis == xpathAxis.SELF) {
+        nextStep.axis = xpathAxis.DESCENDANT;
+        return nextStep;
+      }
+    }
+  }
+  return null;
+}
+
+LocationExpr.prototype.evaluate = function(ctx) {
+  var start;
+  if (this.absolute) {
+    start = ctx.root;
+
+  } else {
+    start = ctx.node;
+  }
+
+  var nodes = [];
+  xPathStep(nodes, this.steps, 0, start, ctx);
+  return new NodeSetValue(nodes);
+};
+
+function xPathStep(nodes, steps, step, input, ctx) {
+  var s = steps[step];
+  var ctx2 = ctx.clone(input);
+  
+  if (ctx.returnOnFirstMatch &amp;&amp; !s.hasPositionalPredicate) {
+    var nodelist = s.evaluate(ctx2).nodeSetValue();
+    // the predicates were not processed in the last evaluate(), so that we can
+    // process them here with the returnOnFirstMatch optimization. We do a
+    // depth-first grab at any nodes that pass the predicate tests. There is no
+    // way to optimize when predicates contain positional selectors, including
+    // indexes or uses of the last() or position() functions, because they
+    // typically require the entire nodelist for context. Process without
+    // optimization if we encounter such selectors.
+    var nLength = nodelist.length;
+    var pLength = s.predicate.length;
+    nodelistLoop:
+    for (var i = 0; i &lt; nLength; ++i) {
+      var n = nodelist[i];
+      for (var j = 0; j &lt; pLength; ++j) {
+        if (!s.predicate[j].evaluate(ctx.clone(n, i, nodelist)).booleanValue()) {
+          continue nodelistLoop;
+        }
+      }
+      // n survived the predicate tests!
+      if (step == steps.length - 1) {
+        nodes.push(n);
+      }
+      else {
+        xPathStep(nodes, steps, step + 1, n, ctx);
+      }
+      if (nodes.length &gt; 0) {
+        break;
+      }
+    }
+  }
+  else {
+    // set returnOnFirstMatch to false for the cloned ExprContext, because
+    // behavior in StepExpr.prototype.evaluate is driven off its value. Note
+    // that the original context may still have true for this value.
+    ctx2.returnOnFirstMatch = false;
+    var nodelist = s.evaluate(ctx2).nodeSetValue();
+    for (var i = 0; i &lt; nodelist.length; ++i) {
+      if (step == steps.length - 1) {
+        nodes.push(nodelist[i]);
+      } else {
+        xPathStep(nodes, steps, step + 1, nodelist[i], ctx);
+      }
+    }
+  }
+}
+
+function StepExpr(axis, nodetest, opt_predicate) {
+  this.axis = axis;
+  this.nodetest = nodetest;
+  this.predicate = opt_predicate || [];
+  this.hasPositionalPredicate = false;
+  for (var i = 0; i &lt; this.predicate.length; ++i) {
+    if (predicateExprHasPositionalSelector(this.predicate[i].expr)) {
+      this.hasPositionalPredicate = true;
+      break;
+    }
+  }
+}
+
+StepExpr.prototype.appendPredicate = function(p) {
+  this.predicate.push(p);
+  if (!this.hasPositionalPredicate) {
+    this.hasPositionalPredicate = predicateExprHasPositionalSelector(p.expr);
+  }
+}
+
+StepExpr.prototype.evaluate = function(ctx) {
+  var input = ctx.node;
+  var nodelist = [];
+  var skipNodeTest = false;
+  
+  if (this.nodetest instanceof NodeTestAny) {
+    skipNodeTest = true;
+  }
+
+  // NOTE(mesch): When this was a switch() statement, it didn't work
+  // in Safari/2.0. Not sure why though; it resulted in the JavaScript
+  // console output &quot;undefined&quot; (without any line number or so).
+
+  if (this.axis ==  xpathAxis.ANCESTOR_OR_SELF) {
+    nodelist.push(input);
+    for (var n = input.parentNode; n; n = n.parentNode) {
+      nodelist.push(n);
+    }
+
+  } else if (this.axis == xpathAxis.ANCESTOR) {
+    for (var n = input.parentNode; n; n = n.parentNode) {
+      nodelist.push(n);
+    }
+
+  } else if (this.axis == xpathAxis.ATTRIBUTE) {
+    if (this.nodetest.name != undefined) {
+      // single-attribute step
+      if (input.attributes) {
+        if (input.attributes instanceof Array) {
+          // probably evaluating on document created by xmlParse()
+          copyArray(nodelist, input.attributes);
+        }
+        else {
+          if (this.nodetest.name == 'style') {
+            var value = input.getAttribute('style');
+            if (value &amp;&amp; typeof(value) != 'string') {
+              // this is the case where indexing into the attributes array
+              // doesn't give us the attribute node in IE - we create our own
+              // node instead
+              nodelist.push(XNode.create(DOM_ATTRIBUTE_NODE, 'style',
+                value.cssText, document));
+            }
+            else {
+              nodelist.push(input.attributes[this.nodetest.name]);
+            }
+          }
+          else {
+            nodelist.push(input.attributes[this.nodetest.name]);
+          }
+        }
+      }
+    }
+    else {
+      // all-attributes step
+      if (ctx.ignoreAttributesWithoutValue) {
+        copyArrayIgnoringAttributesWithoutValue(nodelist, input.attributes);
+      }
+      else {
+        copyArray(nodelist, input.attributes);
+      }
+    }
+    
+  } else if (this.axis == xpathAxis.CHILD) {
+    copyArray(nodelist, input.childNodes);
+
+  } else if (this.axis == xpathAxis.DESCENDANT_OR_SELF) {
+    if (this.nodetest.evaluate(ctx).booleanValue()) {
+      nodelist.push(input);
+    }
+    var tagName = xpathExtractTagNameFromNodeTest(this.nodetest);
+    xpathCollectDescendants(nodelist, input, tagName);
+    if (tagName) skipNodeTest = true;
+
+  } else if (this.axis == xpathAxis.DESCENDANT) {
+    var tagName = xpathExtractTagNameFromNodeTest(this.nodetest);
+    xpathCollectDescendants(nodelist, input, tagName);
+    if (tagName) skipNodeTest = true;
+
+  } else if (this.axis == xpathAxis.FOLLOWING) {
+    for (var n = input; n; n = n.parentNode) {
+      for (var nn = n.nextSibling; nn; nn = nn.nextSibling) {
+        nodelist.push(nn);
+        xpathCollectDescendants(nodelist, nn);
+      }
+    }
+
+  } else if (this.axis == xpathAxis.FOLLOWING_SIBLING) {
+    for (var n = input.nextSibling; n; n = n.nextSibling) {
+      nodelist.push(n);
+    }
+
+  } else if (this.axis == xpathAxis.NAMESPACE) {
+    alert('not implemented: axis namespace');
+
+  } else if (this.axis == xpathAxis.PARENT) {
+    if (input.parentNode) {
+      nodelist.push(input.parentNode);
+    }
+
+  } else if (this.axis == xpathAxis.PRECEDING) {
+    for (var n = input; n; n = n.parentNode) {
+      for (var nn = n.previousSibling; nn; nn = nn.previousSibling) {
+        nodelist.push(nn);
+        xpathCollectDescendantsReverse(nodelist, nn);
+      }
+    }
+
+  } else if (this.axis == xpathAxis.PRECEDING_SIBLING) {
+    for (var n = input.previousSibling; n; n = n.previousSibling) {
+      nodelist.push(n);
+    }
+
+  } else if (this.axis == xpathAxis.SELF) {
+    nodelist.push(input);
+
+  } else {
+    throw 'ERROR -- NO SUCH AXIS: ' + this.axis;
+  }
+
+  if (!skipNodeTest) {
+    // process node test
+    var nodelist0 = nodelist;
+    nodelist = [];
+    for (var i = 0; i &lt; nodelist0.length; ++i) {
+      var n = nodelist0[i];
+      if (this.nodetest.evaluate(ctx.clone(n, i, nodelist0)).booleanValue()) {
+        nodelist.push(n);
+      }
+    }
+  }
+
+  // process predicates
+  if (!ctx.returnOnFirstMatch) {
+    for (var i = 0; i &lt; this.predicate.length; ++i) {
+      var nodelist0 = nodelist;
+      nodelist = [];
+      for (var ii = 0; ii &lt; nodelist0.length; ++ii) {
+        var n = nodelist0[ii];
+        if (this.predicate[i].evaluate(ctx.clone(n, ii, nodelist0)).booleanValue()) {
+          nodelist.push(n);
+        }
+      }
+    }
+  }
+
+  return new NodeSetValue(nodelist);
+};
+
+function NodeTestAny() {
+  this.value = new BooleanValue(true);
+}
+
+NodeTestAny.prototype.evaluate = function(ctx) {
+  return this.value;
+};
+
+function NodeTestElementOrAttribute() {}
+
+NodeTestElementOrAttribute.prototype.evaluate = function(ctx) {
+  return new BooleanValue(
+      ctx.node.nodeType == DOM_ELEMENT_NODE ||
+      ctx.node.nodeType == DOM_ATTRIBUTE_NODE);
+}
+
+function NodeTestText() {}
+
+NodeTestText.prototype.evaluate = function(ctx) {
+  return new BooleanValue(ctx.node.nodeType == DOM_TEXT_NODE);
+}
+
+function NodeTestComment() {}
+
+NodeTestComment.prototype.evaluate = function(ctx) {
+  return new BooleanValue(ctx.node.nodeType == DOM_COMMENT_NODE);
+}
+
+function NodeTestPI(target) {
+  this.target = target;
+}
+
+NodeTestPI.prototype.evaluate = function(ctx) {
+  return new
+  BooleanValue(ctx.node.nodeType == DOM_PROCESSING_INSTRUCTION_NODE &amp;&amp;
+               (!this.target || ctx.node.nodeName == this.target));
+}
+
+function NodeTestNC(nsprefix) {
+  this.regex = new RegExp(&quot;^&quot; + nsprefix + &quot;:&quot;);
+  this.nsprefix = nsprefix;
+}
+
+NodeTestNC.prototype.evaluate = function(ctx) {
+  var n = ctx.node;
+  return new BooleanValue(this.regex.match(n.nodeName));
+}
+
+function NodeTestName(name) {
+  this.name = name;
+  this.re = new RegExp('^' + name + '$', &quot;i&quot;);
+}
+
+NodeTestName.prototype.evaluate = function(ctx) {
+  var n = ctx.node;
+  if (ctx.caseInsensitive) {
+    if (n.nodeName.length != this.name.length) return new BooleanValue(false);
+    return new BooleanValue(this.re.test(n.nodeName));
+  } else {
+    return new BooleanValue(n.nodeName == this.name);
+  }
+}
+
+function PredicateExpr(expr) {
+  this.expr = expr;
+}
+
+PredicateExpr.prototype.evaluate = function(ctx) {
+  var v = this.expr.evaluate(ctx);
+  if (v.type == 'number') {
+    // NOTE(mesch): Internally, position is represented starting with
+    // 0, however in XPath position starts with 1. See functions
+    // position() and last().
+    return new BooleanValue(ctx.position == v.numberValue() - 1);
+  } else {
+    return new BooleanValue(v.booleanValue());
+  }
+};
+
+function FunctionCallExpr(name) {
+  this.name = name;
+  this.args = [];
+}
+
+FunctionCallExpr.prototype.appendArg = function(arg) {
+  this.args.push(arg);
+};
+
+FunctionCallExpr.prototype.evaluate = function(ctx) {
+  var fn = '' + this.name.value;
+  var f = this.xpathfunctions[fn];
+  if (f) {
+    return f.call(this, ctx);
+  } else {
+    xpathLog('XPath NO SUCH FUNCTION ' + fn);
+    return new BooleanValue(false);
+  }
+};
+
+FunctionCallExpr.prototype.xpathfunctions = {
+  'last': function(ctx) {
+    assert(this.args.length == 0);
+    // NOTE(mesch): XPath position starts at 1.
+    return new NumberValue(ctx.contextSize());
+  },
+
+  'position': function(ctx) {
+    assert(this.args.length == 0);
+    // NOTE(mesch): XPath position starts at 1.
+    return new NumberValue(ctx.position + 1);
+  },
+
+  'count': function(ctx) {
+    assert(this.args.length == 1);
+    var v = this.args[0].evaluate(ctx);
+    return new NumberValue(v.nodeSetValue().length);
+  },
+
+  'id': function(ctx) {
+    assert(this.args.length == 1);
+    var e = this.args[0].evaluate(ctx);
+    var ret = [];
+    var ids;
+    if (e.type == 'node-set') {
+      ids = [];
+      var en = e.nodeSetValue();
+      for (var i = 0; i &lt; en.length; ++i) {
+        var v = xmlValue(en[i]).split(/\s+/);
+        for (var ii = 0; ii &lt; v.length; ++ii) {
+          ids.push(v[ii]);
+        }
+      }
+    } else {
+      ids = e.stringValue().split(/\s+/);
+    }
+    var d = ctx.root;
+    for (var i = 0; i &lt; ids.length; ++i) {
+      var n = d.getElementById(ids[i]);
+      if (n) {
+        ret.push(n);
+      }
+    }
+    return new NodeSetValue(ret);
+  },
+
+  'local-name': function(ctx) {
+    alert('not implmented yet: XPath function local-name()');
+  },
+
+  'namespace-uri': function(ctx) {
+    alert('not implmented yet: XPath function namespace-uri()');
+  },
+
+  'name': function(ctx) {
+    assert(this.args.length == 1 || this.args.length == 0);
+    var n;
+    if (this.args.length == 0) {
+      n = [ ctx.node ];
+    } else {
+      n = this.args[0].evaluate(ctx).nodeSetValue();
+    }
+
+    if (n.length == 0) {
+      return new StringValue('');
+    } else {
+      return new StringValue(n[0].nodeName);
+    }
+  },
+
+  'string':  function(ctx) {
+    assert(this.args.length == 1 || this.args.length == 0);
+    if (this.args.length == 0) {
+      return new StringValue(new NodeSetValue([ ctx.node ]).stringValue());
+    } else {
+      return new StringValue(this.args[0].evaluate(ctx).stringValue());
+    }
+  },
+
+  'concat': function(ctx) {
+    var ret = '';
+    for (var i = 0; i &lt; this.args.length; ++i) {
+      ret += this.args[i].evaluate(ctx).stringValue();
+    }
+    return new StringValue(ret);
+  },
+
+  'starts-with': function(ctx) {
+    assert(this.args.length == 2);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).stringValue();
+    return new BooleanValue(s0.indexOf(s1) == 0);
+  },
+  
+  'ends-with': function(ctx) {
+    assert(this.args.length == 2);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).stringValue();
+    var re = new RegExp(RegExp.escape(s1) + '$');
+    return new BooleanValue(re.test(s0));
+  },
+
+  'contains': function(ctx) {
+    assert(this.args.length == 2);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).stringValue();
+    return new BooleanValue(s0.indexOf(s1) != -1);
+  },
+
+  'substring-before': function(ctx) {
+    assert(this.args.length == 2);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).stringValue();
+    var i = s0.indexOf(s1);
+    var ret;
+    if (i == -1) {
+      ret = '';
+    } else {
+      ret = s0.substr(0,i);
+    }
+    return new StringValue(ret);
+  },
+
+  'substring-after': function(ctx) {
+    assert(this.args.length == 2);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).stringValue();
+    var i = s0.indexOf(s1);
+    var ret;
+    if (i == -1) {
+      ret = '';
+    } else {
+      ret = s0.substr(i + s1.length);
+    }
+    return new StringValue(ret);
+  },
+
+  'substring': function(ctx) {
+    // NOTE: XPath defines the position of the first character in a
+    // string to be 1, in JavaScript this is 0 ([XPATH] Section 4.2).
+    assert(this.args.length == 2 || this.args.length == 3);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).numberValue();
+    var ret;
+    if (this.args.length == 2) {
+      var i1 = Math.max(0, Math.round(s1) - 1);
+      ret = s0.substr(i1);
+
+    } else {
+      var s2 = this.args[2].evaluate(ctx).numberValue();
+      var i0 = Math.round(s1) - 1;
+      var i1 = Math.max(0, i0);
+      var i2 = Math.round(s2) - Math.max(0, -i0);
+      ret = s0.substr(i1, i2);
+    }
+    return new StringValue(ret);
+  },
+
+  'string-length': function(ctx) {
+    var s;
+    if (this.args.length &gt; 0) {
+      s = this.args[0].evaluate(ctx).stringValue();
+    } else {
+      s = new NodeSetValue([ ctx.node ]).stringValue();
+    }
+    return new NumberValue(s.length);
+  },
+
+  'normalize-space': function(ctx) {
+    var s;
+    if (this.args.length &gt; 0) {
+      s = this.args[0].evaluate(ctx).stringValue();
+    } else {
+      s = new NodeSetValue([ ctx.node ]).stringValue();
+    }
+    s = s.replace(/^\s*/,'').replace(/\s*$/,'').replace(/\s+/g, ' ');
+    return new StringValue(s);
+  },
+
+  'translate': function(ctx) {
+    assert(this.args.length == 3);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).stringValue();
+    var s2 = this.args[2].evaluate(ctx).stringValue();
+
+    for (var i = 0; i &lt; s1.length; ++i) {
+      s0 = s0.replace(new RegExp(s1.charAt(i), 'g'), s2.charAt(i));
+    }
+    return new StringValue(s0);
+  },
+  
+  'matches': function(ctx) {
+    assert(this.args.length &gt;= 2);
+    var s0 = this.args[0].evaluate(ctx).stringValue();
+    var s1 = this.args[1].evaluate(ctx).stringValue();
+    if (this.args.length &gt; 2) {
+      var s2 = this.args[2].evaluate(ctx).stringValue();
+      if (/[^mi]/.test(s2)) {
+        throw 'Invalid regular expression syntax: ' + s2;
+      }
+    }
+    
+    try {
+      var re = new RegExp(s1, s2);
+    }
+    catch (e) {
+      throw 'Invalid matches argument: ' + s1;
+    }
+    return new BooleanValue(re.test(s0));
+  },
+
+  'boolean': function(ctx) {
+    assert(this.args.length == 1);
+    return new BooleanValue(this.args[0].evaluate(ctx).booleanValue());
+  },
+
+  'not': function(ctx) {
+    assert(this.args.length == 1);
+    var ret = !this.args[0].evaluate(ctx).booleanValue();
+    return new BooleanValue(ret);
+  },
+
+  'true': function(ctx) {
+    assert(this.args.length == 0);
+    return new BooleanValue(true);
+  },
+
+  'false': function(ctx) {
+    assert(this.args.length == 0);
+    return new BooleanValue(false);
+  },
+
+  'lang': function(ctx) {
+    assert(this.args.length == 1);
+    var lang = this.args[0].evaluate(ctx).stringValue();
+    var xmllang;
+    var n = ctx.node;
+    while (n &amp;&amp; n != n.parentNode /* just in case ... */) {
+      xmllang = n.getAttribute('xml:lang');
+      if (xmllang) {
+        break;
+      }
+      n = n.parentNode;
+    }
+    if (!xmllang) {
+      return new BooleanValue(false);
+    } else {
+      var re = new RegExp('^' + lang + '$', 'i');
+      return new BooleanValue(xmllang.match(re) ||
+                              xmllang.replace(/_.*$/,'').match(re));
+    }
+  },
+
+  'number': function(ctx) {
+    assert(this.args.length == 1 || this.args.length == 0);
+
+    if (this.args.length == 1) {
+      return new NumberValue(this.args[0].evaluate(ctx).numberValue());
+    } else {
+      return new NumberValue(new NodeSetValue([ ctx.node ]).numberValue());
+    }
+  },
+
+  'sum': function(ctx) {
+    assert(this.args.length == 1);
+    var n = this.args[0].evaluate(ctx).nodeSetValue();
+    var sum = 0;
+    for (var i = 0; i &lt; n.length; ++i) {
+      sum += xmlValue(n[i]) - 0;
+    }
+    return new NumberValue(sum);
+  },
+
+  'floor': function(ctx) {
+    assert(this.args.length == 1);
+    var num = this.args[0].evaluate(ctx).numberValue();
+    return new NumberValue(Math.floor(num));
+  },
+
+  'ceiling': function(ctx) {
+    assert(this.args.length == 1);
+    var num = this.args[0].evaluate(ctx).numberValue();
+    return new NumberValue(Math.ceil(num));
+  },
+
+  'round': function(ctx) {
+    assert(this.args.length == 1);
+    var num = this.args[0].evaluate(ctx).numberValue();
+    return new NumberValue(Math.round(num));
+  },
+
+  // TODO(mesch): The following functions are custom. There is a
+  // standard that defines how to add functions, which should be
+  // applied here.
+
+  'ext-join': function(ctx) {
+    assert(this.args.length == 2);
+    var nodes = this.args[0].evaluate(ctx).nodeSetValue();
+    var delim = this.args[1].evaluate(ctx).stringValue();
+    var ret = '';
+    for (var i = 0; i &lt; nodes.length; ++i) {
+      if (ret) {
+        ret += delim;
+      }
+      ret += xmlValue(nodes[i]);
+    }
+    return new StringValue(ret);
+  },
+
+  // ext-if() evaluates and returns its second argument, if the
+  // boolean value of its first argument is true, otherwise it
+  // evaluates and returns its third argument.
+
+  'ext-if': function(ctx) {
+    assert(this.args.length == 3);
+    if (this.args[0].evaluate(ctx).booleanValue()) {
+      return this.args[1].evaluate(ctx);
+    } else {
+      return this.args[2].evaluate(ctx);
+    }
+  },
+
+  // ext-cardinal() evaluates its single argument as a number, and
+  // returns the current node that many times. It can be used in the
+  // select attribute to iterate over an integer range.
+
+  'ext-cardinal': function(ctx) {
+    assert(this.args.length &gt;= 1);
+    var c = this.args[0].evaluate(ctx).numberValue();
+    var ret = [];
+    for (var i = 0; i &lt; c; ++i) {
+      ret.push(ctx.node);
+    }
+    return new NodeSetValue(ret);
+  }
+};
+
+function UnionExpr(expr1, expr2) {
+  this.expr1 = expr1;
+  this.expr2 = expr2;
+}
+
+UnionExpr.prototype.evaluate = function(ctx) {
+  var nodes1 = this.expr1.evaluate(ctx).nodeSetValue();
+  var nodes2 = this.expr2.evaluate(ctx).nodeSetValue();
+  var I1 = nodes1.length;
+  for (var i2 = 0; i2 &lt; nodes2.length; ++i2) {
+    var n = nodes2[i2];
+    var inBoth = false;
+    for (var i1 = 0; i1 &lt; I1; ++i1) {
+      if (nodes1[i1] == n) {
+        inBoth = true;
+        i1 = I1; // break inner loop
+      }
+    }
+    if (!inBoth) {
+      nodes1.push(n);
+    }
+  }
+  return new NodeSetValue(nodes1);
+};
+
+function PathExpr(filter, rel) {
+  this.filter = filter;
+  this.rel = rel;
+}
+
+PathExpr.prototype.evaluate = function(ctx) {
+  // the filter expression should be evaluated in its entirety with no
+  // optimization, as we can't backtrack to it after having moved on to
+  // evaluating the relative location path
+  var flag = ctx.returnOnFirstMatch;
+  ctx.setReturnOnFirstMatch(false);
+  var nodes = this.filter.evaluate(ctx).nodeSetValue();
+  ctx.setReturnOnFirstMatch(flag);
+  
+  var nodes1 = [];
+  if (ctx.returnOnFirstMatch) {
+    for (var i = 0; i &lt; nodes.length; ++i) {
+      nodes1 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
+      if (nodes1.length &gt; 0) {
+        break;
+      }
+    }
+    return new NodeSetValue(nodes1);
+  }
+  else {
+    for (var i = 0; i &lt; nodes.length; ++i) {
+      var nodes0 = this.rel.evaluate(ctx.clone(nodes[i], i, nodes)).nodeSetValue();
+      for (var ii = 0; ii &lt; nodes0.length; ++ii) {
+        nodes1.push(nodes0[ii]);
+      }
+    }
+    return new NodeSetValue(nodes1);
+  }
+};
+
+function FilterExpr(expr, predicate) {
+  this.expr = expr;
+  this.predicate = predicate;
+}
+
+FilterExpr.prototype.evaluate = function(ctx) {
+  var nodes = this.expr.evaluate(ctx).nodeSetValue();
+  for (var i = 0; i &lt; this.predicate.length; ++i) {
+    var nodes0 = nodes;
+    nodes = [];
+    for (var j = 0; j &lt; nodes0.length; ++j) {
+      var n = nodes0[j];
+      if (this.predicate[i].evaluate(ctx.clone(n, j, nodes0)).booleanValue()) {
+        nodes.push(n);
+      }
+    }
+  }
+
+  return new NodeSetValue(nodes);
+}
+
+function UnaryMinusExpr(expr) {
+  this.expr = expr;
+}
+
+UnaryMinusExpr.prototype.evaluate = function(ctx) {
+  return new NumberValue(-this.expr.evaluate(ctx).numberValue());
+};
+
+function BinaryExpr(expr1, op, expr2) {
+  this.expr1 = expr1;
+  this.expr2 = expr2;
+  this.op = op;
+}
+
+BinaryExpr.prototype.evaluate = function(ctx) {
+  var ret;
+  switch (this.op.value) {
+    case 'or':
+      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() ||
+                             this.expr2.evaluate(ctx).booleanValue());
+      break;
+
+    case 'and':
+      ret = new BooleanValue(this.expr1.evaluate(ctx).booleanValue() &amp;&amp;
+                             this.expr2.evaluate(ctx).booleanValue());
+      break;
+
+    case '+':
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() +
+                            this.expr2.evaluate(ctx).numberValue());
+      break;
+
+    case '-':
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() -
+                            this.expr2.evaluate(ctx).numberValue());
+      break;
+
+    case '*':
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() *
+                            this.expr2.evaluate(ctx).numberValue());
+      break;
+
+    case 'mod':
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() %
+                            this.expr2.evaluate(ctx).numberValue());
+      break;
+
+    case 'div':
+      ret = new NumberValue(this.expr1.evaluate(ctx).numberValue() /
+                            this.expr2.evaluate(ctx).numberValue());
+      break;
+
+    case '=':
+      ret = this.compare(ctx, function(x1, x2) { return x1 == x2; });
+      break;
+
+    case '!=':
+      ret = this.compare(ctx, function(x1, x2) { return x1 != x2; });
+      break;
+
+    case '&lt;':
+      ret = this.compare(ctx, function(x1, x2) { return x1 &lt; x2; });
+      break;
+
+    case '&lt;=':
+      ret = this.compare(ctx, function(x1, x2) { return x1 &lt;= x2; });
+      break;
+
+    case '&gt;':
+      ret = this.compare(ctx, function(x1, x2) { return x1 &gt; x2; });
+      break;
+
+    case '&gt;=':
+      ret = this.compare(ctx, function(x1, x2) { return x1 &gt;= x2; });
+      break;
+
+    default:
+      alert('BinaryExpr.evaluate: ' + this.op.value);
+  }
+  return ret;
+};
+
+BinaryExpr.prototype.compare = function(ctx, cmp) {
+  var v1 = this.expr1.evaluate(ctx);
+  var v2 = this.expr2.evaluate(ctx);
+
+  var ret;
+  if (v1.type == 'node-set' &amp;&amp; v2.type == 'node-set') {
+    var n1 = v1.nodeSetValue();
+    var n2 = v2.nodeSetValue();
+    ret = false;
+    for (var i1 = 0; i1 &lt; n1.length; ++i1) {
+      for (var i2 = 0; i2 &lt; n2.length; ++i2) {
+        if (cmp(xmlValue(n1[i1]), xmlValue(n2[i2]))) {
+          ret = true;
+          // Break outer loop. Labels confuse the jscompiler and we
+          // don't use them.
+          i2 = n2.length;
+          i1 = n1.length;
+        }
+      }
+    }
+
+  } else if (v1.type == 'node-set' || v2.type == 'node-set') {
+
+    if (v1.type == 'number') {
+      var s = v1.numberValue();
+      var n = v2.nodeSetValue();
+
+      ret = false;
+      for (var i = 0;  i &lt; n.length; ++i) {
+        var nn = xmlValue(n[i]) - 0;
+        if (cmp(s, nn)) {
+          ret = true;
+          break;
+        }
+      }
+
+    } else if (v2.type == 'number') {
+      var n = v1.nodeSetValue();
+      var s = v2.numberValue();
+
+      ret = false;
+      for (var i = 0;  i &lt; n.length; ++i) {
+        var nn = xmlValue(n[i]) - 0;
+        if (cmp(nn, s)) {
+          ret = true;
+          break;
+        }
+      }
+
+    } else if (v1.type == 'string') {
+      var s = v1.stringValue();
+      var n = v2.nodeSetValue();
+
+      ret = false;
+      for (var i = 0;  i &lt; n.length; ++i) {
+        var nn = xmlValue(n[i]);
+        if (cmp(s, nn)) {
+          ret = true;
+          break;
+        }
+      }
+
+    } else if (v2.type == 'string') {
+      var n = v1.nodeSetValue();
+      var s = v2.stringValue();
+
+      ret = false;
+      for (var i = 0;  i &lt; n.length; ++i) {
+        var nn = xmlValue(n[i]);
+        if (cmp(nn, s)) {
+          ret = true;
+          break;
+        }
+      }
+
+    } else {
+      ret = cmp(v1.booleanValue(), v2.booleanValue());
+    }
+
+  } else if (v1.type == 'boolean' || v2.type == 'boolean') {
+    ret = cmp(v1.booleanValue(), v2.booleanValue());
+
+  } else if (v1.type == 'number' || v2.type == 'number') {
+    ret = cmp(v1.numberValue(), v2.numberValue());
+
+  } else {
+    ret = cmp(v1.stringValue(), v2.stringValue());
+  }
+
+  return new BooleanValue(ret);
+}
+
+function LiteralExpr(value) {
+  this.value = value;
+}
+
+LiteralExpr.prototype.evaluate = function(ctx) {
+  return new StringValue(this.value);
+};
+
+function NumberExpr(value) {
+  this.value = value;
+}
+
+NumberExpr.prototype.evaluate = function(ctx) {
+  return new NumberValue(this.value);
+};
+
+function VariableExpr(name) {
+  this.name = name;
+}
+
+VariableExpr.prototype.evaluate = function(ctx) {
+  return ctx.getVariable(this.name);
+}
+
+// Factory functions for semantic values (i.e. Expressions) of the
+// productions in the grammar. When a production is matched to reduce
+// the current parse state stack, the function is called with the
+// semantic values of the matched elements as arguments, and returns
+// another semantic value. The semantic value is a node of the parse
+// tree, an expression object with an evaluate() method that evaluates the
+// expression in an actual context. These factory functions are used
+// in the specification of the grammar rules, below.
+
+function makeTokenExpr(m) {
+  return new TokenExpr(m);
+}
+
+function passExpr(e) {
+  return e;
+}
+
+function makeLocationExpr1(slash, rel) {
+  rel.absolute = true;
+  return rel;
+}
+
+function makeLocationExpr2(dslash, rel) {
+  rel.absolute = true;
+  rel.prependStep(makeAbbrevStep(dslash.value));
+  return rel;
+}
+
+function makeLocationExpr3(slash) {
+  var ret = new LocationExpr();
+  ret.appendStep(makeAbbrevStep('.'));
+  ret.absolute = true;
+  return ret;
+}
+
+function makeLocationExpr4(dslash) {
+  var ret = new LocationExpr();
+  ret.absolute = true;
+  ret.appendStep(makeAbbrevStep(dslash.value));
+  return ret;
+}
+
+function makeLocationExpr5(step) {
+  var ret = new LocationExpr();
+  ret.appendStep(step);
+  return ret;
+}
+
+function makeLocationExpr6(rel, slash, step) {
+  rel.appendStep(step);
+  return rel;
+}
+
+function makeLocationExpr7(rel, dslash, step) {
+  rel.appendStep(makeAbbrevStep(dslash.value));
+  rel.appendStep(step);
+  return rel;
+}
+
+function makeStepExpr1(dot) {
+  return makeAbbrevStep(dot.value);
+}
+
+function makeStepExpr2(ddot) {
+  return makeAbbrevStep(ddot.value);
+}
+
+function makeStepExpr3(axisname, axis, nodetest) {
+  return new StepExpr(axisname.value, nodetest);
+}
+
+function makeStepExpr4(at, nodetest) {
+  return new StepExpr('attribute', nodetest);
+}
+
+function makeStepExpr5(nodetest) {
+  return new StepExpr('child', nodetest);
+}
+
+function makeStepExpr6(step, predicate) {
+  step.appendPredicate(predicate);
+  return step;
+}
+
+function makeAbbrevStep(abbrev) {
+  switch (abbrev) {
+  case '//':
+    return new StepExpr('descendant-or-self', new NodeTestAny);
+
+  case '.':
+    return new StepExpr('self', new NodeTestAny);
+
+  case '..':
+    return new StepExpr('parent', new NodeTestAny);
+  }
+}
+
+function makeNodeTestExpr1(asterisk) {
+  return new NodeTestElementOrAttribute;
+}
+
+function makeNodeTestExpr2(ncname, colon, asterisk) {
+  return new NodeTestNC(ncname.value);
+}
+
+function makeNodeTestExpr3(qname) {
+  return new NodeTestName(qname.value);
+}
+
+function makeNodeTestExpr4(typeo, parenc) {
+  var type = typeo.value.replace(/\s*\($/, '');
+  switch(type) {
+  case 'node':
+    return new NodeTestAny;
+
+  case 'text':
+    return new NodeTestText;
+
+  case 'comment':
+    return new NodeTestComment;
+
+  case 'processing-instruction':
+    return new NodeTestPI('');
+  }
+}
+
+function makeNodeTestExpr5(typeo, target, parenc) {
+  var type = typeo.replace(/\s*\($/, '');
+  if (type != 'processing-instruction') {
+    throw type;
+  }
+  return new NodeTestPI(target.value);
+}
+
+function makePredicateExpr(pareno, expr, parenc) {
+  return new PredicateExpr(expr);
+}
+
+function makePrimaryExpr(pareno, expr, parenc) {
+  return expr;
+}
+
+function makeFunctionCallExpr1(name, pareno, parenc) {
+  return new FunctionCallExpr(name);
+}
+
+function makeFunctionCallExpr2(name, pareno, arg1, args, parenc) {
+  var ret = new FunctionCallExpr(name);
+  ret.appendArg(arg1);
+  for (var i = 0; i &lt; args.length; ++i) {
+    ret.appendArg(args[i]);
+  }
+  return ret;
+}
+
+function makeArgumentExpr(comma, expr) {
+  return expr;
+}
+
+function makeUnionExpr(expr1, pipe, expr2) {
+  return new UnionExpr(expr1, expr2);
+}
+
+function makePathExpr1(filter, slash, rel) {
+  return new PathExpr(filter, rel);
+}
+
+function makePathExpr2(filter, dslash, rel) {
+  rel.prependStep(makeAbbrevStep(dslash.value));
+  return new PathExpr(filter, rel);
+}
+
+function makeFilterExpr(expr, predicates) {
+  if (predicates.length &gt; 0) {
+    return new FilterExpr(expr, predicates);
+  } else {
+    return expr;
+  }
+}
+
+function makeUnaryMinusExpr(minus, expr) {
+  return new UnaryMinusExpr(expr);
+}
+
+function makeBinaryExpr(expr1, op, expr2) {
+  return new BinaryExpr(expr1, op, expr2);
+}
+
+function makeLiteralExpr(token) {
+  // remove quotes from the parsed value:
+  var value = token.value.substring(1, token.value.length - 1);
+  return new LiteralExpr(value);
+}
+
+function makeNumberExpr(token) {
+  return new NumberExpr(token.value);
+}
+
+function makeVariableReference(dollar, name) {
+  return new VariableExpr(name.value);
+}
+
+// Used before parsing for optimization of common simple cases. See
+// the begin of xpathParse() for which they are.
+function makeSimpleExpr(expr) {
+  if (expr.charAt(0) == '$') {
+    return new VariableExpr(expr.substr(1));
+  } else if (expr.charAt(0) == '@') {
+    var a = new NodeTestName(expr.substr(1));
+    var b = new StepExpr('attribute', a);
+    var c = new LocationExpr();
+    c.appendStep(b);
+    return c;
+  } else if (expr.match(/^[0-9]+$/)) {
+    return new NumberExpr(expr);
+  } else {
+    var a = new NodeTestName(expr);
+    var b = new StepExpr('child', a);
+    var c = new LocationExpr();
+    c.appendStep(b);
+    return c;
+  }
+}
+
+function makeSimpleExpr2(expr) {
+  var steps = stringSplit(expr, '/');
+  var c = new LocationExpr();
+  for (var i = 0; i &lt; steps.length; ++i) {
+    var a = new NodeTestName(steps[i]);
+    var b = new StepExpr('child', a);
+    c.appendStep(b);
+  }
+  return c;
+}
+
+// The axes of XPath expressions.
+
+var xpathAxis = {
+  ANCESTOR_OR_SELF: 'ancestor-or-self',
+  ANCESTOR: 'ancestor',
+  ATTRIBUTE: 'attribute',
+  CHILD: 'child',
+  DESCENDANT_OR_SELF: 'descendant-or-self',
+  DESCENDANT: 'descendant',
+  FOLLOWING_SIBLING: 'following-sibling',
+  FOLLOWING: 'following',
+  NAMESPACE: 'namespace',
+  PARENT: 'parent',
+  PRECEDING_SIBLING: 'preceding-sibling',
+  PRECEDING: 'preceding',
+  SELF: 'self'
+};
+
+var xpathAxesRe = [
+    xpathAxis.ANCESTOR_OR_SELF,
+    xpathAxis.ANCESTOR,
+    xpathAxis.ATTRIBUTE,
+    xpathAxis.CHILD,
+    xpathAxis.DESCENDANT_OR_SELF,
+    xpathAxis.DESCENDANT,
+    xpathAxis.FOLLOWING_SIBLING,
+    xpathAxis.FOLLOWING,
+    xpathAxis.NAMESPACE,
+    xpathAxis.PARENT,
+    xpathAxis.PRECEDING_SIBLING,
+    xpathAxis.PRECEDING,
+    xpathAxis.SELF
+].join('|');
+
+
+// The tokens of the language. The label property is just used for
+// generating debug output. The prec property is the precedence used
+// for shift/reduce resolution. Default precedence is 0 as a lookahead
+// token and 2 on the stack. TODO(mesch): this is certainly not
+// necessary and too complicated. Simplify this!
+
+// NOTE: tabular formatting is the big exception, but here it should
+// be OK.
+
+var TOK_PIPE =   { label: &quot;|&quot;,   prec:   17, re: new RegExp(&quot;^\\|&quot;) };
+var TOK_DSLASH = { label: &quot;//&quot;,  prec:   19, re: new RegExp(&quot;^//&quot;)  };
+var TOK_SLASH =  { label: &quot;/&quot;,   prec:   30, re: new RegExp(&quot;^/&quot;)   };
+var TOK_AXIS =   { label: &quot;::&quot;,  prec:   20, re: new RegExp(&quot;^::&quot;)  };
+var TOK_COLON =  { label: &quot;:&quot;,   prec: 1000, re: new RegExp(&quot;^:&quot;)  };
+var TOK_AXISNAME = { label: &quot;[axis]&quot;, re: new RegExp('^(' + xpathAxesRe + ')') };
+var TOK_PARENO = { label: &quot;(&quot;,   prec:   34, re: new RegExp(&quot;^\\(&quot;) };
+var TOK_PARENC = { label: &quot;)&quot;,               re: new RegExp(&quot;^\\)&quot;) };
+var TOK_DDOT =   { label: &quot;..&quot;,  prec:   34, re: new RegExp(&quot;^\\.\\.&quot;) };
+var TOK_DOT =    { label: &quot;.&quot;,   prec:   34, re: new RegExp(&quot;^\\.&quot;) };
+var TOK_AT =     { label: &quot;@&quot;,   prec:   34, re: new RegExp(&quot;^@&quot;)   };
+
+var TOK_COMMA =  { label: &quot;,&quot;,               re: new RegExp(&quot;^,&quot;) };
+
+var TOK_OR =     { label: &quot;or&quot;,  prec:   10, re: new RegExp(&quot;^or\\b&quot;) };
+var TOK_AND =    { label: &quot;and&quot;, prec:   11, re: new RegExp(&quot;^and\\b&quot;) };
+var TOK_EQ =     { label: &quot;=&quot;,   prec:   12, re: new RegExp(&quot;^=&quot;)   };
+var TOK_NEQ =    { label: &quot;!=&quot;,  prec:   12, re: new RegExp(&quot;^!=&quot;)  };
+var TOK_GE =     { label: &quot;&gt;=&quot;,  prec:   13, re: new RegExp(&quot;^&gt;=&quot;)  };
+var TOK_GT =     { label: &quot;&gt;&quot;,   prec:   13, re: new RegExp(&quot;^&gt;&quot;)   };
+var TOK_LE =     { label: &quot;&lt;=&quot;,  prec:   13, re: new RegExp(&quot;^&lt;=&quot;)  };
+var TOK_LT =     { label: &quot;&lt;&quot;,   prec:   13, re: new RegExp(&quot;^&lt;&quot;)   };
+var TOK_PLUS =   { label: &quot;+&quot;,   prec:   14, re: new RegExp(&quot;^\\+&quot;), left: true };
+var TOK_MINUS =  { label: &quot;-&quot;,   prec:   14, re: new RegExp(&quot;^\\-&quot;), left: true };
+var TOK_DIV =    { label: &quot;div&quot;, prec:   15, re: new RegExp(&quot;^div\\b&quot;), left: true };
+var TOK_MOD =    { label: &quot;mod&quot;, prec:   15, re: new RegExp(&quot;^mod\\b&quot;), left: true };
+
+var TOK_BRACKO = { label: &quot;[&quot;,   prec:   32, re: new RegExp(&quot;^\\[&quot;) };
+var TOK_BRACKC = { label: &quot;]&quot;,               re: new RegExp(&quot;^\\]&quot;) };
+var TOK_DOLLAR = { label: &quot;$&quot;,               re: new RegExp(&quot;^\\$&quot;) };
+
+var TOK_NCNAME = { label: &quot;[ncname]&quot;, re: new RegExp('^' + XML_NC_NAME) };
+
+var TOK_ASTERISK = { label: &quot;*&quot;, prec: 15, re: new RegExp(&quot;^\\*&quot;), left: true };
+var TOK_LITERALQ = { label: &quot;[litq]&quot;, prec: 20, re: new RegExp(&quot;^'[^\\']*'&quot;) };
+var TOK_LITERALQQ = {
+  label: &quot;[litqq]&quot;,
+  prec: 20,
+  re: new RegExp('^&quot;[^\\&quot;]*&quot;')
+};
+
+var TOK_NUMBER  = {
+  label: &quot;[number]&quot;,
+  prec: 35,
+  re: new RegExp('^\\d+(\\.\\d*)?') };
+
+var TOK_QNAME = {
+  label: &quot;[qname]&quot;,
+  re: new RegExp('^(' + XML_NC_NAME + ':)?' + XML_NC_NAME)
+};
+
+var TOK_NODEO = {
+  label: &quot;[nodetest-start]&quot;,
+  re: new RegExp('^(processing-instruction|comment|text|node)\\(')
+};
+
+// The table of the tokens of our grammar, used by the lexer: first
+// column the tag, second column a regexp to recognize it in the
+// input, third column the precedence of the token, fourth column a
+// factory function for the semantic value of the token.
+//
+// NOTE: order of this list is important, because the first match
+// counts. Cf. DDOT and DOT, and AXIS and COLON.
+
+var xpathTokenRules = [
+    TOK_DSLASH,
+    TOK_SLASH,
+    TOK_DDOT,
+    TOK_DOT,
+    TOK_AXIS,
+    TOK_COLON,
+    TOK_AXISNAME,
+    TOK_NODEO,
+    TOK_PARENO,
+    TOK_PARENC,
+    TOK_BRACKO,
+    TOK_BRACKC,
+    TOK_AT,
+    TOK_COMMA,
+    TOK_OR,
+    TOK_AND,
+    TOK_NEQ,
+    TOK_EQ,
+    TOK_GE,
+    TOK_GT,
+    TOK_LE,
+    TOK_LT,
+    TOK_PLUS,
+    TOK_MINUS,
+    TOK_ASTERISK,
+    TOK_PIPE,
+    TOK_MOD,
+    TOK_DIV,
+    TOK_LITERALQ,
+    TOK_LITERALQQ,
+    TOK_NUMBER,
+    TOK_QNAME,
+    TOK_NCNAME,
+    TOK_DOLLAR
+];
+
+// All the nonterminals of the grammar. The nonterminal objects are
+// identified by object identity; the labels are used in the debug
+// output only.
+var XPathLocationPath = { label: &quot;LocationPath&quot; };
+var XPathRelativeLocationPath = { label: &quot;RelativeLocationPath&quot; };
+var XPathAbsoluteLocationPath = { label: &quot;AbsoluteLocationPath&quot; };
+var XPathStep = { label: &quot;Step&quot; };
+var XPathNodeTest = { label: &quot;NodeTest&quot; };
+var XPathPredicate = { label: &quot;Predicate&quot; };
+var XPathLiteral = { label: &quot;Literal&quot; };
+var XPathExpr = { label: &quot;Expr&quot; };
+var XPathPrimaryExpr = { label: &quot;PrimaryExpr&quot; };
+var XPathVariableReference = { label: &quot;Variablereference&quot; };
+var XPathNumber = { label: &quot;Number&quot; };
+var XPathFunctionCall = { label: &quot;FunctionCall&quot; };
+var XPathArgumentRemainder = { label: &quot;ArgumentRemainder&quot; };
+var XPathPathExpr = { label: &quot;PathExpr&quot; };
+var XPathUnionExpr = { label: &quot;UnionExpr&quot; };
+var XPathFilterExpr = { label: &quot;FilterExpr&quot; };
+var XPathDigits = { label: &quot;Digits&quot; };
+
+var xpathNonTerminals = [
+    XPathLocationPath,
+    XPathRelativeLocationPath,
+    XPathAbsoluteLocationPath,
+    XPathStep,
+    XPathNodeTest,
+    XPathPredicate,
+    XPathLiteral,
+    XPathExpr,
+    XPathPrimaryExpr,
+    XPathVariableReference,
+    XPathNumber,
+    XPathFunctionCall,
+    XPathArgumentRemainder,
+    XPathPathExpr,
+    XPathUnionExpr,
+    XPathFilterExpr,
+    XPathDigits
+];
+
+// Quantifiers that are used in the productions of the grammar.
+var Q_01 = { label: &quot;?&quot; };
+var Q_MM = { label: &quot;*&quot; };
+var Q_1M = { label: &quot;+&quot; };
+
+// Tag for left associativity (right assoc is implied by undefined).
+var ASSOC_LEFT = true;
+
+// The productions of the grammar. Columns of the table:
+//
+// - target nonterminal,
+// - pattern,
+// - precedence,
+// - semantic value factory
+//
+// The semantic value factory is a function that receives parse tree
+// nodes from the stack frames of the matched symbols as arguments and
+// returns an a node of the parse tree. The node is stored in the top
+// stack frame along with the target object of the rule. The node in
+// the parse tree is an expression object that has an evaluate() method
+// and thus evaluates XPath expressions.
+//
+// The precedence is used to decide between reducing and shifting by
+// comparing the precendence of the rule that is candidate for
+// reducing with the precedence of the look ahead token. Precedence of
+// -1 means that the precedence of the tokens in the pattern is used
+// instead. TODO: It shouldn't be necessary to explicitly assign
+// precedences to rules.
+
+// DGF As it stands, these precedences are purely empirical; we're
+// not sure they can be made to be consistent at all.
+
+var xpathGrammarRules =
+  [
+   [ XPathLocationPath, [ XPathRelativeLocationPath ], 18,
+     passExpr ],
+   [ XPathLocationPath, [ XPathAbsoluteLocationPath ], 18,
+     passExpr ],
+
+   [ XPathAbsoluteLocationPath, [ TOK_SLASH, XPathRelativeLocationPath ], 18,
+     makeLocationExpr1 ],
+   [ XPathAbsoluteLocationPath, [ TOK_DSLASH, XPathRelativeLocationPath ], 18,
+     makeLocationExpr2 ],
+
+   [ XPathAbsoluteLocationPath, [ TOK_SLASH ], 0,
+     makeLocationExpr3 ],
+   [ XPathAbsoluteLocationPath, [ TOK_DSLASH ], 0,
+     makeLocationExpr4 ],
+
+   [ XPathRelativeLocationPath, [ XPathStep ], 31,
+     makeLocationExpr5 ],
+   [ XPathRelativeLocationPath,
+     [ XPathRelativeLocationPath, TOK_SLASH, XPathStep ], 31,
+     makeLocationExpr6 ],
+   [ XPathRelativeLocationPath,
+     [ XPathRelativeLocationPath, TOK_DSLASH, XPathStep ], 31,
+     makeLocationExpr7 ],
+
+   [ XPathStep, [ TOK_DOT ], 33,
+     makeStepExpr1 ],
+   [ XPathStep, [ TOK_DDOT ], 33,
+     makeStepExpr2 ],
+   [ XPathStep,
+     [ TOK_AXISNAME, TOK_AXIS, XPathNodeTest ], 33,
+     makeStepExpr3 ],
+   [ XPathStep, [ TOK_AT, XPathNodeTest ], 33,
+     makeStepExpr4 ],
+   [ XPathStep, [ XPathNodeTest ], 33,
+     makeStepExpr5 ],
+   [ XPathStep, [ XPathStep, XPathPredicate ], 33,
+     makeStepExpr6 ],
+
+   [ XPathNodeTest, [ TOK_ASTERISK ], 33,
+     makeNodeTestExpr1 ],
+   [ XPathNodeTest, [ TOK_NCNAME, TOK_COLON, TOK_ASTERISK ], 33,
+     makeNodeTestExpr2 ],
+   [ XPathNodeTest, [ TOK_QNAME ], 33,
+     makeNodeTestExpr3 ],
+   [ XPathNodeTest, [ TOK_NODEO, TOK_PARENC ], 33,
+     makeNodeTestExpr4 ],
+   [ XPathNodeTest, [ TOK_NODEO, XPathLiteral, TOK_PARENC ], 33,
+     makeNodeTestExpr5 ],
+
+   [ XPathPredicate, [ TOK_BRACKO, XPathExpr, TOK_BRACKC ], 33,
+     makePredicateExpr ],
+
+   [ XPathPrimaryExpr, [ XPathVariableReference ], 33,
+     passExpr ],
+   [ XPathPrimaryExpr, [ TOK_PARENO, XPathExpr, TOK_PARENC ], 33,
+     makePrimaryExpr ],
+   [ XPathPrimaryExpr, [ XPathLiteral ], 30,
+     passExpr ],
+   [ XPathPrimaryExpr, [ XPathNumber ], 30,
+     passExpr ],
+   [ XPathPrimaryExpr, [ XPathFunctionCall ], 31,
+     passExpr ],
+
+   [ XPathFunctionCall, [ TOK_QNAME, TOK_PARENO, TOK_PARENC ], -1,
+     makeFunctionCallExpr1 ],
+   [ XPathFunctionCall,
+     [ TOK_QNAME, TOK_PARENO, XPathExpr, XPathArgumentRemainder, Q_MM,
+       TOK_PARENC ], -1,
+     makeFunctionCallExpr2 ],
+   [ XPathArgumentRemainder, [ TOK_COMMA, XPathExpr ], -1,
+     makeArgumentExpr ],
+
+   [ XPathUnionExpr, [ XPathPathExpr ], 20,
+     passExpr ],
+   [ XPathUnionExpr, [ XPathUnionExpr, TOK_PIPE, XPathPathExpr ], 20,
+     makeUnionExpr ],
+
+   [ XPathPathExpr, [ XPathLocationPath ], 20,
+     passExpr ],
+   [ XPathPathExpr, [ XPathFilterExpr ], 19,
+     passExpr ],
+   [ XPathPathExpr,
+     [ XPathFilterExpr, TOK_SLASH, XPathRelativeLocationPath ], 19,
+     makePathExpr1 ],
+   [ XPathPathExpr,
+     [ XPathFilterExpr, TOK_DSLASH, XPathRelativeLocationPath ], 19,
+     makePathExpr2 ],
+
+   [ XPathFilterExpr, [ XPathPrimaryExpr, XPathPredicate, Q_MM ], 31,
+     makeFilterExpr ],
+
+   [ XPathExpr, [ XPathPrimaryExpr ], 16,
+     passExpr ],
+   [ XPathExpr, [ XPathUnionExpr ], 16,
+     passExpr ],
+
+   [ XPathExpr, [ TOK_MINUS, XPathExpr ], -1,
+     makeUnaryMinusExpr ],
+
+   [ XPathExpr, [ XPathExpr, TOK_OR, XPathExpr ], -1,
+     makeBinaryExpr ],
+   [ XPathExpr, [ XPathExpr, TOK_AND, XPathExpr ], -1,
+     makeBinaryExpr ],
+
+   [ XPathExpr, [ XPathExpr, TOK_EQ, XPathExpr ], -1,
+     makeBinaryExpr ],
+   [ XPathExpr, [ XPathExpr, TOK_NEQ, XPathExpr ], -1,
+     makeBinaryExpr ],
+
+   [ XPathExpr, [ XPathExpr, TOK_LT, XPathExpr ], -1,
+     makeBinaryExpr ],
+   [ XPathExpr, [ XPathExpr, TOK_LE, XPathExpr ], -1,
+     makeBinaryExpr ],
+   [ XPathExpr, [ XPathExpr, TOK_GT, XPathExpr ], -1,
+     makeBinaryExpr ],
+   [ XPathExpr, [ XPathExpr, TOK_GE, XPathExpr ], -1,
+     makeBinaryExpr ],
+
+   [ XPathExpr, [ XPathExpr, TOK_PLUS, XPathExpr ], -1,
+     makeBinaryExpr, ASSOC_LEFT ],
+   [ XPathExpr, [ XPathExpr, TOK_MINUS, XPathExpr ], -1,
+     makeBinaryExpr, ASSOC_LEFT ],
+
+   [ XPathExpr, [ XPathExpr, TOK_ASTERISK, XPathExpr ], -1,
+     makeBinaryExpr, ASSOC_LEFT ],
+   [ XPathExpr, [ XPathExpr, TOK_DIV, XPathExpr ], -1,
+     makeBinaryExpr, ASSOC_LEFT ],
+   [ XPathExpr, [ XPathExpr, TOK_MOD, XPathExpr ], -1,
+     makeBinaryExpr, ASSOC_LEFT ],
+
+   [ XPathLiteral, [ TOK_LITERALQ ], -1,
+     makeLiteralExpr ],
+   [ XPathLiteral, [ TOK_LITERALQQ ], -1,
+     makeLiteralExpr ],
+
+   [ XPathNumber, [ TOK_NUMBER ], -1,
+     makeNumberExpr ],
+
+   [ XPathVariableReference, [ TOK_DOLLAR, TOK_QNAME ], 200,
+     makeVariableReference ]
+   ];
+
+// That function computes some optimizations of the above data
+// structures and will be called right here. It merely takes the
+// counter variables out of the global scope.
+
+var xpathRules = [];
+
+function xpathParseInit() {
+  if (xpathRules.length) {
+    return;
+  }
+
+  // Some simple optimizations for the xpath expression parser: sort
+  // grammar rules descending by length, so that the longest match is
+  // first found.
+
+  xpathGrammarRules.sort(function(a,b) {
+    var la = a[1].length;
+    var lb = b[1].length;
+    if (la &lt; lb) {
+      return 1;
+    } else if (la &gt; lb) {
+      return -1;
+    } else {
+      return 0;
+    }
+  });
+
+  var k = 1;
+  for (var i = 0; i &lt; xpathNonTerminals.length; ++i) {
+    xpathNonTerminals[i].key = k++;
+  }
+
+  for (i = 0; i &lt; xpathTokenRules.length; ++i) {
+    xpathTokenRules[i].key = k++;
+  }
+
+  xpathLog('XPath parse INIT: ' + k + ' rules');
+
+  // Another slight optimization: sort the rules into bins according
+  // to the last element (observing quantifiers), so we can restrict
+  // the match against the stack to the subest of rules that match the
+  // top of the stack.
+  //
+  // TODO(mesch): What we actually want is to compute states as in
+  // bison, so that we don't have to do any explicit and iterated
+  // match against the stack.
+
+  function push_(array, position, element) {
+    if (!array[position]) {
+      array[position] = [];
+    }
+    array[position].push(element);
+  }
+
+  for (i = 0; i &lt; xpathGrammarRules.length; ++i) {
+    var rule = xpathGrammarRules[i];
+    var pattern = rule[1];
+
+    for (var j = pattern.length - 1; j &gt;= 0; --j) {
+      if (pattern[j] == Q_1M) {
+        push_(xpathRules, pattern[j-1].key, rule);
+        break;
+
+      } else if (pattern[j] == Q_MM || pattern[j] == Q_01) {
+        push_(xpathRules, pattern[j-1].key, rule);
+        --j;
+
+      } else {
+        push_(xpathRules, pattern[j].key, rule);
+        break;
+      }
+    }
+  }
+
+  xpathLog('XPath parse INIT: ' + xpathRules.length + ' rule bins');
+
+  var sum = 0;
+  mapExec(xpathRules, function(i) {
+    if (i) {
+      sum += i.length;
+    }
+  });
+
+  xpathLog('XPath parse INIT: ' + (sum / xpathRules.length) +
+           ' average bin size');
+}
+
+// Local utility functions that are used by the lexer or parser.
+
+function xpathCollectDescendants(nodelist, node, opt_tagName) {
+  if (opt_tagName &amp;&amp; node.getElementsByTagName) {
+    copyArray(nodelist, node.getElementsByTagName(opt_tagName));
+    return;
+  }
+  for (var n = node.firstChild; n; n = n.nextSibling) {
+    nodelist.push(n);
+    xpathCollectDescendants(nodelist, n);
+  }
+}
+
+// DGF extract a tag name suitable for getElementsByTagName
+function xpathExtractTagNameFromNodeTest(nodetest) {
+  if (nodetest instanceof NodeTestName) {
+    return nodetest.name;
+  } else if (/* nodetest instanceof NodeTestAny || */ nodetest instanceof NodeTestElementOrAttribute) {
+    // HBC - commented out the NodeTestAny in the above condition; it causes
+    // non-element nodes to be excluded! The XPath spec says &quot;node()&quot; must
+    // match all node types.
+    return &quot;*&quot;;
+  }
+}
+
+function xpathCollectDescendantsReverse(nodelist, node) {
+  for (var n = node.lastChild; n; n = n.previousSibling) {
+    nodelist.push(n);
+    xpathCollectDescendantsReverse(nodelist, n);
+  }
+}
+
+
+// The entry point for the library: match an expression against a DOM
+// node. Returns an XPath value.
+function xpathDomEval(expr, node) {
+  var expr1 = xpathParse(expr);
+  var ret = expr1.evaluate(new ExprContext(node));
+  return ret;
+}
+
+// Utility function to sort a list of nodes. Used by xsltSort() and
+// nxslSelect().
+function xpathSort(input, sort) {
+  if (sort.length == 0) {
+    return;
+  }
+
+  var sortlist = [];
+
+  for (var i = 0; i &lt; input.contextSize(); ++i) {
+    var node = input.nodelist[i];
+    var sortitem = { node: node, key: [] };
+    var context = input.clone(node, 0, [ node ]);
+
+    for (var j = 0; j &lt; sort.length; ++j) {
+      var s = sort[j];
+      var value = s.expr.evaluate(context);
+
+      var evalue;
+      if (s.type == 'text') {
+        evalue = value.stringValue();
+      } else if (s.type == 'number') {
+        evalue = value.numberValue();
+      }
+      sortitem.key.push({ value: evalue, order: s.order });
+    }
+
+    // Make the sort stable by adding a lowest priority sort by
+    // id. This is very convenient and furthermore required by the
+    // spec ([XSLT] - Section 10 Sorting).
+    sortitem.key.push({ value: i, order: 'ascending' });
+
+    sortlist.push(sortitem);
+  }
+
+  sortlist.sort(xpathSortByKey);
+
+  var nodes = [];
+  for (var i = 0; i &lt; sortlist.length; ++i) {
+    nodes.push(sortlist[i].node);
+  }
+  input.nodelist = nodes;
+  input.setNode(0);
+}
+
+
+// Sorts by all order criteria defined. According to the JavaScript
+// spec ([ECMA] Section 11.8.5), the compare operators compare strings
+// as strings and numbers as numbers.
+//
+// NOTE: In browsers which do not follow the spec, this breaks only in
+// the case that numbers should be sorted as strings, which is very
+// uncommon.
+function xpathSortByKey(v1, v2) {
+  // NOTE: Sort key vectors of different length never occur in
+  // xsltSort.
+
+  for (var i = 0; i &lt; v1.key.length; ++i) {
+    var o = v1.key[i].order == 'descending' ? -1 : 1;
+    if (v1.key[i].value &gt; v2.key[i].value) {
+      return +1 * o;
+    } else if (v1.key[i].value &lt; v2.key[i].value) {
+      return -1 * o;
+    }
+  }
+
+  return 0;
+}
+
+
+// Parses and then evaluates the given XPath expression in the given
+// input context. Notice that parsed xpath expressions are cached.
+function xpathEval(select, context) {
+  var expr = xpathParse(select);
+  var ret = expr.evaluate(context);
+  return ret;
+}</diff>
      <filename>selenium-core/xpath/xpath.js</filename>
    </modified>
  </modified>
  <removed type="array">
    <removed>
      <filename>selenium-core/scripts/injection_iframe.html</filename>
    </removed>
    <removed>
      <filename>selenium-core/scripts/js2html.js</filename>
    </removed>
    <removed>
      <filename>selenium-core/scripts/narcissus-defs.js</filename>
    </removed>
    <removed>
      <filename>selenium-core/scripts/narcissus-exec.js</filename>
    </removed>
    <removed>
      <filename>selenium-core/scripts/narcissus-parse.js</filename>
    </removed>
    <removed>
      <filename>selenium-core/scripts/se2html.js</filename>
    </removed>
    <removed>
      <filename>selenium-core/xpath/misc.js</filename>
    </removed>
  </removed>
  <parents type="array">
    <parent>
      <id>35648d73aa3287918abd6a0f3cdcda238597bd9b</id>
    </parent>
  </parents>
  <author>
    <name>eric@8thlight.com</name>
    <email>eric@8thlight.com@9274398c-e119-0410-8437-aa71ef7847aa</email>
  </author>
  <url>http://github.com/paytonrules/selenium-on-rails/commit/fb1727fdc7e6e524804d92d248d082342f6af332</url>
  <id>fb1727fdc7e6e524804d92d248d082342f6af332</id>
  <committed-date>2009-03-07T15:37:44-08:00</committed-date>
  <authored-date>2009-03-07T15:37:44-08:00</authored-date>
  <message>Updating to the latest version of selenium Core.


git-svn-id: https://svn.openqa.org/svn/selenium-on-rails/selenium-on-rails@128 9274398c-e119-0410-8437-aa71ef7847aa</message>
  <tree>a7fccd6c057630a33b3e591762926ad861bda7e7</tree>
  <committer>
    <name>eric@8thlight.com</name>
    <email>eric@8thlight.com@9274398c-e119-0410-8437-aa71ef7847aa</email>
  </committer>
</commit>
