[OpenLayers-Commits] r12176 - in addins/bookmark/release: 2.1 2.1/examples 2.1/lib 2.1/lib/OpenLayers 2.1/lib/OpenLayers/Control 2.1/tests 2.1/tests/Control 2.1/theme 2.1/theme/default 2.2 2.2/examples 2.2/lib 2.2/lib/OpenLayers 2.2/lib/OpenLayers/Control 2.2/tests 2.2/tests/Control 2.2/theme 2.2/theme/default

commits-20090109 at openlayers.org commits-20090109 at openlayers.org
Wed Jul 20 16:51:09 EDT 2011


Author: hpbrantley
Date: 2011-07-20 13:51:08 -0700 (Wed, 20 Jul 2011)
New Revision: 12176

Added:
   addins/bookmark/release/2.1/examples/
   addins/bookmark/release/2.1/examples/bookmark-custom.html
   addins/bookmark/release/2.1/examples/bookmark.html
   addins/bookmark/release/2.1/lib/
   addins/bookmark/release/2.1/lib/OpenLayers/
   addins/bookmark/release/2.1/lib/OpenLayers/Control/
   addins/bookmark/release/2.1/lib/OpenLayers/Control/Bookmark.js
   addins/bookmark/release/2.1/tests/
   addins/bookmark/release/2.1/tests/Control/
   addins/bookmark/release/2.1/tests/Control/Bookmark.html
   addins/bookmark/release/2.1/tests/list-tests.html
   addins/bookmark/release/2.1/tests/run-tests.html
   addins/bookmark/release/2.1/theme/
   addins/bookmark/release/2.1/theme/default/
   addins/bookmark/release/2.1/theme/default/bookmark.css
   addins/bookmark/release/2.2/examples/
   addins/bookmark/release/2.2/examples/bookmark-cookie.html
   addins/bookmark/release/2.2/examples/bookmark-custom.html
   addins/bookmark/release/2.2/examples/bookmark.html
   addins/bookmark/release/2.2/lib/
   addins/bookmark/release/2.2/lib/OpenLayers/
   addins/bookmark/release/2.2/lib/OpenLayers/Control/
   addins/bookmark/release/2.2/lib/OpenLayers/Control/Bookmark.js
   addins/bookmark/release/2.2/tests/
   addins/bookmark/release/2.2/tests/Control/
   addins/bookmark/release/2.2/tests/Control/Bookmark.html
   addins/bookmark/release/2.2/tests/list-tests.html
   addins/bookmark/release/2.2/tests/run-tests.html
   addins/bookmark/release/2.2/theme/
   addins/bookmark/release/2.2/theme/default/
   addins/bookmark/release/2.2/theme/default/bookmark.css
Log:
Fixing out of sync problem


Added: addins/bookmark/release/2.1/examples/bookmark-custom.html
===================================================================
--- addins/bookmark/release/2.1/examples/bookmark-custom.html	                        (rev 0)
+++ addins/bookmark/release/2.1/examples/bookmark-custom.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,99 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>OpenLayers Bookmark Example</title>
+    <link rel="stylesheet" href="http://openlayers.org/api/theme/default/style.css" type="text/css" />
+    <link rel="stylesheet" href="http://www.openlayers.org/dev/examples/style.css" type="text/css" />
+    <link rel="stylesheet" href="../theme/default/bookmark.css" type="text/css" />
+    <style type="text/css">
+        body { position: relative; }
+        #map {
+            width: 512px;
+            height: 512px;
+            border: 1px solid black;
+            position: relative;
+        }
+        #myBookmarks {
+            height: 200px;
+            width: 15em;
+            float: right;
+         }
+         #myBookmarks #olBookmarkElement {
+            cursor : pointer;
+         }
+        input[type="button"] { border: 1px solid black; }
+    </style>
+    <script src="http://openlayers.org/api/OpenLayers.js"></script>
+    <script src="../lib/OpenLayers/Control/Bookmark.js"></script>
+    <script type="text/javascript">
+        var map, bookmarks;
+        function initMap(){
+            map = new OpenLayers.Map('map', { controls: [], theme: null });
+            map.addControl(new OpenLayers.Control.Navigation());
+
+            // outside the map
+            bookmarks = new OpenLayers.Control.Bookmark({ 'div':OpenLayers.Util.getElement('myBookmarks'), 'title': 'Bookmarks outside the map' });
+            map.addControl(bookmarks);
+            bookmarks.add("NASHVILLE",new OpenLayers.Bounds(-87.075,35.956,-86.507,36.425),Msg,"Welcome to Nashville");
+            bookmarks.add("JACKSONVILLE","-82.11031,30.07138,-81.27810,30.63443");
+            bookmarks.add("WASHINGTON DC",new OpenLayers.Bounds(-77.338126,38.639689,-76.788809,39.189005));
+            bookmarks.add("WORLD","-180,-180,180,180");
+
+            OpenLayers.Rico.Corner.round( bookmarks.div , {corners: "tl bl tr br", bgColor: "transparent", color: "darkblue", blend: false});
+            OpenLayers.Rico.Corner.changeOpacity(bookmarks.contentDiv, 0.75);
+
+            // inside the map, docked right middle
+            bookmarks2 = new OpenLayers.Control.Bookmark({ 'title': 'Bookmarks in the map', 'allowRemove': false });
+            map.addControl(bookmarks2);
+            bookmarks2.maximizeControl();
+            bookmarks2.add("ATLANTA",new OpenLayers.Bounds(-85.085373,33.114328,-83.679123,34.520578));
+            bookmarks2.add("BOSTON",new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033));
+            bookmarks2.add("WORLD",new OpenLayers.Bounds(-180,-180,180,180));
+
+            OpenLayers.Rico.Corner.round( bookmarks2.div, {corners: "tl bl", bgColor: "transparent", color: "darkblue", blend: false});
+            OpenLayers.Rico.Corner.changeOpacity(bookmarks2.contentDiv, 0.75);
+
+            bookmarks3 = new OpenLayers.Control.Bookmark({ 'map':map });
+            bookmarks3.add("ATLANTA","-85.085373,33.114328,-83.679123,34.520578",Msg,"Welcome to Atlanta");
+            bookmarks3.add("BOSTON","-71.401901,41.989908,-70.698776,42.693033",Msg,"Welcome to Boston");
+
+            var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                "http://labs.metacarta.com/wms/vmap0",
+                {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+            map.addLayer(ol_wms);
+            if (!map.getCenter()) map.zoomToMaxExtent();
+        }
+
+        function Msg(val) { alert(val); }
+    </script>
+  </head>
+  <body onload="initMap()">
+    <h1 id="title">Bookmark Control Example</h1>
+    <div id="tags"></div>
+    <p id="shortdesc">
+      Demonstrates the use of Bookmarks.<br />
+      Styled with CSS only.  Corners rounded by Rico outside of the class.
+    </p>
+    <div id="myBookmarks"></div>
+    <div id="map"></div>
+    <div id="docs">
+      <p>This demonstrates use of the Bookmarks.<br />Applys to outside bookmarks only.</p>
+   </div>
+   <div>
+      <form>
+         Name: <input type="text" id="name" style="width:100px;border: 1px solid black"><br />
+         Coords: <input type="text" id="coords" style="width:200px;border: 1px solid black">&nbsp;&nbsp;<input type="button" value="Get Current Coords" onclick="document.getElementById('coords').value = map.getExtent().toBBOX()"><br /><br />
+         <input type="button" value="Zoom To" onclick="if(document.getElementById('name').value != ''){ bookmarks.zoomToLabel(document.getElementById('name').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Add" onclick="if(document.getElementById('name').value != '' && document.getElementById('coords').value != ''){ bookmarks.add(document.getElementById('name').value, document.getElementById('coords').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Add Current View" onclick="if(document.getElementById('name').value != ''){ bookmarks.addView(document.getElementById('name').value) } else { alert('Need a name') }">&nbsp;&nbsp;&nbsp;&nbsp;
+         <input type="button" value="Remove" onclick="if(document.getElementById('name').value != ''){ bookmarks.remove(document.getElementById('name').value) } else { alert('Need a name') }">&nbsp;&nbsp;
+         <input type="button" value="Remove All" onclick="bookmarks.clear()"><br /><br />
+	 Bookmarks without a DIV?  Click <a noref onclick="javascript:bookmarks3.zoomToLabel(bookmarks3.find('atlanta')[0][1]);" style="text-decoration:underline;cursor:pointer;">here</a> to go to Atlanta or
+	 <a noref onclick="javascript:bookmarks3.zoomToLabel(bookmarks3.find('boston')[0][1]);" style="text-decoration:underline;cursor:pointer;">here</a>
+         to go to Boston.<br /><br />
+         Added the ability to pass a user defined function to be execute when the user clicks the bookmark.  Click the Boston
+         or Atlanta links above.
+      </form>
+   </div>
+  </body>
+</html>

Added: addins/bookmark/release/2.1/examples/bookmark.html
===================================================================
--- addins/bookmark/release/2.1/examples/bookmark.html	                        (rev 0)
+++ addins/bookmark/release/2.1/examples/bookmark.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,68 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>OpenLayers Bookmark Example</title>
+    <link rel="stylesheet" href="http://openlayers.org/api/theme/default/style.css" type="text/css" />
+    <link rel="stylesheet" href="http://www.openlayers.org/dev/examples/style.css" type="text/css" />
+    <link rel="stylesheet" href="http://svn.openlayers.org/addins/bookmark/trunk/theme/default/bookmark.css" type="text/css" />
+    <style type="text/css">
+        body { position: relative; }
+        #map {
+            width: 512px;
+            height: 512px;
+            border: 1px solid black;
+            position: relative;
+        }
+        input[type="button"] { border: 1px solid black; }
+    </style>
+    <script src="http://openlayers.org/api/OpenLayers.js"></script>
+    <script src="../lib/OpenLayers/Control/Bookmark.js"></script>
+    <script type="text/javascript">
+        var map, bookmark;
+        function initMap(){
+            map = new OpenLayers.Map('map', { controls: [], theme: null });
+            map.addControl(new OpenLayers.Control.Navigation());
+
+            // inside the map, docked right middle
+            bookmark = new OpenLayers.Control.Bookmark({ 'title': 'Bookmarks in map', 'allowRemove': true });
+            map.addControl(bookmark);
+            bookmark.maximizeControl();
+            bookmark.add("ATLANTA",new OpenLayers.Bounds(-85.085373,33.114328,-83.679123,34.520578));
+            bookmark.add("BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033));
+            bookmark.add("WASHINGTON DC","-77.338126,38.639689,-76.788809,39.189005");
+
+            // round the corners with rico
+            OpenLayers.Rico.Corner.round( bookmark.div, {corners: "tl bl", bgColor: "transparent", color: "darkblue", blend: false});
+            OpenLayers.Rico.Corner.changeOpacity(bookmark.contentDiv, 0.75);
+
+            var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                "http://labs.metacarta.com/wms/vmap0",
+                {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+            map.addLayer(ol_wms);
+            if (!map.getCenter()) map.zoomToMaxExtent();
+        }
+    </script>
+  </head>
+  <body onload="initMap()">
+    <h1 id="title">Bookmark Control Example</h1>
+    <div id="tags"></div>
+    <p id="shortdesc">
+      Demonstrates the use of the Bookmark Control.<br />
+      Styled with CSS only.  Corners rounded by Rico outside of the class.
+    </p>
+    <div id="map"></div>
+    <div id="docs">
+      <p>This demonstrates use of the Bookmarks.</p>
+   </div>
+   <div>
+      <form>
+         Name: <input type="text" id="name" style="width:100px;border: 1px solid black"><br />
+         Coords: <input type="text" id="coords" style="width:200px;border: 1px solid black">&nbsp;&nbsp;<input type="button" value="Get Current Coords" onclick="document.getElementById('coords').value = map.getExtent().toBBOX()"><br /><br />
+         <input type="button" value="Zoom To" onclick="if(document.getElementById('name').value != ''){ bookmark.zoomToLabel(document.getElementById('name').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Add" onclick="if(document.getElementById('name').value != '' && document.getElementById('coords').value != ''){ bookmark.add(document.getElementById('name').value, document.getElementById('coords').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Remove" onclick="if(document.getElementById('name').value != ''){ bookmark.remove(document.getElementById('name').value) } else { alert('Need a name') }">&nbsp;&nbsp;
+         <input type="button" value="Remove All" onclick="bookmark.clear()">
+      </form>
+   </div>
+  </body>
+</html>

Added: addins/bookmark/release/2.1/lib/OpenLayers/Control/Bookmark.js
===================================================================
--- addins/bookmark/release/2.1/lib/OpenLayers/Control/Bookmark.js	                        (rev 0)
+++ addins/bookmark/release/2.1/lib/OpenLayers/Control/Bookmark.js	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,659 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Bookmark
+ *     A bookmark view control.  Store an array of locations
+ *     refrenced by a label.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Bookmark = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * Property: type
+     * {String}
+     */
+    type: OpenLayers.Control.TYPE_TOOL,
+
+    /**
+     * Property: stackState
+     * {Boolean} Flag to indicate if the control needs to be redrawn. We have
+     *     this in order to avoid unnecessarily redrawing the control.
+     */
+    stackState: true,
+
+    /**
+     * APIProperty: limit
+     * {Integer} Optional limit on the number of bookmark items to retain.
+     *           Default is 50. 0 is no limit.
+     */
+    limit: 50,
+
+    /**
+     * APIProperty: stack
+     * {Array} Array of items in the list.
+     */
+    stack: null,
+
+    /**
+     * APIProperty: allowRemove
+     * {Boolean}  Allow the bookmark to be removed.
+     *      Default to true.
+     */
+    allowRemove: true,
+
+    // DOM Elements
+
+    /**
+     * Property: contentDiv
+     * {DOMElement}
+     */
+    contentDiv: null,
+
+    /**
+     * Property: dataLblDiv
+     * {DOMElement}
+     */
+    dataLblDiv: null,
+
+    /**
+     * Property: dataElementDiv
+     * {DOMElement}
+     */
+    dataElementDiv: null,
+
+    /**
+     * Property: minimizeDiv
+     * {DOMElement}
+     */
+    minimizeDiv: null,
+
+    /**
+     * Property: maximizeDiv
+     * {DOMElement}
+     */
+    maximizeDiv: null,
+
+    /**
+     * Constructor: OpenLayers.Control.Bookmark
+     *
+     * Parameters:
+     * options - {func} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.initStack();
+    },
+
+    /**
+     * APIMethod: destroy
+     * Destroy the control.
+     */
+    /* FIXME  - not sure i'm stopping observation correctly*/
+    destroy: function() {
+        this.stack=null;
+        OpenLayers.Event.stopObservingElement(this.div);
+        OpenLayers.Event.stopObservingElement(this.minimizeDiv);
+        OpenLayers.Event.stopObservingElement(this.maximizeDiv);
+        OpenLayers.Control.prototype.destroy.apply(this);
+    },
+
+    /**
+     * Method: initStack
+     * Initialize the stack and get ready for adds.
+     */
+    initStack: function() {
+      this.stack = [];
+      this.stackState = true;
+    },
+
+    /**
+     * APIMethod: add
+     * Add an item to the stack as label, bounds.  If no label is specified
+     *      "NONAME" is used.
+     *
+     * Parameters:
+     * label - {String} Label reference to this bookmark
+     * bounds - {Object or String}
+     *      Object is a OpenLayers.bounds object or
+     *      String representation of bounds in format of
+     *     "-180,-180,180,180"
+     *
+     * Returns:
+     * {Boolean} False if limit reached. True if added successfully.
+     */
+    add: function(label, bounds, func, param) {
+      if(this.stack.length == this.limit && this.limit != 0) {
+         return false;  // limit reached
+      }
+      func = (typeof func == "undefined") ? function() {} : func;
+      param = (typeof param == "undefined" || param.length == 0) ? "" : param;
+      label = (label == "undefined" || label.length == 0) ? "NONAME" : label;
+      this.stack.push({ "label":label.toUpperCase(),
+                        "bounds":bounds,
+                        "func": func,
+                        "param": param,
+                        "id": this.stack.length });
+      this.stackState = true;
+      this.redraw();
+      return true;
+    },
+
+    /**
+     * APIMethod: clear
+     * Clear and reset the stack then redraw the div.
+     */
+    clear: function() {
+      this.initStack();
+      this.redraw();
+    },
+
+    /**
+     * APIMethod: remove
+     * Remove item from the stack.
+     *
+     * Parameters:
+     * label - {String} Remove from the stack referenced by label.
+     *
+     * Returns:
+     * {Boolean} False if not found or not allowed to remove.
+     */
+    /* FIXME  - not sure i'm stopping observation correctly*/
+    remove: function(label) {
+      if(!this.allowRemove) {
+         return false;
+      }
+      label = label.toUpperCase();
+      for(var i=0; i<this.stack.length; i++) {
+         if( this.stack[i].label.toUpperCase() == label) {
+            for( var ii=0; ii<this.dataElementDiv.childNodes.length; ii++) {
+               var currentNode = this.dataElementDiv.childNodes[ii].id;
+               var currentLabel = label.replace(/ /g,'_');
+               if(currentNode.replace(/ /g,'_') == "Remove_"+currentLabel) {
+                  OpenLayers.Event.stopObservingElement(
+                     this.dataElementDiv.childNodes[ii]);
+               }
+               if(currentNode.replace(/ /g,'_') == "Link_"+currentLabel) {
+                  OpenLayers.Event.stopObservingElement(
+                     this.dataElementDiv.childNodes[ii]);
+               }
+            }
+            this.stack.splice( i, 1 );
+            this.stackState = true;
+            this.redraw();
+            return true;
+         }
+      }
+      return false;
+    },
+
+    /**
+     * APIMethod: find
+     * Find the first regex matched label and return it.
+     *
+     * Parameters:
+     * label - {String}  Case insensitive string to find.
+     *
+     * Returns:
+     * {Boolean} Return array of label(s) if found. False if not found
+     */
+    find: function(label) {
+      var rt = [];
+      var regEx = new RegExp (label, "i");
+      for(var i=0; i<this.stack.length; i++) {
+         if(regEx.test(this.stack[i].label)) {
+            rt.push([this.stack[i].id,this.stack[i].label]);
+            //return this.stack[i].label; // return first found
+         }
+      }
+      return (rt.length == 0 ? false : rt);
+    },
+
+    /**
+     * APIMethod: zoomToId
+     * Zoom to extents referenced by id
+     *
+     * Parameters:
+     * id - {Integer} stack id reference
+     *
+     * Returns:
+     * {Boolean} False if not found
+     */
+    zoomToId: function(id) {
+       var found=false;
+       if( this.stack[id] != this.stack[id].id ) {
+         if( typeof this.stack[id].bounds == "object" ) {
+            this.map.zoomToExtent( this.stack[id].bounds );
+         } else {
+            this.map.zoomToExtent( new OpenLayers.Bounds.fromString(
+                                       this.stack[id].bounds) );
+         }
+         this.stack[id].func(this.stack[id].param);
+         found=true;
+       } else {
+         // index is misaligned, search the stack for the correct id.
+         for( var i=0; i<this.stack.length; i++ ) {
+            if( this.stack[i].id == id ) {
+               if( typeof this.stack[i].bounds == "object" ) {
+                  this.map.zoomToExtent( this.stack[i].bounds );
+               } else {
+                  this.map.zoomToExtent( new OpenLayers.Bounds.fromString(
+                                             this.stack[i].bounds) );
+               }
+               this.stack[i].func(this.stack[i].param);
+               found=true;
+               break;  // break out of for when found
+            }
+         }
+       }
+       return found;
+    },
+
+    /**
+     * APIMethod: zoomTo (deprecated, use zoomToLabel)
+     * Zoom to extents referenced by label
+     *
+     * Parameters:
+     * label - {String} Label to zoom to.
+     *
+     * Returns:
+     * {Boolean} False if not found
+     */
+    zoomTo: function(label) {
+      OpenLayers.Console.log("zoomTo() deprecated. Use zoomToLabel");
+      return this.zoomToLabel(label);
+    },
+
+    /**
+     * APIMethod: zoomToLabel
+     * Zoom to extents referenced by label
+     *
+     * Parameters:
+     * label - {String} Label to zoom to.
+     *
+     * Returns:
+     * {Boolean} False if not found
+     */
+    zoomToLabel: function(label) {
+      if( label != false ) {
+         for(var i=0; i<this.stack.length; i++) {
+            if( this.stack[i].label.toUpperCase() == label.toUpperCase() ) {
+               if( typeof this.stack[i].bounds == "object" ) {
+                  this.map.zoomToExtent( this.stack[i].bounds );
+               } else {
+                  this.map.zoomToExtent( new OpenLayers.Bounds.fromString(
+                                          this.stack[i].bounds) );
+               }
+               this.stack[i].func(this.stack[i].param);
+               return true;
+            }
+         }
+      }
+      return false;
+    },
+
+    /**
+     * APIMethod: sortStack
+     * Sort stack by sortMethod
+     *
+     * Parameters:
+     * sortMethod - {String} Field to sort on.
+     */
+    sortStack: function(sortMethod) {
+      switch(sortMethod.toLowerCase()) {
+         case "id":
+            this.stack.sort(this.sortById);
+            break;
+         case "label":
+         default:
+            this.stack.sort(this.sortByLabel);
+            break;
+      }
+      this.stackState = true;
+    },
+
+    /**
+     * Method: sortByLabel
+     * Sort stack by label values. Called by sort().
+     */
+    sortByLabel:  function(a, b) {
+         var x = a.label.toLowerCase();
+         var y = b.label.toLowerCase();
+         return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+      },
+
+    /**
+     * Method: sortById
+     * Sort stack by id values. Called by sort().
+     */
+    sortById:  function(a, b) {
+         var x = a.id;
+         var y = b.id;
+         return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+      },
+
+    /**
+     * APIMethod: reindex
+     * Reindex stack; realign id with stack index. Usually called
+     * after sortStack, add or remove.
+     */
+    reindex: function() {
+       for( var i=0; i<this.stack.length; i++ ) {
+          this.stack[i].id = i;
+       }
+       this.stackState = true;
+    },
+
+    /**
+     * APIMethod: maximizeControl
+     * Set up the labels and divs for the control
+     *
+     * Parameters:
+     * e - {Event}
+     */
+    maximizeControl: function(e) {
+        //HACK HACK HACK - find a way to auto-size this
+        this.div.style.width = "13em";
+        this.div.style.height = "";
+
+        this.showControls(false);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);
+        }
+    },
+
+    /**
+     * APIMethod: minimizeControl
+     * Hide all the contents of the control, shrink the size,
+     *     add the maximize icon
+     *
+     * Parameters:
+     * e - {Event}
+     */
+    minimizeControl: function(e) {
+        this.div.style.width = "0px";
+        this.div.style.height = "0px";
+
+        this.showControls(true);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);
+        }
+    },
+
+    /**
+     * Method: draw
+     * Create a div with stack values
+     *
+     * Returns:
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */
+    draw: function() {
+
+        OpenLayers.Control.prototype.draw.apply(this);
+
+        // create layout divs
+        this.loadContents();
+
+        // set mode to minimize
+        if(!this.outsideViewport) {
+            this.minimizeControl();
+        }
+
+        // populate div with current info
+        this.redraw();
+
+        return this.div;
+    },
+
+    /**
+     * Method: loadContents
+     * Set up the labels and divs for the control
+     */
+    loadContents: function() {
+         //configure main div
+         this.div.id = ( this.div.id == ""
+                       ? "olControlBookmark"
+                       : this.div.id );
+         this.div.className = "olControlBookmark";
+         OpenLayers.Event.observe(this.div, "mouseup",
+            OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
+         OpenLayers.Event.observe(this.div, "click", this.ignoreEvent);
+         OpenLayers.Event.observe(this.div, "mousedown",
+            OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
+         OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);
+
+         // layers list div
+         this.contentDiv = document.createElement("div");
+         this.contentDiv.id = this.displayClass + 'Content';
+         this.contentDiv.className = this.displayClass + 'Content';
+
+         this.dataLblDiv = document.createElement("div");
+         this.dataLblDiv.id = this.displayClass + 'Title';
+         this.dataLblDiv.className = this.displayClass + 'Title';
+         this.dataLblDiv.innerHTML = ( this.title == ""
+                                     ? "Bookmarks"
+                                     : this.title );
+        this.contentDiv.appendChild(this.dataLblDiv);
+
+        this.dataElementDiv = document.createElement("div");
+        this.dataElementDiv.id = this.displayClass + 'Elements';
+        this.dataElementDiv.className = this.displayClass + 'Elements';
+
+        this.contentDiv.appendChild(this.dataElementDiv);
+        this.div.appendChild(this.contentDiv);
+
+        // maximimze button
+        this.maximizeDiv = document.createElement("div");
+        this.maximizeDiv.id = this.div.id+'MaximizeDiv';
+        this.maximizeDiv.className = this.displayClass + 'MaximizeButton';
+        OpenLayers.Event.observe(this.maximizeDiv, "click",
+           OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
+        );
+        this.div.appendChild(this.maximizeDiv);
+
+        // minimimze button
+        this.minimizeDiv = document.createElement("div");
+        this.minimizeDiv.id = this.div.id+'MinimizeDiv';
+        this.minimizeDiv.className = this.displayClass + 'MinimizeButton';
+        OpenLayers.Event.observe(this.minimizeDiv, "click",
+           OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
+        );
+        this.div.appendChild(this.minimizeDiv);
+
+    },
+
+    /**
+     * Method: clearStackDiv
+     * Clear the div containing the stack elements.
+     */
+    /* FIXME  - not sure i'm stopping observation correctly*/
+    clearStackDiv: function() {
+      // if no dataEmentDiv drop out gracfully
+      if( this.dataElementDiv == null ) {
+         return;
+      }
+
+      if( this.dataElementDiv != null ) {
+         for(var i=0; i<this.dataElementDiv.childNodes; i++) {
+            OpenLayers.Event.stopObservingElement(
+                              this.dataElementDiv.childNodes[i]);
+         }
+         this.dataElementDiv.innerHTML = "";
+      }
+    },
+
+    /**
+     * Method: redraw
+     * Takes the current state of the control and rebuilds if needed.
+     *
+     * Returns:
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */
+    redraw: function() {
+
+        // only redraw the div if the state has changed, otherwise return
+        // same div.
+        if (!this.stackState) {
+           return this.div;
+        }
+
+        this.clearStackDiv();
+
+        // if no dataEmentDiv drop out gracfully
+        if( this.dataElementDiv == null ) {
+            return;
+        }
+
+        for(var i=0; i<this.stack.length; i++) {
+
+            var bookmarkRow = document.createElement("div");
+            bookmarkRow.className = this.displayClass + 'Row';
+
+            if(this.allowRemove) {
+
+               var removeSpan = document.createElement("span");
+               removeSpan.id = this.displayClass + 'Remove_' +
+                                       this.stack[i].label.replace(/ /g,'_');
+               removeSpan.className = this.displayClass + 'Remove';
+
+               var context = { 'bookmark': this,
+                               'label': this.stack[i].label,
+                               'action': "remove"
+                             };
+
+               OpenLayers.Event.observe(removeSpan, "click",
+                 OpenLayers.Function.bindAsEventListener(this.onBookmarkClick,
+                                                         context)
+               );
+
+               bookmarkRow.appendChild(removeSpan);
+
+            }
+
+            var labelSpan = document.createElement("span");
+            labelSpan.id = this.displayClass + 'Link_' +
+                                this.stack[i].label.replace(/ /g,'_');
+            labelSpan.className = this.displayClass + 'Link';
+            labelSpan.innerHTML = this.stack[i].label;
+
+            var context = { 'bookmark': this,
+                            'label': this.stack[i].label,
+                            'action': 'zoom'
+                          };
+
+            OpenLayers.Event.observe(labelSpan, "click",
+               OpenLayers.Function.bindAsEventListener(this.onBookmarkClick,
+                                                       context)
+            );
+
+            bookmarkRow.appendChild(labelSpan);
+            this.dataElementDiv.appendChild(bookmarkRow);
+        }
+        this.stackState = false;
+        return this.dataElementDiv;
+    },
+
+    /**
+     * Method:
+     * A label has been clicked. Do somehting with/to it.
+     *
+     * Parameters:
+     * e - {Event}
+     *
+     * Context:
+     *  - {<OpenLayers.Control.Bookmark>} bookmark
+     *  - {String} label
+     *  - {String} action
+     */
+    onBookmarkClick: function(e) {
+         switch(this.action) {
+            case "remove":
+               this.bookmark.remove(this.label);
+               break;
+            case "zoom":
+            default:
+               this.bookmark.zoomToLabel(this.label);
+               break
+         }
+        OpenLayers.Event.stop(e);
+    },
+
+    /**
+     * Method: showControls
+     * Hide/Show all controls depending on whether we are
+     *     minimized or not
+     *
+     * Parameters:
+     * minimize - {Boolean}
+     */
+    showControls: function(minimize) {
+        this.maximizeDiv.style.display = minimize ? "block" : "none";
+        this.minimizeDiv.style.display = minimize ? "none" : "block";
+        this.contentDiv.style.display = minimize ? "none" : "block";
+    },
+
+    /**
+     * Method: showToggle
+     * Hide/Show the toggle depending on whether the control is minimized
+     *
+     * Parameters:
+     * minimize - {Boolean}
+     */
+    showToggle: function(minimize) {
+        this.maximizeDiv.style.display = minimize ? "block" : "none";
+        this.minimizeDiv.style.display = minimize ? "none" : "block";
+    },
+
+    /**
+      * Method: ignoreEvent
+      *
+      * Parameters:
+      * evt - {Event}
+      */
+    ignoreEvent: function(evt) {
+       OpenLayers.Event.stop(evt);
+    },
+
+    /**
+      * Method: mouseDown
+      * Register a local 'mouseDown' flag so that we'll know whether or not
+      *     to ignore a mouseUp event
+      *
+      * Parameters:
+      * evt - {Event}
+      */
+    mouseDown: function(evt) {
+        this.isMouseDown = true;
+        this.ignoreEvent(evt);
+    },
+
+    /**
+      * Method: mouseUp
+      * If the 'isMouseDown' flag has been set, that means that the drag was
+      *     started from within the control, and thus we can
+      *     ignore the mouseup. Otherwise, let the Event continue.
+      *
+      * Parameters:
+      * evt - {Event}
+      */
+    mouseUp: function(evt) {
+        if (this.isMouseDown) {
+            this.isMouseDown = false;
+            this.ignoreEvent(evt);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.Bookmark"
+});
+
+OpenLayers.Control.Bookmark.VERSION_NUMBER = '2.1';
\ No newline at end of file

Added: addins/bookmark/release/2.1/tests/Control/Bookmark.html
===================================================================
--- addins/bookmark/release/2.1/tests/Control/Bookmark.html	                        (rev 0)
+++ addins/bookmark/release/2.1/tests/Control/Bookmark.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,204 @@
+<html>
+<head>
+  <script src="http://openlayers.org/api/OpenLayers.js"></script>
+  <script src="../../lib/OpenLayers/Control/Bookmark.js"></script>
+  <script type="text/javascript">
+    var map;
+
+    function test_Control_Bookmark_constructor (t) {
+        t.plan( 2 );
+
+        var control = new OpenLayers.Control.Bookmark();
+        t.ok( control instanceof OpenLayers.Control.Bookmark, "new OpenLayers.Control.Bookmark returns object" );
+        t.eq( control.displayClass,  "olControlBookmark", "displayClass is correct" );
+    }
+
+    function test_Control_Bookmark_draw (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        var div = control.draw();
+        t.ok( control.div != null, "draw makes a div" );
+        t.ok( div != null, "draw returns its div" );
+    }
+
+    function test_Control_Bookmark_outsideViewport (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark({'div':OpenLayers.Util.getElement('bookmark')});
+        map.addControl(control);
+        t.eq(control.div.style.width, "250px", "Div is not minimized when added.");
+        control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+        t.eq(control.div.style.width, "0px", "Div is minimized when added.");
+    }
+
+    function test_Control_Bookmark_loadContents(t) {
+        t.plan( 5 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        t.ok(control.contentDiv != null, "correctly makes content div");
+        t.ok(control.dataLblDiv != null, "correctly makes data label div");
+        t.ok(control.dataElementDiv != null, "correctly makes data element div");
+        t.ok(control.maximizeDiv != null, "correctly makes maximize div");
+        t.ok(control.minimizeDiv != null, "correctly makes minimize div");
+    }
+
+    function test_Control_Bookmark_add_remove (t) {
+        t.plan( 4 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        t.ok(control.stack.length == 1, "correctly added bookmark using object");
+
+        control.remove( "BOSTON" );
+        t.ok(control.stack.length == 0, "correctly removed bookmark object");
+
+        control.add( "BOSTON", "-71.401901,41.989908,-70.698776,42.693033" );
+        t.ok(control.stack.length == 1, "correctly added bookmark using string");
+
+        control.remove( "BOSTON" );
+        t.ok(control.stack.length == 0, "correctly removed bookmark string");
+    }
+
+    function test_Control_Bookmark_find (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        found = control.find("BOSTON");
+        t.ok(found.length == 1, "correctly found single bookmark");
+
+        found = "";
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        found = control.find("BOSTON");
+        t.ok(found.length == 3, "correctly found multiple bookmarks");
+    }
+
+    function test_Control_Bookmark_zoomToLabel (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                   "http://labs.metacarta.com/wms/vmap0",
+                   {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+        map.addLayer( layer );
+        map.zoomToMaxExtent();
+
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        var mapExtent = map.getExtent().toBBOX();
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.zoomToLabel(control.find("BOSTON")[0]);
+        var currentExtent = map.getExtent().toBBOX();
+
+        // note: the map extents may not match due to map zoom levels/resolutions. all
+        // we can really tell is the extents have changed.
+        t.ok(currentExtent != mapExtent, "correctly changed map extents by label");
+    }
+
+    function test_Control_Bookmark_zoomToId (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                   "http://labs.metacarta.com/wms/vmap0",
+                   {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+        map.addLayer( layer );
+        map.zoomToMaxExtent();
+
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        var mapExtent = map.getExtent().toBBOX();
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "BOSTON TOO", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.zoomToId(1);
+        var currentExtent = map.getExtent().toBBOX();
+
+        // note: the map extents may not match due to map zoom levels/resolutions. all
+        // we can really tell is the extents have changed.
+        t.ok(currentExtent != mapExtent, "correctly changed map extents by id");
+    }
+
+    function test_Control_Bookmark_sortByLabel (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "SECOND", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "FIRST", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+
+        control.sortStack("label");
+        t.ok(control.stack[0].label != "SECOND", "correctly sorted by label");
+    }
+
+    function test_Control_Bookmark_sortById (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "SECOND", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "FIRST", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+
+        control.sortStack("label");
+        t.ok(control.stack[0].label != "SECOND", "correctly sorted by label");
+        control.sortStack("id");
+        t.ok(control.stack[0].id == 0, "correctly sorted by id");
+    }
+
+    function test_Control_Bookmark_reindex (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "SECOND", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "FIRST", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+
+        control.sortStack("label");
+        control.reindex();
+        t.ok(control.stack[0].id == 0 && control.stack[0].label == "FIRST", "correctly reindexed stack");
+    }
+
+    function test_Control_Bookmark_clear (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        t.ok(control.stack.length == 1, "correctly added bookmark using object");
+        control.clear();
+        t.ok(control.stack.length == 0, "correctly clear bookmark");
+    }
+  </script>
+</head>
+<body>
+    <div id="map" style="width: 1024px; height: 512px;"/>
+    <div id="bookmark" style="width:250px; height:256px;" />
+</body>
+</html>

Added: addins/bookmark/release/2.1/tests/list-tests.html
===================================================================
--- addins/bookmark/release/2.1/tests/list-tests.html	                        (rev 0)
+++ addins/bookmark/release/2.1/tests/list-tests.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,3 @@
+<ul id="testlist">
+    <li>Control/Bookmark.html</li>
+</ul>

Added: addins/bookmark/release/2.1/tests/run-tests.html
===================================================================
--- addins/bookmark/release/2.1/tests/run-tests.html	                        (rev 0)
+++ addins/bookmark/release/2.1/tests/run-tests.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,2408 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head><title> Run the testsuite</title>
+<noscript>Javascript is disabled in your browser. This page cannot be displayed correctly without Javascript. Sorry. <br/> If you want to view this page, please change your browser settings so that Javascript is enabled.</noscript>
+<!--
+Test.AnotherWay version 0.5
+
+Copyright (c) 2005 Artem Khodush, http://straytree.org
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), 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.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<style type="text/css">
+* { padding: 0; margin: 0; }
+html { height: 99%; }
+body { height: 98%; font: normal normal 10pt sans-serif }
+#col1 {  float: left; width: 27em; margin: 0 0 0 1em; overflow: visible; }
+#col2 {  position: relative; height: 98%; margin: 0 0.5em 0 28em; }
+#col1_header { margin-top: 0.5em; }
+#scroller { height: 400px; overflow: auto;}
+#testtable { margin: 0 0 2em 0; width: 97%; }
+#run_buttons { margin-bottom: 4em; }
+
+#right_header { padding-top: 0.8em; }
+#results_count { float: left; }
+.active_tab 			{ float: right; padding: 0 1em 0.2em 1em; background: #0af; border: 1px solid #048; border-bottom: none; cursor: pointer; cursor: hand;
+					 position: relative; top: -0.2em; }
+.inactive_tab 			{ float: right; padding: 0 1em 0 1em; background: #9bb; color: #444; border: 1px solid #9bb; border-bottom: none; cursor: pointer; cursor: hand; }
+.inactive_mouseover_tab 	{ float: right; padding: 0 1em 0 1em; background: #9bb; color: #062; border: 1px solid #062; border-bottom: none; cursor: pointer; cursor: hand; }
+
+#right_frame { overflow: auto; position: relative; top: -0.2em; clear: right; height: 95%; border: 1px solid #048; }
+
+#debug { display: none; }
+#debug p { margin: 2px 0 0 5em; text-indent: -4.8em; }
+
+#error { display: none; color: #c22; }
+
+#results p { margin: 0 0 2px 0; }
+/* cursor indicating that detailed results may be expanded/contracted */
+#results p.badtest { cursor: text; }
+#results p.ok, #results p.fail { cursor: pointer; cursor: hand; }
+
+/* colored squares in the results window at the left of test page names */
+#results p.ok .bullet { background: #6d6; }
+#results p.fail .bullet { background:  #d46; }
+#results p.badtest .bullet { background: #ea3; }
+#results p.loading .bullet { background: #48f; }
+#results p.running .bullet { background: #26e; }
+#results p.waiting .bullet { background: #04d; }
+/* highlight in the results line */
+#results p .warning { background: #ffc; }
+
+/* layout of the detailed results */
+.result_detail { padding-left: 3em; }
+.result_exception_detail { padding-left: 4em; }
+.result_exception_stack_detail { padding-left: 5em;  }
+.result_micro_detail { padding-left: 6em; }
+/* colouring in the detailed results */
+.result_detail .fail, .result_exception_detail .fail,  .result_micro_detail .fail { background: #ffd8d8; }
+
+/* "start recording" controls*/
+#record_div { margin-top: 3em; }
+#record_div p { margin-bottom: 0.5em; }
+#record_select { width: 88%; }
+#record_input { width: 53%; }
+</style>
+<script type="text/javascript">
+<!--
+if( typeof( Test )=="undefined" ) {
+	Test={};
+}
+Test.AnotherWay={};
+
+Test.AnotherWay._g_test_iframe=null; // frame where to load test pages
+Test.AnotherWay._g_test_frame_no_clear=false; // true - leave last page displayed after tests end
+Test.AnotherWay._g_test_page_urls=[]; // array of: { url: url, convention: "anotherway" or "jsan" }
+Test.AnotherWay._g_test_object_for_jsan=null; // test object for filling by tests that adhere to jsan Test.Simple calling convention
+Test.AnotherWay._g_pages_to_run=null; // list of pages to run automatically after loading
+Test.AnotherWay._g_run_on_main_load=false; // special handling for run_pages_to_run when it might be called before onload or before list of test pages is known.
+Test.AnotherWay._g_run_on_list_load=false;
+Test.AnotherWay._g_main_loaded=false;
+
+Test.AnotherWay._run_pages_to_run=function( called_from_outside )
+{
+	if( !Test.AnotherWay._g_main_loaded ) {
+		Test.AnotherWay._g_run_on_main_load=true;
+	}else {
+		var a_pages=Test.AnotherWay._g_pages_to_run;
+		if( a_pages=="all" ) {
+			for( var i=0; i<Test.AnotherWay._g_test_page_urls.length; ++i ) {
+				Test.AnotherWay._run_test_page( "test"+i );
+			}
+		}else if( a_pages!=null ) {
+			for( var run_i=0; run_i<a_pages.length; ++run_i ) {
+				var run_page=a_pages[run_i];
+				var found=false;
+				for( var all_i=0; all_i<Test.AnotherWay._g_test_page_urls.length; ++all_i ) {
+					if( run_page==Test.AnotherWay._g_test_page_urls[all_i].url ) {
+						Test.AnotherWay._run_test_page( "test"+all_i, called_from_outside );
+						found=true;
+						break;
+					}
+				}
+				if( !found ) {
+					Test.AnotherWay._show_error( "page specified to run is not found in the page list: "+run_page );
+					break;
+				}
+			}
+		}
+	}
+}
+
+Test.AnotherWay._add_test_page_url=function( test_url, convention )
+{
+	var table=document.getElementById( "testtable" );
+	var record_select=document.getElementById( "record_select" );
+	var index=Test.AnotherWay._g_test_page_urls.length;
+
+	// trim spaces.
+	if( test_url.match( "^(\\s*)(.*\\S)(\\s*)$" ) ) {
+		test_url=RegExp.$2;
+	}
+
+	Test.AnotherWay._g_test_page_urls[index]={ url: test_url, convention: convention };
+	var row=table.insertRow( -1 );
+
+	var cell;
+	var cell_child;
+	cell=row.insertCell( -1 );
+	cell_child=document.createElement( "input" );
+	cell_child.type="checkbox";
+	cell_child.id="checkbox"+index;
+    cell_child.checked='checked';
+    cell_child.defaultChecked='checked';
+	cell.appendChild( cell_child );
+
+	cell=row.insertCell( -1 );
+	cell.setAttribute( "width", "75%" );
+	cell.appendChild( document.createTextNode( test_url ) );
+
+	cell=row.insertCell( -1 );
+	cell_child=document.createElement( "input" );
+	cell_child.type="button";
+	cell_child.id="test"+index;
+	cell_child.value=" run ";
+	cell_child.onclick=Test.AnotherWay._run_one_onclick;
+	cell.appendChild( cell_child );
+
+	cell=row.insertCell( -1 );
+	cell.setAttribute( "width", "8em" );
+	cell_child=document.createElement( "span" );
+	cell.appendChild( cell_child );
+
+	var option=document.createElement( "option" );
+	option.appendChild( document.createTextNode( test_url ) );
+	record_select.appendChild( option );
+}
+Test.AnotherWay._show_error=function( msg )
+{
+	var error_div=document.getElementById( "error" );
+	error_div.innerHTML="";
+	error_div.appendChild( document.createTextNode( msg ) );
+	error_div.style.display="block";
+}
+
+// read urls from the list in the html file inside the list_iframe
+// fill on-screen list with urls and "run" buttons, and fill the g_test_page_urls object.
+Test.AnotherWay._list_iframe_onload=function()
+{
+	if( window.frames.list_iframe!=null && window.frames.list_iframe.location!="" && window.frames.list_iframe.location!="about:blank" ) {
+		var list_doc=window.frames.list_iframe.document;
+		var list=list_doc.getElementById( "testlist" );
+		if( list!=null ) {
+			for( var i=0; i<list.childNodes.length; ++i ) {
+				var item=list.childNodes[i];
+				if( item.nodeName=="LI" || item.nodeName=="li" ) {
+					var convention="anotherway";
+					if( Test.AnotherWay._get_css_class( item )=="jsan" ) {
+						convention="jsan";
+					}
+					Test.AnotherWay._add_test_page_url( item.innerHTML, convention );
+				}
+			}
+			if( Test.AnotherWay._g_run_on_list_load ) {
+				Test.AnotherWay._g_run_on_list_load=false;
+				Test.AnotherWay._run_pages_to_run();
+			}
+		}else {
+			Test.AnotherWay._show_error( "no list with id 'testlist' in a list file "+window.frames.list_iframe.location );
+		}
+	}
+}
+
+Test.AnotherWay._map_checkboxes=function( f )
+{
+	var table=document.getElementById( "testtable" );
+	var checks=table.getElementsByTagName( "INPUT" );
+	for( var i=0; i<checks.length; ++i ) {
+		if( checks[i].type=="checkbox" && checks[i].id.match( /^checkbox(\d+)$/ ) ) {
+			f( checks[i], RegExp.$1 );
+		}
+	}
+}
+Test.AnotherWay._run_all_onclick=function()
+{
+	Test.AnotherWay._map_checkboxes( function( c, id ) { Test.AnotherWay._run_test_page( "test"+id ); } );
+}
+Test.AnotherWay._run_selected_onclick=function()
+{
+	Test.AnotherWay._map_checkboxes( function( c, id ) { if( c.checked ) Test.AnotherWay._run_test_page( "test"+id ); } );
+}
+Test.AnotherWay._unselect_all_onclick=function()
+{
+	Test.AnotherWay._map_checkboxes( function( c, id ) { c.checked=false; } );
+}
+Test.AnotherWay._run_one_onclick=function()
+{
+	Test.AnotherWay._run_test_page( this.id );
+}
+
+// construct an object that will gather results of running one test function
+Test.AnotherWay._test_object_t=function( fun_name )
+{
+	this.name=fun_name; // name of the test function
+	this.n_plan=null; // planned number of assertions
+	this.n_ok=0; // # of ok assertions
+	this.n_fail=0; // # of failed assertions
+	this.exception=""; // if the function throwed an exception, it's its message
+	this.exception_stack=[]; // strings: function call stack from the exception
+	this.assertions=[]; // assertion results: array of { ok: 1 or 0, name: string }
+	this.wait_result_milliseconds=0; // how long to wait before collecting results from the test
+	this.second_wait_msg=null; // <p> status message (in addition to the page wait_msg)
+	this.delay_actions=[]; // array of actions to be perfomed after the test function returns
+				//	action : { acton_kind: "call" | "window" | "replay"
+				//				when "call": 		{ call_fn call_delay_milliseconds } call_fn takes nothing
+				//				when "window" : 	{ wnd_url wnd_wnd wnd_fn wnd_timeout_milliseconds wnd_dont_close } wnd_fn takes wnd
+				//				wnen "replay" : 	{ replay_wnd replay_events replay_event_i replay_checkpoints } checkpoint_fn takes this, wnd
+				//	}
+	this.delay_action_i=null; // index of delay action currently being performed
+	this.delay_prev_timer_time=0;	// for counting time while performing delay_actions
+	this.delay_current_milliseconds_left=0; // time left before the next action, runs down
+	this.delay_total_milliseconds_left=0; 	// for indication: total estimated time for all actions, runs up and down
+}
+
+Test.AnotherWay._test_object_t.prototype.ok=function( cond, name )
+{
+	if( cond ) {
+		++this.n_ok;
+		cond=1;
+	}else {
+		++this.n_fail;
+		cond=0;
+	}
+	this.assertions.push( { ok: cond, name: name } );
+}
+Test.AnotherWay._test_object_t.prototype.fail=function( name )
+{
+	this.ok( false, name );
+}
+Test.AnotherWay._test_object_t.prototype.plan=function( n )
+{
+	this.n_plan=n;
+}
+Test.AnotherWay._test_object_t.prototype.wait_result=function( seconds )
+{
+	this.wait_result_milliseconds=1000*seconds;
+}
+Test.AnotherWay._eq_fail_msg=function( path, what, expected, got )
+{
+	return "eq: "+path+" "+what+" differ: got "+got+", but expected "+expected;
+}
+Test.AnotherWay._array_eq=function( expected, got, path, msg )
+{
+	if( expected.length!=got.length ) {
+		msg.msg=Test.AnotherWay._eq_fail_msg( path, "array length", expected.length, got.length );
+		return false;
+	}
+	for( var i=0; i<expected.length; ++i ) {
+		if( !Test.AnotherWay._thing_eq( expected[i], got[i], path+"["+i+"]", msg ) ) {
+			return false;
+		}
+	}
+	return true;
+}
+Test.AnotherWay._object_eq=function( expected, got, path, msg )
+{
+	var v;
+	for( v in expected ) {
+		if( ! (v in got) ) {
+			msg.msg=Test.AnotherWay._eq_fail_msg( path+"."+v, "properties", expected[v], "undefined" );
+			return false;
+		}
+		if( !Test.AnotherWay._thing_eq( expected[v], got[v], path+"."+v, msg ) ) {
+			return false;
+		}
+	}
+	for( v in got ) {
+		if( ! (v in expected) ) {
+			msg.msg=Test.AnotherWay._eq_fail_msg( path+"."+v, "properties", "undefined", got[v] );
+			return false;
+		}
+	}
+	return true;
+}
+Test.AnotherWay._constructor_name=function( x )
+{
+	if( x==null ) {
+		return "";
+	}
+	var s="unknown";
+	try {
+		s=typeof( x.constructor );
+		if( s!="unknown" ) {
+			s=x.constructor.toString();
+		}
+	}catch( e ) {
+		s="unknown";
+	}
+	if( s=="unknown" ) {
+		// hackish attempt to guess a type
+		var is_array=true;
+		var index=0;
+		for( i in x ) {
+			if( i!=index ) {
+				is_array=false;
+			}
+			++index;
+		}
+		return is_array ? "Array" : "Object"; // for empty arrays/objects, this will be wrong half the time
+	}else if( s.match( /^\s*function\s+(\w+)\s*\(/ ) ) {
+		return RegExp.$1;
+	}else {
+	  var c = '';
+	  switch(typeof x) {
+	    case 'string':
+	      c = 'String';
+	      break;
+	    case 'object':
+	      c = 'Object';
+	      break;
+	    default:
+	      c = '';
+	  }
+		return c;
+	}
+}
+Test.AnotherWay._is_array=function( x )
+{
+	return Test.AnotherWay._constructor_name( x )=="Array";
+}
+Test.AnotherWay._is_value_type=function( x )
+{
+	cn=Test.AnotherWay._constructor_name( x );
+	return cn=="Number" || cn=="String" || cn=="Boolean" || cn=="Date";
+}
+Test.AnotherWay._thing_eq=function( expected, got, path, msg )
+{
+	if( expected==null && got==null ) {
+		return true;
+	}else if( (expected==null && got!=null) || (expected!=null && got==null) ) {
+		msg.msg=Test.AnotherWay._eq_fail_msg( path, "values", expected, got );
+		return false;
+	}else {
+		var expected_cn=Test.AnotherWay._constructor_name( expected );
+		var got_cn=Test.AnotherWay._constructor_name( got );
+		if( expected_cn!=got_cn ) {
+			msg.msg=Test.AnotherWay._eq_fail_msg( path, "types", expected_cn, got_cn );
+			return false;
+		}else {
+			if( Test.AnotherWay._is_array( expected ) ) {
+				return Test.AnotherWay._array_eq( expected, got, path, msg );
+			}else if( Test.AnotherWay._is_value_type( expected ) ) {
+				if( expected!=got ) {
+					msg.msg=Test.AnotherWay._eq_fail_msg( path, "values", expected, got );
+					return false;
+				}else {
+					return true;
+				}
+			}else { // just a plain object
+				return Test.AnotherWay._object_eq( expected, got, path, msg );
+			}
+		}
+	}
+}
+Test.AnotherWay._test_object_t.prototype.eq=function( got, expected, name )
+{
+	var msg={};
+	if( Test.AnotherWay._thing_eq( expected, got, "", msg ) ) {
+		this.ok( 1, name );
+	}else {
+		this.fail( name+". "+msg.msg );
+	}
+}
+Test.AnotherWay._test_object_t.prototype.like=function( got, expected, name )
+{
+	if( got.match( expected )!=null ) {
+		this.ok( 1, name );
+	}else {
+		this.fail( name+": got "+got+", but expected it to match: "+expected );
+	}
+}
+Test.AnotherWay._g_html_eq_span=null;
+Test.AnotherWay._html_eq_string_to_node=function( string_or_node, what, msg )
+{
+	if( string_or_node.nodeType!=null ) {
+		string_or_node=Test.AnotherWay._html_eq_node_to_string( string_or_node ); // double trip - to make properties assigned in scripts available as html node attributes
+	}
+	if( Test.AnotherWay._g_html_eq_span==null ) {
+		Test.AnotherWay._g_html_eq_span=document.createElement( "span" );
+	}
+	Test.AnotherWay._g_html_eq_span.innerHTML=string_or_node;
+	if( Test.AnotherWay._g_html_eq_span.childNodes.length!=1 ) {
+		msg.msg="bad "+what+" html string given (should contain exactly one outermost element): "+string_or_node;
+	}
+	return Test.AnotherWay._g_html_eq_span.childNodes[0].cloneNode( true );
+}
+Test.AnotherWay._html_eq_node_to_string=function( node ) {
+	if( Test.AnotherWay._g_html_eq_span==null ) {
+		Test.AnotherWay._g_html_eq_span=document.createElement( "span" );
+	}
+	Test.AnotherWay._g_html_eq_span.innerHTML="";
+	if( node.outerHTML!=null ) {
+		Test.AnotherWay._g_html_eq_span.innerHTML=node.outerHTML;
+	}else {
+            var clone = node.cloneNode(true);
+            var node = Test.AnotherWay._g_html_eq_span;
+            if(node.ownerDocument && node.ownerDocument.importNode) {
+                if(node.ownerDocument != clone.ownerDocument) {
+                    clone = node.ownerDocument.importNode(clone, true);
+                }
+            }
+            node.appendChild(clone);
+	}
+	return Test.AnotherWay._g_html_eq_span.innerHTML;
+}
+Test.AnotherWay._html_eq_path_msg=function( path )
+{
+	var msg="";
+	for( var i=0; i<path.length; ++i ) {
+		msg+=" [node "+path[i].node;
+		if( path[i].id!=null && path[i].id!="" ) {
+			msg+=" id "+path[i].id;
+		}else if( path[i].index!=null ) {
+			msg+=" at index "+path[i].index;
+		}
+		msg+="] "
+	}
+	return msg;
+}
+Test.AnotherWay._html_eq_fail_msg=function( path, what, expected, got )
+{
+	return Test.AnotherWay._html_eq_path_msg( path )+": "+what+" differ: got "+got+", but expected "+expected;
+}
+Test.AnotherWay._html_eq_remove_blank=function( text )
+{
+	if( text==null ) {
+		return "";
+	}else if( text.match( "^(\\s*)(.*\\S)(\\s*)$" ) ) {
+		return RegExp.$2;
+	}else if( text.match( "\s*" ) ) {
+		return "";
+	}
+	return text;
+}
+Test.AnotherWay._html_eq_remove_blank_nodes=function( node )
+{
+	var to_remove=[];
+	for( var child=node.firstChild; child!=null; child=child.nextSibling ) {
+		if( child.nodeType==3 ) {
+			var value=Test.AnotherWay._html_eq_remove_blank( child.nodeValue );
+			if( value=="" ) {
+				to_remove.push( child );
+			}else {
+				child.nodeValue=value;
+			}
+		}
+	}
+	for( var i=0; i<to_remove.length; ++i ) {
+		node.removeChild( to_remove[i] );
+	}
+}
+Test.AnotherWay._html_node_type_text=function( node_type )
+{
+	if( node_type==1 ) {
+		return "1 (html element)";
+	}else if( node_type==3 ) {
+		return "3 (text)";
+	}else {
+		return node_type;
+	}
+}
+Test.AnotherWay._html_eq_node=function( expected, got, path, msg, expected_loc_base, got_loc_base )
+{
+	if( expected.nodeType!=got.nodeType ) {
+		msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "node types", Test.AnotherWay._html_node_type_text( expected.nodeType ), Test.AnotherWay._html_node_type_text( got.nodeType ) );
+		return false;
+	}else if( expected.nodeType==3 ) {
+		if( expected.nodeValue!=got.nodeValue ) {
+			msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "text", expected.nodeValue, got.nodeValue );
+			return false;
+		}
+	}else if( expected.nodeType==1 ) {
+		if( expected.nodeName!=got.nodeName ) {
+			msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "node names", expected.nodeName, got.nodeName );
+			return false;
+		}
+		// compare attributes
+		var expected_attrs={};
+		var got_attrs={};
+		var i;
+		var a;
+		for( i=0; i<expected.attributes.length; ++i ) {
+			a=expected.attributes[i];
+			if( a.specified ) {
+				expected_attrs[a.name]=1;
+			}
+		}
+		for( i=0; i<got.attributes.length; ++i ) {
+			a=got.attributes[i];
+			if( a.specified ) {
+				got_attrs[a.name]=1;
+			}
+		}
+		for( a in expected_attrs ) {
+			if( ! (a in got_attrs) ) {
+				msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute sets differ: expected attribute "+a+" is missing";
+				return false;
+			}
+		}
+		for( a in got_attrs ) {
+			if( ! (a in expected_attrs) ) {
+				msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute sets differ: got extra attribute "+a;
+				return false;
+			}
+		}
+		for( a in expected_attrs ) {
+			var expected_value=expected.getAttribute( a );
+			var got_value=got.getAttribute( a );
+			if( typeof( expected_value )=="string" && typeof( got_value )=="string" ) {
+				expected_value=Test.AnotherWay._html_eq_remove_blank( expected_value );
+				got_value=Test.AnotherWay._html_eq_remove_blank( got_value );
+				var ok=expected_value==got_value;
+				if( !ok && (a=="href" || a=="HREF" )  ) { // try relative hrefs
+					var expected_relative_value=expected_value;
+					if( expected_loc_base!=null && expected_value.substring( 0, expected_loc_base.length )==expected_loc_base ) {
+						expected_relative_value=expected_value.substring( expected_loc_base.length );
+					}
+					var got_relative_value=got_value;
+					if( got_loc_base!=null && got_value.substring( 0, got_loc_base.length )==got_loc_base ) {
+						got_relative_value=got_value.substring( got_loc_base.length );
+					}
+					ok=expected_relative_value==got_relative_value;
+				}
+				if( !ok ) {
+					msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "attribute "+a+" values", expected_value, got_value );
+					return false;
+				}
+			}else if( typeof( expected_value )=="function" && typeof( got_value )=="function" ) {
+				expected_value=expected_value.toString();
+				got_value=got_value.toString();
+				if( expected_value!=got_value ) {
+					msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "attribute "+a+" values", expected_value, got_value );
+					return false;
+				}
+			}else {
+				var value_msg={};
+				if( !Test.AnotherWay._thing_eq( expected_value, got_value, "", value_msg ) ) {
+					msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute "+a+" values differ: "+value_msg.msg;
+					return false;
+				}
+			}
+		}
+		// compare child nodes
+		Test.AnotherWay._html_eq_remove_blank_nodes( expected );
+		Test.AnotherWay._html_eq_remove_blank_nodes( got );
+		var expected_length=expected.childNodes.length;
+		var got_length=got.childNodes.length;
+		if( expected_length<got_length ) {
+			msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": got "+(got_length-expected_length)+" extra child nodes";
+			return false;
+		}else if( expected_length>got_length ) {
+			msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": expected "+(expected_length-got_length)+" more child nodes";
+			return false;
+		}else {
+			for( i=0; i<expected_length; ++i ) {
+				var expected_node=expected.childNodes[i];
+				path.push( { node: expected_node.nodeName, id: expected_node.id, index: i } );
+				var eq=Test.AnotherWay._html_eq_node( expected_node, got.childNodes[i], path, msg, expected_loc_base, got_loc_base );
+				path.pop();
+				if( !eq ) {
+					return false;
+				}
+			}
+		}
+	}
+	return true;
+}
+Test.AnotherWay._html_eq_get_loc_base=function( node )
+{
+	var loc_base=document.location;
+	if( node.ownerDocument!=null ) {
+		loc_base=node.ownerDocument.location;
+	}
+	if( loc_base!=null ) {
+		loc_base=loc_base.href;
+		var slash_pos=loc_base.lastIndexOf( "/" );
+		if( slash_pos!=-1 ) {
+			loc_base=loc_base.substring( 0, slash_pos+1 );
+		}
+	}
+	return loc_base;
+}
+Test.AnotherWay._test_object_t.prototype.html_eq=function( got, expected, name )
+{
+	var msg={};
+	var expected_node=Test.AnotherWay._html_eq_string_to_node( expected, "expected", msg );
+	if( msg.msg!=null ) {
+		this.fail( name+" html_eq: "+msg.msg );
+	}else {
+		var got_node=Test.AnotherWay._html_eq_string_to_node( got, "got", msg );
+		if( msg.msg!=null ) {
+			this.fail( name+" html_eq: "+msg.msg );
+		}else {
+			var expected_loc_base=Test.AnotherWay._html_eq_get_loc_base( expected );
+			var got_loc_base=Test.AnotherWay._html_eq_get_loc_base( got );
+			if( Test.AnotherWay._html_eq_node( expected_node, got_node, [], msg, expected_loc_base, got_loc_base ) ) {
+				this.ok( 1, name );
+			}else {
+				var msg=name+" html_eq "+msg.msg;
+				var expected_str=Test.AnotherWay._html_eq_node_to_string( expected_node );
+				var got_str=Test.AnotherWay._html_eq_node_to_string( got_node );
+				msg+=".\n got html: "+got_str;
+				msg+=".\n expected html: "+expected_str;
+				this.fail( msg );
+			}
+		}
+	}
+}
+Test.AnotherWay._debug_pane_print=function( msg )
+{
+	var d=new Date();
+	var p=document.createElement( "p" );
+	p.appendChild( document.createTextNode( d.toLocaleTimeString()+" "+msg ) );
+	var debug_pane=document.getElementById( "debug" );
+	debug_pane.appendChild( p );
+	var debug_tab=document.getElementById( "debug_tab" );
+	var results_tab=document.getElementById( "results_tab" );
+	debug_tab.style.visibility="visible";
+	results_tab.style.visibility="visible";
+}
+Test.AnotherWay._test_object_t.prototype.debug_print=function( msg )
+{
+	Test.AnotherWay._debug_pane_print( this.name+": "+msg );
+}
+Test.AnotherWay._test_object_t.prototype.delay_call=function()
+{
+	var timeout_ms=200;
+	for( var i=0; i<arguments.length; ++i ) {
+		if( typeof( arguments[i] )!="function" ) {
+			timeout_ms=1000*arguments[i];
+		}else {
+			var action={ action_kind: "call", call_delay_milliseconds: timeout_ms, call_fn: arguments[i] };
+			this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+			this.delay_actions.push( action );
+		}
+	}
+}
+Test.AnotherWay._test_object_t.prototype.open_window=function( url, fn, timeout_seconds )
+{
+	if( timeout_seconds==null ) {
+		timeout_seconds=4;
+	}
+	var no_close=document.getElementById( "dont_close_test_windows" );
+	var action={ action_kind: "window", wnd_url: url.toString(), wnd_wnd: null, wnd_fn: fn, wnd_timeout_milliseconds: timeout_seconds*1000, wnd_no_close: no_close.checked };
+	this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+	this.delay_actions.push( action );
+}
+Test.AnotherWay._test_object_t.prototype.replay_events=function( wnd, events )
+{
+	if( Test.AnotherWay._g_no_record_msg!=null ) {
+		this.fail( "replay_events: "+Test.AnotherWay._g_no_record_msg );
+	}else {
+		var action={ action_kind: "replay", replay_wnd: wnd, replay_events: events.events, replay_event_i: null, replay_checkpoints: events.checkpoints };
+		this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+		this.delay_actions.push( action );
+	}
+}
+Test.AnotherWay._action_estimate_milliseconds=function( action )
+{
+	var ms=0;
+	if( action.action_kind=="call" ) {
+		ms=action.call_delay_milliseconds;
+	}else if( action.action_kind=="window" ) {
+		ms=0;
+	}else if( action.action_kind=="replay" ) {
+		ms=0;
+		for( var i=0; i<action.replay_events.length; ++i ) {
+			ms+=action.replay_events[i]["time"]-0;
+		}
+	}
+	return ms;
+}
+
+Test.AnotherWay._g_timeout_granularity=200;
+Test.AnotherWay._g_tests_queue=[]; // vector of { url: string, test_objects : array of test_object_t, test_object_i: int, wait_msg: <p> object, loading_timeout_milliseconds: int, timeout_id: id }
+
+// load one html page, schedule further processing
+Test.AnotherWay._run_test_page=function( id, called_from_outside )
+{
+	if( id.match( /^test(\d+)/ ) ) {
+		id=RegExp.$1;
+		Test.AnotherWay._g_tests_queue.push( {
+			url: Test.AnotherWay._g_test_page_urls[id].url,
+			convention: Test.AnotherWay._g_test_page_urls[id].convention,
+			test_objects: []
+			} );
+		if( Test.AnotherWay._g_tests_queue.length==1 ) {
+			if( !called_from_outside ) {
+				// Crap. Be careful stepping around.
+				// For Mozilla and Opera, when this file is included into the frameset page that is in another directory (and _g_outside_path_correction!=null)
+				// but the test pages are started from within it (by "run" buttons), then:
+				// depending on whether the page is the first one loaded into the test frame or not,
+				// the base url for relative test pages differs.
+				// Crap, like I said.
+				Test.AnotherWay._g_tests_queue[0].suppress_outside_path_correction=true;
+			}
+			Test.AnotherWay._start_loading_page();
+		}
+	}
+}
+Test.AnotherWay._load_next_page=function()
+{
+	Test.AnotherWay._g_tests_queue.splice( 0, 1 );
+	if( Test.AnotherWay._g_tests_queue.length>0 ) {
+		Test.AnotherWay._start_loading_page();
+	}else {
+		if( !Test.AnotherWay._g_test_frame_no_clear ) {
+			Test.AnotherWay._g_test_iframe.location.replace( "about:blank" );
+		}
+	}
+}
+Test.AnotherWay._g_opera_path_correction=null; // ugly wart to support opera
+Test.AnotherWay._g_outside_path_correction=null; // ugly wart to accomodate Opera and Mozilla, where relative url relates to the directory where the page that calls this function is located
+Test.AnotherWay._set_iframe_location=function( iframe, loc, outside_path_correction )
+{
+	// allow to load only locations with the same origin
+	var proto_end=loc.indexOf( "://" );
+	if( proto_end!=-1 ) { // otherwise, it's safe to assume (for Opera, Mozilla and IE ) that loc will be treated as relative
+		var main_loc=window.location.href;
+		var host_end=loc.substring( proto_end+3 ).indexOf( "/" );
+		var ok=false;
+		if( host_end!=-1 ) {
+			var loc_origin=loc.substring( 0, proto_end+3+host_end+1 );
+			if( main_loc.length>=loc_origin.length && main_loc.substring( 0, loc_origin.length )==loc_origin ) {
+				ok=true;
+			}
+		}
+		if( !ok ) {
+			return { msg: "test pages may have only urls with the same origin as "+main_loc };
+		}
+	}
+	// opera cannot handle urls relative to file:// without assistance
+	if( window.opera!=null && window.location.protocol=="file:" && loc.indexOf( ":" )==-1 ) {
+		var base=window.location.href;
+		var q_pos=base.indexOf( "?" );
+		if( q_pos!=-1 ) {
+			base=base.substring( 0, q_pos );
+		}
+		var slash_pos=base.lastIndexOf( "/" );
+		if( slash_pos!=-1 ) {
+			base=base.substring( 0, slash_pos+1 );
+			Test.AnotherWay._g_opera_path_correction=base;
+			loc=base+loc;
+		}
+	}
+	// if this function is called from another page, and if that page is in another directory, correction is needed
+	if( outside_path_correction!=null ) {
+		var pos=loc.indexOf( outside_path_correction );
+		if( pos==0 ) {
+			loc=loc.substring( outside_path_correction.length+1 );
+		}
+	}
+	if( iframe.location!=null ) {
+		iframe.location.replace( loc );
+	}else {
+		iframe.src=loc;
+	}
+	return {};
+}
+Test.AnotherWay._start_loading_page=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	test_page.loading_timeout_milliseconds=12000;
+	test_page.timeout_id=setTimeout( Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity );
+	test_page.wait_msg=Test.AnotherWay._print_counter_result( test_page.url, "loading...", test_page.loading_timeout_milliseconds, "loading" );
+	if( test_page.convention=="jsan" ) {
+		// the tests in that page will run when it's loading, so the test object must be ready
+		Test.AnotherWay._g_test_object_for_jsan=new Test.AnotherWay._test_object_t( test_page.url );
+	}
+	var outside_path_correction=null;
+	if( Test.AnotherWay._g_outside_path_correction!=null && !test_page.suppress_outside_path_correction ) {
+		outside_path_correction=Test.AnotherWay._g_outside_path_correction;
+	}
+	var result=Test.AnotherWay._set_iframe_location( Test.AnotherWay._g_test_iframe, test_page.url, outside_path_correction );
+	if( result.msg!=null ) {
+		Test.AnotherWay._unprint_result( test_page.wait_msg );
+		Test.AnotherWay._print_result( test_page.url, result.msg, "badtest", null );
+		Test.AnotherWay._load_next_page();
+	}
+}
+
+Test.AnotherWay._loading_timeout=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	test_page.loading_timeout_milliseconds-=Test.AnotherWay._g_timeout_granularity;
+	if( test_page.loading_timeout_milliseconds>0 ) {
+		Test.AnotherWay._update_msg_counter( test_page.wait_msg, (test_page.loading_timeout_milliseconds/1000).toFixed() );
+		test_page.timeout_id=setTimeout( Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity );
+	}else {
+		Test.AnotherWay._unprint_result( test_page.wait_msg );
+		Test.AnotherWay._print_result( test_page.url, "Unable to load test page. Timeout expired", "badtest", null );
+		Test.AnotherWay._load_next_page();
+	}
+}
+
+Test.AnotherWay._strip_query_and_hash=function( s )
+{
+	var i=s.lastIndexOf( "#" );
+	if( i!=-1 ) {
+		s=s.substring( 0, i );
+	}
+	i=s.lastIndexOf( "?" );
+	if( i!=-1 ) {
+		s=s.substring( 0, i );
+	}
+	return s;
+}
+Test.AnotherWay._is_url_loaded=function( url, wnd )
+{
+	var loaded=false;
+	if( wnd!=null && wnd.location!=null ) {
+		// after some popup blocker interference, location may behave strange..
+		var location_s="";
+		location_s+=wnd.location;
+		if( location_s!="" ) {
+			var pathname=wnd.location.pathname;
+			var expected_url=url;
+			var i=expected_url.lastIndexOf( "#" );
+			if( i!=-1 ) {
+				expected_url=expected_url.substring( 0, i );
+			}
+			i=expected_url.lastIndexOf( "?" );
+			if( i!=-1 ) {
+				expected_url=expected_url.substring( 0, i );
+			}
+			i=expected_url.lastIndexOf( "/" );
+			if( i!=-1 && i!=expected_url.length-1 ) {
+				expected_url=expected_url.substring( i+1 );
+			}
+			i=pathname.indexOf( expected_url )
+			if( wnd.location.href==url || (i!=-1 && i==pathname.length-expected_url.length) ) {
+				if( /*window.opera==null*/wnd.document.readyState==null || wnd.document.readyState=="complete" ) { // for opera (and IE?), getElementById does not work until..
+					loaded=true;
+				}
+			}
+		}
+	}
+	return loaded;
+}
+// find and run all test functions in the g_cur_page html page.
+Test.AnotherWay._test_page_onload=function()
+{
+	if( Test.AnotherWay._g_tests_queue.length==0 ) {
+		return;
+	}
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	if( !Test.AnotherWay._is_url_loaded( test_page.url, Test.AnotherWay._g_test_iframe ) ) {
+		return;
+	}
+	clearTimeout( test_page.timeout_id );
+	Test.AnotherWay._unprint_result( test_page.wait_msg );
+
+	if( test_page.convention=="anotherway" ) {
+		// get test function names (those beginning with "test")
+		if( typeof( Test.AnotherWay._g_test_iframe.document.scripts )!='undefined' ) { // IE
+			for( var i=0; i<Test.AnotherWay._g_test_iframe.document.scripts.length; ++i ) {
+				var script_text=Test.AnotherWay._g_test_iframe.document.scripts[i].text;
+				var fun_sig="function test";
+				var fun_start=script_text.indexOf( fun_sig );
+
+				while( fun_start!=-1 ) {
+					script_text=script_text.substring( fun_start, script_text.length );
+					var fun_end=script_text.indexOf( '(' );
+					var fun_name=script_text.substring( "function ".length,  fun_end );
+                                        var whitespace = fun_name.indexOf( ' ' );
+                                        if (whitespace >= 0)
+                                            fun_name = fun_name.substring( 0, whitespace );
+					test_page.test_objects.push( new Test.AnotherWay._test_object_t( fun_name ) );
+					script_text=script_text.substring( fun_end, script_text.length );
+					fun_start=script_text.indexOf( fun_sig  );
+				}
+			}
+		}else { // otherwise (not IE) it ought to work like this
+			for( var i in Test.AnotherWay._g_test_iframe) {
+				// Hack to prevent failure in FF3.0b1
+                if (i == "innerWidth" || i == "innerHeight") { continue; }
+                if( typeof( Test.AnotherWay._g_test_iframe[i] )=='function' ) {
+					if( i.substring( 0, 4 )=="test" ) {
+						test_page.test_objects.push( new Test.AnotherWay._test_object_t( i ) );
+					}
+				}
+			}
+		}
+	}else if( test_page.convention=="jsan" ) {
+		// the test object is already filled with results
+		test_page.test_objects.push( Test.AnotherWay._g_test_object_for_jsan );
+	}
+
+	if( test_page.test_objects.length==0 ) {
+		Test.AnotherWay._print_result( test_page.url,  "No test functions defined in the page", "badtest", null );
+		Test.AnotherWay._load_next_page();
+		return;
+	}
+
+	test_page.wait_msg=Test.AnotherWay._print_result( test_page.url, "running tests..<span class=\"counter\">"+test_page.test_objects.length+"</span>", "running", null );
+
+	test_page.test_object_i=0;
+	Test.AnotherWay._run_more_tests();
+}
+
+Test.AnotherWay._handle_exception=function( o, e, title )
+{
+	var s=title+": "+typeof( e )+": ";
+	if( e.message!=null ) {
+		s+=e.message;
+	}else if( e.description!=null ) {
+		s+=e.description;
+	}else {
+		s+=e.toString();
+	}
+//	if( e.location!=null ) {  // XXX figure out how to display exception location if it's present (like in mozilla)
+//		s+=" location: "+e.location.toString();
+//	}
+	o.exception=s;
+	s=[];
+	if( e.stack ) {
+		var lines=e.stack.split( "\n" );
+		for( var i=0; i<lines.length; ++i ) {
+			// format of the line: func_name(args)@file_name:line_no
+			if( lines[i].match( /(\w*)\(([^\)]*)\)@(.*):([^:]*)$/ ) ) {
+				var func_name=RegExp.$1;
+				if( func_name.length==0 ) {
+					func_name="<anonymous>";
+				}
+				s.push( "in "+func_name+"( "+RegExp.$2+") at "+RegExp.$3+" line "+RegExp.$4+"\n" );
+			}
+		}
+	}
+	o.exception_stack=s;
+}
+
+Test.AnotherWay._run_more_tests=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	while( test_page.test_object_i<test_page.test_objects.length ) {
+		Test.AnotherWay._update_msg_counter( test_page.wait_msg, (1+test_page.test_object_i)+"/"+test_page.test_objects.length );
+		var o=test_page.test_objects[test_page.test_object_i];
+		if( test_page.convention=="anotherway" ) {
+			try {
+				Test.AnotherWay._g_test_iframe[o.name]( o );
+			}catch( e ) {
+				Test.AnotherWay._handle_exception( o, e, "" );
+			}
+		} // for "jsan" convention, test has run already
+		if( o.delay_actions.length>0 || o.wait_result_milliseconds>0 ) {
+			o.delay_total_milliseconds_left+=o.wait_result_milliseconds;
+			Test.AnotherWay._delay_actions_timeout();
+			return;
+		}
+		++test_page.test_object_i;
+	}
+	Test.AnotherWay._unprint_result( test_page.wait_msg );
+	Test.AnotherWay._print_result( test_page.url, null, null, test_page.test_objects );
+	Test.AnotherWay._load_next_page();
+}
+
+Test.AnotherWay._delay_actions_timeout=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	var test_object=test_page.test_objects[test_page.test_object_i];
+	var finished=true;
+	if( test_object.delay_action_i==null ) {
+		// set up to start first action
+		test_object.delay_action_i=-1;
+	}else {
+		// perform current action
+		var milliseconds_passed=(new Date()).getTime()-test_object.delay_prev_timer_time;
+		test_object.delay_current_milliseconds_left-=milliseconds_passed;
+		test_object.delay_total_milliseconds_left-=milliseconds_passed;
+		finished=Test.AnotherWay._delay_continue_action( test_object, milliseconds_passed );
+	}
+	while( finished && test_object.delay_action_i<test_object.delay_actions.length ) {
+		++test_object.delay_action_i; // start next action
+		finished=Test.AnotherWay._delay_start_action( test_object );
+	}
+	if( test_object.delay_action_i<=test_object.delay_actions.length ) { // any more actions left ?
+		test_object.delay_prev_timer_time=(new Date()).getTime();
+		var next_timeout=Test.AnotherWay._g_timeout_granularity;
+		if( test_object.delay_current_milliseconds_left<next_timeout ) {
+			next_timeout=test_object.delay_current_milliseconds_left;
+		}
+		if( test_object.second_wait_msg!=null ) {
+			Test.AnotherWay._update_msg_counter( test_object.second_wait_msg, (test_object.delay_total_milliseconds_left/1000).toFixed() );
+		}
+		setTimeout( Test.AnotherWay._delay_actions_timeout, next_timeout );
+	}else { // no more actions left. run the next test.
+		if( test_object.second_wait_msg!=null ) {
+			Test.AnotherWay._unprint_result( test_object.second_wait_msg );
+			test_object.second_wait_msg=null;
+		}
+		++test_page.test_object_i;
+		Test.AnotherWay._run_more_tests();
+	}
+}
+Test.AnotherWay._delay_start_action=function( test_object )
+{
+	var finished=false;
+	var wait_msg="";
+	if( test_object.delay_action_i==test_object.delay_actions.length ) {
+		if( test_object.wait_result_milliseconds>0 ) {
+			test_object.delay_current_milliseconds_left=test_object.wait_result_milliseconds; // wait for result
+			wait_msg="waiting for results..";
+		}else {
+			++test_object.delay_action_i; // dont wait for result
+		}
+	}else {
+		var action=test_object.delay_actions[test_object.delay_action_i];
+		if( action.action_kind=="call" ) {
+			test_object.delay_current_milliseconds_left=action.call_delay_milliseconds;
+			wait_msg="performing delayed calls..";
+		}else if( action.action_kind=="window" ) {
+			if( Test.AnotherWay._g_opera_path_correction!=null && action.wnd_url.indexOf( ":" )==-1 ) {
+				action.wnd_url=Test.AnotherWay._g_opera_path_correction+action.wnd_url;
+			}
+			action.wnd_wnd=window.open( action.wnd_url, "_blank" );
+			if( action.wnd_wnd==null ) {
+				finished=true;
+				test_object.fail( "unable to open window for "+action.wnd_url );
+			}else {
+				test_object.delay_current_milliseconds_left=action.wnd_timeout_milliseconds;
+				wait_msg="opening window..";
+			}
+		}else if( action.action_kind=="replay" ) {
+			if( action.replay_events.length==0 ) {
+				finished=true;
+			}else {
+				action.replay_event_i=0;
+				test_object.delay_current_milliseconds_left=action.replay_events[0]["time"];
+				wait_msg="replaying events..";
+			}
+		}
+	}
+	if( test_object.second_wait_msg!=null ) {
+		Test.AnotherWay._unprint_result( test_object.second_wait_msg );
+	}
+	if( wait_msg!="" ) {
+		var test_page=Test.AnotherWay._g_tests_queue[0];
+		test_object.second_wait_msg=Test.AnotherWay._print_counter_result( test_page.url, wait_msg, test_object.delay_total_milliseconds_left, "waiting" );
+	}else {
+		test_object.second_wait_msg=null;
+	}
+	return finished;
+}
+Test.AnotherWay._delay_continue_action=function( test_object, milliseconds_passed )
+{
+	var finished=test_object.delay_current_milliseconds_left<=0;
+	if( test_object.delay_action_i==test_object.delay_actions.length ) { // action is "waiting for results"
+		if( test_object.n_plan!=null && test_object.n_plan==test_object.n_ok+test_object.n_fail ) {
+			finished=true; // if all assertions results are recorded, don't wait any more
+		}
+		if( finished ) {
+			++test_object.delay_action_i; // move on to the next test
+		}
+	}else {
+		var action=test_object.delay_actions[test_object.delay_action_i];
+		if( action.action_kind=="call" ) {
+			if( finished ) {
+				try {
+					action.call_fn();
+				}catch( e ) {
+					Test.AnotherWay._handle_exception( test_object, e, "in delay_call" );
+				}
+			}
+		}else if( action.action_kind=="window" ) {
+			test_object.delay_total_milliseconds_left+=milliseconds_passed; // for "window", the countdown is suspended since it's unknown how long it will take
+			if( Test.AnotherWay._is_url_loaded( action.wnd_url, action.wnd_wnd ) ) {
+				try {
+					action.wnd_fn( action.wnd_wnd );
+				}catch( e ) {
+					Test.AnotherWay._handle_exception( test_object, e, "in open_window function call" );
+				}
+				finished=true;
+			}else if( finished ) {
+				test_object.fail(  "unable to open window for url '"+action.wnd_url+"'. timeout expired" );
+			}
+		}else if( action.action_kind=="replay" ) {
+			if( finished ) {
+//				try {
+					Test.AnotherWay._delay_replay_event( test_object, action.replay_wnd, action.replay_events[action.replay_event_i], action.replay_checkpoints );
+//				}catch( e ) { // disabled, until I know how to gel location info from an exception
+//					Test.AnotherWay._handle_exception( test_object, e, "while replaying event" );
+//				}
+				++action.replay_event_i;
+				finished=action.replay_event_i==action.replay_events.length;
+				if( !finished ) {
+					test_object.delay_current_milliseconds_left=action.replay_events[action.replay_event_i]["time"];
+				}
+			}
+		}
+	}
+	return finished;
+}
+Test.AnotherWay._delay_replay_event=function( test_object, wnd, event, checkpoints )
+{
+	if( event.type=="_checkpoint" ) {
+		var checkpoint_n=event.which;
+		var prev_n_fail=test_object.n_fail;
+		checkpoints[checkpoint_n]( test_object, wnd );
+		var flash_color= prev_n_fail==test_object.n_fail ? "#2f2" : "#f22" ;
+		Test.AnotherWay._record_flash_border( flash_color );
+	}else if( event.type=="click" || event.type=="mouseover" || event.type=="mouseout" || event.type=="mousemove" || event.type=="mousedown" || event.type=="mouseup" ) {
+		var target=Test.AnotherWay._record_node_path_to_node( event["target"], wnd.document );
+		if( target!=null ) {
+			Test.AnotherWay._record_control_update_highlight( target, "ball", event );
+			var e=wnd.document.createEvent( "MouseEvents" );
+			var related_target=Test.AnotherWay._record_node_path_to_node( event["relatedTarget"], wnd.document );
+			e.initMouseEvent(
+				event["type"],
+				event["cancelable"],
+				event["bubbles"],
+				wnd.document.defaultView,
+				event["detail"],
+				event["screenX"],
+				event["screenY"],
+				event["clientX"],
+				event["clientY"],
+				event["ctrlKey"],
+				event["altKey"],
+				event["shiftKey"],
+				event["metaKey"],
+				event["button"],
+				Test.AnotherWay._record_node_path_to_node( event["relatedTarget"], wnd.document )
+			);
+			// Firefox 1.0.6 somehow loses relatedTarget somewhere on the way. Pass through our own, for those who choose to care.
+			e.passThroughRelatedTarget=related_target;
+			target.dispatchEvent( e );
+		}
+	}else if( event.type=="keyup" || event.type=="keydown" || event.type=="keypress" ) {
+		var e=wnd.document.createEvent( "KeyboardEvents" ); // forget it. Apparently it's not supported neither by mozilla nor by opera.
+		e.initKeyboardEvent(
+				event["type"],
+				event["cancelable"],
+				event["bubbles"],
+				wnd.document.defaultView,
+				event["which"],
+				event["which"],
+				event["ctrlKey"],
+				event["altKey"],
+				event["shiftKey"],
+				event["metaKey"],
+				false
+		);
+		wnd.document.dispatchEvent( e );
+	}
+}
+
+Test.AnotherWay._print_counter_result=function( url, msg, milliseconds, style )
+{
+	return Test.AnotherWay._print_result( url, msg+"<span class=\"counter\">"+(milliseconds/1000).toFixed()+"</span>", style, null );
+}
+
+Test.AnotherWay._g_result_count=0; // for assigning unique ids to result paragraphs
+
+// number of pages tested
+Test.AnotherWay._g_ok_pages=0;
+Test.AnotherWay._g_fail_pages=0;
+
+Test.AnotherWay._print_result=function( url, msg, style, test_objects )
+{
+	var results=document.getElementById( "results" );
+	var r=results.appendChild( document.createElement( "p" ) );
+	r.id="result"+Test.AnotherWay._g_result_count;
+	++Test.AnotherWay._g_result_count;
+	r.onclick=Test.AnotherWay._toggle_detail;
+	var text="<span class=\"bullet\">&nbsp;&nbsp;&nbsp;</span>&nbsp;";
+	if( url!="" ) {
+		text+=url+":  ";
+	}
+	if( msg!=null ) {
+		text+=msg;
+	}
+	if( test_objects!=null ) {
+		// compose summary and detail texts
+		var total_ok=0;
+		var total_detail_ok=0;
+		var total_fail=0;
+		var total_detail_fail=0;
+		var no_plan=0;
+
+		var detail=results.appendChild( document.createElement( "div" ) );
+
+		if( r.id.match( /^result(\d+)$/ ) ) {
+			detail.id="result_detail"+RegExp.$1;
+		}
+
+		for( var i=0; i<test_objects.length; ++i ) {
+			var o=test_objects[i];
+			var p;
+			var p_text;
+			p=document.createElement( "P" );
+			Test.AnotherWay._set_css_class( p, "result_detail" );
+			p_text=o.name;
+			if( o.n_fail>0 || o.exception || (o.n_plan!=null && o.n_plan!=o.n_ok+o.n_fail) || (o.n_plan==null && o.n_ok==0 && o.n_fail==0)) {
+				++total_fail;
+				p_text+=" <span class=\"fail\">";
+				if( o.n_plan!=null && o.n_plan!=o.n_ok+o.n_fail) {
+					p_text+="planned "+o.n_plan+" assertions but got "+(o.n_ok+o.n_fail)+"; ";
+				}
+				if(o.n_plan==null && o.n_ok==0 && o.n_fail==0) {
+					p_text+="test did not output anything";
+				}else {
+					p_text+=" fail "+o.n_fail;
+				}
+				p_text+="</span>";
+			}else {
+				++total_ok;
+			}
+			p_text+=" ok "+o.n_ok;
+			if( o.n_plan==null ) {
+				no_plan=1;
+				p_text+=" <span class=\"warning\">no plan</span>";
+			}
+			p.innerHTML=p_text;
+			detail.appendChild( p );
+			if( o.exception ) {
+				p=document.createElement( "P" );
+				Test.AnotherWay._set_css_class( p, "result_exception_detail" );
+				p.innerHTML="<span class=\"fail\">exception:</span> "+o.exception;
+				detail.appendChild( p );
+				p=document.createElement( "P" );
+				Test.AnotherWay._set_css_class( p, "result_exception_stack_detail" );
+				p.innerHTML=o.exception_stack.join( "<br/>" );
+				detail.appendChild( p );
+			}
+			for( var ii=0; ii<o.assertions.length; ++ii ) {
+				var oo=o.assertions[ii];
+				var status=oo.ok ? "ok" : "<span class=\"fail\">fail</span>";
+				p=document.createElement( "P" );
+				Test.AnotherWay._set_css_class( p, "result_micro_detail" );
+				p.innerHTML=status;
+				p.appendChild( document.createTextNode( " "+oo.name ) );
+				detail.appendChild( p );
+			}
+			total_detail_ok+=o.n_ok;
+			total_detail_fail+=o.n_fail;
+		}
+		if( total_fail || total_detail_fail ) {
+			text+=" fail "+total_fail;
+		}
+		text+=" ok "+total_ok+" (detailed:";
+		if( total_fail || total_detail_fail ) {
+			text+=" fail "+total_detail_fail;
+		}
+		text+=" ok "+total_detail_ok+")";
+		if( no_plan ) {
+			text+=" <span class=\"warning\">no plan</span>";
+		}
+		style= total_fail==0 ? "ok" : "fail";
+		detail.style.display= style=="fail" ? "block" : "none";
+		detail.style.cursor="text";
+	}
+	if( style!=null ) {
+		Test.AnotherWay._set_css_class( r, style );
+		if( style=="ok" ) {
+			++Test.AnotherWay._g_ok_pages;
+		}else if( style=="fail" || style=="badtest" ) {
+			++Test.AnotherWay._g_fail_pages;
+		}
+		var pages_total="";
+		if( Test.AnotherWay._g_fail_pages>0 ) {
+			pages_total+=" fail "+Test.AnotherWay._g_fail_pages;
+		}
+		pages_total+=" ok "+Test.AnotherWay._g_ok_pages;
+		Test.AnotherWay._update_results_total( pages_total );
+	}
+	r.innerHTML=text;
+	if( results.scrollHeight!=null && results.scrollTop!=null && results.offsetHeight!=null ) {
+		results.scrollTop=results.scrollHeight-results.offsetHeight;
+	}
+	// when test_objects is not null, the results are final - good time to clean up
+	if( test_objects!=null ) {
+		for( var i=0; i<test_objects.length; ++i ) {
+			var actions=test_objects[i].delay_actions;
+			for( var action_i=0; action_i<actions.length; ++action_i ) {
+				var action=actions[action_i];
+				if( action.action_kind=="window" && action.wnd_wnd!=null && !action.wnd_no_close ) {
+					action.wnd_wnd.close();
+					action.wnd_wnd=null;
+				}
+			}
+		}
+	}
+	return r;
+}
+Test.AnotherWay._unprint_result=function( child )
+{
+	var results=document.getElementById( "results" );
+	results.removeChild( child );
+}
+Test.AnotherWay._toggle_detail=function()
+{
+	if( this.id.match( /^result(\d+)$/ ) ) {
+		var detail=document.getElementById( "result_detail"+RegExp.$1 );
+		if( detail!=null ) {
+			if( detail.style.display=="none" ) {
+				detail.style.display="block";
+			}else if( detail.style.display=="block" ) {
+				detail.style.display="none";
+			}
+		}
+	}
+}
+Test.AnotherWay._update_msg_counter=function( msg, text )
+{
+	for( var i=0; i<msg.childNodes.length; ++i ) {
+		var item=msg.childNodes[i];
+		if( item.nodeName=="SPAN" && Test.AnotherWay._get_css_class( item )=="counter" ) {
+			item.innerHTML=text;
+		}
+	}
+}
+Test.AnotherWay._update_results_total=function( msg )
+{
+	var total=document.getElementById( "total" );
+	if( total ) {
+		total.innerHTML=msg;
+	}
+}
+Test.AnotherWay._results_clear_onclick=function()
+{
+	var results=document.getElementById( "results" );
+	results.innerHTML="";
+	Test.AnotherWay._update_results_total( "" );
+	Test.AnotherWay._g_ok_pages=0;
+	Test.AnotherWay._g_fail_pages=0;
+	var debug=document.getElementById( "debug" );
+	debug.innerHTML="";
+}
+
+Test.AnotherWay._get_css_class=function( o )
+{
+	var c=o.getAttribute( "className" );
+	if( c==null || c=="" ) {
+		c=o.getAttribute( "class" );
+	}
+	return c;
+}
+Test.AnotherWay._set_css_class=function( o, css_class )
+{
+	o.setAttribute( "className", css_class );
+	o.setAttribute( "class", css_class );
+}
+
+Test.AnotherWay._tab_onclick=function()
+{
+	var tab=this;
+	var tabs=[ document.getElementById( "debug_tab" ), document.getElementById( "results_tab" ) ];
+	var panes=[ document.getElementById( "debug" ), document.getElementById( "results" ) ];
+	for( var i=0; i<tabs.length; ++i ) {
+		if( tab==tabs[i] ) {
+			Test.AnotherWay._set_css_class( tabs[i], "active_tab" );
+			panes[i].style.display="block";
+		}else {
+			Test.AnotherWay._set_css_class( tabs[i], "inactive_tab" );
+			panes[i].style.display="none";
+		}
+	}
+}
+Test.AnotherWay._tab_mouseover=function()
+{
+	if( Test.AnotherWay._get_css_class( this )=="inactive_tab" ) {
+		Test.AnotherWay._set_css_class( this, "inactive_mouseover_tab" );
+	}
+}
+Test.AnotherWay._tab_mouseout=function()
+{
+	if( Test.AnotherWay._get_css_class( this )=="inactive_mouseover_tab" ) {
+		Test.AnotherWay._set_css_class( this, "inactive_tab" );
+	}
+}
+
+// recording mouse input
+Test.AnotherWay._record_check_onfocus=function()
+{
+	var o=this;
+	var check_select=o.type!="text";
+	var div=document.getElementById( "record_div" );
+	var inputs=div.getElementsByTagName( "input" );
+	for( var i=0; i<inputs.length; ++i ) {
+		var input=inputs[i];
+		if( input.type=="radio" ) {
+			if( input.value=="select" ) {
+				input.checked=check_select;
+			}else if( input.value=="input" ) {
+				input.checked=!check_select;
+			}
+		}
+	}
+}
+
+Test.AnotherWay._g_no_record_msg=null; // not null - recording is unavailable
+Test.AnotherWay._g_record_timeout_cnt=0; // opening window for a page for recording
+Test.AnotherWay._g_record_url=null;
+Test.AnotherWay._g_record_wnd=null;
+Test.AnotherWay._g_record_random_id=null; // added to element ids of record_control div so that they do not clash with ids already in the page for which input is recorded
+Test.AnotherWay._g_record_keydown=null; // recording control - which key is down
+Test.AnotherWay._g_record_ctrl_keydown=false;
+Test.AnotherWay._g_record_shift_keydown=false;
+Test.AnotherWay._g_record_control_visible=true; // recording control ui state
+Test.AnotherWay._g_record_started;
+Test.AnotherWay._g_record_paused;
+Test.AnotherWay._g_record_include_mousemove=false;
+Test.AnotherWay._g_record_start_time; // for time references
+Test.AnotherWay._g_record_pause_start_time;
+Test.AnotherWay._g_record_update_time_interval; // showing time in the control ui
+Test.AnotherWay._g_record_waiting_for_results=false; // waiting for results window to open
+Test.AnotherWay._g_record_events; // recorded events
+Test.AnotherWay._g_record_under_cursor; // track element under cursor
+Test.AnotherWay._g_record_checkpoint_count; // for checkpoint numbering
+Test.AnotherWay._g_record_mouse_over_record_control; // for avoiding record control highlight on mouseover
+Test.AnotherWay._g_record_highlighted_element={ element: null, x: null, y: null };
+
+Test.AnotherWay._record_control_get_element=function( id )
+{
+	if( Test.AnotherWay._g_record_wnd!=null && Test.AnotherWay._g_record_wnd.document!=null ) {
+		return Test.AnotherWay._g_record_wnd.document.getElementById( id+Test.AnotherWay._g_record_random_id );
+	}else {
+		return null;
+	}
+}
+Test.AnotherWay._record_start_onclick=function() // "record" button on the run_tests.html: open a window for a page for which input is recorded
+{
+	if( Test.AnotherWay._g_no_record_msg!=null ) {
+		alert( Test.AnotherWay._g_no_record_msg );
+		return;
+	}
+	if( Test.AnotherWay._g_record_timeout_cnt>0
+	    || (Test.AnotherWay._g_record_wnd!=null && (Test.AnotherWay._g_record_wnd.closed!=null && !Test.AnotherWay._g_record_wnd.closed)) ) { // in opera, closed is null.
+		alert( "there is already window opened for recording input for a page "+Test.AnotherWay._g_record_url );
+		return;
+	}
+	var div=document.getElementById( "record_div" );
+	var inputs=div.getElementsByTagName( "input" );
+	var url=null;
+	for( var i=0; i<inputs.length; ++i ) {
+		var input=inputs[i];
+		if( input.type=="radio" ) {
+			if( input.value=="select" && input.checked ) {
+				var index=document.getElementById( "record_select" ).selectedIndex;
+				if( index>0 ) {
+					url=Test.AnotherWay._g_test_page_urls[index-1].url;
+				}
+			}else if( input.value=="input" && input.checked ) {
+				url=document.getElementById( "record_input" ).value;
+			}
+		}
+	}
+	if( url!=null ) {
+		Test.AnotherWay._g_record_url=url;
+		Test.AnotherWay._g_record_wnd=window.open( url, "_blank" );
+		if( Test.AnotherWay._g_record_wnd==null ) {
+			alert( "unable to open new window for a page: "+url );
+		}else {
+			Test.AnotherWay._g_record_timeout_cnt=50;
+			setTimeout( Test.AnotherWay._record_window_timeout, 100 );
+		}
+	}
+}
+Test.AnotherWay._record_window_timeout=function()
+{
+	if( Test.AnotherWay._is_url_loaded( Test.AnotherWay._g_record_url, Test.AnotherWay._g_record_wnd ) ) {
+		Test.AnotherWay._record_window_setup( Test.AnotherWay._g_record_wnd );
+	}else {
+		if( --Test.AnotherWay._g_record_timeout_cnt>0 ) {
+			setTimeout( Test.AnotherWay._record_window_timeout, 100 );
+		}else {
+			alert( "timeout expired while opening new window for a page: "+Test.AnotherWay._g_record_url );
+			Test.AnotherWay._g_record_wnd=null;
+			Test.AnotherWay._g_record_url=null;
+			Test.AnotherWay._g_record_timeout_cnt=0;
+		}
+	}
+}
+Test.AnotherWay._record_control_randomize_id=function( e, r )
+{
+	if( e.id!="" ) {
+		e.id=e.id+r;
+	}
+	for( var c=e.firstChild; c!=null; c=c.nextSibling ) {
+		Test.AnotherWay._record_control_randomize_id( c, r );
+	}
+}
+Test.AnotherWay._record_window_setup=function( wnd ) // insert recording control into the page for which input is recorded
+{
+	Test.AnotherWay._g_record_timeout_cnt=0;
+	var this_div=document.getElementById( "record_control" );
+	var record_control=wnd.document.importNode( this_div, true );
+	Test.AnotherWay._g_record_random_id=(1000*Math.random()).toFixed();
+	Test.AnotherWay._record_control_randomize_id( record_control, Test.AnotherWay._g_record_random_id );
+	Test.AnotherWay._g_record_control_visible=true;
+	Test.AnotherWay._g_record_started=false;
+	Test.AnotherWay._g_record_paused=false;
+	Test.AnotherWay._g_record_checkpoint_count=0;
+	Test.AnotherWay._g_record_mouse_over_record_control=false;
+	var doc=wnd.document;
+	doc.body.appendChild( record_control );
+	// opera sans-serif font is different
+	if( window.opera ) {
+		cursor_over_indicator=Test.AnotherWay._record_control_get_element( "record_cursor_over" );
+		cursor_over_indicator.style.width="18em";
+		cursor_over_indicator.style.height="2em";
+		cursor_over_indicator.style.fontSize="7pt";
+	}
+	doc.addEventListener( "keydown", Test.AnotherWay._record_control_keydown, true );
+	doc.addEventListener( "keyup", Test.AnotherWay._record_control_keyup, true );
+//	doc.addEventListener( "keypress", Test.AnotherWay._record_event, true ); // replaying is not supported by any known browser
+
+	doc.body.addEventListener( "mousemove", Test.AnotherWay._record_on_mousemove, true );
+	doc.body.addEventListener( "click", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mouseover", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mouseout", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mousedown", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mouseup", Test.AnotherWay._record_event, true );
+}
+Test.AnotherWay._record_control_key_disabled=function( k )
+{
+	if( k=="c" ) {
+		return !Test.AnotherWay._g_record_started;
+	}else if( k=="p" ) {
+		return !Test.AnotherWay._g_record_started;
+	}else if( k=="s" ) {
+		return Test.AnotherWay._g_record_waiting_for_results;
+	}else {
+		return false;
+	}
+}
+
+Test.AnotherWay._record_control_update_ui=function()
+{
+	var keydown_color="#fff";
+	var disabled_color="#aaa";
+	var button_color="#adf";
+	var active_color="#fdf";
+
+	var display={};
+	display[false]="none";
+	display[true]="inline";
+
+	var s_button=Test.AnotherWay._record_control_get_element( "record_s" );
+	var record_on=Test.AnotherWay._record_control_get_element( "record_on" );
+	var record_off=Test.AnotherWay._record_control_get_element( "record_off" );
+
+	s_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "s" ) ? disabled_color
+		: Test.AnotherWay._g_record_keydown=="s" ? keydown_color : Test.AnotherWay._g_record_started ? active_color : button_color;
+	record_on.style.display=display[!Test.AnotherWay._g_record_started];
+	record_off.style.display=display[Test.AnotherWay._g_record_started];
+
+	var h_button=Test.AnotherWay._record_control_get_element( "record_h" );
+	h_button.style.backgroundColor= Test.AnotherWay._g_record_keydown=="h" ? keydown_color : button_color;
+
+	var p_button=Test.AnotherWay._record_control_get_element( "record_p" );
+	var record_pause_on=Test.AnotherWay._record_control_get_element( "record_pause_on" );
+	var record_pause_off=Test.AnotherWay._record_control_get_element( "record_pause_off" );
+	p_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "p" ) ? disabled_color
+		: Test.AnotherWay._g_record_keydown=="p" ? keydown_color : Test.AnotherWay._g_record_paused ? active_color : button_color;
+	record_pause_on.style.display=display[!Test.AnotherWay._g_record_paused];
+	record_pause_off.style.display=display[Test.AnotherWay._g_record_paused];
+
+	var m_button=Test.AnotherWay._record_control_get_element( "record_m" );
+	var record_include_mousemove=Test.AnotherWay._record_control_get_element( "record_include_mousemove" );
+	var record_omit_mousemove=Test.AnotherWay._record_control_get_element( "record_omit_mousemove" );
+	m_button.style.backgroundColor= Test.AnotherWay._g_record_keydown=="m" ? keydown_color : Test.AnotherWay._g_record_include_mousemove ? active_color : button_color;
+	record_include_mousemove.style.display=display[!Test.AnotherWay._g_record_include_mousemove];
+	record_omit_mousemove.style.display=display[Test.AnotherWay._g_record_include_mousemove];
+
+	var c_button=Test.AnotherWay._record_control_get_element( "record_c" );
+	c_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "c" ) ? disabled_color
+		: Test.AnotherWay._g_record_keydown=="c" ? keydown_color : button_color;
+
+	var record_indicator=Test.AnotherWay._record_control_get_element( "record_indicator" );
+	record_indicator.style.display=display[Test.AnotherWay._g_record_started];
+
+	var pause_indicator=Test.AnotherWay._record_control_get_element( "record_pause_indicator" );
+	pause_indicator.style.display=display[Test.AnotherWay._g_record_paused];
+
+	var record_control=Test.AnotherWay._record_control_get_element( "record_control" );
+	record_control.style.display= Test.AnotherWay._g_record_control_visible ? "block" : "none";
+
+	var shift_button=Test.AnotherWay._record_control_get_element( "record_shift_key" );
+	shift_button.style.backgroundColor= Test.AnotherWay._g_record_shift_keydown ? keydown_color : button_color;
+
+	var ctrl_button=Test.AnotherWay._record_control_get_element( "record_ctrl_key" );
+	ctrl_button.style.backgroundColor= Test.AnotherWay._g_record_ctrl_keydown ? keydown_color : button_color;
+}
+Test.AnotherWay._record_format_time=function( t )
+{
+	t=new Date( t );
+	var m=t.getMinutes();
+	var s=t.getSeconds();
+	var str= m==0 ? "" : m+"m ";
+	str+=s+"s.";
+	return str;
+}
+Test.AnotherWay._record_control_update_time=function()
+{
+	var time_display=Test.AnotherWay._record_control_get_element( "record_time" );
+	if( time_display!=null ) {
+		time_display.innerHTML=Test.AnotherWay._record_format_time( (new Date()).getTime()-Test.AnotherWay._g_record_start_time );
+	}
+}
+Test.AnotherWay._record_control_update_highlight=function( elem, style, event )
+{
+	if( elem==null ) {
+		Test.AnotherWay._record_highlight_border( null );
+	}else {
+		var pos=Test.AnotherWay._get_page_coords( elem );
+		if( style=="ball" || elem!=Test.AnotherWay._g_record_highlighted_element.element || pos.x!=Test.AnotherWay._g_record_highlighted_element.x || pos.y!=Test.AnotherWay._g_record_highlighted_element.y ) {
+			Test.AnotherWay._g_record_highlighted_element={ element: elem, x: pos.x, y: pos.y };
+			Test.AnotherWay._record_highlight_border( elem, style, event );
+		}
+	}
+}
+Test.AnotherWay._record_decode_key=function( event )
+{
+	var k=null;
+	if( event==null ) {
+		k=Test.AnotherWay._g_record_wnd.event.keyCode;
+	}else {
+		k=event.which;
+	}
+	if( k==83 ) {
+		return "s";
+	}else if( k==72 ) {
+		return "h";
+	}else if( k==73 ) {
+		return "i";
+	}else if( k==80 ) {
+		return "p";
+	}else if( k==67 ) {
+		return "c";
+	}else if( k==77 ) {
+		return "m";
+	}else if( k==16 ) {
+		return "shift";
+	}else if( k==17 ) {
+		return "ctrl";
+	}else if( k==18 ) {
+		return "alt";
+	}else if( k==19 ) {
+		return "pause";
+	}else if( k==123 ) {
+		return "f12";
+	}
+	return "";
+}
+Test.AnotherWay._record_control_keydown=function( event )
+{
+	var handled=false;
+	var k=Test.AnotherWay._record_decode_key( event );
+	if( k=="shift" ) {
+		Test.AnotherWay._g_record_shift_keydown=true;
+	}else if( k=="ctrl" ) {
+		Test.AnotherWay._g_record_ctrl_keydown=true;
+	}else if( k!="" && (Test.AnotherWay._g_record_keydown==null || Test.AnotherWay._g_record_keydown==k) ) {
+		if( Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown && !Test.AnotherWay._record_control_key_disabled( k ) ) {
+			Test.AnotherWay._g_record_keydown=k;
+			handled=true;
+		}
+	}else {
+		Test.AnotherWay._g_record_keydown="";
+	}
+	Test.AnotherWay._record_control_update_ui();
+	if( !handled ) {
+//		Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+	}
+	return;
+}
+Test.AnotherWay._record_control_keyup=function( event )
+{
+	var handled=false;
+	var k=Test.AnotherWay._record_decode_key( event );
+	if( k=="shift" ) {
+		Test.AnotherWay._g_record_shift_keydown=false;
+	}else if( k=="ctrl" ) {
+		Test.AnotherWay._g_record_ctrl_keydown=false;
+	}else if( k!="" && k==Test.AnotherWay._g_record_keydown && Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown  ) {
+		if( k=="s" ) {
+			Test.AnotherWay._g_record_started=!Test.AnotherWay._g_record_started;
+			if( Test.AnotherWay._g_record_started ) {
+				Test.AnotherWay._g_record_events=[];
+				Test.AnotherWay._g_record_start_time=(new Date()).getTime();
+				Test.AnotherWay._record_control_update_time();
+				Test.AnotherWay._g_record_update_time_interval=window.setInterval( Test.AnotherWay._record_control_update_time, 200 );
+			}else {
+				Test.AnotherWay._record_control_update_highlight( null );
+				if( !Test.AnotherWay._g_record_paused ) {
+					window.clearInterval( Test.AnotherWay._g_record_update_time_interval );
+				}
+				Test.AnotherWay._g_record_waiting_for_results=true;
+				// open a new window for self, pass a parameter to dump recorded events as javascript code there
+				// (the easiest way to obtain a document from the same origin, so it's writable, is to open this same page again)
+				Test.AnotherWay._g_record_paused=false;
+				var loc=window.location;
+				loc=loc.protocol+"//"+loc.host+loc.pathname+"?recording_results="+Test.AnotherWay._g_record_random_id;
+				if( window.open( loc, "_blank" )==null ) {
+					alert( "unable to open new window for results" );
+				}
+			}
+			handled=true;
+		}else if( k=="h" ) {
+			Test.AnotherWay._g_record_control_visible=!Test.AnotherWay._g_record_control_visible;
+			handled=true;
+		}else if( k=="p" ) {
+			Test.AnotherWay._g_record_paused=!Test.AnotherWay._g_record_paused;
+			if( Test.AnotherWay._g_record_paused ) {
+				Test.AnotherWay._g_record_pause_start_time=(new Date()).getTime();
+				if( Test.AnotherWay._g_record_started ) {
+					window.clearInterval( Test.AnotherWay._g_record_update_time_interval );
+				}
+				Test.AnotherWay._record_control_update_highlight( null );
+			}else {
+				var pause_duration=(new Date()).getTime()-Test.AnotherWay._g_record_pause_start_time;
+				Test.AnotherWay._g_record_start_time+=pause_duration;
+				Test.AnotherWay._g_record_update_time_interval=window.setInterval( Test.AnotherWay._record_control_update_time, 200 );
+			}
+			handled=true;
+		}else if( k=="m" ) {
+			Test.AnotherWay._g_record_include_mousemove=!Test.AnotherWay._g_record_include_mousemove;
+			handled=true;
+		}else if( k=="c" ) {
+			var o=Test.AnotherWay._record_checkpoint();
+			Test.AnotherWay._record_display_checkpoint( o );
+			Test.AnotherWay._record_flash_border( "#24d" );
+			handled=true;
+		}
+	}
+	Test.AnotherWay._g_record_keydown=null;
+	Test.AnotherWay._record_control_update_ui();
+	if( !handled ) {
+//		Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+	}
+	return;
+}
+Test.AnotherWay._record_html_node_path=function( node )
+{
+	if( node==null ) {
+		return null;
+	}
+	var path=[];
+	while( true ) {
+		if( node.id!=null && node.id!="" ) {
+			path.unshift( "#"+node.id+" "+node.nodeName );
+			break;
+		}else {
+			var parent_node=node.parentNode;
+			if( parent_node==null ) {
+				return []; // no BODY up the path - this node is screwed (browsers differ in what's above the body), discard
+			}else {
+				var i=0;
+				var found=false;
+				for( var child=parent_node.firstChild; child!=null; child=child.nextSibling ) {
+					if( child==node ) {
+						found=true;
+						break;
+					}
+					if( child.nodeType==1 ) { // count only HTML element nodes
+						++i;
+					}
+				}
+				if( !found ) {
+					i=-1;
+				}
+				path.unshift( i+" "+node.nodeName );
+				if( parent_node.nodeName=="BODY" || parent_node.nodeName=="body" ) {
+					break;
+				}
+				node=parent_node;
+			}
+		}
+	}
+	return path;
+}
+Test.AnotherWay._record_node_path_to_string=function( path )
+{
+	var s="";
+	if( path!=null ) {
+		for( var i=0; i<path.length; ++i ) {
+			s+= i==0 ? "" : ", ";
+			var elem=path[i].split( " " );
+			if( elem[0].charAt( 0 )=="#" ) {
+				s+=elem[1]+" "+elem[0];
+			}else {
+				s+=elem[1]+" ["+elem[0]+"]";
+			}
+		}
+	}
+	return s;
+}
+Test.AnotherWay._record_node_path_to_node=function( path_str, doc )
+{
+	if( path_str==null ) {
+		return null;
+	}
+	var path=path_str.split( "," );
+	var node=doc.body;
+	for( var i=0; i<path.length; ++i ) {
+		var node_i=path[i].split( " " )[0];
+		if( node_i.charAt( 0 )=="#" ) {
+			node=doc.getElementById( node_i.substring( 1 ) );
+		}else {
+			if( node_i<0 || node_i>=node.childNodes.length ) {
+				node=null;
+			}else {
+				node=node.firstChild;
+				while( node!=null ) {
+					if( node.nodeType==1 ) {  // count only HTML element nodes
+						if( node_i==0 ) {
+							break;
+						}
+						--node_i;
+					}
+					node=node.nextSibling;
+				}
+			}
+		}
+		if( node==null ) {
+			return null;
+		}
+	}
+	return node;
+}
+Test.AnotherWay._record_control_contains_id=function( s )
+{
+	return s.match( /^#record_[\w_]+/ ) && s.match( Test.AnotherWay._g_record_random_id );
+}
+Test.AnotherWay._record_checkpoint=function()
+{
+	var o={ type: "_checkpoint", time: (new Date()).getTime()-Test.AnotherWay._g_record_start_time, which: Test.AnotherWay._g_record_checkpoint_count++,
+			target: Test.AnotherWay._record_html_node_path( Test.AnotherWay._g_record_under_cursor ) };
+	Test.AnotherWay._g_record_events.push( o );
+	return o;
+}
+Test.AnotherWay._record_event=function( event )
+{
+	var unneeded=["rangeOffset","eventPhase","timeStamp","isTrusted","popupWindowFeatures","rangeOffset"];
+	if( Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused ) {
+		var o={};
+		for( var n in event ) {
+			var needed=!n.match( /^[A-Z0-9_]+$/ );
+			if( needed ) {
+				for( var ui=0; ui<unneeded.length; ++ui ) {
+					if( unneeded[ui]==n ) {
+						needed=false;
+						break;
+					}
+				}
+				if( needed ) {
+					var value=event[n];
+					if( typeof( value )!="object" && typeof( value )!="function" ) {
+						o[n]=value;
+					}else if( n=="target" || n=="relatedTarget" ) {
+						o[n]=Test.AnotherWay._record_html_node_path( value );
+					}
+				}
+			}
+		}
+		o["time"]=(new Date()).getTime()-Test.AnotherWay._g_record_start_time;
+		var over_record_control= o["target"]!=null && o["target"][0]!=null && Test.AnotherWay._record_control_contains_id( o["target"][0] );
+		if( !over_record_control ) {
+			Test.AnotherWay._g_record_events.push( o );
+		}
+	}
+	return true;
+}
+Test.AnotherWay._record_on_mousemove=function( event )
+{
+	var path=Test.AnotherWay._record_html_node_path( event.target );
+	var new_mouse_over_record_control= path!=null && path[0]!=null && Test.AnotherWay._record_control_contains_id( path[0] );
+	if( new_mouse_over_record_control!=Test.AnotherWay._g_record_mouse_over_record_control ) {
+		Test.AnotherWay._g_record_mouse_over_record_control=new_mouse_over_record_control;
+		Test.AnotherWay._record_control_update_ui();
+	}
+	if( event.target!=null && event.target!=Test.AnotherWay._g_record_under_cursor ) {
+		Test.AnotherWay._g_record_under_cursor=event.target;
+		var s="";
+		if( path==null || path[0]==null || !Test.AnotherWay._record_control_contains_id( path[0] ) ) {
+			s=Test.AnotherWay._record_node_path_to_string( path );
+		}
+		if( s=="" ) {
+			s="&nbsp;";
+		}
+		var cursor_over_indicator=Test.AnotherWay._record_control_get_element( "record_cursor_over" );
+		cursor_over_indicator.innerHTML=s;
+	}
+
+	var highlight_element=null;
+	if( !Test.AnotherWay._g_record_mouse_over_record_control && Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused ) {
+		highlight_element=event.target;
+	}
+	// highlight border disabled on recording - it causes page to scroll, issuing spurious mouseover/mouseout event
+	//Test.AnotherWay._record_control_update_highlight( highlight_element, "border" );
+
+	if( Test.AnotherWay._g_record_include_mousemove ) {
+		Test.AnotherWay._record_event( event );
+	}
+	return true;
+}
+Test.AnotherWay._record_display_checkpoint=function( o )
+{
+	var checkpoints_div=Test.AnotherWay._record_control_get_element( "record_checkpoints" );
+	var p=checkpoints_div.appendChild( checkpoints_div.ownerDocument.createElement( "div" ) );
+	p.style.marginTop="3px";
+	p.style.font="normal normal 8pt sans-serif";
+	p.style.color="#000";
+	p.style.textAligh="left";
+	p.style.position="relative";
+	p.style.width="100%";
+	var checkpoint_text="";
+	checkpoint_text+="#"+(o.which+1);
+	checkpoint_text+="  "+Test.AnotherWay._record_format_time( o.time );
+	if( o.target!=null ) {
+		checkpoint_text+=Test.AnotherWay._record_node_path_to_string( o.target );
+	}
+	p.appendChild( p.ownerDocument.createTextNode( checkpoint_text ) );
+}
+Test.AnotherWay._record_save_results=function( doc )
+{
+	// strange, but DOM-style append does not work here in opera 8.
+	var append=function( s ) { doc.write( "<div>"+s+"</div>" ); };
+	append( "/* paste this data into your javascript and pass it as an argument to replay_events method */" );
+	append( "{ checkpoints: [" );
+	var first_checkpoint=true;
+	for( var i=0; i<Test.AnotherWay._g_record_events.length; ++i ) {
+		var o=Test.AnotherWay._g_record_events[i];
+		if( o.type=="_checkpoint" ) {
+			var str= first_checkpoint ? "" : "}, ";
+			str+="function( tst, wnd ) { // #"+o.which+" time "+Test.AnotherWay._record_format_time( o.time )+" cursor was over "+Test.AnotherWay._record_node_path_to_string( o.target );
+			append( str );
+			first_checkpoint=false;
+		}
+	}
+	if( !first_checkpoint ) {
+		append( "}" );
+	}
+	append( "], events: [ " );
+	var prev_time=0;
+	for( var i=0; i<Test.AnotherWay._g_record_events.length; ++i ) {
+		var o=Test.AnotherWay._g_record_events[i];
+		var s="";
+		s+= "{";
+		var n_first=true;
+		for( var n in o ) {
+			if( n=="time" ) { // convert to relative time
+				var cur_time=o[n]-0;
+				o[n]=cur_time-prev_time;
+				prev_time=cur_time;
+			}
+			s+=n_first ? n : ", "+n;
+			s+=":";
+			if( o[n]==null ) {
+				s+="null";
+			}else {
+				s+="\""+o[n]+"\"";
+			}
+			n_first=false;
+		}
+		s+= i==Test.AnotherWay._g_record_events.length-1 ? "}" : "},";
+		append( s );
+	}
+	append( "] }" );
+	append( ";" );
+}
+
+Test.AnotherWay._g_record_border; // border highlighting element under cursor
+Test.AnotherWay._g_record_border_flashes=[]; // array of { color: color, timeout: milliseconds }
+Test.AnotherWay._g_record_border_flashing=false;
+Test.AnotherWay._g_record_border_normal_color="#d4b";
+Test.AnotherWay._record_flash_border_timeout=function()
+{
+	var color=Test.AnotherWay._g_record_border_normal_color;
+	var timeout=null;
+	if( Test.AnotherWay._g_record_border_flashes.length!=0 ) {
+		color=Test.AnotherWay._g_record_border_flashes[0].color;
+		timeout=Test.AnotherWay._g_record_border_flashes[0].timeout;
+		Test.AnotherWay._g_record_border_flashes.splice( 0, 1 );
+	}
+	if( Test.AnotherWay._g_record_border!=null ) {
+		for( var i=0; i<Test.AnotherWay._g_record_border.length; ++i ) {
+			Test.AnotherWay._g_record_border[i].style.backgroundColor=color;
+		}
+	}
+	if( timeout!=null ) {
+		setTimeout( Test.AnotherWay._record_flash_border_timeout, timeout );
+	}else {
+		Test.AnotherWay._g_record_border_flashing=false;
+	}
+}
+Test.AnotherWay._get_page_coords=function( elm )
+{
+	var point = { x: 0, y: 0 };
+	while( elm )  {
+		point.x+=elm.offsetLeft;
+		point.y+=elm.offsetTop;
+		elm=elm.offsetParent;
+	 }
+	return point;
+}
+Test.AnotherWay._set_page_coords=function( elm, x, y )
+{
+	var parent_coords={ x: 0, y: 0 };
+	if( elm.offsetParent )  {
+		parent_coords=Test.AnotherWay._get_page_coords( elm.offsetParent );
+	}
+	var new_x=x-parent_coords.x;
+	if( new_x<0 ) {
+		new_x=0;
+	}
+	elm.style.left=new_x+'px';
+	var new_y=y-parent_coords.y;
+	if( new_y<0 ) {
+		new_y=0;
+	}
+	elm.style.top=new_y+'px';
+}
+Test.AnotherWay._record_setup_highlight_positions=function( element, style, coords, positions )
+{
+	if( style=="border" ) {
+		var width=element.clientWidth;
+		var height=element.clientHeight;
+		var step=0;
+		var thickness=2;
+		var fudge_expand=4;
+		positions.push( { x: coords.x-step-thickness, y: coords.y-step-thickness, width: width+2*step+2*thickness+fudge_expand, height: thickness } );
+		positions.push( { x: coords.x+width+step+fudge_expand, y: coords.y-step-thickness, width: thickness, height: height+2*step+2*thickness+fudge_expand } );
+		positions.push( { x:positions[0].x, y:positions[0].y, width:positions[0].width, height:positions[0].height } );
+		positions.push( { x:positions[1].x, y:positions[1].y, width:positions[1].width, height:positions[1].height } );
+		positions[2].y+=height+thickness+2*step+fudge_expand;
+		positions[3].x-=width+thickness+2*step+fudge_expand;
+	}else if( style=="ball" ) {
+		positions.push( { x: coords.x+2, y: coords.y, width: 2, height: 6 } );
+		positions.push( { x: coords.x, y: coords.y+2, width: 6, height: 2 } );
+		positions.push( { x: coords.x+1, y: coords.y+1, width: 4, height: 4 } );
+	}
+}
+Test.AnotherWay._record_highlight_border=function( element, style, event ) // null - hide border
+{
+	if( element!=null ) {
+		if( Test.AnotherWay._g_record_border==null || Test.AnotherWay._g_record_border[0].ownerDocument!=element.ownerDocument ) {
+			Test.AnotherWay._g_record_border=[];
+			var n= style=="border" ? 4 : style=="ball" ? 3 : 0;
+			for( var i=0; i<4; ++i ) {
+				var b=element.ownerDocument.createElement( "div" );
+				b.style.position="absolute";
+				b.style.zIndex="1";
+				b.style.backgroundColor=Test.AnotherWay._g_record_border_normal_color;
+				element.ownerDocument.body.appendChild( b );
+				Test.AnotherWay._g_record_border.push( b );
+			}
+		}
+		var coords=null;
+		if( style=="border" ) {
+			coords=Test.AnotherWay._get_page_coords( element );
+		}else if( style=="ball" ) {
+			if( event!=null ) {
+				if( event.pageX!=null && event.pageY!=null ) {
+					coords={ x: event.pageX-0, y: event.pageY-0 };
+				}else if( event.clientX!=null && event.clientY!=null ) {
+					var doc=element.ownerDocument;
+					if( doc!=null ) {
+						coords={ x: (event.clientX-0)+doc.body.scrollLeft, y: (event.clientY-0)+doc.body.scrollTop };
+					}
+				}
+			}
+		}
+		if( coords!=null && element.clientWidth!=null && element.clientHeight!=null ) {
+			var positions=[];
+			Test.AnotherWay._record_setup_highlight_positions( element, style, coords, positions );
+			for( var i=0; i<positions.length; ++i ) {
+				var b=Test.AnotherWay._g_record_border[i];
+				var p=positions[i];
+				Test.AnotherWay._set_page_coords( b, p.x, p.y );
+				b.style.width=p.width+"px";
+				b.style.height=p.height+"px";
+				b.style.display="block";
+			}
+		}
+	}else {
+		if( Test.AnotherWay._g_record_border!=null ) {
+			for( var i=0; i<Test.AnotherWay._g_record_border.length; ++i ) {
+				Test.AnotherWay._g_record_border[i].style.display="none";
+			}
+		}
+	}
+}
+Test.AnotherWay._record_flash_border=function( color )
+{
+	if( Test.AnotherWay._g_record_border_flashing ) { //already
+		Test.AnotherWay._g_record_border_flashes.push( { color: Test.AnotherWay._g_record_border_normal_color, timeout:300 } );
+		Test.AnotherWay._g_record_border_flashes.push( { color: color, timeout:600 } );
+	}else {
+		Test.AnotherWay._g_record_border_flashing=true;
+		Test.AnotherWay._g_record_border_flashes.push( { color: color, timeout:600 } );
+		Test.AnotherWay._record_flash_border_timeout();
+	}
+}
+Test.AnotherWay._record_prepare_doc_for_results=function()
+{
+	document.open();
+	document.write( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" );
+	document.write( "<html><head><title> Input recording results</title>" );
+	document.write( "<style type=\"text/css\">" );
+	document.write( "body { font: normal normal smaller sans-serif; }" );
+	document.write( "div { margin-top: 3px; }" );
+	document.write( "</style></head><body>" );
+	// opera and mozilla disagree over who the opener is.
+	if( typeof( window.opener.Test )!="undefined" && typeof( window.opener.Test.AnotherWay )!="undefined" ) {
+		window.opener.Test.AnotherWay._record_save_results( document );
+		window.opener.Test.AnotherWay._g_record_waiting_for_results=false;
+		window.opener.Test.AnotherWay._record_control_update_ui();
+	}else if( typeof( window.opener.opener.Test  )!="undefined" && typeof( window.opener.opener.Test.AnotherWay )!="undefined" ) {
+		window.opener.opener.Test.AnotherWay._record_save_results( document );
+		window.opener.opener.Test.AnotherWay._g_record_waiting_for_results=false;
+		window.opener.opener.Test.AnotherWay._record_control_update_ui();
+	}
+	document.write( "</body>" );
+	document.close();
+}
+
+// global initialization
+onload=function()
+{
+	if( window.opera ) {
+		var good_opera=typeof( window.opera.version )=="function";
+		good_opera=good_opera && window.opera.version().match( /^\s*(\d+)/ );
+		good_opera=good_opera && RegExp.$1>=8;
+	}
+	var span=document.createElement( "SPAN" );
+	span.innerHTML="<!--[if IE]><br /><![endif]-"+"->";
+	var is_ie=span.getElementsByTagName( "BR" ).length>0;
+
+	Test.AnotherWay._g_test_iframe=window.frames.test_iframe;
+
+	var query_str=window.location.search;
+	if( query_str.charAt( 0 )=="?" ) {
+		query_str=query_str.substring( 1 );
+	}
+	var testlist_page="list-tests.html";
+	var auto_run=false;
+	if( query_str!="" ) {
+		var params=[query_str];
+		if( query_str.indexOf( ";" )!=-1 ) {
+			params=query_str.split( ";" );
+		}else if( query_str.indexOf( "&" )!=-1 ) {
+			params=query_str.split( "&" );
+		}
+		for( var param_i=0; param_i<params.length; ++param_i ) {
+			var param=params[param_i].split( "=" );
+			if( param[0]=="recording_results" ) {
+				if( window.opener!=null ) {
+					// we were told to show recording results - replace everything in the document with the results
+					Test.AnotherWay._record_prepare_doc_for_results();
+					return;
+				}
+			}else if( param[0]=="testpage" ) {
+				Test.AnotherWay._add_test_page_url( decodeURIComponent( param[1] ), "anotherway" );
+			}else if( param[0]=="jsantestpage" ) {
+				Test.AnotherWay._add_test_page_url( decodeURIComponent( param[1] ), "jsan" );
+			}else if( param[0]=="testlist" ) {
+				testlist_page=decodeURIComponent( param[1] );
+			}else if( param[0]=="testframe" ) {
+				if( window.opera && !good_opera ) {
+					Test.AnotherWay._show_error( "testframe parameter does not work in versions of Opera prior to 8.0. Sorry (pathches are welcome)." );
+					// Opera 7 barfs on attempt to access frame.frameElement.
+					// if someone knows a way to assign onload handler to that iframe in Opera 7
+					// without disrupting code that works in other browsers, patches are welcome.
+				}else {
+					var frame_path=param[1].split( "." );
+					var frame=top;
+					for( var frame_path_i=0; frame_path_i<frame_path.length; ++frame_path_i ) {
+						frame=frame[frame_path[frame_path_i]];
+					}
+					if( frame==null ) {
+						Test.AnotherWay._show_error( "unable to find frame specified for loading test pages: "+param[1] );
+					}else {
+						if( frame.frameElement!=null ) { // for the following assignement to onload to work, frameElement is required
+							frame=frame.frameElement;
+						}
+						Test.AnotherWay._g_test_iframe=frame;
+					}
+				}
+			}else if( param[0]=="testframe_no_clear" ) {
+				Test.AnotherWay._g_test_frame_no_clear=true;
+			}else if( param[0]=="run" ) {
+				auto_run=true;
+				if( param[1]=="all" ) {
+					Test.AnotherWay._g_pages_to_run="all";
+				}else {
+					if( Test.AnotherWay._g_pages_to_run==null || Test.AnotherWay._g_pages_to_run=="all" ) {
+						Test.AnotherWay._g_pages_to_run=[];
+					}
+					var pages=param[1].split( "," );
+					for( var i=0; i<pages.length; ++i ) {
+						Test.AnotherWay._g_pages_to_run.push( pages[i] );
+					}
+				}
+			}
+		}
+	}
+	if( Test.AnotherWay._g_test_page_urls.length==0 ) {  // if no individual pages were given on the command line, load the list
+		var result=Test.AnotherWay._set_iframe_location( window.frames["list_iframe"], testlist_page );
+		if( result.msg!=null ) {
+			Test.AnotherWay._show_error( result.msg );
+		}
+		Test.AnotherWay._g_run_on_list_load=auto_run;
+	}else {
+		Test.AnotherWay._g_run_on_main_load=auto_run;
+	}
+
+	var f=Test.AnotherWay._g_test_iframe;
+	try {
+		if( f.attachEvent!=null ) {
+			f.attachEvent( "onload", Test.AnotherWay._test_page_onload );
+		}else {
+			f.onload=Test.AnotherWay._test_page_onload;
+		}
+		if( Test.AnotherWay._g_test_iframe.nodeType!=null && Test.AnotherWay._g_test_iframe.contentWindow!=null ) { // it's iframe element, not the iframe. we need iframe.
+			Test.AnotherWay._g_test_iframe=Test.AnotherWay._g_test_iframe.contentWindow;
+		}
+	}catch(e) {
+		// ignore stupid opera error if the frame has onload handler assigned in the inline html
+	}
+	var handlers={
+		"run_all": { "onclick": Test.AnotherWay._run_all_onclick },
+		"run_selected": { "onclick": Test.AnotherWay._run_selected_onclick },
+		"unselect_all": { "onclick": Test.AnotherWay._unselect_all_onclick },
+		"record_select": { "onfocus": Test.AnotherWay._record_check_onfocus },
+		"record_input": { "onfocus": Test.AnotherWay._record_check_onfocus },
+		"record_start": { "onclick": Test.AnotherWay._record_start_onclick },
+		"clear_btn": { "onclick": Test.AnotherWay._results_clear_onclick },
+		"results_tab": { "onclick": Test.AnotherWay._tab_onclick, "onmouseover": Test.AnotherWay._tab_mouseover, "onmouseout": Test.AnotherWay._tab_mouseout },
+		"debug_tab": { "onclick": Test.AnotherWay._tab_onclick, "onmouseover": Test.AnotherWay._tab_mouseover, "onmouseout": Test.AnotherWay._tab_mouseout }
+	};
+	for( var hs in handlers ) {
+		var o=document.getElementById( hs );
+		if( o!=null ) {
+			for( var h in handlers[hs] ) {
+				o[h]=handlers[hs][h];
+			}
+		}else {
+			Test.AnotherWay._show_error( "unable to set "+h+" handler: id "+hs+" not found" );
+		}
+	}
+
+	if( window.opera && !good_opera ) {
+		Test.AnotherWay._g_no_record_msg="Input events recording and replaying is not available in opera versions prior to 8.0.";
+	}
+	if( is_ie ) {
+		Test.AnotherWay._g_no_record_msg="Input events recording and replaying is not available in internet explorer.";
+	}
+ 	if( Test.AnotherWay._g_no_record_msg!=null ) {
+		var no_record_p=document.getElementById( "record_not_supported" );
+		no_record_p.style.display="block";
+		no_record_p.appendChild( document.createTextNode( Test.AnotherWay._g_no_record_msg ) );
+	}
+
+	Test.AnotherWay._g_main_loaded=true;
+	if( Test.AnotherWay._g_run_on_main_load ) {
+		Test.AnotherWay._g_run_on_main_load=false;
+		Test.AnotherWay._run_pages_to_run();
+	}
+}
+// -->
+</script>
+<script type="text/javascript" src="xml_eq.js"></script>
+</head><body>
+
+<div id="col1">
+<div id="col1_header">Test pages:</div>
+<div id="scroller">
+<table id="testtable">
+</table>
+</div>
+<div id="run_buttons">
+<input type="button" value=" clear " id="clear_btn" />
+<input type="button" value=" run all " id="run_all" />
+<input type="button" value=" run selected " id="run_selected" />
+<input type="button" value=" unselect all " id="unselect_all" />
+</div>
+<input type="checkbox" id="dont_close_test_windows" /> do not close windows opened by tests
+<div id="error"></div>
+<div id="record_div">
+<p id="record_not_supported" style="display:none"></p>
+<p>Record mouse input for the page:</p>
+<p><input type="radio" name="record_choose" value="select" checked="checked" /> <select id="record_select"><option selected="selected">-- select a page: --</option></select></p>
+<p><input type="radio" name="record_choose" value="input" /> or enter page url: <input type="text" id="record_input" /></p>
+<p><input type="button" value=" record " id="record_start" /></p>
+</div>
+</div>
+
+<div id="col2">
+<div id="right_header">
+<span id="results_count">Results: <span id="total"></span></span>
+<span id="results_tab" class="active_tab" style="visibility:hidden">Results</span>
+<span id="debug_tab" class="inactive_tab" style="visibility:hidden">Debug</span>
+</div>
+<div id="right_frame">
+<div id="results"></div>
+<div id="debug"></div>
+</div>
+</div>
+
+<span style="display:none">
+<iframe name="list_iframe" onload="Test.AnotherWay._list_iframe_onload();"></iframe>
+<iframe name="test_iframe" onload="Test.AnotherWay._test_page_onload();"></iframe>
+
+<!-- record_control div is to be imported into other documents, so all its styles are inline -->
+-<div id="record_control" style="position:absolute;bottom:0;left:0;margin:0;padding:0.5em;width:22em;height:22em;border:1px solid;background:#ffd;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+
+<p style="margin:0 0 0 0; padding:0">
+&nbsp;
+<span style="display:none;font-weight:bold;color:#408" id="record_indicator">
+recording. <span style="font-weight:normal">time: <span id="record_time"></span></span><span id="record_pause_indicator"> paused</span>
+</span>
+</p>
+
+<div id="record_cursor_over" style="margin:0;padding:2px;width:14em;height:1.1em;overflow:hidden;float:right;border:1px solid #777;background:#fff;font: normal normal 8pt sans-serif;position:relative;top:3px;color:#000;text-align:left;">&nbsp;</div>
+<p style="margin:2px 0 0 0; padding:0">
+cursor is over
+</p>
+
+<p style="margin:8px 0 0 0; padding:0;">
+ keyboard control: press
+ <span id="record_ctrl_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">ctrl</span> -
+ <span id="record_shift_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">shift</span> -
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_s" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">s</span>
+<span id="record_on">to <b>start</b> recording</span>
+<span id="record_off" style="display:none">to <b>stop</b> recording</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_h" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">h</span>
+<span>to <b>hide/show</b> this window</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_m" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">m</span>
+<span id="record_include_mousemove">to <b> record</b> mousemove</span>
+<span id="record_omit_mousemove" style="display:none">to <b>omit</b> mousemove</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_p" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">p</span>
+<span id="record_pause_on">to <b> pause</b> recording</span>
+<span id="record_pause_off" style="display:none">to <b>continue</b> recording</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_c" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">c</span>
+<span>to add checkpoint</span>
+</p>
+
+<p style="margin:6px 0 0 0; padding:0">
+checkpoints:
+</p>
+<div id="record_checkpoints" style="position:relative;width:100%;height:6em;overflow:auto;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+</div>
+</div>
+
+</span>
+</body></html>

Added: addins/bookmark/release/2.1/theme/default/bookmark.css
===================================================================
--- addins/bookmark/release/2.1/theme/default/bookmark.css	                        (rev 0)
+++ addins/bookmark/release/2.1/theme/default/bookmark.css	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,73 @@
+/* Bookmark Control Styles */
+
+/* bookmark main div */
+.olControlBookmark {
+   position: absolute;
+   top: 100px;
+   right: 0px;
+   /*margin: 10px 0px 10px 10px; /* top right bottom left */
+   width: 175px;
+}
+/* bookmark content container */
+.olControlBookmarkContent {
+   background-color: darkblue;
+   width: 100%;
+   height: 100%;
+   font-family: arial, sans-serif;
+   font-size: smaller;
+   font-weight: bold;
+   color: white;
+}
+/* bookmark title */
+.olControlBookmarkTitle {
+   text-align: center;
+   padding-bottom: 5px;
+}
+/* bookmark data elements */
+.olControlBookmarkElements {
+   text-align: left;
+   background-color: transparent;
+   overflow: hidden;
+}
+/* each row contains a remove element and link element */
+.olControlBookmarkRow {
+   height: 20px;
+   vertical-align: top;
+   position: relative;
+}
+.olControlBookmarkRemove {
+   margin-left: 10px;
+   background-image: url("http://openlayers.org/api/theme/default/img/close.gif");
+   background-repeat: no-repeat;
+   background-color: white;
+   width: 17px;
+   height: 17px;
+   display: block;
+   cursor: pointer;
+}
+.olControlBookmarkLink {
+   position: absolute;
+   left: 35px;
+   top: 0px;
+   cursor: pointer;
+}
+.olControlBookmarkMaximizeButton {
+   background-image: url("http://openlayers.org/api/img/layer-switcher-maximize.png");
+   background-repeat: no-repeat;
+   width:  18px;
+   height: 18px;
+   display: none;
+   position: absolute;
+   top: 0px;
+   right: 0px;
+}
+.olControlBookmarkMinimizeButton {
+   background-image: url("http://openlayers.org/api/img/layer-switcher-minimize.png");
+   background-repeat: no-repeat;
+   width:  18px;
+   height: 18px;
+   display: none;
+   position: absolute;
+   top: 0px; /* upper right corner of parent div */
+   right: 0px;
+}
\ No newline at end of file

Added: addins/bookmark/release/2.2/examples/bookmark-cookie.html
===================================================================
--- addins/bookmark/release/2.2/examples/bookmark-cookie.html	                        (rev 0)
+++ addins/bookmark/release/2.2/examples/bookmark-cookie.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,101 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>OpenLayers Bookmark Example</title>
+    <link rel="stylesheet" href="http://openlayers.org/api/theme/default/style.css" type="text/css" />
+    <link rel="stylesheet" href="http://www.openlayers.org/dev/examples/style.css" type="text/css" />
+    <link rel="stylesheet" href="../theme/default/bookmark.css" type="text/css" />
+    <style type="text/css">
+        body { position: relative; }
+        #map {
+            width: 512px;
+            height: 512px;
+            border: 1px solid black;
+            position: relative;
+        }
+        #logWindow {
+            width: 512px;
+            height: 50px;
+            border: 1px solid black;
+            position: relative;
+        }
+        #myBookmarks {
+            height: 200px;
+            width: 15em;
+            float: right;
+         }
+         #myBookmarks #olBookmarkElement {
+            cursor : pointer;
+         }
+        input[type="button"] { border: 1px solid black; }
+    </style>
+    <script src="http://openlayers.org/api/OpenLayers.js"></script>
+    <script src="../lib/OpenLayers/Control/Bookmark.js"></script>
+    <script type="text/javascript">
+        var map, bookmarks;
+        var logWindow = document.getElementById('logWindow');
+        function initMap(){
+            map = new OpenLayers.Map('map', { controls: [], theme: null });
+            map.addControl(new OpenLayers.Control.Navigation());
+
+            // no attachment to div
+            bookmarks = new OpenLayers.Control.Bookmark({ 'map':map });
+            bookmarks.add("ATLANTA","-85.085373,33.114328,-83.679123,34.520578");
+            bookmarks.add("BOSTON","-71.401901,41.989908,-70.698776,42.693033");
+
+            var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                "http://labs.metacarta.com/wms/vmap0",
+                {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+            map.addLayer(ol_wms);
+            if (!map.getCenter()) map.zoomToMaxExtent();
+        }
+        function writeLog(data) {
+            if(typeof logWindow == "undefined") {
+               var logWindow = document.getElementById('logWindow');
+            }
+            logWindow.innerHTML=data+"<br />";
+        }
+    </script>
+  </head>
+  <body onload="initMap()">
+    <h1 id="title">Bookmark Control Example</h1>
+    <div id="tags"></div>
+    <p id="shortdesc">
+      Demonstrates the use of Bookmarks storage in a cookie.<br />
+    </p>
+    <div id="map"></div>
+    <div id="docs">
+      <p>This demonstrates use of the Bookmarks storage in a Cookie.</p>
+   </div>
+   <div id="logWindow"></div>
+   <div>
+      <input type="button" value="Show Stack" onclick="javascript:writeLog(bookmarks.toString());">
+      <input type="button" value="Clear Stack" onclick="javascript:writeLog(bookmarks.clear());">
+      <input type="button" value="Cookie Status" onclick="javascript:writeLog(bookmarks.check());">
+      <input type="button" value="Store Stack" onclick="javascript:writeLog(bookmarks.store());">
+      <input type="button" value="Load Cookie" onclick="javascript:writeLog(bookmarks.load());">
+      <input type="button" value="Clear Cookie" onclick="javascript:writeLog(bookmarks.expire());">
+      <br /><br />
+      Use these links to test the navigation. If nothing happens when you click, that<br />
+      bookmark is no longer defined or not found.<br /><br />
+      Click
+      <a noref onclick="javascript:f=bookmarks.find('atlanta');if(f!=false)bookmarks.zoomToLabel(f[0][1]);" style="text-decoration:underline;cursor:pointer;">here</a> to go to Atlanta
+      or
+      <a noref onclick="javascript:f=bookmarks.find('boston');if(f!=false)bookmarks.zoomToLabel(f[0][1]);" style="text-decoration:underline;cursor:pointer;">here</a> to go to Boston.
+      <br /><br />
+      Testing process<br /><br />
+      When you first load, two bookmarks are created.
+      <ol>
+         <li>Click "Show Stack" to view them.</li>
+         <li>Click "Cookie Status" to see if a cookie has been previously defined. <br />
+         If not, you should see false.</li>
+         <li>Click "Store Stack".</li>
+         <li>If yes, you should see the contents as a string.</li>
+         <li>You can close your browser and revisit this page.</li>
+         <li>Upon returning, click "Clear Stack" to remove all bookmarks.</li>
+         <li>Click "Load Cookie" to repopulate the stack. Navigation should function normally.</li>
+         <li>Remove the cookie with "Clear Cookie".</li>
+     </ol>
+   </div>
+  </body>
+</html>

Added: addins/bookmark/release/2.2/examples/bookmark-custom.html
===================================================================
--- addins/bookmark/release/2.2/examples/bookmark-custom.html	                        (rev 0)
+++ addins/bookmark/release/2.2/examples/bookmark-custom.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,99 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>OpenLayers Bookmark Example</title>
+    <link rel="stylesheet" href="http://openlayers.org/api/theme/default/style.css" type="text/css" />
+    <link rel="stylesheet" href="http://www.openlayers.org/dev/examples/style.css" type="text/css" />
+    <link rel="stylesheet" href="../theme/default/bookmark.css" type="text/css" />
+    <style type="text/css">
+        body { position: relative; }
+        #map {
+            width: 512px;
+            height: 512px;
+            border: 1px solid black;
+            position: relative;
+        }
+        #myBookmarks {
+            height: 200px;
+            width: 15em;
+            float: right;
+         }
+         #myBookmarks #olBookmarkElement {
+            cursor : pointer;
+         }
+        input[type="button"] { border: 1px solid black; }
+    </style>
+    <script src="http://openlayers.org/api/OpenLayers.js"></script>
+    <script src="../lib/OpenLayers/Control/Bookmark.js"></script>
+    <script type="text/javascript">
+        var map, bookmarks;
+        function initMap(){
+            map = new OpenLayers.Map('map', { controls: [], theme: null });
+            map.addControl(new OpenLayers.Control.Navigation());
+
+            // outside the map
+            bookmarks = new OpenLayers.Control.Bookmark({ 'div':OpenLayers.Util.getElement('myBookmarks'), 'title': 'Bookmarks outside the map' });
+            map.addControl(bookmarks);
+            bookmarks.add("NASHVILLE",new OpenLayers.Bounds(-87.075,35.956,-86.507,36.425),Msg,"Welcome to Nashville");
+            bookmarks.add("JACKSONVILLE","-82.11031,30.07138,-81.27810,30.63443");
+            bookmarks.add("WASHINGTON DC",new OpenLayers.Bounds(-77.338126,38.639689,-76.788809,39.189005));
+            bookmarks.add("WORLD","-180,-180,180,180");
+
+            OpenLayers.Rico.Corner.round( bookmarks.div , {corners: "tl bl tr br", bgColor: "transparent", color: "darkblue", blend: false});
+            OpenLayers.Rico.Corner.changeOpacity(bookmarks.contentDiv, 0.75);
+
+            // inside the map, docked right middle
+            bookmarks2 = new OpenLayers.Control.Bookmark({ 'title': 'Bookmarks in the map', 'allowRemove': false });
+            map.addControl(bookmarks2);
+            bookmarks2.maximizeControl();
+            bookmarks2.add("ATLANTA",new OpenLayers.Bounds(-85.085373,33.114328,-83.679123,34.520578));
+            bookmarks2.add("BOSTON",new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033));
+            bookmarks2.add("WORLD",new OpenLayers.Bounds(-180,-180,180,180));
+
+            OpenLayers.Rico.Corner.round( bookmarks2.div, {corners: "tl bl", bgColor: "transparent", color: "darkblue", blend: false});
+            OpenLayers.Rico.Corner.changeOpacity(bookmarks2.contentDiv, 0.75);
+
+            bookmarks3 = new OpenLayers.Control.Bookmark({ 'map':map });
+            bookmarks3.add("ATLANTA","-85.085373,33.114328,-83.679123,34.520578",Msg,"Welcome to Atlanta");
+            bookmarks3.add("BOSTON","-71.401901,41.989908,-70.698776,42.693033",Msg,"Welcome to Boston");
+
+            var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                "http://labs.metacarta.com/wms/vmap0",
+                {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+            map.addLayer(ol_wms);
+            if (!map.getCenter()) map.zoomToMaxExtent();
+        }
+
+        function Msg(val) { alert(val); }
+    </script>
+  </head>
+  <body onload="initMap()">
+    <h1 id="title">Bookmark Control Example</h1>
+    <div id="tags"></div>
+    <p id="shortdesc">
+      Demonstrates the use of Bookmarks.<br />
+      Styled with CSS only.  Corners rounded by Rico outside of the class.
+    </p>
+    <div id="myBookmarks"></div>
+    <div id="map"></div>
+    <div id="docs">
+      <p>This demonstrates use of the Bookmarks.<br />Applys to outside bookmarks only.</p>
+   </div>
+   <div>
+      <form>
+         Name: <input type="text" id="name" style="width:100px;border: 1px solid black"><br />
+         Coords: <input type="text" id="coords" style="width:200px;border: 1px solid black">&nbsp;&nbsp;<input type="button" value="Get Current Coords" onclick="document.getElementById('coords').value = map.getExtent().toBBOX()"><br /><br />
+         <input type="button" value="Zoom To" onclick="if(document.getElementById('name').value != ''){ bookmarks.zoomToLabel(document.getElementById('name').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Add" onclick="if(document.getElementById('name').value != '' && document.getElementById('coords').value != ''){ bookmarks.add(document.getElementById('name').value, document.getElementById('coords').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Add Current View" onclick="if(document.getElementById('name').value != ''){ bookmarks.addView(document.getElementById('name').value) } else { alert('Need a name') }">&nbsp;&nbsp;&nbsp;&nbsp;
+         <input type="button" value="Remove" onclick="if(document.getElementById('name').value != ''){ bookmarks.remove(document.getElementById('name').value) } else { alert('Need a name') }">&nbsp;&nbsp;
+         <input type="button" value="Remove All" onclick="bookmarks.clear()"><br /><br />
+	 Bookmarks without a DIV?  Click <a noref onclick="javascript:f=bookmarks.find('atlanta');if(f!=false)bookmarks.zoomToLabel(f[0][1]);" style="text-decoration:underline;cursor:pointer;">here</a> to go to Atlanta or
+	 <a noref onclick="javascript:f=bookmarks.find('boston');if(f!=false)bookmarks.zoomToLabel(f[0][1]);" style="text-decoration:underline;cursor:pointer;">here</a>
+         to go to Boston.<br /><br />
+         Added the ability to pass a user defined function to be execute when the user clicks the bookmark.  Click the Boston
+         or Atlanta links above.
+      </form>
+   </div>
+  </body>
+</html>

Added: addins/bookmark/release/2.2/examples/bookmark.html
===================================================================
--- addins/bookmark/release/2.2/examples/bookmark.html	                        (rev 0)
+++ addins/bookmark/release/2.2/examples/bookmark.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,68 @@
+<html xmlns="http://www.w3.org/1999/xhtml">
+  <head>
+    <title>OpenLayers Bookmark Example</title>
+    <link rel="stylesheet" href="http://openlayers.org/api/theme/default/style.css" type="text/css" />
+    <link rel="stylesheet" href="http://www.openlayers.org/dev/examples/style.css" type="text/css" />
+    <link rel="stylesheet" href="http://svn.openlayers.org/addins/bookmark/trunk/theme/default/bookmark.css" type="text/css" />
+    <style type="text/css">
+        body { position: relative; }
+        #map {
+            width: 512px;
+            height: 512px;
+            border: 1px solid black;
+            position: relative;
+        }
+        input[type="button"] { border: 1px solid black; }
+    </style>
+    <script src="http://openlayers.org/api/OpenLayers.js"></script>
+    <script src="../lib/OpenLayers/Control/Bookmark.js"></script>
+    <script type="text/javascript">
+        var map, bookmark;
+        function initMap(){
+            map = new OpenLayers.Map('map', { controls: [], theme: null });
+            map.addControl(new OpenLayers.Control.Navigation());
+
+            // inside the map, docked right middle
+            bookmark = new OpenLayers.Control.Bookmark({ 'title': 'Bookmarks in map', 'allowRemove': true });
+            map.addControl(bookmark);
+            bookmark.maximizeControl();
+            bookmark.add("ATLANTA",new OpenLayers.Bounds(-85.085373,33.114328,-83.679123,34.520578));
+            bookmark.add("BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033));
+            bookmark.add("WASHINGTON DC","-77.338126,38.639689,-76.788809,39.189005");
+
+            // round the corners with rico
+            OpenLayers.Rico.Corner.round( bookmark.div, {corners: "tl bl", bgColor: "transparent", color: "darkblue", blend: false});
+            OpenLayers.Rico.Corner.changeOpacity(bookmark.contentDiv, 0.75);
+
+            var ol_wms = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                "http://labs.metacarta.com/wms/vmap0",
+                {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+            map.addLayer(ol_wms);
+            if (!map.getCenter()) map.zoomToMaxExtent();
+        }
+    </script>
+  </head>
+  <body onload="initMap()">
+    <h1 id="title">Bookmark Control Example</h1>
+    <div id="tags"></div>
+    <p id="shortdesc">
+      Demonstrates the use of the Bookmark Control.<br />
+      Styled with CSS only.  Corners rounded by Rico outside of the class.
+    </p>
+    <div id="map"></div>
+    <div id="docs">
+      <p>This demonstrates use of the Bookmarks.</p>
+   </div>
+   <div>
+      <form>
+         Name: <input type="text" id="name" style="width:100px;border: 1px solid black"><br />
+         Coords: <input type="text" id="coords" style="width:200px;border: 1px solid black">&nbsp;&nbsp;<input type="button" value="Get Current Coords" onclick="document.getElementById('coords').value = map.getExtent().toBBOX()"><br /><br />
+         <input type="button" value="Zoom To" onclick="if(document.getElementById('name').value != ''){ bookmark.zoomToLabel(document.getElementById('name').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Add" onclick="if(document.getElementById('name').value != '' && document.getElementById('coords').value != ''){ bookmark.add(document.getElementById('name').value, document.getElementById('coords').value) } else { alert('Need a name and coords') }">&nbsp;&nbsp;
+         <input type="button" value="Remove" onclick="if(document.getElementById('name').value != ''){ bookmark.remove(document.getElementById('name').value) } else { alert('Need a name') }">&nbsp;&nbsp;
+         <input type="button" value="Remove All" onclick="bookmark.clear()">
+      </form>
+   </div>
+  </body>
+</html>

Added: addins/bookmark/release/2.2/lib/OpenLayers/Control/Bookmark.js
===================================================================
--- addins/bookmark/release/2.2/lib/OpenLayers/Control/Bookmark.js	                        (rev 0)
+++ addins/bookmark/release/2.2/lib/OpenLayers/Control/Bookmark.js	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,791 @@
+/* Copyright (c) 2006-2008 MetaCarta, Inc., published under the Clear BSD
+ * license.  See http://svn.openlayers.org/trunk/openlayers/license.txt for the
+ * full text of the license. */
+
+/**
+ * @requires OpenLayers/Control.js
+ */
+
+/**
+ * Class: OpenLayers.Control.Bookmark
+ *     A bookmark view control.  Store an array of locations
+ *     refrenced by a label.
+ *
+ * Inherits from:
+ *  - <OpenLayers.Control>
+ */
+OpenLayers.Control.Bookmark = OpenLayers.Class(OpenLayers.Control, {
+
+    /**
+     * Property: type
+     * {String}
+     */
+    type: OpenLayers.Control.TYPE_TOOL,
+
+    /**
+     * Property: stackState
+     * {Boolean} Flag to indicate if the control needs to be redrawn. We have
+     *     this in order to avoid unnecessarily redrawing the control.
+     */
+    stackState: true,
+
+    /**
+     * APIProperty: limit
+     * {Integer} Optional limit on the number of bookmark items to retain.
+     *           Default is 50. 0 is no limit.
+     */
+    limit: 50,
+
+    /**
+     * APIProperty: stack
+     * {Array} Array of items in the list.
+     */
+    stack: null,
+
+    /**
+     * APIProperty: allowRemove
+     * {Boolean}  Allow the bookmark to be removed.
+     *      Default to true.
+     */
+    allowRemove: true,
+
+    /**
+     * APIProperty: allowDuplicates
+     * {Boolean}  Allow the bookmarks to be duplicated. If not, overwrite
+     *            existing.
+     *      Default to false.
+     */
+    allowDuplicates: false,
+
+    /**
+     * Property: cookieName
+     * {String}
+     */
+    cookieName: "olBookMark",
+
+    /**
+     * Property: cookieExpires
+     * {Integer} Default days to expire the cookie.
+     */
+    cookieExpires: 30,
+
+    /**
+     * Property: cookiePath
+     * {String}
+     */
+    cookiePath: "/",
+
+    /**
+     * Property: cookieSeperator
+     * {String} Seperates the label and bounds.
+     */
+    cookieSeperator: ":",
+
+    /**
+     * Property: cookieDelimiter
+     * {String} Seperates bookmarks.
+     */
+    cookieDelimiter: "|",
+ 
+    // DOM Elements
+
+    /**
+     * Property: contentDiv
+     * {DOMElement}
+     */
+    contentDiv: null,
+
+    /**
+     * Property: dataLblDiv
+     * {DOMElement}
+     */
+    dataLblDiv: null,
+
+    /**
+     * Property: dataElementDiv
+     * {DOMElement}
+     */
+    dataElementDiv: null,
+
+    /**
+     * Property: minimizeDiv
+     * {DOMElement}
+     */
+    minimizeDiv: null,
+
+    /**
+     * Property: maximizeDiv
+     * {DOMElement}
+     */
+    maximizeDiv: null,
+
+    /**
+     * Constructor: OpenLayers.Control.Bookmark
+     *
+     * Parameters:
+     * options - {func} An optional object whose properties will be used
+     *     to extend the control.
+     */
+    initialize: function(options) {
+        OpenLayers.Control.prototype.initialize.apply(this, [options]);
+        this.initStack();
+    },
+
+    /**
+     * APIMethod: destroy
+     * Destroy the control.
+     */
+    /* FIXME  - not sure i'm stopping observation correctly*/
+    destroy: function() {
+        this.stack=null;
+        OpenLayers.Event.stopObservingElement(this.div);
+        OpenLayers.Event.stopObservingElement(this.minimizeDiv);
+        OpenLayers.Event.stopObservingElement(this.maximizeDiv);
+        OpenLayers.Control.prototype.destroy.apply(this);
+    },
+
+    /**
+     * Method: initStack
+     * Initialize the stack and get ready for adds.
+     */
+    initStack: function() {
+      this.stack = [];
+      this.stackState = true;
+    },
+
+    /**
+     * APIMethod: add
+     * Add an item to the stack as label, bounds.  If no label is specified
+     *      "NONAME" is used.
+     *
+     * Parameters:
+     * label - {String} Label reference to this bookmark
+     * bounds - {Object or String}
+     *      Object is a OpenLayers.bounds object or
+     *      String representation of bounds in format of
+     *     "-180,-180,180,180"
+     *
+     * Returns:
+     * {Boolean} false if limit reached. true if added successfully.
+     */
+    add: function(label, bounds, func, param) {
+      if(this.stack.length == this.limit && this.limit != 0) {
+         OpenLayers.Console.log("limit reached");
+         return false;  // limit reached
+      }
+      if(!this.allowDuplicates) {
+        if(this.find(label)) {
+            this.remove(label, true);
+        }
+      }
+      func = (typeof func == "undefined") ? function() {} : func;
+      param = (typeof param == "undefined" || param.length == 0) ? "" : param;
+      label = (label == "undefined" || label.length == 0) ? "NONAME" : label;
+      this.stack.push({ "label":label.toUpperCase(),
+                        "bounds":bounds,
+                        "func": func,
+                        "param": param,
+                        "id": this.stack.length });
+      OpenLayers.Console.log(label + " added with bounds " + bounds);
+      this.stackState = true;
+      this.redraw();
+      return true;
+    },
+
+    /**
+     * APIMethod: clear
+     * Clear and reset the stack then redraw the div.
+     */
+    clear: function() {
+      this.initStack();
+      this.redraw();
+      return true;
+    },
+
+    /**
+     * APIMethod: remove
+     * Remove item from the stack.
+     *
+     * Parameters:
+     * label - {String} Remove from the stack referenced by label.
+     *
+     * Returns:
+     * {Boolean} False if not found or not allowed to remove.
+     */
+    /* FIXME  - not sure i'm stopping observation correctly*/
+    remove: function(label, force) {
+      var force = force||false
+      if(!this.allowRemove && !force) {
+         return false;
+      }
+      label = label.toUpperCase();
+      for(var i=0; i<this.stack.length; i++) {
+         if( this.stack[i].label.toUpperCase() == label) {
+            for( var ii=0; ii<this.dataElementDiv.childNodes.length; ii++) {
+               var currentNode = this.dataElementDiv.childNodes[ii].id;
+               var currentLabel = label.replace(/ /g,'_');
+               if(currentNode.replace(/ /g,'_') == "Remove_"+currentLabel) {
+                  OpenLayers.Event.stopObservingElement(
+                     this.dataElementDiv.childNodes[ii]);
+               }
+               if(currentNode.replace(/ /g,'_') == "Link_"+currentLabel) {
+                  OpenLayers.Event.stopObservingElement(
+                     this.dataElementDiv.childNodes[ii]);
+               }
+            }
+            this.stack.splice( i, 1 );
+            this.stackState = true;
+            this.redraw();
+            return true;
+         }
+      }
+      return false;
+    },
+
+    /**
+     * APIMethod: find
+     * Find the first regex matched label and return it.
+     *
+     * Parameters:
+     * label - {String}  Case insensitive string to find.
+     *
+     * Returns:
+     * {Boolean} Return array of label(s) if found. False if not found
+     */
+    find: function(label) {
+      var rt = [];
+      var regEx = new RegExp (label, "i");
+      for(var i=0; i<this.stack.length; i++) {
+         if(regEx.test(this.stack[i].label)) {
+            rt.push([this.stack[i].id,this.stack[i].label]);
+         }
+      }
+      return (rt.length == 0 ? false : rt);
+    },
+
+    /**
+     * APIMethod: zoomToId
+     * Zoom to extents referenced by id
+     *
+     * Parameters:
+     * id - {Integer} stack id reference
+     *
+     * Returns:
+     * {Boolean} False if not found
+     */
+    zoomToId: function(id) {
+       var found=false;
+       if( this.stack[id] != this.stack[id].id ) {
+         if( typeof this.stack[id].bounds == "object" ) {
+            this.map.zoomToExtent( this.stack[id].bounds );
+         } else {
+            this.map.zoomToExtent( new OpenLayers.Bounds.fromString(
+                                       this.stack[id].bounds) );
+         }
+         this.stack[id].func(this.stack[id].param);
+         found=true;
+       } else {
+         // index is misaligned, search the stack for the correct id,
+         // then reindex.
+         OpenLayers.Console.log("stack misaligned. searching for id.");
+         for( var i=0; i<this.stack.length; i++ ) {
+            if( this.stack[i].id == id ) {
+               if( typeof this.stack[i].bounds == "object" ) {
+                  this.map.zoomToExtent( this.stack[i].bounds );
+               } else {
+                  this.map.zoomToExtent( new OpenLayers.Bounds.fromString(
+                                             this.stack[i].bounds) );
+               }
+               this.stack[i].func(this.stack[i].param);
+               found=true;
+               break;  // break out of for when found
+            }
+         }
+         this.reindex();
+       }
+       return found;
+    },
+
+    /**
+     * APIMethod: zoomTo (deprecated, use zoomToLabel)
+     * Zoom to extents referenced by label
+     *
+     * Parameters:
+     * label - {String} Label to zoom to.
+     *
+     * Returns:
+     * {Boolean} False if not found
+     */
+    zoomTo: function(label) {
+      OpenLayers.Console.log("zoomTo() deprecated. Use zoomToLabel");
+      return this.zoomToLabel(label);
+    },
+
+    /**
+     * APIMethod: zoomToLabel
+     * Zoom to extents referenced by label
+     *
+     * Parameters:
+     * label - {String} Label to zoom to.
+     *
+     * Returns:
+     * {Boolean} False if not found
+     */
+    zoomToLabel: function(label) {
+      f = this.find(label);
+      if(f != false) {
+         OpenLayers.Console.log("found label " + f[0][1]);
+         this.zoomToId(f[0][0]);
+         return true;
+      }
+      OpenLayers.Console.log(label + " not found");
+      return false;
+    },
+
+    /**
+     * APIMethod: sortStack
+     * Sort stack by sortMethod
+     *
+     * Parameters:
+     * sortMethod - {String} Field to sort on.
+     */
+    sortStack: function(sortMethod) {
+      switch(sortMethod.toLowerCase()) {
+         case "id":
+            this.stack.sort(this.sortById);
+            break;
+         case "label":
+         default:
+            this.stack.sort(this.sortByLabel);
+            break;
+      }
+      this.stackState = true;
+    },
+
+    /**
+     * Method: sortByLabel
+     * Sort stack by label values. Called by sort().
+     */
+    sortByLabel:  function(a, b) {
+         var x = a.label.toLowerCase();
+         var y = b.label.toLowerCase();
+         return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+      },
+
+    /**
+     * Method: sortById
+     * Sort stack by id values. Called by sort().
+     */
+    sortById:  function(a, b) {
+         var x = a.id;
+         var y = b.id;
+         return ((x < y) ? -1 : ((x > y) ? 1 : 0));
+      },
+
+    /**
+     * APIMethod: reindex
+     * Reindex stack; realign id with stack index. Usually called
+     * after sortStack, add or remove.
+     */
+    reindex: function() {
+       OpenLayers.Console.log("reindexing");
+       for( var i=0; i<this.stack.length; i++ ) {
+          this.stack[i].id = i;
+       }
+       this.stackState = true;
+    },
+
+    /**
+     * APIMethod: store
+     * Store the contents of the stack to a cookie referenced by
+     * cookieName.
+     */
+    store: function( value, expires) {
+      var expires = expires||this.cookieExpires
+      var value   = value||this.toString();
+      if (expires) {
+         var date = new Date();
+         date.setTime(date.getTime()+(expires*24*60*60*1000));
+         expires = "; expires="+date.toGMTString();
+      }
+      document.cookie = this.cookieName +"="+ value +
+                        expires + "; path=" + this.cookiePath;
+      OpenLayers.Console.log("cookie stored");
+      return true;
+    },
+
+    /**
+     * Method: toString
+     * Convert stack array to string
+     *
+     * Returns:
+     * String. label + cookieSeperator + bounds + cookieDelimiter
+     */
+    toString: function() {
+      var toString = "";
+      for(var i=0; i<this.stack.length; i++) {
+         toString += this.stack[i].label + this.cookieSeperator +
+                     ( typeof this.stack[i].bounds == "object"
+                        ? this.stack[i].bounds.toBBOX()
+                        : this.stack[i].bounds ) + this.cookieDelimiter;
+      }
+      toString = toString.substr(0, toString.length-1);
+      OpenLayers.Console.log(toString);
+      return toString;
+    },
+
+    /**
+     * APIMethod: load
+     * Load the contents of the cookie referenced by cookieName
+     * to the stack.
+     *
+     * Returns:
+     * false if not found.
+     */
+    load: function() {
+      var allCookies=document.cookie.split(";");
+      f = this.check();
+      if(f != false) {
+         var items = f.split(this.cookieDelimiter);
+         for(var ii in items) {
+            var bookmark = items[ii].split(this.cookieSeperator);
+            this.add(bookmark[0],bookmark[1]);
+         }
+         return true;
+      }
+      OpenLayers.Console.log("cookie not found");
+      return false;
+    },
+
+    /**
+     * APIMethod: check
+     * Check for the existance of cookie referenced by cookieName
+     *
+     * Returns:
+     * {String} contents of cookie if found.
+     * false if not found.
+     */
+    check: function() {
+      var allCookies=document.cookie.split(";");
+      for(var i in allCookies) {
+         keyval = allCookies[i].split("=");
+         if(keyval[0].replace(/^\s+|\s+$/g,"") == this.cookieName) {
+            return keyval[1];
+         }
+      }
+      OpenLayers.Console.log("cookie not found");
+      return false;
+   },
+    /**
+     * APIMethod: expire
+     * Expire cookie referenced by cookieName.
+     */
+    expire: function() {
+      return this.store("", -1);
+    },
+
+    /**
+     * APIMethod: maximizeControl
+     * Set up the labels and divs for the control
+     *
+     * Parameters:
+     * e - {Event}
+     */
+    maximizeControl: function(e) {
+        //HACK HACK HACK - find a way to auto-size this
+        this.div.style.width = "13em";
+        this.div.style.height = "";
+
+        this.showControls(false);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);
+        }
+    },
+
+    /**
+     * APIMethod: minimizeControl
+     * Hide all the contents of the control, shrink the size,
+     *     add the maximize icon
+     *
+     * Parameters:
+     * e - {Event}
+     */
+    minimizeControl: function(e) {
+        this.div.style.width = "0px";
+        this.div.style.height = "0px";
+
+        this.showControls(true);
+
+        if (e != null) {
+            OpenLayers.Event.stop(e);
+        }
+    },
+
+    /**
+     * Method: draw
+     * Create a div with stack values
+     *
+     * Returns:
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */
+    draw: function() {
+
+        OpenLayers.Control.prototype.draw.apply(this);
+
+        // create layout divs
+        this.loadContents();
+
+        // set mode to minimize
+        if(!this.outsideViewport) {
+            this.minimizeControl();
+        }
+
+        // populate div with current info
+        this.redraw();
+
+        return this.div;
+    },
+
+    /**
+     * Method: loadContents
+     * Set up the labels and divs for the control
+     */
+    loadContents: function() {
+         //configure main div
+         this.div.id = ( this.div.id == ""
+                       ? "olControlBookmark"
+                       : this.div.id );
+         this.div.className = "olControlBookmark";
+         OpenLayers.Event.observe(this.div, "mouseup",
+            OpenLayers.Function.bindAsEventListener(this.mouseUp, this));
+         OpenLayers.Event.observe(this.div, "click", this.ignoreEvent);
+         OpenLayers.Event.observe(this.div, "mousedown",
+            OpenLayers.Function.bindAsEventListener(this.mouseDown, this));
+         OpenLayers.Event.observe(this.div, "dblclick", this.ignoreEvent);
+
+         // layers list div
+         this.contentDiv = document.createElement("div");
+         this.contentDiv.id = this.displayClass + 'Content';
+         this.contentDiv.className = this.displayClass + 'Content';
+
+         this.dataLblDiv = document.createElement("div");
+         this.dataLblDiv.id = this.displayClass + 'Title';
+         this.dataLblDiv.className = this.displayClass + 'Title';
+         this.dataLblDiv.innerHTML = ( this.title == ""
+                                     ? "Bookmarks"
+                                     : this.title );
+        this.contentDiv.appendChild(this.dataLblDiv);
+
+        this.dataElementDiv = document.createElement("div");
+        this.dataElementDiv.id = this.displayClass + 'Elements';
+        this.dataElementDiv.className = this.displayClass + 'Elements';
+
+        this.contentDiv.appendChild(this.dataElementDiv);
+        this.div.appendChild(this.contentDiv);
+
+        // maximimze button
+        this.maximizeDiv = document.createElement("div");
+        this.maximizeDiv.id = this.div.id+'MaximizeDiv';
+        this.maximizeDiv.className = this.displayClass + 'MaximizeButton';
+        OpenLayers.Event.observe(this.maximizeDiv, "click",
+           OpenLayers.Function.bindAsEventListener(this.maximizeControl, this)
+        );
+        this.div.appendChild(this.maximizeDiv);
+
+        // minimimze button
+        this.minimizeDiv = document.createElement("div");
+        this.minimizeDiv.id = this.div.id+'MinimizeDiv';
+        this.minimizeDiv.className = this.displayClass + 'MinimizeButton';
+        OpenLayers.Event.observe(this.minimizeDiv, "click",
+           OpenLayers.Function.bindAsEventListener(this.minimizeControl, this)
+        );
+        this.div.appendChild(this.minimizeDiv);
+
+    },
+
+    /**
+     * Method: clearStackDiv
+     * Clear the div containing the stack elements.
+     */
+    /* FIXME  - not sure i'm stopping observation correctly*/
+    clearStackDiv: function() {
+      // if no dataEmentDiv drop out gracfully
+      if( this.dataElementDiv == null ) {
+         return;
+      }
+
+      if( this.dataElementDiv != null ) {
+         for(var i=0; i<this.dataElementDiv.childNodes; i++) {
+            OpenLayers.Event.stopObservingElement(
+                              this.dataElementDiv.childNodes[i]);
+         }
+         this.dataElementDiv.innerHTML = "";
+      }
+    },
+
+    /**
+     * Method: redraw
+     * Takes the current state of the control and rebuilds if needed.
+     *
+     * Returns:
+     * {DOMElement} A reference to the DIV DOMElement containing the control
+     */
+    redraw: function() {
+
+        // only redraw the div if the state has changed, otherwise return
+        // same div.
+        if (!this.stackState) {
+           return this.div;
+        }
+
+        this.clearStackDiv();
+
+        // if no dataEmentDiv drop out gracfully
+        if( this.dataElementDiv == null ) {
+            return;
+        }
+
+        for(var i=0; i<this.stack.length; i++) {
+
+            var bookmarkRow = document.createElement("div");
+            bookmarkRow.className = this.displayClass + 'Row';
+
+            if(this.allowRemove) {
+
+               var removeSpan = document.createElement("span");
+               removeSpan.id = this.displayClass + 'Remove_' +
+                                       this.stack[i].label.replace(/ /g,'_');
+               removeSpan.className = this.displayClass + 'Remove';
+
+               var context = { 'bookmark': this,
+                               'label': this.stack[i].label,
+                               'action': "remove"
+                             };
+
+               OpenLayers.Event.observe(removeSpan, "click",
+                 OpenLayers.Function.bindAsEventListener(this.onBookmarkClick,
+                                                         context)
+               );
+
+               bookmarkRow.appendChild(removeSpan);
+
+            }
+
+            var labelSpan = document.createElement("span");
+            labelSpan.id = this.displayClass + 'Link_' +
+                                this.stack[i].label.replace(/ /g,'_');
+            labelSpan.className = this.displayClass + 'Link';
+            labelSpan.innerHTML = this.stack[i].label;
+
+            var context = { 'bookmark': this,
+                            'label': this.stack[i].label,
+                            'action': 'zoom'
+                          };
+
+            OpenLayers.Event.observe(labelSpan, "click",
+               OpenLayers.Function.bindAsEventListener(this.onBookmarkClick,
+                                                       context)
+            );
+
+            bookmarkRow.appendChild(labelSpan);
+            this.dataElementDiv.appendChild(bookmarkRow);
+        }
+        this.stackState = false;
+        return this.dataElementDiv;
+    },
+
+    /**
+     * Method:
+     * A label has been clicked. Do somehting with/to it.
+     *
+     * Parameters:
+     * e - {Event}
+     *
+     * Context:
+     *  - {<OpenLayers.Control.Bookmark>} bookmark
+     *  - {String} label
+     *  - {String} action
+     */
+    onBookmarkClick: function(e) {
+         switch(this.action) {
+            case "remove":
+               this.bookmark.remove(this.label);
+               break;
+            case "zoom":
+            default:
+               this.bookmark.zoomToLabel(this.label);
+               break
+         }
+        OpenLayers.Event.stop(e);
+    },
+
+    /**
+     * Method: showControls
+     * Hide/Show all controls depending on whether we are
+     *     minimized or not
+     *
+     * Parameters:
+     * minimize - {Boolean}
+     */
+    showControls: function(minimize) {
+        this.maximizeDiv.style.display = minimize ? "block" : "none";
+        this.minimizeDiv.style.display = minimize ? "none" : "block";
+        this.contentDiv.style.display = minimize ? "none" : "block";
+    },
+
+    /**
+     * Method: showToggle
+     * Hide/Show the toggle depending on whether the control is minimized
+     *
+     * Parameters:
+     * minimize - {Boolean}
+     */
+    showToggle: function(minimize) {
+        this.maximizeDiv.style.display = minimize ? "block" : "none";
+        this.minimizeDiv.style.display = minimize ? "none" : "block";
+    },
+
+    /**
+      * Method: ignoreEvent
+      *
+      * Parameters:
+      * evt - {Event}
+      */
+    ignoreEvent: function(evt) {
+       OpenLayers.Event.stop(evt);
+    },
+
+    /**
+      * Method: mouseDown
+      * Register a local 'mouseDown' flag so that we'll know whether or not
+      *     to ignore a mouseUp event
+      *
+      * Parameters:
+      * evt - {Event}
+      */
+    mouseDown: function(evt) {
+        this.isMouseDown = true;
+        this.ignoreEvent(evt);
+    },
+
+    /**
+      * Method: mouseUp
+      * If the 'isMouseDown' flag has been set, that means that the drag was
+      *     started from within the control, and thus we can
+      *     ignore the mouseup. Otherwise, let the Event continue.
+      *
+      * Parameters:
+      * evt - {Event}
+      */
+    mouseUp: function(evt) {
+        if (this.isMouseDown) {
+            this.isMouseDown = false;
+            this.ignoreEvent(evt);
+        }
+    },
+
+    CLASS_NAME: "OpenLayers.Control.Bookmark"
+});
+
+OpenLayers.Control.Bookmark.VERSION_NUMBER = '2.2';
\ No newline at end of file

Added: addins/bookmark/release/2.2/tests/Control/Bookmark.html
===================================================================
--- addins/bookmark/release/2.2/tests/Control/Bookmark.html	                        (rev 0)
+++ addins/bookmark/release/2.2/tests/Control/Bookmark.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,251 @@
+<html>
+<head>
+  <script src="http://openlayers.org/api/OpenLayers.js"></script>
+  <script src="../../lib/OpenLayers/Control/Bookmark.js"></script>
+  <script type="text/javascript">
+    var map;
+
+    function test_Control_Bookmark_constructor (t) {
+        t.plan( 2 );
+
+        var control = new OpenLayers.Control.Bookmark();
+        t.ok( control instanceof OpenLayers.Control.Bookmark, "new OpenLayers.Control.Bookmark returns object" );
+        t.eq( control.displayClass,  "olControlBookmark", "displayClass is correct" );
+    }
+
+    function test_Control_Bookmark_draw (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        var div = control.draw();
+        t.ok( control.div != null, "draw makes a div" );
+        t.ok( div != null, "draw returns its div" );
+    }
+
+    function test_Control_Bookmark_outsideViewport (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark({'div':OpenLayers.Util.getElement('bookmark')});
+        map.addControl(control);
+        t.eq(control.div.style.width, "250px", "Div is not minimized when added.");
+        control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+        t.eq(control.div.style.width, "0px", "Div is minimized when added.");
+    }
+
+    function test_Control_Bookmark_loadContents(t) {
+        t.plan( 5 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        t.ok(control.contentDiv != null, "correctly makes content div");
+        t.ok(control.dataLblDiv != null, "correctly makes data label div");
+        t.ok(control.dataElementDiv != null, "correctly makes data element div");
+        t.ok(control.maximizeDiv != null, "correctly makes maximize div");
+        t.ok(control.minimizeDiv != null, "correctly makes minimize div");
+    }
+
+    function test_Control_Bookmark_add_remove (t) {
+        t.plan( 4 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        t.ok(control.stack.length == 1, "correctly added bookmark using object");
+
+        control.remove( "BOSTON" );
+        t.ok(control.stack.length == 0, "correctly removed bookmark object");
+
+        control.add( "BOSTON", "-71.401901,41.989908,-70.698776,42.693033" );
+        t.ok(control.stack.length == 1, "correctly added bookmark using string");
+
+        control.remove( "BOSTON" );
+        t.ok(control.stack.length == 0, "correctly removed bookmark string");
+    }
+
+    function test_Control_Bookmark_find (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        found = control.find("BOSTON");
+        t.ok(found.length == 1, "correctly found single bookmark");
+
+        found = "";
+        control.allowDuplicates = true;
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        found = control.find("BOSTON");
+        t.ok(found.length == 3, "correctly found multiple bookmarks");
+    }
+
+    function test_Control_Bookmark_zoomToLabel (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                   "http://labs.metacarta.com/wms/vmap0",
+                   {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+        map.addLayer( layer );
+        map.zoomToMaxExtent();
+
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        var mapExtent = map.getExtent().toBBOX();
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.zoomToLabel(control.find("BOSTON")[0][1]);
+        var currentExtent = map.getExtent().toBBOX();
+
+        // note: the map extents may not match due to map zoom levels/resolutions. all
+        // we can really tell is the extents have changed.
+        t.ok(currentExtent != mapExtent, "correctly changed map extents by label");
+    }
+
+    function test_Control_Bookmark_zoomToId (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var layer = new OpenLayers.Layer.WMS( "OpenLayers WMS",
+                   "http://labs.metacarta.com/wms/vmap0",
+                   {layers: 'basic'}, {'displayInLayerSwitcher':false} );
+
+        map.addLayer( layer );
+        map.zoomToMaxExtent();
+
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        var mapExtent = map.getExtent().toBBOX();
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "BOSTON TOO", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.zoomToId(1);
+        var currentExtent = map.getExtent().toBBOX();
+
+        // note: the map extents may not match due to map zoom levels/resolutions. all
+        // we can really tell is the extents have changed.
+        t.ok(currentExtent != mapExtent, "correctly changed map extents by id");
+    }
+
+    function test_Control_Bookmark_sortByLabel (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "SECOND", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "FIRST", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+
+        control.sortStack("label");
+        t.ok(control.stack[0].label != "SECOND", "correctly sorted by label");
+    }
+
+    function test_Control_Bookmark_sortById (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "SECOND", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "FIRST", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+
+        control.sortStack("label");
+        t.ok(control.stack[0].label != "SECOND", "correctly sorted by label");
+        control.sortStack("id");
+        t.ok(control.stack[0].id == 0, "correctly sorted by id");
+    }
+
+    function test_Control_Bookmark_reindex (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "SECOND", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        control.add( "FIRST", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+
+        control.sortStack("label");
+        control.reindex();
+        t.ok(control.stack[0].id == 0 && control.stack[0].label == "FIRST", "correctly reindexed stack");
+    }
+
+    function test_Control_Bookmark_clear (t) {
+        t.plan( 2 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        t.ok(control.stack.length == 1, "correctly added bookmark using object");
+        control.clear();
+        t.ok(control.stack.length == 0, "correctly clear bookmark");
+    }
+
+    function test_Control_Bookmark_store_expire (t) {
+        t.plan( 3 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        t.ok(control.stack.length == 1, "correctly added bookmark using object");
+        control.store();
+        t.ok(control.check() != false, "correctly added bookmark cookie");
+        control.expire();
+        t.ok(control.check() == false, "correctly expired bookmark cookie");
+    }
+
+    function test_Control_Bookmark_load (t) {
+        t.plan( 5 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        t.ok(control.stack.length == 1, "correctly added bookmark using object");
+        control.store();
+        t.ok(control.check() != false, "correctly added bookmark cookie");
+        control.clear();
+        t.ok(control.stack.length == 0, "correctly clear bookmark");
+        control.load();
+        t.ok(control.check() != false, "correctly loaded bookmark cookie");
+        control.expire();
+        t.ok(control.check() == false, "correctly expired bookmark cookie");
+    }
+
+    function test_Control_Bookmark_toString (t) {
+        t.plan( 1 );
+
+        var map = new OpenLayers.Map('map');
+        var control = new OpenLayers.Control.Bookmark();
+        map.addControl(control);
+
+        control.add( "BOSTON", new OpenLayers.Bounds(-71.401901,41.989908,-70.698776,42.693033) );
+        t.ok(typeof control.toString() == "string", "correctly returned bookmark as string");
+    }
+
+  </script>
+</head>
+<body>
+    <div id="map" style="width: 1024px; height: 512px;"/>
+    <div id="bookmark" style="width:250px; height:256px;" />
+</body>
+</html>

Added: addins/bookmark/release/2.2/tests/list-tests.html
===================================================================
--- addins/bookmark/release/2.2/tests/list-tests.html	                        (rev 0)
+++ addins/bookmark/release/2.2/tests/list-tests.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,3 @@
+<ul id="testlist">
+    <li>Control/Bookmark.html</li>
+</ul>

Added: addins/bookmark/release/2.2/tests/run-tests.html
===================================================================
--- addins/bookmark/release/2.2/tests/run-tests.html	                        (rev 0)
+++ addins/bookmark/release/2.2/tests/run-tests.html	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,2408 @@
+<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01//EN" "http://www.w3.org/TR/html4/strict.dtd">
+<html><head><title> Run the testsuite</title>
+<noscript>Javascript is disabled in your browser. This page cannot be displayed correctly without Javascript. Sorry. <br/> If you want to view this page, please change your browser settings so that Javascript is enabled.</noscript>
+<!--
+Test.AnotherWay version 0.5
+
+Copyright (c) 2005 Artem Khodush, http://straytree.org
+
+Permission is hereby granted, free of charge, to any person obtaining
+a copy of this software and associated documentation files (the
+"Software"), 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.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+-->
+<style type="text/css">
+* { padding: 0; margin: 0; }
+html { height: 99%; }
+body { height: 98%; font: normal normal 10pt sans-serif }
+#col1 {  float: left; width: 27em; margin: 0 0 0 1em; overflow: visible; }
+#col2 {  position: relative; height: 98%; margin: 0 0.5em 0 28em; }
+#col1_header { margin-top: 0.5em; }
+#scroller { height: 400px; overflow: auto;}
+#testtable { margin: 0 0 2em 0; width: 97%; }
+#run_buttons { margin-bottom: 4em; }
+
+#right_header { padding-top: 0.8em; }
+#results_count { float: left; }
+.active_tab 			{ float: right; padding: 0 1em 0.2em 1em; background: #0af; border: 1px solid #048; border-bottom: none; cursor: pointer; cursor: hand;
+					 position: relative; top: -0.2em; }
+.inactive_tab 			{ float: right; padding: 0 1em 0 1em; background: #9bb; color: #444; border: 1px solid #9bb; border-bottom: none; cursor: pointer; cursor: hand; }
+.inactive_mouseover_tab 	{ float: right; padding: 0 1em 0 1em; background: #9bb; color: #062; border: 1px solid #062; border-bottom: none; cursor: pointer; cursor: hand; }
+
+#right_frame { overflow: auto; position: relative; top: -0.2em; clear: right; height: 95%; border: 1px solid #048; }
+
+#debug { display: none; }
+#debug p { margin: 2px 0 0 5em; text-indent: -4.8em; }
+
+#error { display: none; color: #c22; }
+
+#results p { margin: 0 0 2px 0; }
+/* cursor indicating that detailed results may be expanded/contracted */
+#results p.badtest { cursor: text; }
+#results p.ok, #results p.fail { cursor: pointer; cursor: hand; }
+
+/* colored squares in the results window at the left of test page names */
+#results p.ok .bullet { background: #6d6; }
+#results p.fail .bullet { background:  #d46; }
+#results p.badtest .bullet { background: #ea3; }
+#results p.loading .bullet { background: #48f; }
+#results p.running .bullet { background: #26e; }
+#results p.waiting .bullet { background: #04d; }
+/* highlight in the results line */
+#results p .warning { background: #ffc; }
+
+/* layout of the detailed results */
+.result_detail { padding-left: 3em; }
+.result_exception_detail { padding-left: 4em; }
+.result_exception_stack_detail { padding-left: 5em;  }
+.result_micro_detail { padding-left: 6em; }
+/* colouring in the detailed results */
+.result_detail .fail, .result_exception_detail .fail,  .result_micro_detail .fail { background: #ffd8d8; }
+
+/* "start recording" controls*/
+#record_div { margin-top: 3em; }
+#record_div p { margin-bottom: 0.5em; }
+#record_select { width: 88%; }
+#record_input { width: 53%; }
+</style>
+<script type="text/javascript">
+<!--
+if( typeof( Test )=="undefined" ) {
+	Test={};
+}
+Test.AnotherWay={};
+
+Test.AnotherWay._g_test_iframe=null; // frame where to load test pages
+Test.AnotherWay._g_test_frame_no_clear=false; // true - leave last page displayed after tests end
+Test.AnotherWay._g_test_page_urls=[]; // array of: { url: url, convention: "anotherway" or "jsan" }
+Test.AnotherWay._g_test_object_for_jsan=null; // test object for filling by tests that adhere to jsan Test.Simple calling convention
+Test.AnotherWay._g_pages_to_run=null; // list of pages to run automatically after loading
+Test.AnotherWay._g_run_on_main_load=false; // special handling for run_pages_to_run when it might be called before onload or before list of test pages is known.
+Test.AnotherWay._g_run_on_list_load=false;
+Test.AnotherWay._g_main_loaded=false;
+
+Test.AnotherWay._run_pages_to_run=function( called_from_outside )
+{
+	if( !Test.AnotherWay._g_main_loaded ) {
+		Test.AnotherWay._g_run_on_main_load=true;
+	}else {
+		var a_pages=Test.AnotherWay._g_pages_to_run;
+		if( a_pages=="all" ) {
+			for( var i=0; i<Test.AnotherWay._g_test_page_urls.length; ++i ) {
+				Test.AnotherWay._run_test_page( "test"+i );
+			}
+		}else if( a_pages!=null ) {
+			for( var run_i=0; run_i<a_pages.length; ++run_i ) {
+				var run_page=a_pages[run_i];
+				var found=false;
+				for( var all_i=0; all_i<Test.AnotherWay._g_test_page_urls.length; ++all_i ) {
+					if( run_page==Test.AnotherWay._g_test_page_urls[all_i].url ) {
+						Test.AnotherWay._run_test_page( "test"+all_i, called_from_outside );
+						found=true;
+						break;
+					}
+				}
+				if( !found ) {
+					Test.AnotherWay._show_error( "page specified to run is not found in the page list: "+run_page );
+					break;
+				}
+			}
+		}
+	}
+}
+
+Test.AnotherWay._add_test_page_url=function( test_url, convention )
+{
+	var table=document.getElementById( "testtable" );
+	var record_select=document.getElementById( "record_select" );
+	var index=Test.AnotherWay._g_test_page_urls.length;
+
+	// trim spaces.
+	if( test_url.match( "^(\\s*)(.*\\S)(\\s*)$" ) ) {
+		test_url=RegExp.$2;
+	}
+
+	Test.AnotherWay._g_test_page_urls[index]={ url: test_url, convention: convention };
+	var row=table.insertRow( -1 );
+
+	var cell;
+	var cell_child;
+	cell=row.insertCell( -1 );
+	cell_child=document.createElement( "input" );
+	cell_child.type="checkbox";
+	cell_child.id="checkbox"+index;
+    cell_child.checked='checked';
+    cell_child.defaultChecked='checked';
+	cell.appendChild( cell_child );
+
+	cell=row.insertCell( -1 );
+	cell.setAttribute( "width", "75%" );
+	cell.appendChild( document.createTextNode( test_url ) );
+
+	cell=row.insertCell( -1 );
+	cell_child=document.createElement( "input" );
+	cell_child.type="button";
+	cell_child.id="test"+index;
+	cell_child.value=" run ";
+	cell_child.onclick=Test.AnotherWay._run_one_onclick;
+	cell.appendChild( cell_child );
+
+	cell=row.insertCell( -1 );
+	cell.setAttribute( "width", "8em" );
+	cell_child=document.createElement( "span" );
+	cell.appendChild( cell_child );
+
+	var option=document.createElement( "option" );
+	option.appendChild( document.createTextNode( test_url ) );
+	record_select.appendChild( option );
+}
+Test.AnotherWay._show_error=function( msg )
+{
+	var error_div=document.getElementById( "error" );
+	error_div.innerHTML="";
+	error_div.appendChild( document.createTextNode( msg ) );
+	error_div.style.display="block";
+}
+
+// read urls from the list in the html file inside the list_iframe
+// fill on-screen list with urls and "run" buttons, and fill the g_test_page_urls object.
+Test.AnotherWay._list_iframe_onload=function()
+{
+	if( window.frames.list_iframe!=null && window.frames.list_iframe.location!="" && window.frames.list_iframe.location!="about:blank" ) {
+		var list_doc=window.frames.list_iframe.document;
+		var list=list_doc.getElementById( "testlist" );
+		if( list!=null ) {
+			for( var i=0; i<list.childNodes.length; ++i ) {
+				var item=list.childNodes[i];
+				if( item.nodeName=="LI" || item.nodeName=="li" ) {
+					var convention="anotherway";
+					if( Test.AnotherWay._get_css_class( item )=="jsan" ) {
+						convention="jsan";
+					}
+					Test.AnotherWay._add_test_page_url( item.innerHTML, convention );
+				}
+			}
+			if( Test.AnotherWay._g_run_on_list_load ) {
+				Test.AnotherWay._g_run_on_list_load=false;
+				Test.AnotherWay._run_pages_to_run();
+			}
+		}else {
+			Test.AnotherWay._show_error( "no list with id 'testlist' in a list file "+window.frames.list_iframe.location );
+		}
+	}
+}
+
+Test.AnotherWay._map_checkboxes=function( f )
+{
+	var table=document.getElementById( "testtable" );
+	var checks=table.getElementsByTagName( "INPUT" );
+	for( var i=0; i<checks.length; ++i ) {
+		if( checks[i].type=="checkbox" && checks[i].id.match( /^checkbox(\d+)$/ ) ) {
+			f( checks[i], RegExp.$1 );
+		}
+	}
+}
+Test.AnotherWay._run_all_onclick=function()
+{
+	Test.AnotherWay._map_checkboxes( function( c, id ) { Test.AnotherWay._run_test_page( "test"+id ); } );
+}
+Test.AnotherWay._run_selected_onclick=function()
+{
+	Test.AnotherWay._map_checkboxes( function( c, id ) { if( c.checked ) Test.AnotherWay._run_test_page( "test"+id ); } );
+}
+Test.AnotherWay._unselect_all_onclick=function()
+{
+	Test.AnotherWay._map_checkboxes( function( c, id ) { c.checked=false; } );
+}
+Test.AnotherWay._run_one_onclick=function()
+{
+	Test.AnotherWay._run_test_page( this.id );
+}
+
+// construct an object that will gather results of running one test function
+Test.AnotherWay._test_object_t=function( fun_name )
+{
+	this.name=fun_name; // name of the test function
+	this.n_plan=null; // planned number of assertions
+	this.n_ok=0; // # of ok assertions
+	this.n_fail=0; // # of failed assertions
+	this.exception=""; // if the function throwed an exception, it's its message
+	this.exception_stack=[]; // strings: function call stack from the exception
+	this.assertions=[]; // assertion results: array of { ok: 1 or 0, name: string }
+	this.wait_result_milliseconds=0; // how long to wait before collecting results from the test
+	this.second_wait_msg=null; // <p> status message (in addition to the page wait_msg)
+	this.delay_actions=[]; // array of actions to be perfomed after the test function returns
+				//	action : { acton_kind: "call" | "window" | "replay"
+				//				when "call": 		{ call_fn call_delay_milliseconds } call_fn takes nothing
+				//				when "window" : 	{ wnd_url wnd_wnd wnd_fn wnd_timeout_milliseconds wnd_dont_close } wnd_fn takes wnd
+				//				wnen "replay" : 	{ replay_wnd replay_events replay_event_i replay_checkpoints } checkpoint_fn takes this, wnd
+				//	}
+	this.delay_action_i=null; // index of delay action currently being performed
+	this.delay_prev_timer_time=0;	// for counting time while performing delay_actions
+	this.delay_current_milliseconds_left=0; // time left before the next action, runs down
+	this.delay_total_milliseconds_left=0; 	// for indication: total estimated time for all actions, runs up and down
+}
+
+Test.AnotherWay._test_object_t.prototype.ok=function( cond, name )
+{
+	if( cond ) {
+		++this.n_ok;
+		cond=1;
+	}else {
+		++this.n_fail;
+		cond=0;
+	}
+	this.assertions.push( { ok: cond, name: name } );
+}
+Test.AnotherWay._test_object_t.prototype.fail=function( name )
+{
+	this.ok( false, name );
+}
+Test.AnotherWay._test_object_t.prototype.plan=function( n )
+{
+	this.n_plan=n;
+}
+Test.AnotherWay._test_object_t.prototype.wait_result=function( seconds )
+{
+	this.wait_result_milliseconds=1000*seconds;
+}
+Test.AnotherWay._eq_fail_msg=function( path, what, expected, got )
+{
+	return "eq: "+path+" "+what+" differ: got "+got+", but expected "+expected;
+}
+Test.AnotherWay._array_eq=function( expected, got, path, msg )
+{
+	if( expected.length!=got.length ) {
+		msg.msg=Test.AnotherWay._eq_fail_msg( path, "array length", expected.length, got.length );
+		return false;
+	}
+	for( var i=0; i<expected.length; ++i ) {
+		if( !Test.AnotherWay._thing_eq( expected[i], got[i], path+"["+i+"]", msg ) ) {
+			return false;
+		}
+	}
+	return true;
+}
+Test.AnotherWay._object_eq=function( expected, got, path, msg )
+{
+	var v;
+	for( v in expected ) {
+		if( ! (v in got) ) {
+			msg.msg=Test.AnotherWay._eq_fail_msg( path+"."+v, "properties", expected[v], "undefined" );
+			return false;
+		}
+		if( !Test.AnotherWay._thing_eq( expected[v], got[v], path+"."+v, msg ) ) {
+			return false;
+		}
+	}
+	for( v in got ) {
+		if( ! (v in expected) ) {
+			msg.msg=Test.AnotherWay._eq_fail_msg( path+"."+v, "properties", "undefined", got[v] );
+			return false;
+		}
+	}
+	return true;
+}
+Test.AnotherWay._constructor_name=function( x )
+{
+	if( x==null ) {
+		return "";
+	}
+	var s="unknown";
+	try {
+		s=typeof( x.constructor );
+		if( s!="unknown" ) {
+			s=x.constructor.toString();
+		}
+	}catch( e ) {
+		s="unknown";
+	}
+	if( s=="unknown" ) {
+		// hackish attempt to guess a type
+		var is_array=true;
+		var index=0;
+		for( i in x ) {
+			if( i!=index ) {
+				is_array=false;
+			}
+			++index;
+		}
+		return is_array ? "Array" : "Object"; // for empty arrays/objects, this will be wrong half the time
+	}else if( s.match( /^\s*function\s+(\w+)\s*\(/ ) ) {
+		return RegExp.$1;
+	}else {
+	  var c = '';
+	  switch(typeof x) {
+	    case 'string':
+	      c = 'String';
+	      break;
+	    case 'object':
+	      c = 'Object';
+	      break;
+	    default:
+	      c = '';
+	  }
+		return c;
+	}
+}
+Test.AnotherWay._is_array=function( x )
+{
+	return Test.AnotherWay._constructor_name( x )=="Array";
+}
+Test.AnotherWay._is_value_type=function( x )
+{
+	cn=Test.AnotherWay._constructor_name( x );
+	return cn=="Number" || cn=="String" || cn=="Boolean" || cn=="Date";
+}
+Test.AnotherWay._thing_eq=function( expected, got, path, msg )
+{
+	if( expected==null && got==null ) {
+		return true;
+	}else if( (expected==null && got!=null) || (expected!=null && got==null) ) {
+		msg.msg=Test.AnotherWay._eq_fail_msg( path, "values", expected, got );
+		return false;
+	}else {
+		var expected_cn=Test.AnotherWay._constructor_name( expected );
+		var got_cn=Test.AnotherWay._constructor_name( got );
+		if( expected_cn!=got_cn ) {
+			msg.msg=Test.AnotherWay._eq_fail_msg( path, "types", expected_cn, got_cn );
+			return false;
+		}else {
+			if( Test.AnotherWay._is_array( expected ) ) {
+				return Test.AnotherWay._array_eq( expected, got, path, msg );
+			}else if( Test.AnotherWay._is_value_type( expected ) ) {
+				if( expected!=got ) {
+					msg.msg=Test.AnotherWay._eq_fail_msg( path, "values", expected, got );
+					return false;
+				}else {
+					return true;
+				}
+			}else { // just a plain object
+				return Test.AnotherWay._object_eq( expected, got, path, msg );
+			}
+		}
+	}
+}
+Test.AnotherWay._test_object_t.prototype.eq=function( got, expected, name )
+{
+	var msg={};
+	if( Test.AnotherWay._thing_eq( expected, got, "", msg ) ) {
+		this.ok( 1, name );
+	}else {
+		this.fail( name+". "+msg.msg );
+	}
+}
+Test.AnotherWay._test_object_t.prototype.like=function( got, expected, name )
+{
+	if( got.match( expected )!=null ) {
+		this.ok( 1, name );
+	}else {
+		this.fail( name+": got "+got+", but expected it to match: "+expected );
+	}
+}
+Test.AnotherWay._g_html_eq_span=null;
+Test.AnotherWay._html_eq_string_to_node=function( string_or_node, what, msg )
+{
+	if( string_or_node.nodeType!=null ) {
+		string_or_node=Test.AnotherWay._html_eq_node_to_string( string_or_node ); // double trip - to make properties assigned in scripts available as html node attributes
+	}
+	if( Test.AnotherWay._g_html_eq_span==null ) {
+		Test.AnotherWay._g_html_eq_span=document.createElement( "span" );
+	}
+	Test.AnotherWay._g_html_eq_span.innerHTML=string_or_node;
+	if( Test.AnotherWay._g_html_eq_span.childNodes.length!=1 ) {
+		msg.msg="bad "+what+" html string given (should contain exactly one outermost element): "+string_or_node;
+	}
+	return Test.AnotherWay._g_html_eq_span.childNodes[0].cloneNode( true );
+}
+Test.AnotherWay._html_eq_node_to_string=function( node ) {
+	if( Test.AnotherWay._g_html_eq_span==null ) {
+		Test.AnotherWay._g_html_eq_span=document.createElement( "span" );
+	}
+	Test.AnotherWay._g_html_eq_span.innerHTML="";
+	if( node.outerHTML!=null ) {
+		Test.AnotherWay._g_html_eq_span.innerHTML=node.outerHTML;
+	}else {
+            var clone = node.cloneNode(true);
+            var node = Test.AnotherWay._g_html_eq_span;
+            if(node.ownerDocument && node.ownerDocument.importNode) {
+                if(node.ownerDocument != clone.ownerDocument) {
+                    clone = node.ownerDocument.importNode(clone, true);
+                }
+            }
+            node.appendChild(clone);
+	}
+	return Test.AnotherWay._g_html_eq_span.innerHTML;
+}
+Test.AnotherWay._html_eq_path_msg=function( path )
+{
+	var msg="";
+	for( var i=0; i<path.length; ++i ) {
+		msg+=" [node "+path[i].node;
+		if( path[i].id!=null && path[i].id!="" ) {
+			msg+=" id "+path[i].id;
+		}else if( path[i].index!=null ) {
+			msg+=" at index "+path[i].index;
+		}
+		msg+="] "
+	}
+	return msg;
+}
+Test.AnotherWay._html_eq_fail_msg=function( path, what, expected, got )
+{
+	return Test.AnotherWay._html_eq_path_msg( path )+": "+what+" differ: got "+got+", but expected "+expected;
+}
+Test.AnotherWay._html_eq_remove_blank=function( text )
+{
+	if( text==null ) {
+		return "";
+	}else if( text.match( "^(\\s*)(.*\\S)(\\s*)$" ) ) {
+		return RegExp.$2;
+	}else if( text.match( "\s*" ) ) {
+		return "";
+	}
+	return text;
+}
+Test.AnotherWay._html_eq_remove_blank_nodes=function( node )
+{
+	var to_remove=[];
+	for( var child=node.firstChild; child!=null; child=child.nextSibling ) {
+		if( child.nodeType==3 ) {
+			var value=Test.AnotherWay._html_eq_remove_blank( child.nodeValue );
+			if( value=="" ) {
+				to_remove.push( child );
+			}else {
+				child.nodeValue=value;
+			}
+		}
+	}
+	for( var i=0; i<to_remove.length; ++i ) {
+		node.removeChild( to_remove[i] );
+	}
+}
+Test.AnotherWay._html_node_type_text=function( node_type )
+{
+	if( node_type==1 ) {
+		return "1 (html element)";
+	}else if( node_type==3 ) {
+		return "3 (text)";
+	}else {
+		return node_type;
+	}
+}
+Test.AnotherWay._html_eq_node=function( expected, got, path, msg, expected_loc_base, got_loc_base )
+{
+	if( expected.nodeType!=got.nodeType ) {
+		msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "node types", Test.AnotherWay._html_node_type_text( expected.nodeType ), Test.AnotherWay._html_node_type_text( got.nodeType ) );
+		return false;
+	}else if( expected.nodeType==3 ) {
+		if( expected.nodeValue!=got.nodeValue ) {
+			msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "text", expected.nodeValue, got.nodeValue );
+			return false;
+		}
+	}else if( expected.nodeType==1 ) {
+		if( expected.nodeName!=got.nodeName ) {
+			msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "node names", expected.nodeName, got.nodeName );
+			return false;
+		}
+		// compare attributes
+		var expected_attrs={};
+		var got_attrs={};
+		var i;
+		var a;
+		for( i=0; i<expected.attributes.length; ++i ) {
+			a=expected.attributes[i];
+			if( a.specified ) {
+				expected_attrs[a.name]=1;
+			}
+		}
+		for( i=0; i<got.attributes.length; ++i ) {
+			a=got.attributes[i];
+			if( a.specified ) {
+				got_attrs[a.name]=1;
+			}
+		}
+		for( a in expected_attrs ) {
+			if( ! (a in got_attrs) ) {
+				msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute sets differ: expected attribute "+a+" is missing";
+				return false;
+			}
+		}
+		for( a in got_attrs ) {
+			if( ! (a in expected_attrs) ) {
+				msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute sets differ: got extra attribute "+a;
+				return false;
+			}
+		}
+		for( a in expected_attrs ) {
+			var expected_value=expected.getAttribute( a );
+			var got_value=got.getAttribute( a );
+			if( typeof( expected_value )=="string" && typeof( got_value )=="string" ) {
+				expected_value=Test.AnotherWay._html_eq_remove_blank( expected_value );
+				got_value=Test.AnotherWay._html_eq_remove_blank( got_value );
+				var ok=expected_value==got_value;
+				if( !ok && (a=="href" || a=="HREF" )  ) { // try relative hrefs
+					var expected_relative_value=expected_value;
+					if( expected_loc_base!=null && expected_value.substring( 0, expected_loc_base.length )==expected_loc_base ) {
+						expected_relative_value=expected_value.substring( expected_loc_base.length );
+					}
+					var got_relative_value=got_value;
+					if( got_loc_base!=null && got_value.substring( 0, got_loc_base.length )==got_loc_base ) {
+						got_relative_value=got_value.substring( got_loc_base.length );
+					}
+					ok=expected_relative_value==got_relative_value;
+				}
+				if( !ok ) {
+					msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "attribute "+a+" values", expected_value, got_value );
+					return false;
+				}
+			}else if( typeof( expected_value )=="function" && typeof( got_value )=="function" ) {
+				expected_value=expected_value.toString();
+				got_value=got_value.toString();
+				if( expected_value!=got_value ) {
+					msg.msg=Test.AnotherWay._html_eq_fail_msg( path, "attribute "+a+" values", expected_value, got_value );
+					return false;
+				}
+			}else {
+				var value_msg={};
+				if( !Test.AnotherWay._thing_eq( expected_value, got_value, "", value_msg ) ) {
+					msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": attribute "+a+" values differ: "+value_msg.msg;
+					return false;
+				}
+			}
+		}
+		// compare child nodes
+		Test.AnotherWay._html_eq_remove_blank_nodes( expected );
+		Test.AnotherWay._html_eq_remove_blank_nodes( got );
+		var expected_length=expected.childNodes.length;
+		var got_length=got.childNodes.length;
+		if( expected_length<got_length ) {
+			msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": got "+(got_length-expected_length)+" extra child nodes";
+			return false;
+		}else if( expected_length>got_length ) {
+			msg.msg=Test.AnotherWay._html_eq_path_msg( path )+": expected "+(expected_length-got_length)+" more child nodes";
+			return false;
+		}else {
+			for( i=0; i<expected_length; ++i ) {
+				var expected_node=expected.childNodes[i];
+				path.push( { node: expected_node.nodeName, id: expected_node.id, index: i } );
+				var eq=Test.AnotherWay._html_eq_node( expected_node, got.childNodes[i], path, msg, expected_loc_base, got_loc_base );
+				path.pop();
+				if( !eq ) {
+					return false;
+				}
+			}
+		}
+	}
+	return true;
+}
+Test.AnotherWay._html_eq_get_loc_base=function( node )
+{
+	var loc_base=document.location;
+	if( node.ownerDocument!=null ) {
+		loc_base=node.ownerDocument.location;
+	}
+	if( loc_base!=null ) {
+		loc_base=loc_base.href;
+		var slash_pos=loc_base.lastIndexOf( "/" );
+		if( slash_pos!=-1 ) {
+			loc_base=loc_base.substring( 0, slash_pos+1 );
+		}
+	}
+	return loc_base;
+}
+Test.AnotherWay._test_object_t.prototype.html_eq=function( got, expected, name )
+{
+	var msg={};
+	var expected_node=Test.AnotherWay._html_eq_string_to_node( expected, "expected", msg );
+	if( msg.msg!=null ) {
+		this.fail( name+" html_eq: "+msg.msg );
+	}else {
+		var got_node=Test.AnotherWay._html_eq_string_to_node( got, "got", msg );
+		if( msg.msg!=null ) {
+			this.fail( name+" html_eq: "+msg.msg );
+		}else {
+			var expected_loc_base=Test.AnotherWay._html_eq_get_loc_base( expected );
+			var got_loc_base=Test.AnotherWay._html_eq_get_loc_base( got );
+			if( Test.AnotherWay._html_eq_node( expected_node, got_node, [], msg, expected_loc_base, got_loc_base ) ) {
+				this.ok( 1, name );
+			}else {
+				var msg=name+" html_eq "+msg.msg;
+				var expected_str=Test.AnotherWay._html_eq_node_to_string( expected_node );
+				var got_str=Test.AnotherWay._html_eq_node_to_string( got_node );
+				msg+=".\n got html: "+got_str;
+				msg+=".\n expected html: "+expected_str;
+				this.fail( msg );
+			}
+		}
+	}
+}
+Test.AnotherWay._debug_pane_print=function( msg )
+{
+	var d=new Date();
+	var p=document.createElement( "p" );
+	p.appendChild( document.createTextNode( d.toLocaleTimeString()+" "+msg ) );
+	var debug_pane=document.getElementById( "debug" );
+	debug_pane.appendChild( p );
+	var debug_tab=document.getElementById( "debug_tab" );
+	var results_tab=document.getElementById( "results_tab" );
+	debug_tab.style.visibility="visible";
+	results_tab.style.visibility="visible";
+}
+Test.AnotherWay._test_object_t.prototype.debug_print=function( msg )
+{
+	Test.AnotherWay._debug_pane_print( this.name+": "+msg );
+}
+Test.AnotherWay._test_object_t.prototype.delay_call=function()
+{
+	var timeout_ms=200;
+	for( var i=0; i<arguments.length; ++i ) {
+		if( typeof( arguments[i] )!="function" ) {
+			timeout_ms=1000*arguments[i];
+		}else {
+			var action={ action_kind: "call", call_delay_milliseconds: timeout_ms, call_fn: arguments[i] };
+			this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+			this.delay_actions.push( action );
+		}
+	}
+}
+Test.AnotherWay._test_object_t.prototype.open_window=function( url, fn, timeout_seconds )
+{
+	if( timeout_seconds==null ) {
+		timeout_seconds=4;
+	}
+	var no_close=document.getElementById( "dont_close_test_windows" );
+	var action={ action_kind: "window", wnd_url: url.toString(), wnd_wnd: null, wnd_fn: fn, wnd_timeout_milliseconds: timeout_seconds*1000, wnd_no_close: no_close.checked };
+	this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+	this.delay_actions.push( action );
+}
+Test.AnotherWay._test_object_t.prototype.replay_events=function( wnd, events )
+{
+	if( Test.AnotherWay._g_no_record_msg!=null ) {
+		this.fail( "replay_events: "+Test.AnotherWay._g_no_record_msg );
+	}else {
+		var action={ action_kind: "replay", replay_wnd: wnd, replay_events: events.events, replay_event_i: null, replay_checkpoints: events.checkpoints };
+		this.delay_total_milliseconds_left+=Test.AnotherWay._action_estimate_milliseconds( action );
+		this.delay_actions.push( action );
+	}
+}
+Test.AnotherWay._action_estimate_milliseconds=function( action )
+{
+	var ms=0;
+	if( action.action_kind=="call" ) {
+		ms=action.call_delay_milliseconds;
+	}else if( action.action_kind=="window" ) {
+		ms=0;
+	}else if( action.action_kind=="replay" ) {
+		ms=0;
+		for( var i=0; i<action.replay_events.length; ++i ) {
+			ms+=action.replay_events[i]["time"]-0;
+		}
+	}
+	return ms;
+}
+
+Test.AnotherWay._g_timeout_granularity=200;
+Test.AnotherWay._g_tests_queue=[]; // vector of { url: string, test_objects : array of test_object_t, test_object_i: int, wait_msg: <p> object, loading_timeout_milliseconds: int, timeout_id: id }
+
+// load one html page, schedule further processing
+Test.AnotherWay._run_test_page=function( id, called_from_outside )
+{
+	if( id.match( /^test(\d+)/ ) ) {
+		id=RegExp.$1;
+		Test.AnotherWay._g_tests_queue.push( {
+			url: Test.AnotherWay._g_test_page_urls[id].url,
+			convention: Test.AnotherWay._g_test_page_urls[id].convention,
+			test_objects: []
+			} );
+		if( Test.AnotherWay._g_tests_queue.length==1 ) {
+			if( !called_from_outside ) {
+				// Crap. Be careful stepping around.
+				// For Mozilla and Opera, when this file is included into the frameset page that is in another directory (and _g_outside_path_correction!=null)
+				// but the test pages are started from within it (by "run" buttons), then:
+				// depending on whether the page is the first one loaded into the test frame or not,
+				// the base url for relative test pages differs.
+				// Crap, like I said.
+				Test.AnotherWay._g_tests_queue[0].suppress_outside_path_correction=true;
+			}
+			Test.AnotherWay._start_loading_page();
+		}
+	}
+}
+Test.AnotherWay._load_next_page=function()
+{
+	Test.AnotherWay._g_tests_queue.splice( 0, 1 );
+	if( Test.AnotherWay._g_tests_queue.length>0 ) {
+		Test.AnotherWay._start_loading_page();
+	}else {
+		if( !Test.AnotherWay._g_test_frame_no_clear ) {
+			Test.AnotherWay._g_test_iframe.location.replace( "about:blank" );
+		}
+	}
+}
+Test.AnotherWay._g_opera_path_correction=null; // ugly wart to support opera
+Test.AnotherWay._g_outside_path_correction=null; // ugly wart to accomodate Opera and Mozilla, where relative url relates to the directory where the page that calls this function is located
+Test.AnotherWay._set_iframe_location=function( iframe, loc, outside_path_correction )
+{
+	// allow to load only locations with the same origin
+	var proto_end=loc.indexOf( "://" );
+	if( proto_end!=-1 ) { // otherwise, it's safe to assume (for Opera, Mozilla and IE ) that loc will be treated as relative
+		var main_loc=window.location.href;
+		var host_end=loc.substring( proto_end+3 ).indexOf( "/" );
+		var ok=false;
+		if( host_end!=-1 ) {
+			var loc_origin=loc.substring( 0, proto_end+3+host_end+1 );
+			if( main_loc.length>=loc_origin.length && main_loc.substring( 0, loc_origin.length )==loc_origin ) {
+				ok=true;
+			}
+		}
+		if( !ok ) {
+			return { msg: "test pages may have only urls with the same origin as "+main_loc };
+		}
+	}
+	// opera cannot handle urls relative to file:// without assistance
+	if( window.opera!=null && window.location.protocol=="file:" && loc.indexOf( ":" )==-1 ) {
+		var base=window.location.href;
+		var q_pos=base.indexOf( "?" );
+		if( q_pos!=-1 ) {
+			base=base.substring( 0, q_pos );
+		}
+		var slash_pos=base.lastIndexOf( "/" );
+		if( slash_pos!=-1 ) {
+			base=base.substring( 0, slash_pos+1 );
+			Test.AnotherWay._g_opera_path_correction=base;
+			loc=base+loc;
+		}
+	}
+	// if this function is called from another page, and if that page is in another directory, correction is needed
+	if( outside_path_correction!=null ) {
+		var pos=loc.indexOf( outside_path_correction );
+		if( pos==0 ) {
+			loc=loc.substring( outside_path_correction.length+1 );
+		}
+	}
+	if( iframe.location!=null ) {
+		iframe.location.replace( loc );
+	}else {
+		iframe.src=loc;
+	}
+	return {};
+}
+Test.AnotherWay._start_loading_page=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	test_page.loading_timeout_milliseconds=12000;
+	test_page.timeout_id=setTimeout( Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity );
+	test_page.wait_msg=Test.AnotherWay._print_counter_result( test_page.url, "loading...", test_page.loading_timeout_milliseconds, "loading" );
+	if( test_page.convention=="jsan" ) {
+		// the tests in that page will run when it's loading, so the test object must be ready
+		Test.AnotherWay._g_test_object_for_jsan=new Test.AnotherWay._test_object_t( test_page.url );
+	}
+	var outside_path_correction=null;
+	if( Test.AnotherWay._g_outside_path_correction!=null && !test_page.suppress_outside_path_correction ) {
+		outside_path_correction=Test.AnotherWay._g_outside_path_correction;
+	}
+	var result=Test.AnotherWay._set_iframe_location( Test.AnotherWay._g_test_iframe, test_page.url, outside_path_correction );
+	if( result.msg!=null ) {
+		Test.AnotherWay._unprint_result( test_page.wait_msg );
+		Test.AnotherWay._print_result( test_page.url, result.msg, "badtest", null );
+		Test.AnotherWay._load_next_page();
+	}
+}
+
+Test.AnotherWay._loading_timeout=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	test_page.loading_timeout_milliseconds-=Test.AnotherWay._g_timeout_granularity;
+	if( test_page.loading_timeout_milliseconds>0 ) {
+		Test.AnotherWay._update_msg_counter( test_page.wait_msg, (test_page.loading_timeout_milliseconds/1000).toFixed() );
+		test_page.timeout_id=setTimeout( Test.AnotherWay._loading_timeout, Test.AnotherWay._g_timeout_granularity );
+	}else {
+		Test.AnotherWay._unprint_result( test_page.wait_msg );
+		Test.AnotherWay._print_result( test_page.url, "Unable to load test page. Timeout expired", "badtest", null );
+		Test.AnotherWay._load_next_page();
+	}
+}
+
+Test.AnotherWay._strip_query_and_hash=function( s )
+{
+	var i=s.lastIndexOf( "#" );
+	if( i!=-1 ) {
+		s=s.substring( 0, i );
+	}
+	i=s.lastIndexOf( "?" );
+	if( i!=-1 ) {
+		s=s.substring( 0, i );
+	}
+	return s;
+}
+Test.AnotherWay._is_url_loaded=function( url, wnd )
+{
+	var loaded=false;
+	if( wnd!=null && wnd.location!=null ) {
+		// after some popup blocker interference, location may behave strange..
+		var location_s="";
+		location_s+=wnd.location;
+		if( location_s!="" ) {
+			var pathname=wnd.location.pathname;
+			var expected_url=url;
+			var i=expected_url.lastIndexOf( "#" );
+			if( i!=-1 ) {
+				expected_url=expected_url.substring( 0, i );
+			}
+			i=expected_url.lastIndexOf( "?" );
+			if( i!=-1 ) {
+				expected_url=expected_url.substring( 0, i );
+			}
+			i=expected_url.lastIndexOf( "/" );
+			if( i!=-1 && i!=expected_url.length-1 ) {
+				expected_url=expected_url.substring( i+1 );
+			}
+			i=pathname.indexOf( expected_url )
+			if( wnd.location.href==url || (i!=-1 && i==pathname.length-expected_url.length) ) {
+				if( /*window.opera==null*/wnd.document.readyState==null || wnd.document.readyState=="complete" ) { // for opera (and IE?), getElementById does not work until..
+					loaded=true;
+				}
+			}
+		}
+	}
+	return loaded;
+}
+// find and run all test functions in the g_cur_page html page.
+Test.AnotherWay._test_page_onload=function()
+{
+	if( Test.AnotherWay._g_tests_queue.length==0 ) {
+		return;
+	}
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	if( !Test.AnotherWay._is_url_loaded( test_page.url, Test.AnotherWay._g_test_iframe ) ) {
+		return;
+	}
+	clearTimeout( test_page.timeout_id );
+	Test.AnotherWay._unprint_result( test_page.wait_msg );
+
+	if( test_page.convention=="anotherway" ) {
+		// get test function names (those beginning with "test")
+		if( typeof( Test.AnotherWay._g_test_iframe.document.scripts )!='undefined' ) { // IE
+			for( var i=0; i<Test.AnotherWay._g_test_iframe.document.scripts.length; ++i ) {
+				var script_text=Test.AnotherWay._g_test_iframe.document.scripts[i].text;
+				var fun_sig="function test";
+				var fun_start=script_text.indexOf( fun_sig );
+
+				while( fun_start!=-1 ) {
+					script_text=script_text.substring( fun_start, script_text.length );
+					var fun_end=script_text.indexOf( '(' );
+					var fun_name=script_text.substring( "function ".length,  fun_end );
+                                        var whitespace = fun_name.indexOf( ' ' );
+                                        if (whitespace >= 0)
+                                            fun_name = fun_name.substring( 0, whitespace );
+					test_page.test_objects.push( new Test.AnotherWay._test_object_t( fun_name ) );
+					script_text=script_text.substring( fun_end, script_text.length );
+					fun_start=script_text.indexOf( fun_sig  );
+				}
+			}
+		}else { // otherwise (not IE) it ought to work like this
+			for( var i in Test.AnotherWay._g_test_iframe) {
+				// Hack to prevent failure in FF3.0b1
+                if (i == "innerWidth" || i == "innerHeight") { continue; }
+                if( typeof( Test.AnotherWay._g_test_iframe[i] )=='function' ) {
+					if( i.substring( 0, 4 )=="test" ) {
+						test_page.test_objects.push( new Test.AnotherWay._test_object_t( i ) );
+					}
+				}
+			}
+		}
+	}else if( test_page.convention=="jsan" ) {
+		// the test object is already filled with results
+		test_page.test_objects.push( Test.AnotherWay._g_test_object_for_jsan );
+	}
+
+	if( test_page.test_objects.length==0 ) {
+		Test.AnotherWay._print_result( test_page.url,  "No test functions defined in the page", "badtest", null );
+		Test.AnotherWay._load_next_page();
+		return;
+	}
+
+	test_page.wait_msg=Test.AnotherWay._print_result( test_page.url, "running tests..<span class=\"counter\">"+test_page.test_objects.length+"</span>", "running", null );
+
+	test_page.test_object_i=0;
+	Test.AnotherWay._run_more_tests();
+}
+
+Test.AnotherWay._handle_exception=function( o, e, title )
+{
+	var s=title+": "+typeof( e )+": ";
+	if( e.message!=null ) {
+		s+=e.message;
+	}else if( e.description!=null ) {
+		s+=e.description;
+	}else {
+		s+=e.toString();
+	}
+//	if( e.location!=null ) {  // XXX figure out how to display exception location if it's present (like in mozilla)
+//		s+=" location: "+e.location.toString();
+//	}
+	o.exception=s;
+	s=[];
+	if( e.stack ) {
+		var lines=e.stack.split( "\n" );
+		for( var i=0; i<lines.length; ++i ) {
+			// format of the line: func_name(args)@file_name:line_no
+			if( lines[i].match( /(\w*)\(([^\)]*)\)@(.*):([^:]*)$/ ) ) {
+				var func_name=RegExp.$1;
+				if( func_name.length==0 ) {
+					func_name="<anonymous>";
+				}
+				s.push( "in "+func_name+"( "+RegExp.$2+") at "+RegExp.$3+" line "+RegExp.$4+"\n" );
+			}
+		}
+	}
+	o.exception_stack=s;
+}
+
+Test.AnotherWay._run_more_tests=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	while( test_page.test_object_i<test_page.test_objects.length ) {
+		Test.AnotherWay._update_msg_counter( test_page.wait_msg, (1+test_page.test_object_i)+"/"+test_page.test_objects.length );
+		var o=test_page.test_objects[test_page.test_object_i];
+		if( test_page.convention=="anotherway" ) {
+			try {
+				Test.AnotherWay._g_test_iframe[o.name]( o );
+			}catch( e ) {
+				Test.AnotherWay._handle_exception( o, e, "" );
+			}
+		} // for "jsan" convention, test has run already
+		if( o.delay_actions.length>0 || o.wait_result_milliseconds>0 ) {
+			o.delay_total_milliseconds_left+=o.wait_result_milliseconds;
+			Test.AnotherWay._delay_actions_timeout();
+			return;
+		}
+		++test_page.test_object_i;
+	}
+	Test.AnotherWay._unprint_result( test_page.wait_msg );
+	Test.AnotherWay._print_result( test_page.url, null, null, test_page.test_objects );
+	Test.AnotherWay._load_next_page();
+}
+
+Test.AnotherWay._delay_actions_timeout=function()
+{
+	var test_page=Test.AnotherWay._g_tests_queue[0];
+	var test_object=test_page.test_objects[test_page.test_object_i];
+	var finished=true;
+	if( test_object.delay_action_i==null ) {
+		// set up to start first action
+		test_object.delay_action_i=-1;
+	}else {
+		// perform current action
+		var milliseconds_passed=(new Date()).getTime()-test_object.delay_prev_timer_time;
+		test_object.delay_current_milliseconds_left-=milliseconds_passed;
+		test_object.delay_total_milliseconds_left-=milliseconds_passed;
+		finished=Test.AnotherWay._delay_continue_action( test_object, milliseconds_passed );
+	}
+	while( finished && test_object.delay_action_i<test_object.delay_actions.length ) {
+		++test_object.delay_action_i; // start next action
+		finished=Test.AnotherWay._delay_start_action( test_object );
+	}
+	if( test_object.delay_action_i<=test_object.delay_actions.length ) { // any more actions left ?
+		test_object.delay_prev_timer_time=(new Date()).getTime();
+		var next_timeout=Test.AnotherWay._g_timeout_granularity;
+		if( test_object.delay_current_milliseconds_left<next_timeout ) {
+			next_timeout=test_object.delay_current_milliseconds_left;
+		}
+		if( test_object.second_wait_msg!=null ) {
+			Test.AnotherWay._update_msg_counter( test_object.second_wait_msg, (test_object.delay_total_milliseconds_left/1000).toFixed() );
+		}
+		setTimeout( Test.AnotherWay._delay_actions_timeout, next_timeout );
+	}else { // no more actions left. run the next test.
+		if( test_object.second_wait_msg!=null ) {
+			Test.AnotherWay._unprint_result( test_object.second_wait_msg );
+			test_object.second_wait_msg=null;
+		}
+		++test_page.test_object_i;
+		Test.AnotherWay._run_more_tests();
+	}
+}
+Test.AnotherWay._delay_start_action=function( test_object )
+{
+	var finished=false;
+	var wait_msg="";
+	if( test_object.delay_action_i==test_object.delay_actions.length ) {
+		if( test_object.wait_result_milliseconds>0 ) {
+			test_object.delay_current_milliseconds_left=test_object.wait_result_milliseconds; // wait for result
+			wait_msg="waiting for results..";
+		}else {
+			++test_object.delay_action_i; // dont wait for result
+		}
+	}else {
+		var action=test_object.delay_actions[test_object.delay_action_i];
+		if( action.action_kind=="call" ) {
+			test_object.delay_current_milliseconds_left=action.call_delay_milliseconds;
+			wait_msg="performing delayed calls..";
+		}else if( action.action_kind=="window" ) {
+			if( Test.AnotherWay._g_opera_path_correction!=null && action.wnd_url.indexOf( ":" )==-1 ) {
+				action.wnd_url=Test.AnotherWay._g_opera_path_correction+action.wnd_url;
+			}
+			action.wnd_wnd=window.open( action.wnd_url, "_blank" );
+			if( action.wnd_wnd==null ) {
+				finished=true;
+				test_object.fail( "unable to open window for "+action.wnd_url );
+			}else {
+				test_object.delay_current_milliseconds_left=action.wnd_timeout_milliseconds;
+				wait_msg="opening window..";
+			}
+		}else if( action.action_kind=="replay" ) {
+			if( action.replay_events.length==0 ) {
+				finished=true;
+			}else {
+				action.replay_event_i=0;
+				test_object.delay_current_milliseconds_left=action.replay_events[0]["time"];
+				wait_msg="replaying events..";
+			}
+		}
+	}
+	if( test_object.second_wait_msg!=null ) {
+		Test.AnotherWay._unprint_result( test_object.second_wait_msg );
+	}
+	if( wait_msg!="" ) {
+		var test_page=Test.AnotherWay._g_tests_queue[0];
+		test_object.second_wait_msg=Test.AnotherWay._print_counter_result( test_page.url, wait_msg, test_object.delay_total_milliseconds_left, "waiting" );
+	}else {
+		test_object.second_wait_msg=null;
+	}
+	return finished;
+}
+Test.AnotherWay._delay_continue_action=function( test_object, milliseconds_passed )
+{
+	var finished=test_object.delay_current_milliseconds_left<=0;
+	if( test_object.delay_action_i==test_object.delay_actions.length ) { // action is "waiting for results"
+		if( test_object.n_plan!=null && test_object.n_plan==test_object.n_ok+test_object.n_fail ) {
+			finished=true; // if all assertions results are recorded, don't wait any more
+		}
+		if( finished ) {
+			++test_object.delay_action_i; // move on to the next test
+		}
+	}else {
+		var action=test_object.delay_actions[test_object.delay_action_i];
+		if( action.action_kind=="call" ) {
+			if( finished ) {
+				try {
+					action.call_fn();
+				}catch( e ) {
+					Test.AnotherWay._handle_exception( test_object, e, "in delay_call" );
+				}
+			}
+		}else if( action.action_kind=="window" ) {
+			test_object.delay_total_milliseconds_left+=milliseconds_passed; // for "window", the countdown is suspended since it's unknown how long it will take
+			if( Test.AnotherWay._is_url_loaded( action.wnd_url, action.wnd_wnd ) ) {
+				try {
+					action.wnd_fn( action.wnd_wnd );
+				}catch( e ) {
+					Test.AnotherWay._handle_exception( test_object, e, "in open_window function call" );
+				}
+				finished=true;
+			}else if( finished ) {
+				test_object.fail(  "unable to open window for url '"+action.wnd_url+"'. timeout expired" );
+			}
+		}else if( action.action_kind=="replay" ) {
+			if( finished ) {
+//				try {
+					Test.AnotherWay._delay_replay_event( test_object, action.replay_wnd, action.replay_events[action.replay_event_i], action.replay_checkpoints );
+//				}catch( e ) { // disabled, until I know how to gel location info from an exception
+//					Test.AnotherWay._handle_exception( test_object, e, "while replaying event" );
+//				}
+				++action.replay_event_i;
+				finished=action.replay_event_i==action.replay_events.length;
+				if( !finished ) {
+					test_object.delay_current_milliseconds_left=action.replay_events[action.replay_event_i]["time"];
+				}
+			}
+		}
+	}
+	return finished;
+}
+Test.AnotherWay._delay_replay_event=function( test_object, wnd, event, checkpoints )
+{
+	if( event.type=="_checkpoint" ) {
+		var checkpoint_n=event.which;
+		var prev_n_fail=test_object.n_fail;
+		checkpoints[checkpoint_n]( test_object, wnd );
+		var flash_color= prev_n_fail==test_object.n_fail ? "#2f2" : "#f22" ;
+		Test.AnotherWay._record_flash_border( flash_color );
+	}else if( event.type=="click" || event.type=="mouseover" || event.type=="mouseout" || event.type=="mousemove" || event.type=="mousedown" || event.type=="mouseup" ) {
+		var target=Test.AnotherWay._record_node_path_to_node( event["target"], wnd.document );
+		if( target!=null ) {
+			Test.AnotherWay._record_control_update_highlight( target, "ball", event );
+			var e=wnd.document.createEvent( "MouseEvents" );
+			var related_target=Test.AnotherWay._record_node_path_to_node( event["relatedTarget"], wnd.document );
+			e.initMouseEvent(
+				event["type"],
+				event["cancelable"],
+				event["bubbles"],
+				wnd.document.defaultView,
+				event["detail"],
+				event["screenX"],
+				event["screenY"],
+				event["clientX"],
+				event["clientY"],
+				event["ctrlKey"],
+				event["altKey"],
+				event["shiftKey"],
+				event["metaKey"],
+				event["button"],
+				Test.AnotherWay._record_node_path_to_node( event["relatedTarget"], wnd.document )
+			);
+			// Firefox 1.0.6 somehow loses relatedTarget somewhere on the way. Pass through our own, for those who choose to care.
+			e.passThroughRelatedTarget=related_target;
+			target.dispatchEvent( e );
+		}
+	}else if( event.type=="keyup" || event.type=="keydown" || event.type=="keypress" ) {
+		var e=wnd.document.createEvent( "KeyboardEvents" ); // forget it. Apparently it's not supported neither by mozilla nor by opera.
+		e.initKeyboardEvent(
+				event["type"],
+				event["cancelable"],
+				event["bubbles"],
+				wnd.document.defaultView,
+				event["which"],
+				event["which"],
+				event["ctrlKey"],
+				event["altKey"],
+				event["shiftKey"],
+				event["metaKey"],
+				false
+		);
+		wnd.document.dispatchEvent( e );
+	}
+}
+
+Test.AnotherWay._print_counter_result=function( url, msg, milliseconds, style )
+{
+	return Test.AnotherWay._print_result( url, msg+"<span class=\"counter\">"+(milliseconds/1000).toFixed()+"</span>", style, null );
+}
+
+Test.AnotherWay._g_result_count=0; // for assigning unique ids to result paragraphs
+
+// number of pages tested
+Test.AnotherWay._g_ok_pages=0;
+Test.AnotherWay._g_fail_pages=0;
+
+Test.AnotherWay._print_result=function( url, msg, style, test_objects )
+{
+	var results=document.getElementById( "results" );
+	var r=results.appendChild( document.createElement( "p" ) );
+	r.id="result"+Test.AnotherWay._g_result_count;
+	++Test.AnotherWay._g_result_count;
+	r.onclick=Test.AnotherWay._toggle_detail;
+	var text="<span class=\"bullet\">&nbsp;&nbsp;&nbsp;</span>&nbsp;";
+	if( url!="" ) {
+		text+=url+":  ";
+	}
+	if( msg!=null ) {
+		text+=msg;
+	}
+	if( test_objects!=null ) {
+		// compose summary and detail texts
+		var total_ok=0;
+		var total_detail_ok=0;
+		var total_fail=0;
+		var total_detail_fail=0;
+		var no_plan=0;
+
+		var detail=results.appendChild( document.createElement( "div" ) );
+
+		if( r.id.match( /^result(\d+)$/ ) ) {
+			detail.id="result_detail"+RegExp.$1;
+		}
+
+		for( var i=0; i<test_objects.length; ++i ) {
+			var o=test_objects[i];
+			var p;
+			var p_text;
+			p=document.createElement( "P" );
+			Test.AnotherWay._set_css_class( p, "result_detail" );
+			p_text=o.name;
+			if( o.n_fail>0 || o.exception || (o.n_plan!=null && o.n_plan!=o.n_ok+o.n_fail) || (o.n_plan==null && o.n_ok==0 && o.n_fail==0)) {
+				++total_fail;
+				p_text+=" <span class=\"fail\">";
+				if( o.n_plan!=null && o.n_plan!=o.n_ok+o.n_fail) {
+					p_text+="planned "+o.n_plan+" assertions but got "+(o.n_ok+o.n_fail)+"; ";
+				}
+				if(o.n_plan==null && o.n_ok==0 && o.n_fail==0) {
+					p_text+="test did not output anything";
+				}else {
+					p_text+=" fail "+o.n_fail;
+				}
+				p_text+="</span>";
+			}else {
+				++total_ok;
+			}
+			p_text+=" ok "+o.n_ok;
+			if( o.n_plan==null ) {
+				no_plan=1;
+				p_text+=" <span class=\"warning\">no plan</span>";
+			}
+			p.innerHTML=p_text;
+			detail.appendChild( p );
+			if( o.exception ) {
+				p=document.createElement( "P" );
+				Test.AnotherWay._set_css_class( p, "result_exception_detail" );
+				p.innerHTML="<span class=\"fail\">exception:</span> "+o.exception;
+				detail.appendChild( p );
+				p=document.createElement( "P" );
+				Test.AnotherWay._set_css_class( p, "result_exception_stack_detail" );
+				p.innerHTML=o.exception_stack.join( "<br/>" );
+				detail.appendChild( p );
+			}
+			for( var ii=0; ii<o.assertions.length; ++ii ) {
+				var oo=o.assertions[ii];
+				var status=oo.ok ? "ok" : "<span class=\"fail\">fail</span>";
+				p=document.createElement( "P" );
+				Test.AnotherWay._set_css_class( p, "result_micro_detail" );
+				p.innerHTML=status;
+				p.appendChild( document.createTextNode( " "+oo.name ) );
+				detail.appendChild( p );
+			}
+			total_detail_ok+=o.n_ok;
+			total_detail_fail+=o.n_fail;
+		}
+		if( total_fail || total_detail_fail ) {
+			text+=" fail "+total_fail;
+		}
+		text+=" ok "+total_ok+" (detailed:";
+		if( total_fail || total_detail_fail ) {
+			text+=" fail "+total_detail_fail;
+		}
+		text+=" ok "+total_detail_ok+")";
+		if( no_plan ) {
+			text+=" <span class=\"warning\">no plan</span>";
+		}
+		style= total_fail==0 ? "ok" : "fail";
+		detail.style.display= style=="fail" ? "block" : "none";
+		detail.style.cursor="text";
+	}
+	if( style!=null ) {
+		Test.AnotherWay._set_css_class( r, style );
+		if( style=="ok" ) {
+			++Test.AnotherWay._g_ok_pages;
+		}else if( style=="fail" || style=="badtest" ) {
+			++Test.AnotherWay._g_fail_pages;
+		}
+		var pages_total="";
+		if( Test.AnotherWay._g_fail_pages>0 ) {
+			pages_total+=" fail "+Test.AnotherWay._g_fail_pages;
+		}
+		pages_total+=" ok "+Test.AnotherWay._g_ok_pages;
+		Test.AnotherWay._update_results_total( pages_total );
+	}
+	r.innerHTML=text;
+	if( results.scrollHeight!=null && results.scrollTop!=null && results.offsetHeight!=null ) {
+		results.scrollTop=results.scrollHeight-results.offsetHeight;
+	}
+	// when test_objects is not null, the results are final - good time to clean up
+	if( test_objects!=null ) {
+		for( var i=0; i<test_objects.length; ++i ) {
+			var actions=test_objects[i].delay_actions;
+			for( var action_i=0; action_i<actions.length; ++action_i ) {
+				var action=actions[action_i];
+				if( action.action_kind=="window" && action.wnd_wnd!=null && !action.wnd_no_close ) {
+					action.wnd_wnd.close();
+					action.wnd_wnd=null;
+				}
+			}
+		}
+	}
+	return r;
+}
+Test.AnotherWay._unprint_result=function( child )
+{
+	var results=document.getElementById( "results" );
+	results.removeChild( child );
+}
+Test.AnotherWay._toggle_detail=function()
+{
+	if( this.id.match( /^result(\d+)$/ ) ) {
+		var detail=document.getElementById( "result_detail"+RegExp.$1 );
+		if( detail!=null ) {
+			if( detail.style.display=="none" ) {
+				detail.style.display="block";
+			}else if( detail.style.display=="block" ) {
+				detail.style.display="none";
+			}
+		}
+	}
+}
+Test.AnotherWay._update_msg_counter=function( msg, text )
+{
+	for( var i=0; i<msg.childNodes.length; ++i ) {
+		var item=msg.childNodes[i];
+		if( item.nodeName=="SPAN" && Test.AnotherWay._get_css_class( item )=="counter" ) {
+			item.innerHTML=text;
+		}
+	}
+}
+Test.AnotherWay._update_results_total=function( msg )
+{
+	var total=document.getElementById( "total" );
+	if( total ) {
+		total.innerHTML=msg;
+	}
+}
+Test.AnotherWay._results_clear_onclick=function()
+{
+	var results=document.getElementById( "results" );
+	results.innerHTML="";
+	Test.AnotherWay._update_results_total( "" );
+	Test.AnotherWay._g_ok_pages=0;
+	Test.AnotherWay._g_fail_pages=0;
+	var debug=document.getElementById( "debug" );
+	debug.innerHTML="";
+}
+
+Test.AnotherWay._get_css_class=function( o )
+{
+	var c=o.getAttribute( "className" );
+	if( c==null || c=="" ) {
+		c=o.getAttribute( "class" );
+	}
+	return c;
+}
+Test.AnotherWay._set_css_class=function( o, css_class )
+{
+	o.setAttribute( "className", css_class );
+	o.setAttribute( "class", css_class );
+}
+
+Test.AnotherWay._tab_onclick=function()
+{
+	var tab=this;
+	var tabs=[ document.getElementById( "debug_tab" ), document.getElementById( "results_tab" ) ];
+	var panes=[ document.getElementById( "debug" ), document.getElementById( "results" ) ];
+	for( var i=0; i<tabs.length; ++i ) {
+		if( tab==tabs[i] ) {
+			Test.AnotherWay._set_css_class( tabs[i], "active_tab" );
+			panes[i].style.display="block";
+		}else {
+			Test.AnotherWay._set_css_class( tabs[i], "inactive_tab" );
+			panes[i].style.display="none";
+		}
+	}
+}
+Test.AnotherWay._tab_mouseover=function()
+{
+	if( Test.AnotherWay._get_css_class( this )=="inactive_tab" ) {
+		Test.AnotherWay._set_css_class( this, "inactive_mouseover_tab" );
+	}
+}
+Test.AnotherWay._tab_mouseout=function()
+{
+	if( Test.AnotherWay._get_css_class( this )=="inactive_mouseover_tab" ) {
+		Test.AnotherWay._set_css_class( this, "inactive_tab" );
+	}
+}
+
+// recording mouse input
+Test.AnotherWay._record_check_onfocus=function()
+{
+	var o=this;
+	var check_select=o.type!="text";
+	var div=document.getElementById( "record_div" );
+	var inputs=div.getElementsByTagName( "input" );
+	for( var i=0; i<inputs.length; ++i ) {
+		var input=inputs[i];
+		if( input.type=="radio" ) {
+			if( input.value=="select" ) {
+				input.checked=check_select;
+			}else if( input.value=="input" ) {
+				input.checked=!check_select;
+			}
+		}
+	}
+}
+
+Test.AnotherWay._g_no_record_msg=null; // not null - recording is unavailable
+Test.AnotherWay._g_record_timeout_cnt=0; // opening window for a page for recording
+Test.AnotherWay._g_record_url=null;
+Test.AnotherWay._g_record_wnd=null;
+Test.AnotherWay._g_record_random_id=null; // added to element ids of record_control div so that they do not clash with ids already in the page for which input is recorded
+Test.AnotherWay._g_record_keydown=null; // recording control - which key is down
+Test.AnotherWay._g_record_ctrl_keydown=false;
+Test.AnotherWay._g_record_shift_keydown=false;
+Test.AnotherWay._g_record_control_visible=true; // recording control ui state
+Test.AnotherWay._g_record_started;
+Test.AnotherWay._g_record_paused;
+Test.AnotherWay._g_record_include_mousemove=false;
+Test.AnotherWay._g_record_start_time; // for time references
+Test.AnotherWay._g_record_pause_start_time;
+Test.AnotherWay._g_record_update_time_interval; // showing time in the control ui
+Test.AnotherWay._g_record_waiting_for_results=false; // waiting for results window to open
+Test.AnotherWay._g_record_events; // recorded events
+Test.AnotherWay._g_record_under_cursor; // track element under cursor
+Test.AnotherWay._g_record_checkpoint_count; // for checkpoint numbering
+Test.AnotherWay._g_record_mouse_over_record_control; // for avoiding record control highlight on mouseover
+Test.AnotherWay._g_record_highlighted_element={ element: null, x: null, y: null };
+
+Test.AnotherWay._record_control_get_element=function( id )
+{
+	if( Test.AnotherWay._g_record_wnd!=null && Test.AnotherWay._g_record_wnd.document!=null ) {
+		return Test.AnotherWay._g_record_wnd.document.getElementById( id+Test.AnotherWay._g_record_random_id );
+	}else {
+		return null;
+	}
+}
+Test.AnotherWay._record_start_onclick=function() // "record" button on the run_tests.html: open a window for a page for which input is recorded
+{
+	if( Test.AnotherWay._g_no_record_msg!=null ) {
+		alert( Test.AnotherWay._g_no_record_msg );
+		return;
+	}
+	if( Test.AnotherWay._g_record_timeout_cnt>0
+	    || (Test.AnotherWay._g_record_wnd!=null && (Test.AnotherWay._g_record_wnd.closed!=null && !Test.AnotherWay._g_record_wnd.closed)) ) { // in opera, closed is null.
+		alert( "there is already window opened for recording input for a page "+Test.AnotherWay._g_record_url );
+		return;
+	}
+	var div=document.getElementById( "record_div" );
+	var inputs=div.getElementsByTagName( "input" );
+	var url=null;
+	for( var i=0; i<inputs.length; ++i ) {
+		var input=inputs[i];
+		if( input.type=="radio" ) {
+			if( input.value=="select" && input.checked ) {
+				var index=document.getElementById( "record_select" ).selectedIndex;
+				if( index>0 ) {
+					url=Test.AnotherWay._g_test_page_urls[index-1].url;
+				}
+			}else if( input.value=="input" && input.checked ) {
+				url=document.getElementById( "record_input" ).value;
+			}
+		}
+	}
+	if( url!=null ) {
+		Test.AnotherWay._g_record_url=url;
+		Test.AnotherWay._g_record_wnd=window.open( url, "_blank" );
+		if( Test.AnotherWay._g_record_wnd==null ) {
+			alert( "unable to open new window for a page: "+url );
+		}else {
+			Test.AnotherWay._g_record_timeout_cnt=50;
+			setTimeout( Test.AnotherWay._record_window_timeout, 100 );
+		}
+	}
+}
+Test.AnotherWay._record_window_timeout=function()
+{
+	if( Test.AnotherWay._is_url_loaded( Test.AnotherWay._g_record_url, Test.AnotherWay._g_record_wnd ) ) {
+		Test.AnotherWay._record_window_setup( Test.AnotherWay._g_record_wnd );
+	}else {
+		if( --Test.AnotherWay._g_record_timeout_cnt>0 ) {
+			setTimeout( Test.AnotherWay._record_window_timeout, 100 );
+		}else {
+			alert( "timeout expired while opening new window for a page: "+Test.AnotherWay._g_record_url );
+			Test.AnotherWay._g_record_wnd=null;
+			Test.AnotherWay._g_record_url=null;
+			Test.AnotherWay._g_record_timeout_cnt=0;
+		}
+	}
+}
+Test.AnotherWay._record_control_randomize_id=function( e, r )
+{
+	if( e.id!="" ) {
+		e.id=e.id+r;
+	}
+	for( var c=e.firstChild; c!=null; c=c.nextSibling ) {
+		Test.AnotherWay._record_control_randomize_id( c, r );
+	}
+}
+Test.AnotherWay._record_window_setup=function( wnd ) // insert recording control into the page for which input is recorded
+{
+	Test.AnotherWay._g_record_timeout_cnt=0;
+	var this_div=document.getElementById( "record_control" );
+	var record_control=wnd.document.importNode( this_div, true );
+	Test.AnotherWay._g_record_random_id=(1000*Math.random()).toFixed();
+	Test.AnotherWay._record_control_randomize_id( record_control, Test.AnotherWay._g_record_random_id );
+	Test.AnotherWay._g_record_control_visible=true;
+	Test.AnotherWay._g_record_started=false;
+	Test.AnotherWay._g_record_paused=false;
+	Test.AnotherWay._g_record_checkpoint_count=0;
+	Test.AnotherWay._g_record_mouse_over_record_control=false;
+	var doc=wnd.document;
+	doc.body.appendChild( record_control );
+	// opera sans-serif font is different
+	if( window.opera ) {
+		cursor_over_indicator=Test.AnotherWay._record_control_get_element( "record_cursor_over" );
+		cursor_over_indicator.style.width="18em";
+		cursor_over_indicator.style.height="2em";
+		cursor_over_indicator.style.fontSize="7pt";
+	}
+	doc.addEventListener( "keydown", Test.AnotherWay._record_control_keydown, true );
+	doc.addEventListener( "keyup", Test.AnotherWay._record_control_keyup, true );
+//	doc.addEventListener( "keypress", Test.AnotherWay._record_event, true ); // replaying is not supported by any known browser
+
+	doc.body.addEventListener( "mousemove", Test.AnotherWay._record_on_mousemove, true );
+	doc.body.addEventListener( "click", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mouseover", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mouseout", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mousedown", Test.AnotherWay._record_event, true );
+	doc.body.addEventListener( "mouseup", Test.AnotherWay._record_event, true );
+}
+Test.AnotherWay._record_control_key_disabled=function( k )
+{
+	if( k=="c" ) {
+		return !Test.AnotherWay._g_record_started;
+	}else if( k=="p" ) {
+		return !Test.AnotherWay._g_record_started;
+	}else if( k=="s" ) {
+		return Test.AnotherWay._g_record_waiting_for_results;
+	}else {
+		return false;
+	}
+}
+
+Test.AnotherWay._record_control_update_ui=function()
+{
+	var keydown_color="#fff";
+	var disabled_color="#aaa";
+	var button_color="#adf";
+	var active_color="#fdf";
+
+	var display={};
+	display[false]="none";
+	display[true]="inline";
+
+	var s_button=Test.AnotherWay._record_control_get_element( "record_s" );
+	var record_on=Test.AnotherWay._record_control_get_element( "record_on" );
+	var record_off=Test.AnotherWay._record_control_get_element( "record_off" );
+
+	s_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "s" ) ? disabled_color
+		: Test.AnotherWay._g_record_keydown=="s" ? keydown_color : Test.AnotherWay._g_record_started ? active_color : button_color;
+	record_on.style.display=display[!Test.AnotherWay._g_record_started];
+	record_off.style.display=display[Test.AnotherWay._g_record_started];
+
+	var h_button=Test.AnotherWay._record_control_get_element( "record_h" );
+	h_button.style.backgroundColor= Test.AnotherWay._g_record_keydown=="h" ? keydown_color : button_color;
+
+	var p_button=Test.AnotherWay._record_control_get_element( "record_p" );
+	var record_pause_on=Test.AnotherWay._record_control_get_element( "record_pause_on" );
+	var record_pause_off=Test.AnotherWay._record_control_get_element( "record_pause_off" );
+	p_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "p" ) ? disabled_color
+		: Test.AnotherWay._g_record_keydown=="p" ? keydown_color : Test.AnotherWay._g_record_paused ? active_color : button_color;
+	record_pause_on.style.display=display[!Test.AnotherWay._g_record_paused];
+	record_pause_off.style.display=display[Test.AnotherWay._g_record_paused];
+
+	var m_button=Test.AnotherWay._record_control_get_element( "record_m" );
+	var record_include_mousemove=Test.AnotherWay._record_control_get_element( "record_include_mousemove" );
+	var record_omit_mousemove=Test.AnotherWay._record_control_get_element( "record_omit_mousemove" );
+	m_button.style.backgroundColor= Test.AnotherWay._g_record_keydown=="m" ? keydown_color : Test.AnotherWay._g_record_include_mousemove ? active_color : button_color;
+	record_include_mousemove.style.display=display[!Test.AnotherWay._g_record_include_mousemove];
+	record_omit_mousemove.style.display=display[Test.AnotherWay._g_record_include_mousemove];
+
+	var c_button=Test.AnotherWay._record_control_get_element( "record_c" );
+	c_button.style.backgroundColor= Test.AnotherWay._record_control_key_disabled( "c" ) ? disabled_color
+		: Test.AnotherWay._g_record_keydown=="c" ? keydown_color : button_color;
+
+	var record_indicator=Test.AnotherWay._record_control_get_element( "record_indicator" );
+	record_indicator.style.display=display[Test.AnotherWay._g_record_started];
+
+	var pause_indicator=Test.AnotherWay._record_control_get_element( "record_pause_indicator" );
+	pause_indicator.style.display=display[Test.AnotherWay._g_record_paused];
+
+	var record_control=Test.AnotherWay._record_control_get_element( "record_control" );
+	record_control.style.display= Test.AnotherWay._g_record_control_visible ? "block" : "none";
+
+	var shift_button=Test.AnotherWay._record_control_get_element( "record_shift_key" );
+	shift_button.style.backgroundColor= Test.AnotherWay._g_record_shift_keydown ? keydown_color : button_color;
+
+	var ctrl_button=Test.AnotherWay._record_control_get_element( "record_ctrl_key" );
+	ctrl_button.style.backgroundColor= Test.AnotherWay._g_record_ctrl_keydown ? keydown_color : button_color;
+}
+Test.AnotherWay._record_format_time=function( t )
+{
+	t=new Date( t );
+	var m=t.getMinutes();
+	var s=t.getSeconds();
+	var str= m==0 ? "" : m+"m ";
+	str+=s+"s.";
+	return str;
+}
+Test.AnotherWay._record_control_update_time=function()
+{
+	var time_display=Test.AnotherWay._record_control_get_element( "record_time" );
+	if( time_display!=null ) {
+		time_display.innerHTML=Test.AnotherWay._record_format_time( (new Date()).getTime()-Test.AnotherWay._g_record_start_time );
+	}
+}
+Test.AnotherWay._record_control_update_highlight=function( elem, style, event )
+{
+	if( elem==null ) {
+		Test.AnotherWay._record_highlight_border( null );
+	}else {
+		var pos=Test.AnotherWay._get_page_coords( elem );
+		if( style=="ball" || elem!=Test.AnotherWay._g_record_highlighted_element.element || pos.x!=Test.AnotherWay._g_record_highlighted_element.x || pos.y!=Test.AnotherWay._g_record_highlighted_element.y ) {
+			Test.AnotherWay._g_record_highlighted_element={ element: elem, x: pos.x, y: pos.y };
+			Test.AnotherWay._record_highlight_border( elem, style, event );
+		}
+	}
+}
+Test.AnotherWay._record_decode_key=function( event )
+{
+	var k=null;
+	if( event==null ) {
+		k=Test.AnotherWay._g_record_wnd.event.keyCode;
+	}else {
+		k=event.which;
+	}
+	if( k==83 ) {
+		return "s";
+	}else if( k==72 ) {
+		return "h";
+	}else if( k==73 ) {
+		return "i";
+	}else if( k==80 ) {
+		return "p";
+	}else if( k==67 ) {
+		return "c";
+	}else if( k==77 ) {
+		return "m";
+	}else if( k==16 ) {
+		return "shift";
+	}else if( k==17 ) {
+		return "ctrl";
+	}else if( k==18 ) {
+		return "alt";
+	}else if( k==19 ) {
+		return "pause";
+	}else if( k==123 ) {
+		return "f12";
+	}
+	return "";
+}
+Test.AnotherWay._record_control_keydown=function( event )
+{
+	var handled=false;
+	var k=Test.AnotherWay._record_decode_key( event );
+	if( k=="shift" ) {
+		Test.AnotherWay._g_record_shift_keydown=true;
+	}else if( k=="ctrl" ) {
+		Test.AnotherWay._g_record_ctrl_keydown=true;
+	}else if( k!="" && (Test.AnotherWay._g_record_keydown==null || Test.AnotherWay._g_record_keydown==k) ) {
+		if( Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown && !Test.AnotherWay._record_control_key_disabled( k ) ) {
+			Test.AnotherWay._g_record_keydown=k;
+			handled=true;
+		}
+	}else {
+		Test.AnotherWay._g_record_keydown="";
+	}
+	Test.AnotherWay._record_control_update_ui();
+	if( !handled ) {
+//		Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+	}
+	return;
+}
+Test.AnotherWay._record_control_keyup=function( event )
+{
+	var handled=false;
+	var k=Test.AnotherWay._record_decode_key( event );
+	if( k=="shift" ) {
+		Test.AnotherWay._g_record_shift_keydown=false;
+	}else if( k=="ctrl" ) {
+		Test.AnotherWay._g_record_ctrl_keydown=false;
+	}else if( k!="" && k==Test.AnotherWay._g_record_keydown && Test.AnotherWay._g_record_ctrl_keydown && Test.AnotherWay._g_record_shift_keydown  ) {
+		if( k=="s" ) {
+			Test.AnotherWay._g_record_started=!Test.AnotherWay._g_record_started;
+			if( Test.AnotherWay._g_record_started ) {
+				Test.AnotherWay._g_record_events=[];
+				Test.AnotherWay._g_record_start_time=(new Date()).getTime();
+				Test.AnotherWay._record_control_update_time();
+				Test.AnotherWay._g_record_update_time_interval=window.setInterval( Test.AnotherWay._record_control_update_time, 200 );
+			}else {
+				Test.AnotherWay._record_control_update_highlight( null );
+				if( !Test.AnotherWay._g_record_paused ) {
+					window.clearInterval( Test.AnotherWay._g_record_update_time_interval );
+				}
+				Test.AnotherWay._g_record_waiting_for_results=true;
+				// open a new window for self, pass a parameter to dump recorded events as javascript code there
+				// (the easiest way to obtain a document from the same origin, so it's writable, is to open this same page again)
+				Test.AnotherWay._g_record_paused=false;
+				var loc=window.location;
+				loc=loc.protocol+"//"+loc.host+loc.pathname+"?recording_results="+Test.AnotherWay._g_record_random_id;
+				if( window.open( loc, "_blank" )==null ) {
+					alert( "unable to open new window for results" );
+				}
+			}
+			handled=true;
+		}else if( k=="h" ) {
+			Test.AnotherWay._g_record_control_visible=!Test.AnotherWay._g_record_control_visible;
+			handled=true;
+		}else if( k=="p" ) {
+			Test.AnotherWay._g_record_paused=!Test.AnotherWay._g_record_paused;
+			if( Test.AnotherWay._g_record_paused ) {
+				Test.AnotherWay._g_record_pause_start_time=(new Date()).getTime();
+				if( Test.AnotherWay._g_record_started ) {
+					window.clearInterval( Test.AnotherWay._g_record_update_time_interval );
+				}
+				Test.AnotherWay._record_control_update_highlight( null );
+			}else {
+				var pause_duration=(new Date()).getTime()-Test.AnotherWay._g_record_pause_start_time;
+				Test.AnotherWay._g_record_start_time+=pause_duration;
+				Test.AnotherWay._g_record_update_time_interval=window.setInterval( Test.AnotherWay._record_control_update_time, 200 );
+			}
+			handled=true;
+		}else if( k=="m" ) {
+			Test.AnotherWay._g_record_include_mousemove=!Test.AnotherWay._g_record_include_mousemove;
+			handled=true;
+		}else if( k=="c" ) {
+			var o=Test.AnotherWay._record_checkpoint();
+			Test.AnotherWay._record_display_checkpoint( o );
+			Test.AnotherWay._record_flash_border( "#24d" );
+			handled=true;
+		}
+	}
+	Test.AnotherWay._g_record_keydown=null;
+	Test.AnotherWay._record_control_update_ui();
+	if( !handled ) {
+//		Test.AnotherWay._record_event( event ); // replaying is not supported in any known browser
+	}
+	return;
+}
+Test.AnotherWay._record_html_node_path=function( node )
+{
+	if( node==null ) {
+		return null;
+	}
+	var path=[];
+	while( true ) {
+		if( node.id!=null && node.id!="" ) {
+			path.unshift( "#"+node.id+" "+node.nodeName );
+			break;
+		}else {
+			var parent_node=node.parentNode;
+			if( parent_node==null ) {
+				return []; // no BODY up the path - this node is screwed (browsers differ in what's above the body), discard
+			}else {
+				var i=0;
+				var found=false;
+				for( var child=parent_node.firstChild; child!=null; child=child.nextSibling ) {
+					if( child==node ) {
+						found=true;
+						break;
+					}
+					if( child.nodeType==1 ) { // count only HTML element nodes
+						++i;
+					}
+				}
+				if( !found ) {
+					i=-1;
+				}
+				path.unshift( i+" "+node.nodeName );
+				if( parent_node.nodeName=="BODY" || parent_node.nodeName=="body" ) {
+					break;
+				}
+				node=parent_node;
+			}
+		}
+	}
+	return path;
+}
+Test.AnotherWay._record_node_path_to_string=function( path )
+{
+	var s="";
+	if( path!=null ) {
+		for( var i=0; i<path.length; ++i ) {
+			s+= i==0 ? "" : ", ";
+			var elem=path[i].split( " " );
+			if( elem[0].charAt( 0 )=="#" ) {
+				s+=elem[1]+" "+elem[0];
+			}else {
+				s+=elem[1]+" ["+elem[0]+"]";
+			}
+		}
+	}
+	return s;
+}
+Test.AnotherWay._record_node_path_to_node=function( path_str, doc )
+{
+	if( path_str==null ) {
+		return null;
+	}
+	var path=path_str.split( "," );
+	var node=doc.body;
+	for( var i=0; i<path.length; ++i ) {
+		var node_i=path[i].split( " " )[0];
+		if( node_i.charAt( 0 )=="#" ) {
+			node=doc.getElementById( node_i.substring( 1 ) );
+		}else {
+			if( node_i<0 || node_i>=node.childNodes.length ) {
+				node=null;
+			}else {
+				node=node.firstChild;
+				while( node!=null ) {
+					if( node.nodeType==1 ) {  // count only HTML element nodes
+						if( node_i==0 ) {
+							break;
+						}
+						--node_i;
+					}
+					node=node.nextSibling;
+				}
+			}
+		}
+		if( node==null ) {
+			return null;
+		}
+	}
+	return node;
+}
+Test.AnotherWay._record_control_contains_id=function( s )
+{
+	return s.match( /^#record_[\w_]+/ ) && s.match( Test.AnotherWay._g_record_random_id );
+}
+Test.AnotherWay._record_checkpoint=function()
+{
+	var o={ type: "_checkpoint", time: (new Date()).getTime()-Test.AnotherWay._g_record_start_time, which: Test.AnotherWay._g_record_checkpoint_count++,
+			target: Test.AnotherWay._record_html_node_path( Test.AnotherWay._g_record_under_cursor ) };
+	Test.AnotherWay._g_record_events.push( o );
+	return o;
+}
+Test.AnotherWay._record_event=function( event )
+{
+	var unneeded=["rangeOffset","eventPhase","timeStamp","isTrusted","popupWindowFeatures","rangeOffset"];
+	if( Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused ) {
+		var o={};
+		for( var n in event ) {
+			var needed=!n.match( /^[A-Z0-9_]+$/ );
+			if( needed ) {
+				for( var ui=0; ui<unneeded.length; ++ui ) {
+					if( unneeded[ui]==n ) {
+						needed=false;
+						break;
+					}
+				}
+				if( needed ) {
+					var value=event[n];
+					if( typeof( value )!="object" && typeof( value )!="function" ) {
+						o[n]=value;
+					}else if( n=="target" || n=="relatedTarget" ) {
+						o[n]=Test.AnotherWay._record_html_node_path( value );
+					}
+				}
+			}
+		}
+		o["time"]=(new Date()).getTime()-Test.AnotherWay._g_record_start_time;
+		var over_record_control= o["target"]!=null && o["target"][0]!=null && Test.AnotherWay._record_control_contains_id( o["target"][0] );
+		if( !over_record_control ) {
+			Test.AnotherWay._g_record_events.push( o );
+		}
+	}
+	return true;
+}
+Test.AnotherWay._record_on_mousemove=function( event )
+{
+	var path=Test.AnotherWay._record_html_node_path( event.target );
+	var new_mouse_over_record_control= path!=null && path[0]!=null && Test.AnotherWay._record_control_contains_id( path[0] );
+	if( new_mouse_over_record_control!=Test.AnotherWay._g_record_mouse_over_record_control ) {
+		Test.AnotherWay._g_record_mouse_over_record_control=new_mouse_over_record_control;
+		Test.AnotherWay._record_control_update_ui();
+	}
+	if( event.target!=null && event.target!=Test.AnotherWay._g_record_under_cursor ) {
+		Test.AnotherWay._g_record_under_cursor=event.target;
+		var s="";
+		if( path==null || path[0]==null || !Test.AnotherWay._record_control_contains_id( path[0] ) ) {
+			s=Test.AnotherWay._record_node_path_to_string( path );
+		}
+		if( s=="" ) {
+			s="&nbsp;";
+		}
+		var cursor_over_indicator=Test.AnotherWay._record_control_get_element( "record_cursor_over" );
+		cursor_over_indicator.innerHTML=s;
+	}
+
+	var highlight_element=null;
+	if( !Test.AnotherWay._g_record_mouse_over_record_control && Test.AnotherWay._g_record_started && !Test.AnotherWay._g_record_paused ) {
+		highlight_element=event.target;
+	}
+	// highlight border disabled on recording - it causes page to scroll, issuing spurious mouseover/mouseout event
+	//Test.AnotherWay._record_control_update_highlight( highlight_element, "border" );
+
+	if( Test.AnotherWay._g_record_include_mousemove ) {
+		Test.AnotherWay._record_event( event );
+	}
+	return true;
+}
+Test.AnotherWay._record_display_checkpoint=function( o )
+{
+	var checkpoints_div=Test.AnotherWay._record_control_get_element( "record_checkpoints" );
+	var p=checkpoints_div.appendChild( checkpoints_div.ownerDocument.createElement( "div" ) );
+	p.style.marginTop="3px";
+	p.style.font="normal normal 8pt sans-serif";
+	p.style.color="#000";
+	p.style.textAligh="left";
+	p.style.position="relative";
+	p.style.width="100%";
+	var checkpoint_text="";
+	checkpoint_text+="#"+(o.which+1);
+	checkpoint_text+="  "+Test.AnotherWay._record_format_time( o.time );
+	if( o.target!=null ) {
+		checkpoint_text+=Test.AnotherWay._record_node_path_to_string( o.target );
+	}
+	p.appendChild( p.ownerDocument.createTextNode( checkpoint_text ) );
+}
+Test.AnotherWay._record_save_results=function( doc )
+{
+	// strange, but DOM-style append does not work here in opera 8.
+	var append=function( s ) { doc.write( "<div>"+s+"</div>" ); };
+	append( "/* paste this data into your javascript and pass it as an argument to replay_events method */" );
+	append( "{ checkpoints: [" );
+	var first_checkpoint=true;
+	for( var i=0; i<Test.AnotherWay._g_record_events.length; ++i ) {
+		var o=Test.AnotherWay._g_record_events[i];
+		if( o.type=="_checkpoint" ) {
+			var str= first_checkpoint ? "" : "}, ";
+			str+="function( tst, wnd ) { // #"+o.which+" time "+Test.AnotherWay._record_format_time( o.time )+" cursor was over "+Test.AnotherWay._record_node_path_to_string( o.target );
+			append( str );
+			first_checkpoint=false;
+		}
+	}
+	if( !first_checkpoint ) {
+		append( "}" );
+	}
+	append( "], events: [ " );
+	var prev_time=0;
+	for( var i=0; i<Test.AnotherWay._g_record_events.length; ++i ) {
+		var o=Test.AnotherWay._g_record_events[i];
+		var s="";
+		s+= "{";
+		var n_first=true;
+		for( var n in o ) {
+			if( n=="time" ) { // convert to relative time
+				var cur_time=o[n]-0;
+				o[n]=cur_time-prev_time;
+				prev_time=cur_time;
+			}
+			s+=n_first ? n : ", "+n;
+			s+=":";
+			if( o[n]==null ) {
+				s+="null";
+			}else {
+				s+="\""+o[n]+"\"";
+			}
+			n_first=false;
+		}
+		s+= i==Test.AnotherWay._g_record_events.length-1 ? "}" : "},";
+		append( s );
+	}
+	append( "] }" );
+	append( ";" );
+}
+
+Test.AnotherWay._g_record_border; // border highlighting element under cursor
+Test.AnotherWay._g_record_border_flashes=[]; // array of { color: color, timeout: milliseconds }
+Test.AnotherWay._g_record_border_flashing=false;
+Test.AnotherWay._g_record_border_normal_color="#d4b";
+Test.AnotherWay._record_flash_border_timeout=function()
+{
+	var color=Test.AnotherWay._g_record_border_normal_color;
+	var timeout=null;
+	if( Test.AnotherWay._g_record_border_flashes.length!=0 ) {
+		color=Test.AnotherWay._g_record_border_flashes[0].color;
+		timeout=Test.AnotherWay._g_record_border_flashes[0].timeout;
+		Test.AnotherWay._g_record_border_flashes.splice( 0, 1 );
+	}
+	if( Test.AnotherWay._g_record_border!=null ) {
+		for( var i=0; i<Test.AnotherWay._g_record_border.length; ++i ) {
+			Test.AnotherWay._g_record_border[i].style.backgroundColor=color;
+		}
+	}
+	if( timeout!=null ) {
+		setTimeout( Test.AnotherWay._record_flash_border_timeout, timeout );
+	}else {
+		Test.AnotherWay._g_record_border_flashing=false;
+	}
+}
+Test.AnotherWay._get_page_coords=function( elm )
+{
+	var point = { x: 0, y: 0 };
+	while( elm )  {
+		point.x+=elm.offsetLeft;
+		point.y+=elm.offsetTop;
+		elm=elm.offsetParent;
+	 }
+	return point;
+}
+Test.AnotherWay._set_page_coords=function( elm, x, y )
+{
+	var parent_coords={ x: 0, y: 0 };
+	if( elm.offsetParent )  {
+		parent_coords=Test.AnotherWay._get_page_coords( elm.offsetParent );
+	}
+	var new_x=x-parent_coords.x;
+	if( new_x<0 ) {
+		new_x=0;
+	}
+	elm.style.left=new_x+'px';
+	var new_y=y-parent_coords.y;
+	if( new_y<0 ) {
+		new_y=0;
+	}
+	elm.style.top=new_y+'px';
+}
+Test.AnotherWay._record_setup_highlight_positions=function( element, style, coords, positions )
+{
+	if( style=="border" ) {
+		var width=element.clientWidth;
+		var height=element.clientHeight;
+		var step=0;
+		var thickness=2;
+		var fudge_expand=4;
+		positions.push( { x: coords.x-step-thickness, y: coords.y-step-thickness, width: width+2*step+2*thickness+fudge_expand, height: thickness } );
+		positions.push( { x: coords.x+width+step+fudge_expand, y: coords.y-step-thickness, width: thickness, height: height+2*step+2*thickness+fudge_expand } );
+		positions.push( { x:positions[0].x, y:positions[0].y, width:positions[0].width, height:positions[0].height } );
+		positions.push( { x:positions[1].x, y:positions[1].y, width:positions[1].width, height:positions[1].height } );
+		positions[2].y+=height+thickness+2*step+fudge_expand;
+		positions[3].x-=width+thickness+2*step+fudge_expand;
+	}else if( style=="ball" ) {
+		positions.push( { x: coords.x+2, y: coords.y, width: 2, height: 6 } );
+		positions.push( { x: coords.x, y: coords.y+2, width: 6, height: 2 } );
+		positions.push( { x: coords.x+1, y: coords.y+1, width: 4, height: 4 } );
+	}
+}
+Test.AnotherWay._record_highlight_border=function( element, style, event ) // null - hide border
+{
+	if( element!=null ) {
+		if( Test.AnotherWay._g_record_border==null || Test.AnotherWay._g_record_border[0].ownerDocument!=element.ownerDocument ) {
+			Test.AnotherWay._g_record_border=[];
+			var n= style=="border" ? 4 : style=="ball" ? 3 : 0;
+			for( var i=0; i<4; ++i ) {
+				var b=element.ownerDocument.createElement( "div" );
+				b.style.position="absolute";
+				b.style.zIndex="1";
+				b.style.backgroundColor=Test.AnotherWay._g_record_border_normal_color;
+				element.ownerDocument.body.appendChild( b );
+				Test.AnotherWay._g_record_border.push( b );
+			}
+		}
+		var coords=null;
+		if( style=="border" ) {
+			coords=Test.AnotherWay._get_page_coords( element );
+		}else if( style=="ball" ) {
+			if( event!=null ) {
+				if( event.pageX!=null && event.pageY!=null ) {
+					coords={ x: event.pageX-0, y: event.pageY-0 };
+				}else if( event.clientX!=null && event.clientY!=null ) {
+					var doc=element.ownerDocument;
+					if( doc!=null ) {
+						coords={ x: (event.clientX-0)+doc.body.scrollLeft, y: (event.clientY-0)+doc.body.scrollTop };
+					}
+				}
+			}
+		}
+		if( coords!=null && element.clientWidth!=null && element.clientHeight!=null ) {
+			var positions=[];
+			Test.AnotherWay._record_setup_highlight_positions( element, style, coords, positions );
+			for( var i=0; i<positions.length; ++i ) {
+				var b=Test.AnotherWay._g_record_border[i];
+				var p=positions[i];
+				Test.AnotherWay._set_page_coords( b, p.x, p.y );
+				b.style.width=p.width+"px";
+				b.style.height=p.height+"px";
+				b.style.display="block";
+			}
+		}
+	}else {
+		if( Test.AnotherWay._g_record_border!=null ) {
+			for( var i=0; i<Test.AnotherWay._g_record_border.length; ++i ) {
+				Test.AnotherWay._g_record_border[i].style.display="none";
+			}
+		}
+	}
+}
+Test.AnotherWay._record_flash_border=function( color )
+{
+	if( Test.AnotherWay._g_record_border_flashing ) { //already
+		Test.AnotherWay._g_record_border_flashes.push( { color: Test.AnotherWay._g_record_border_normal_color, timeout:300 } );
+		Test.AnotherWay._g_record_border_flashes.push( { color: color, timeout:600 } );
+	}else {
+		Test.AnotherWay._g_record_border_flashing=true;
+		Test.AnotherWay._g_record_border_flashes.push( { color: color, timeout:600 } );
+		Test.AnotherWay._record_flash_border_timeout();
+	}
+}
+Test.AnotherWay._record_prepare_doc_for_results=function()
+{
+	document.open();
+	document.write( "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\" \"http://www.w3.org/TR/html4/strict.dtd\">" );
+	document.write( "<html><head><title> Input recording results</title>" );
+	document.write( "<style type=\"text/css\">" );
+	document.write( "body { font: normal normal smaller sans-serif; }" );
+	document.write( "div { margin-top: 3px; }" );
+	document.write( "</style></head><body>" );
+	// opera and mozilla disagree over who the opener is.
+	if( typeof( window.opener.Test )!="undefined" && typeof( window.opener.Test.AnotherWay )!="undefined" ) {
+		window.opener.Test.AnotherWay._record_save_results( document );
+		window.opener.Test.AnotherWay._g_record_waiting_for_results=false;
+		window.opener.Test.AnotherWay._record_control_update_ui();
+	}else if( typeof( window.opener.opener.Test  )!="undefined" && typeof( window.opener.opener.Test.AnotherWay )!="undefined" ) {
+		window.opener.opener.Test.AnotherWay._record_save_results( document );
+		window.opener.opener.Test.AnotherWay._g_record_waiting_for_results=false;
+		window.opener.opener.Test.AnotherWay._record_control_update_ui();
+	}
+	document.write( "</body>" );
+	document.close();
+}
+
+// global initialization
+onload=function()
+{
+	if( window.opera ) {
+		var good_opera=typeof( window.opera.version )=="function";
+		good_opera=good_opera && window.opera.version().match( /^\s*(\d+)/ );
+		good_opera=good_opera && RegExp.$1>=8;
+	}
+	var span=document.createElement( "SPAN" );
+	span.innerHTML="<!--[if IE]><br /><![endif]-"+"->";
+	var is_ie=span.getElementsByTagName( "BR" ).length>0;
+
+	Test.AnotherWay._g_test_iframe=window.frames.test_iframe;
+
+	var query_str=window.location.search;
+	if( query_str.charAt( 0 )=="?" ) {
+		query_str=query_str.substring( 1 );
+	}
+	var testlist_page="list-tests.html";
+	var auto_run=false;
+	if( query_str!="" ) {
+		var params=[query_str];
+		if( query_str.indexOf( ";" )!=-1 ) {
+			params=query_str.split( ";" );
+		}else if( query_str.indexOf( "&" )!=-1 ) {
+			params=query_str.split( "&" );
+		}
+		for( var param_i=0; param_i<params.length; ++param_i ) {
+			var param=params[param_i].split( "=" );
+			if( param[0]=="recording_results" ) {
+				if( window.opener!=null ) {
+					// we were told to show recording results - replace everything in the document with the results
+					Test.AnotherWay._record_prepare_doc_for_results();
+					return;
+				}
+			}else if( param[0]=="testpage" ) {
+				Test.AnotherWay._add_test_page_url( decodeURIComponent( param[1] ), "anotherway" );
+			}else if( param[0]=="jsantestpage" ) {
+				Test.AnotherWay._add_test_page_url( decodeURIComponent( param[1] ), "jsan" );
+			}else if( param[0]=="testlist" ) {
+				testlist_page=decodeURIComponent( param[1] );
+			}else if( param[0]=="testframe" ) {
+				if( window.opera && !good_opera ) {
+					Test.AnotherWay._show_error( "testframe parameter does not work in versions of Opera prior to 8.0. Sorry (pathches are welcome)." );
+					// Opera 7 barfs on attempt to access frame.frameElement.
+					// if someone knows a way to assign onload handler to that iframe in Opera 7
+					// without disrupting code that works in other browsers, patches are welcome.
+				}else {
+					var frame_path=param[1].split( "." );
+					var frame=top;
+					for( var frame_path_i=0; frame_path_i<frame_path.length; ++frame_path_i ) {
+						frame=frame[frame_path[frame_path_i]];
+					}
+					if( frame==null ) {
+						Test.AnotherWay._show_error( "unable to find frame specified for loading test pages: "+param[1] );
+					}else {
+						if( frame.frameElement!=null ) { // for the following assignement to onload to work, frameElement is required
+							frame=frame.frameElement;
+						}
+						Test.AnotherWay._g_test_iframe=frame;
+					}
+				}
+			}else if( param[0]=="testframe_no_clear" ) {
+				Test.AnotherWay._g_test_frame_no_clear=true;
+			}else if( param[0]=="run" ) {
+				auto_run=true;
+				if( param[1]=="all" ) {
+					Test.AnotherWay._g_pages_to_run="all";
+				}else {
+					if( Test.AnotherWay._g_pages_to_run==null || Test.AnotherWay._g_pages_to_run=="all" ) {
+						Test.AnotherWay._g_pages_to_run=[];
+					}
+					var pages=param[1].split( "," );
+					for( var i=0; i<pages.length; ++i ) {
+						Test.AnotherWay._g_pages_to_run.push( pages[i] );
+					}
+				}
+			}
+		}
+	}
+	if( Test.AnotherWay._g_test_page_urls.length==0 ) {  // if no individual pages were given on the command line, load the list
+		var result=Test.AnotherWay._set_iframe_location( window.frames["list_iframe"], testlist_page );
+		if( result.msg!=null ) {
+			Test.AnotherWay._show_error( result.msg );
+		}
+		Test.AnotherWay._g_run_on_list_load=auto_run;
+	}else {
+		Test.AnotherWay._g_run_on_main_load=auto_run;
+	}
+
+	var f=Test.AnotherWay._g_test_iframe;
+	try {
+		if( f.attachEvent!=null ) {
+			f.attachEvent( "onload", Test.AnotherWay._test_page_onload );
+		}else {
+			f.onload=Test.AnotherWay._test_page_onload;
+		}
+		if( Test.AnotherWay._g_test_iframe.nodeType!=null && Test.AnotherWay._g_test_iframe.contentWindow!=null ) { // it's iframe element, not the iframe. we need iframe.
+			Test.AnotherWay._g_test_iframe=Test.AnotherWay._g_test_iframe.contentWindow;
+		}
+	}catch(e) {
+		// ignore stupid opera error if the frame has onload handler assigned in the inline html
+	}
+	var handlers={
+		"run_all": { "onclick": Test.AnotherWay._run_all_onclick },
+		"run_selected": { "onclick": Test.AnotherWay._run_selected_onclick },
+		"unselect_all": { "onclick": Test.AnotherWay._unselect_all_onclick },
+		"record_select": { "onfocus": Test.AnotherWay._record_check_onfocus },
+		"record_input": { "onfocus": Test.AnotherWay._record_check_onfocus },
+		"record_start": { "onclick": Test.AnotherWay._record_start_onclick },
+		"clear_btn": { "onclick": Test.AnotherWay._results_clear_onclick },
+		"results_tab": { "onclick": Test.AnotherWay._tab_onclick, "onmouseover": Test.AnotherWay._tab_mouseover, "onmouseout": Test.AnotherWay._tab_mouseout },
+		"debug_tab": { "onclick": Test.AnotherWay._tab_onclick, "onmouseover": Test.AnotherWay._tab_mouseover, "onmouseout": Test.AnotherWay._tab_mouseout }
+	};
+	for( var hs in handlers ) {
+		var o=document.getElementById( hs );
+		if( o!=null ) {
+			for( var h in handlers[hs] ) {
+				o[h]=handlers[hs][h];
+			}
+		}else {
+			Test.AnotherWay._show_error( "unable to set "+h+" handler: id "+hs+" not found" );
+		}
+	}
+
+	if( window.opera && !good_opera ) {
+		Test.AnotherWay._g_no_record_msg="Input events recording and replaying is not available in opera versions prior to 8.0.";
+	}
+	if( is_ie ) {
+		Test.AnotherWay._g_no_record_msg="Input events recording and replaying is not available in internet explorer.";
+	}
+ 	if( Test.AnotherWay._g_no_record_msg!=null ) {
+		var no_record_p=document.getElementById( "record_not_supported" );
+		no_record_p.style.display="block";
+		no_record_p.appendChild( document.createTextNode( Test.AnotherWay._g_no_record_msg ) );
+	}
+
+	Test.AnotherWay._g_main_loaded=true;
+	if( Test.AnotherWay._g_run_on_main_load ) {
+		Test.AnotherWay._g_run_on_main_load=false;
+		Test.AnotherWay._run_pages_to_run();
+	}
+}
+// -->
+</script>
+<script type="text/javascript" src="xml_eq.js"></script>
+</head><body>
+
+<div id="col1">
+<div id="col1_header">Test pages:</div>
+<div id="scroller">
+<table id="testtable">
+</table>
+</div>
+<div id="run_buttons">
+<input type="button" value=" clear " id="clear_btn" />
+<input type="button" value=" run all " id="run_all" />
+<input type="button" value=" run selected " id="run_selected" />
+<input type="button" value=" unselect all " id="unselect_all" />
+</div>
+<input type="checkbox" id="dont_close_test_windows" /> do not close windows opened by tests
+<div id="error"></div>
+<div id="record_div">
+<p id="record_not_supported" style="display:none"></p>
+<p>Record mouse input for the page:</p>
+<p><input type="radio" name="record_choose" value="select" checked="checked" /> <select id="record_select"><option selected="selected">-- select a page: --</option></select></p>
+<p><input type="radio" name="record_choose" value="input" /> or enter page url: <input type="text" id="record_input" /></p>
+<p><input type="button" value=" record " id="record_start" /></p>
+</div>
+</div>
+
+<div id="col2">
+<div id="right_header">
+<span id="results_count">Results: <span id="total"></span></span>
+<span id="results_tab" class="active_tab" style="visibility:hidden">Results</span>
+<span id="debug_tab" class="inactive_tab" style="visibility:hidden">Debug</span>
+</div>
+<div id="right_frame">
+<div id="results"></div>
+<div id="debug"></div>
+</div>
+</div>
+
+<span style="display:none">
+<iframe name="list_iframe" onload="Test.AnotherWay._list_iframe_onload();"></iframe>
+<iframe name="test_iframe" onload="Test.AnotherWay._test_page_onload();"></iframe>
+
+<!-- record_control div is to be imported into other documents, so all its styles are inline -->
+-<div id="record_control" style="position:absolute;bottom:0;left:0;margin:0;padding:0.5em;width:22em;height:22em;border:1px solid;background:#ffd;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+
+<p style="margin:0 0 0 0; padding:0">
+&nbsp;
+<span style="display:none;font-weight:bold;color:#408" id="record_indicator">
+recording. <span style="font-weight:normal">time: <span id="record_time"></span></span><span id="record_pause_indicator"> paused</span>
+</span>
+</p>
+
+<div id="record_cursor_over" style="margin:0;padding:2px;width:14em;height:1.1em;overflow:hidden;float:right;border:1px solid #777;background:#fff;font: normal normal 8pt sans-serif;position:relative;top:3px;color:#000;text-align:left;">&nbsp;</div>
+<p style="margin:2px 0 0 0; padding:0">
+cursor is over
+</p>
+
+<p style="margin:8px 0 0 0; padding:0;">
+ keyboard control: press
+ <span id="record_ctrl_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">ctrl</span> -
+ <span id="record_shift_key" style="border:1px solid #226;background:#adf;padding:0 0.5em">shift</span> -
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_s" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">s</span>
+<span id="record_on">to <b>start</b> recording</span>
+<span id="record_off" style="display:none">to <b>stop</b> recording</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_h" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">h</span>
+<span>to <b>hide/show</b> this window</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_m" style="border:1px solid #226;background:#adf;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">m</span>
+<span id="record_include_mousemove">to <b> record</b> mousemove</span>
+<span id="record_omit_mousemove" style="display:none">to <b>omit</b> mousemove</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_p" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">p</span>
+<span id="record_pause_on">to <b> pause</b> recording</span>
+<span id="record_pause_off" style="display:none">to <b>continue</b> recording</span>
+</p>
+
+<p style="margin:4px 0 0 0; padding:0">
+<span id="record_c" style="border:1px solid #226;background:#aaa;width:1.2em;float:left;font-weight:bold;text-align:center;margin-right:0.5em">c</span>
+<span>to add checkpoint</span>
+</p>
+
+<p style="margin:6px 0 0 0; padding:0">
+checkpoints:
+</p>
+<div id="record_checkpoints" style="position:relative;width:100%;height:6em;overflow:auto;font: normal normal 8pt sans-serif; color:#000; text-align: left">
+</div>
+</div>
+
+</span>
+</body></html>

Added: addins/bookmark/release/2.2/theme/default/bookmark.css
===================================================================
--- addins/bookmark/release/2.2/theme/default/bookmark.css	                        (rev 0)
+++ addins/bookmark/release/2.2/theme/default/bookmark.css	2011-07-20 20:51:08 UTC (rev 12176)
@@ -0,0 +1,73 @@
+/* Bookmark Control Styles */
+
+/* bookmark main div */
+.olControlBookmark {
+   position: absolute;
+   top: 100px;
+   right: 0px;
+   /*margin: 10px 0px 10px 10px; /* top right bottom left */
+   width: 175px;
+}
+/* bookmark content container */
+.olControlBookmarkContent {
+   background-color: darkblue;
+   width: 100%;
+   height: 100%;
+   font-family: arial, sans-serif;
+   font-size: smaller;
+   font-weight: bold;
+   color: white;
+}
+/* bookmark title */
+.olControlBookmarkTitle {
+   text-align: center;
+   padding-bottom: 5px;
+}
+/* bookmark data elements */
+.olControlBookmarkElements {
+   text-align: left;
+   background-color: transparent;
+   overflow: hidden;
+}
+/* each row contains a remove element and link element */
+.olControlBookmarkRow {
+   height: 20px;
+   vertical-align: top;
+   position: relative;
+}
+.olControlBookmarkRemove {
+   margin-left: 10px;
+   background-image: url("http://openlayers.org/api/theme/default/img/close.gif");
+   background-repeat: no-repeat;
+   background-color: white;
+   width: 17px;
+   height: 17px;
+   display: block;
+   cursor: pointer;
+}
+.olControlBookmarkLink {
+   position: absolute;
+   left: 35px;
+   top: 0px;
+   cursor: pointer;
+}
+.olControlBookmarkMaximizeButton {
+   background-image: url("http://openlayers.org/api/img/layer-switcher-maximize.png");
+   background-repeat: no-repeat;
+   width:  18px;
+   height: 18px;
+   display: none;
+   position: absolute;
+   top: 0px;
+   right: 0px;
+}
+.olControlBookmarkMinimizeButton {
+   background-image: url("http://openlayers.org/api/img/layer-switcher-minimize.png");
+   background-repeat: no-repeat;
+   width:  18px;
+   height: 18px;
+   display: none;
+   position: absolute;
+   top: 0px; /* upper right corner of parent div */
+   right: 0px;
+}
\ No newline at end of file



More information about the Commits mailing list