Merge "Update/refresh RS version tests + add 17 support."
diff --git a/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java b/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
index d675edc..881b8a4 100644
--- a/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
+++ b/suite/pts/deviceTests/dram/src/com/android/pts/dram/BandwidthTest.java
@@ -21,7 +21,8 @@
 import android.util.Log;
 import android.view.WindowManager;
 
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsAndroidTestCase;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -39,6 +40,13 @@
     private static final int KB = 1024;
     private static final int MB = 1024 * 1024;
 
+    @Override
+    protected void setUp() throws Exception {
+        super.setUp();
+        // warm-up
+        MemoryNative.runMemcpy(2 * MB, 100);
+    }
+
     public void testMemcpyK004() {
         doRunMemcpy(4 * KB);
     }
@@ -93,13 +101,20 @@
 
     private void doRunMemcpy(int bufferSize) {
         double[] result = new double[REPETITION];
-        for (int i = 0; i < REPETITION; i++) {
-            result[i] = MemoryNative.runMemcpy(bufferSize, REPEAT_IN_EACH_CALL);
+        int repeatInEachCall = REPEAT_IN_EACH_CALL;
+        if (bufferSize < (1 * MB)) {
+            // too small buffer size finishes too early to give accurate result.
+            repeatInEachCall *= (1 * MB / bufferSize);
         }
-        getReportLog().printArray("ms", result, false);
+        for (int i = 0; i < REPETITION; i++) {
+            result[i] = MemoryNative.runMemcpy(bufferSize, repeatInEachCall);
+        }
+        getReportLog().printArray("memcpy time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         double[] mbps = ReportLog.calcRatePerSecArray(
-                (double)bufferSize * REPEAT_IN_EACH_CALL / 1024.0 / 1024.0, result);
-        getReportLog().printArray("MB/s", mbps, true);
+                (double)bufferSize * repeatInEachCall / 1024.0 / 1024.0, result);
+        getReportLog().printArray("memcpy throughput", mbps, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStat(mbps);
         WindowManager wm = (WindowManager) getContext().getSystemService(Context.WINDOW_SERVICE);
         Point size = new Point();
@@ -108,8 +123,7 @@
         double pixels = size.x * size.y;
         // now this represents how many times the whole screen can be copied in a sec.
         double screensPerSecAverage = stat.mAverage / pixels * 1024.0 * 1024.0 / 4.0;
-        double screensPerSecStddev = stat.mStddev / pixels * 1024.0 * 1024.0 / 4.0;
-        getReportLog().printSummary("screen copies per sec", screensPerSecAverage,
-                PerfResultType.HIGHER_BETTER, screensPerSecStddev);
+        getReportLog().printSummary("memcpy in fps", screensPerSecAverage,
+                ResultType.HIGHER_BETTER, ResultUnit.FPS);
     }
 }
diff --git a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
index 1e39441..087cf0d 100644
--- a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
+++ b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/FileUtil.java
@@ -19,7 +19,6 @@
 import java.io.BufferedReader;
 import java.io.File;
 import java.io.FileInputStream;
-import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.InputStreamReader;
@@ -28,7 +27,8 @@
 
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
 import com.android.pts.util.SystemUtil;
@@ -299,12 +299,14 @@
         randomFile.close();
         double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.printArray("MB/s",
-                mbps, true);
-        report.printArray("Rd amount", rdAmount, true);
+        report.printArray("read throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        // This is just the amount of IO returned from kernel. So this is performance neutral.
+        report.printArray("read amount", rdAmount, ResultType.NEUTRAL, ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.printSummary("MB/s", stat.mAverage, PerfResultType.HIGHER_BETTER, stat.mStddev);
+        report.printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 
     /**
@@ -349,12 +351,14 @@
         randomFile.close();
         double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / runsInOneGo / 1024 / 1024,
                 times);
-        report.printArray("MB/s",
-                mbps, true);
-        report.printArray("Wr amount", wrAmount, true);
+        report.printArray("write throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        report.printArray("write amount", wrAmount, ResultType.NEUTRAL,
+                ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
 
-        report.printSummary("MB/s", stat.mAverage, PerfResultType.HIGHER_BETTER, stat.mStddev);
+        report.printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 
     /**
@@ -387,11 +391,12 @@
             randomFile.close();
             double[] mbps = ReportLog.calcRatePerSecArray((double)bufferSize / 1024 / 1024,
                     times);
-            report.printArray(i + "-th round MB/s",
-                    mbps, true);
+            report.printArray(i + "-th round throughput",
+                    mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
             ReportLog.copyArray(mbps, mbpsAll, i * numberRepeatInOneRun);
         }
         Stat.StatResult stat = Stat.getStat(mbpsAll);
-        report.printSummary("MB/s", stat.mAverage, PerfResultType.HIGHER_BETTER, stat.mStddev);
+        report.printSummary("update throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 }
diff --git a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
index 52dcb16..4a9c3dd 100644
--- a/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
+++ b/suite/pts/deviceTests/filesystemperf/src/com/android/pts/filesystemperf/SequentialRWTest.java
@@ -19,7 +19,8 @@
 import android.cts.util.TimeoutReq;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsAndroidTestCase;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -48,7 +49,8 @@
     public void testSingleSequentialWrite() throws Exception {
         final int numberOfFiles =(int)(FileUtil.getFileSizeExceedingMemory(
                 getContext(), BUFFER_SIZE) / BUFFER_SIZE);
-        getReportLog().printValue("files", numberOfFiles);
+        getReportLog().printValue("files", numberOfFiles, ResultType.NEUTRAL,
+                ResultUnit.COUNT);
         final byte[] data = FileUtil.generateRandomData(BUFFER_SIZE);
         final File[] files = FileUtil.createNewFiles(getContext(), DIR_SEQ_WR,
                 numberOfFiles);
@@ -62,12 +64,13 @@
             }
         });
         double[] mbps = ReportLog.calcRatePerSecArray((double)BUFFER_SIZE / 1024 / 1024, times);
-        getReportLog().printArray("try " + numberOfFiles + " files, result MB/s",
-                mbps, true);
-        getReportLog().printArray("Wr amount", wrAmount, true);
+        getReportLog().printArray("write throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
+        getReportLog().printArray("write amount", wrAmount, ResultType.NEUTRAL,
+                ResultUnit.BYTE);
         Stat.StatResult stat = Stat.getStat(mbps);
-        getReportLog().printSummary("MB/s", stat.mAverage, PerfResultType.HIGHER_BETTER,
-                stat.mStddev);
+        getReportLog().printSummary("write throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 
     @TimeoutReq(minutes = 60)
@@ -85,8 +88,9 @@
         final File file = FileUtil.createNewFilledFile(getContext(),
                 DIR_SEQ_RD, fileSize);
         long finish = System.currentTimeMillis();
-        getReportLog().printValue("write size " + fileSize + " result MB/s",
-                ReportLog.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start));
+        getReportLog().printValue("write throughput for test file of length " + fileSize,
+                ReportLog.calcRatePerSec((double)fileSize / 1024 / 1024, finish - start),
+                ResultType.HIGHER_BETTER, ResultUnit.MBPS);
 
         final int NUMBER_READ = 10;
 
@@ -105,10 +109,10 @@
             }
         });
         double[] mbps = ReportLog.calcRatePerSecArray((double)fileSize / 1024 / 1024, times);
-        getReportLog().printArray("read MB/s",
-                mbps, true);
+        getReportLog().printArray("read throughput",
+                mbps, ResultType.HIGHER_BETTER, ResultUnit.MBPS);
         Stat.StatResult stat = Stat.getStat(mbps);
-        getReportLog().printSummary("MB/s", stat.mAverage, PerfResultType.HIGHER_BETTER,
-                stat.mStddev);
+        getReportLog().printSummary("read throughput", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.MBPS);
     }
 }
diff --git a/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java
index 4231250..8b17171 100644
--- a/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java
+++ b/suite/pts/deviceTests/simplecpu/src/com/android/pts/simplecpu/SimpleCpuTest.java
@@ -18,7 +18,8 @@
 
 import android.cts.util.TimeoutReq;
 
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsAndroidTestCase;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -96,10 +97,11 @@
         for (int i = 0; i < numberRepeat; i++) {
             result[i] = CpuNative.runSort(arrayLength, numberRepeatInEachCall);
         }
-        getReportLog().printArray("ms", result, false);
+        getReportLog().printArray("sorting time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(result);
-        getReportLog().printSummary("ms", stat.mAverage, PerfResultType.LOWER_BETTER,
-                stat.mStddev);
+        getReportLog().printSummary("sorting time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 
     /**
@@ -114,10 +116,11 @@
         for (int i = 0; i < numberRepeat; i++) {
             result[i] = CpuNative.runMatrixMultiplication(n, numberRepeatInEachCall);
         }
-        getReportLog().printArray("ms", result, false);
+        getReportLog().printArray("matrix mutiplication time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(result);
-        getReportLog().printSummary("ms", stat.mAverage, PerfResultType.LOWER_BETTER,
-                stat.mStddev);
+        getReportLog().printSummary("matrix mutiplication time", stat.mAverage,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
     }
 
 }
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java
index d6d5f06..a2fd8d9 100644
--- a/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/FpsTest.java
@@ -22,7 +22,8 @@
 import android.view.Display;
 import android.view.WindowManager;
 
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsActivityInstrumentationTestCase2;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -118,10 +119,13 @@
             }
         }
         Log.i(TAG, " fps nominal " + fpsNominal + " fps measured " + fpsMeasured);
-        getReportLog().printArray("intervals ms", intervals, false);
-        getReportLog().printArray("jankiness ms", jankiness, false);
-        getReportLog().printSummaryFull("Frame interval",
-                "max delay ms", maxDelay, PerfResultType.LOWER_BETTER,
-                "number of jank", jankNumber, PerfResultType.LOWER_BETTER);
+        getReportLog().printArray("intervals", intervals, ResultType.NEUTRAL,
+                ResultUnit.MS);
+        getReportLog().printArray("jankiness", jankiness, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        getReportLog().printValue("number of jank", jankNumber, ResultType.LOWER_BETTER,
+                ResultUnit.COUNT);
+        getReportLog().printSummary("max jank delay", maxDelay, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 }
diff --git a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
index 5324669..9fd43de 100644
--- a/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
+++ b/suite/pts/deviceTests/ui/src/com/android/pts/ui/ScrollingTest.java
@@ -18,7 +18,8 @@
 import android.cts.util.TimeoutReq;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsActivityInstrumentationTestCase2;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
@@ -66,9 +67,10 @@
                 assertTrue(activity.scrollToTop());
             }
         });
-        getReportLog().printArray("ms", results, false);
+        getReportLog().printArray("scrolling time", results, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(results);
-        getReportLog().printSummary("Time ms", stat.mAverage, PerfResultType.LOWER_BETTER,
-                stat.mStddev);
+        getReportLog().printSummary("scrolling time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 }
diff --git a/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java b/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
index 2e99bb8..dd7b834 100644
--- a/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
+++ b/suite/pts/hostTests/bootup/src/com/android/pts/bootup/BootupTimeTest.java
@@ -19,7 +19,8 @@
 import android.cts.util.TimeoutReq;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
 import com.android.pts.util.Stat.StatResult;
@@ -66,9 +67,11 @@
                 rebootDevice();
             }
         });
-        mReport.printArray("time in ms", result, false);
+        mReport.printArray("bootup time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         StatResult stat = Stat.getStat(result);
-        mReport.printSummary("time in ms", stat.mAverage, PerfResultType.LOWER_BETTER, stat.mStddev);
+        mReport.printSummary("bootup time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 
     private void rebootDevice() throws DeviceNotAvailableException {
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/benchmarks.json b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/benchmarks.json
new file mode 100644
index 0000000..74628cd
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/benchmarks.json
@@ -0,0 +1,359 @@
+var ROBOHORNET_DATA = {
+  /**
+   * Benchmark version.
+   *
+   * @type {string}
+   */
+  version: 'RH-A1',
+
+  /**
+   * List of product, application and library tags.
+   *
+   * name: Short human readable name of tag.
+   */
+  productTags: {
+    G_SPREADSHEETS: {
+      name: 'Google Spreadsheets'
+    },
+    G_PRESENTATIONS: {
+      name: 'Google Presentations'
+    },
+    G_MAPS: {
+      name: 'Google Maps'
+    },
+    YUI: {
+      name: 'YUI'
+    },
+    JQUERY: {
+      name: 'jQuery'
+    },
+    EMBER: {
+      name: 'Ember'
+    },
+    HANDLEBARS: {
+      name: 'Handlebars'
+    },
+    METAMORPH: {
+      name: 'Metamorph.js'
+    },
+    CAPPUCCINO: {
+      name: 'Cappuccino'
+    }
+  },
+
+
+  /**
+   * List of technology tags.
+   *
+   * name: Short human readable name of tag.
+   */
+  technologyTags: {
+    TABLE: {
+      name: 'Table'
+    },
+    DOM: {
+      name: 'DOM'
+    },
+    CSS_SELECTORS: {
+      name: 'CSS Selectors'
+    },
+    CANVAS: {
+      name: 'Canvas'
+    },
+    SCROLLING: {
+      name: 'Scrolling'
+    },
+    SVG: {
+      name: 'SVG'
+    },
+    JS: {
+      name: 'Javascript'
+    },
+    MATH: {
+      name: 'Math'
+    }
+  },
+
+  /**
+   * List of benchmarks. Array of objects.
+   *
+   * name:         Short human readable name of benchmark.
+   * description:  Description of benchmark.
+   * filename:     Filename of benchmark, each benchmark file must implement a test
+   *               function and may implement a setUp and tearDown function.
+   * runs:         List of parameters to feed to test, setUp and tearDown
+   *               functions. For each entry a test run is constructed and the
+   *               parameter in the second field is fed to the test function.
+   *               The first field is a short human readable description of the
+   *               parameter.
+   * weight:       Weight of test as relative to the other tests in the file.
+   *               A percentage weight will be computed based on the relative
+   *               weight of each benchmark.
+   * baselineTime: Baseline time, in milliseconds.
+   */
+  benchmarks: [
+    {
+      name: 'Add Rows to Table',
+      issueNumber: 10,
+      description: 'Tests adding rows to an existing table',
+      filename: 'tests/addrow.html',
+      runs: [
+        ['250 rows',   250],
+        ['1000 rows', 1000]
+      ],
+      weight: 2,
+      baselineTime: 49.68,
+      tags: ['G_SPREADSHEETS', 'TABLE', 'DOM', 'YUI', 'JQUERY', 'EMBER'],
+      extended: false
+    },
+
+    {
+      name: 'Add Columns to Table',
+      issueNumber: 11,
+      description: 'Tests adding columns to an existing table',
+      filename: 'tests/addcol.html',
+      runs: [
+        ['250 columns',   250],
+        ['1000 columns', 1000]
+      ],
+      weight: 1.5,
+      baselineTime: 45.29,
+      tags: ['G_SPREADSHEETS', 'TABLE', 'DOM', 'YUI'],
+      extended: false
+    },
+
+    {
+      name: 'Descendant Selector',
+      issueNumber: 12,
+      description: 'Tests descendant selectors at different DOM depths',
+      filename: 'tests/descendantselector.html',
+      runs: [
+        ['1000 nodes deep', 1000]
+      ],
+      weight: 2,
+      baselineTime: 87.19,
+      tags: ['DOM', 'CSS_SELECTORS', 'YUI'],
+      extended: false
+    },
+
+    {
+      name: '2D Canvas toDataURL',
+      issueNumber: 14,
+      description: 'Test converting a 2D canvas to a data URI',
+      filename: 'tests/canvastodataurl.html',
+      runs: [
+        ['256x256, 1000 lines', [256, 1000]],
+        ['512x512, 1000 lines', [512, 1000]]
+      ],
+      weight: 2,
+      baselineTime: 304.56,
+      tags: ['CANVAS', 'G_PRESENTATIONS'],
+      extended: false
+    },
+
+    {
+      name: '2D Canvas clearRect',
+      issueNumber: 43,
+      description: 'Test clearing a 2D canvas',
+      filename: 'tests/canvasclear.html',
+      runs: [
+        ['256x256', 256],
+        ['512x512', 512]
+      ],
+      weight: 2,
+      baselineTime: 16.73,
+      tags: ['CANVAS', 'G_PRESENTATIONS', 'CAPPUCCINO'],
+      extended: false
+    },
+
+    {
+      name: 'innerHTML Table',
+      issueNumber: 15,
+      description: 'Test table render speed after innerHTML.',
+      filename: 'tests/createtable.html',
+      runs: [
+        ['200x10', [200, 10]],
+        ['200x50', [200, 50]],
+        ['200x100', [200, 100]]
+      ],
+      weight: 2,
+      baselineTime: 283.53,
+      tags: ['DOM', 'G_SPREADSHEETS', 'TABLE', 'YUI', 'JQUERY', 'EMBER'],
+      extended: false
+    },
+
+    {
+      name: 'Table scrolling',
+      issueNumber: 16,
+      description: 'Test scrolling speed using scrollTop',
+      filename: 'tests/table_scrolltop.html',
+      runs: [
+        ['500x10', [500, 10]],
+        ['500x50', [500, 50]],
+        ['1000,10', [1000, 10]],
+        ['1000,50', [1000, 50]]
+      ],
+      weight: 2,
+      baselineTime: 964.37,
+      tags: ['DOM', 'G_SPREADSHEETS', 'TABLE', 'SCROLLING', 'YUI'],
+      extended: false
+    },
+
+    {
+      name: 'Resize columns',
+      issueNumber: 17,
+      description: 'Test resizing columns in a table',
+      filename: 'tests/resizecol.html',
+      runs: [
+        ['500x10', [500, 10]],
+        ['500x50', [500, 50]]
+      ],
+      weight: 2,
+      baselineTime: 2739.90,
+      tags: ['DOM', 'TABLE', 'G_SPREADSHEETS', 'YUI'],
+      extended: false
+    },
+
+    {
+      name: 'SVG resize',
+      issueNumber: 18,
+      description: 'Test resizing SVGs',
+      filename: 'tests/svgresize.html',
+      runs: [
+        ['50 SVGs', 50],
+        ['100 SVGs', 100]
+      ],
+      weight: 2,
+      baselineTime: 502.73,
+      tags: ['SVG', 'G_PRESENTATIONS', 'YUI'],
+      extended: false
+    },
+
+    {
+      name: 'Object Scope Access',
+      issueNumber: 31,
+      description: 'Test different methods to access variables from an object',
+      filename: 'tests/object_scope.html',
+      runs: [
+        ['100,000 runs', 100000],
+        ['1,000,000 runs', 1000000],
+        ['10,000,000 runs', 10000000]
+      ],
+      weight: 1,
+      baselineTime: 100.0,
+      tags: ['JS'],
+      extended: false
+    },
+
+    {
+      name: 'ES5 Property Accessors',
+      issueNumber: 19,
+      description: 'Test ES5 getter/setters',
+      filename: 'tests/property_accessors.html',
+      runs: [
+        ['Getter', 'GET'],
+        ['Setter', 'SET'],
+        ['Combined', '']
+      ],
+      weight: 1,
+      baselineTime: 89.57,
+      tags: ['JS', 'EMBER'],
+      extended: false
+    },
+
+    {
+      name: 'Argument instantiation',
+      issueNumber: 21,
+      description: 'Test referencing the arguments array',
+      filename: 'tests/varargs.html',
+      runs: [
+        ['100 Instantiations', 100],
+        ['1000 Instantiations', 1000],
+        ['1000000 Instantiations', 1000000]
+      ],
+      weight: 2,
+      baselineTime: 315.13,
+      tags: ['JS', 'YUI', 'JQUERY', 'EMBER', 'CAPPUCCINO'],
+      extended: false
+    },
+
+    {
+      name: 'Animated GIFS',
+      issueNumber: 22,
+      description: 'Test scrolling lots of animated GIFs',
+      filename: 'tests/animated_gifs.html',
+      runs: [
+        ['20x10 GIFs', [20, 10]],
+        ['100x10 GIFs', [100, 10]],
+        ['100x100 GIFs', [100, 100]]
+      ],
+      weight: 0.25,
+      baselineTime: 127.69,
+      tags: ['DOM', 'SCROLLING'],
+      extended: false
+    },
+
+    {
+      name: 'offsetHeight triggers reflow',
+      issueNumber: 30,
+      description: 'Test the affect of forcing a reflow by calling offsetHeight',
+      filename: 'tests/offsetreflow.html',
+      runs: [
+        ['100 Reflows', 100],
+        ['1000 Reflows', 1000],
+        ['10000 Reflows', 10000]
+      ],
+      weight: 3,
+      baselineTime: 723.69,
+      tags: ['DOM', 'G_SPREADSHEETS', 'YUI', 'JQUERY', 'EMBER'],
+      extended: false
+    },
+
+    {
+      name: 'DOM Range API',
+      issueNumber: 9,
+      description: 'Test replacing a number of DOM nodes using the Range API',
+      filename: 'tests/range.html',
+      runs: [
+        ['50 Nodes', 50],
+        ['100 Nodes', 100]
+      ],
+      weight: 1,
+      baselineTime: 103.79,
+      tags: ['DOM', 'YUI', 'METAMORPH'],
+      extended: false
+    },
+
+    {
+      name: 'Write to localStorage',
+      issueNumber: 23,
+      description: 'Test the localStorage write performance',
+      filename: 'tests/localstorage_write.html',
+      runs: [
+        ['50 Writes', 50],
+        ['100 Writes', 100],
+        ['1000 Writes', 1000]
+      ],
+      weight: 2,
+      baselineTime: 49.84,
+      tags: ['JS', 'YUI'],
+      extended: false
+    },
+
+    {
+      name: 'Read from localStorage',
+      issueNumber: 24,
+      description: 'Test the localStorage read performance',
+      filename: 'tests/localstorage_read.html',
+      runs: [
+        ['50 Reads', 50],
+        ['100 Reads', 100],
+        ['1000 Reads', 1000]
+      ],
+      weight: 2,
+      baselineTime: 33.93,
+      tags: ['JS', 'YUI'],
+      extended: false
+    }
+  ]
+};
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Black.ttf b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Black.ttf
new file mode 100755
index 0000000..6848db0
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Black.ttf
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Black.woff b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Black.woff
new file mode 100644
index 0000000..5282c2e
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Black.woff
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Bold.ttf b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Bold.ttf
new file mode 100755
index 0000000..7434369
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Bold.ttf
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Bold.woff b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Bold.woff
new file mode 100644
index 0000000..68729b7
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Bold.woff
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-BoldItalic.woff b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-BoldItalic.woff
new file mode 100644
index 0000000..6f396fc
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-BoldItalic.woff
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Hairline.woff b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Hairline.woff
new file mode 100644
index 0000000..2ed3609
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Hairline.woff
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Italic.woff b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Italic.woff
new file mode 100644
index 0000000..03c8be2
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Italic.woff
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Regular.ttf b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Regular.ttf
new file mode 100755
index 0000000..04ea8ef
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Regular.ttf
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Regular.woff b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Regular.woff
new file mode 100644
index 0000000..26a056b
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/fonts/Lato-Regular.woff
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/icon.png b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/icon.png
new file mode 100644
index 0000000..961dc75
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/icon.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/main.css b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/main.css
new file mode 100644
index 0000000..d44c922
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/main.css
@@ -0,0 +1,557 @@
+/*
+ * Orange: #F05A2A
+ * Light-Orange: #F6943D
+ * Yellow: #FCCF4D
+ * Light-Yellow: #FDE7A7
+ * Very-Light-Yellow: #FEF1CA
+ * Dark-Gray: #454545
+ * Light-Gray: #AAAAAA;
+ */
+
+@font-face {
+  font-family: 'Lato';
+  font-style: italic;
+  font-weight: 400;
+  src: local('Lato Italic'), local('Lato-Italic'), url('fonts/Lato-Italic.woff') format('woff');
+}
+@font-face {
+  font-family: 'Lato';
+  font-style: italic;
+  font-weight: 700;
+  src: local('Lato Bold Italic'), local('Lato-BoldItalic'), url('fonts/Lato-BoldItalic.woff') format('woff');
+}
+@font-face {
+  font-family: 'Lato';
+  font-style: normal;
+  font-weight: 700;
+  src: local('Lato Bold'), local('Lato-Bold'), url('fonts/Lato-Bold.woff') format('woff');
+}
+@font-face {
+  font-family: 'Lato';
+  font-style: normal;
+  font-weight: 400;
+  src: local('Lato Regular'), local('Lato-Regular'), url('fonts/Lato-Regular.woff') format('woff');
+}
+@font-face {
+  font-family: 'Lato';
+  font-style: normal;
+  font-weight: 100;
+  src: local('Lato Hairline'), local('Lato-Hairline'), url('fonts/Lato-Hairline.woff') format('woff');
+}
+@font-face {
+  font-family: 'Lato';
+  font-style: normal;
+  font-weight: 900;
+  src: local('Lato Black'), local('Lato-Black'), url('fonts/Lato-Black.woff') format('woff');
+}
+
+html {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+}
+
+body {
+  margin: 0;
+  height: 100%;
+  font-family: Lato, Tahoma, sans-serif;
+  color: #454545;
+  background-color: #FEF1CA;
+  background-image: -webkit-linear-gradient(top, #FDE7A7, #FEF1CA 300px);
+  background-image: -moz-linear-gradient(top, #FDE7A7, #FEF1CA 300px);
+  background-image: -o-linear-gradient(top, #FDE7A7, #FEF1CA 300px);
+  background-repeat: no-repeat;
+  font-size: 14px;
+  overflow-x: hidden;
+}
+
+table {
+  border-spacing: 0;
+}
+
+th, td {
+  padding: 0 1ex;
+}
+
+thead small {
+  padding-left: 1em;
+  font-size: x-small;
+}
+
+a {
+  color: inherit;
+}
+
+#sidebar {
+  float: left;
+  width: 300px;
+  margin-left: 15px;
+  margin-right: 10px;
+}
+
+#test-container {
+  margin: 0 auto;
+  width: 1050px;
+}
+
+#iframe-wrap {
+  height: 300px;
+  margin: 9px 23px 0 0;
+  overflow: hidden;
+  width: 300px;
+  display: inline-block;
+  vertical-align: top;
+}
+
+iframe {
+  height: 300px;
+  width: 300px;
+}
+
+#tests-wrap {
+  display: inline-block;
+}
+
+#tests > thead > tr > td {
+  border-bottom: 1px solid #454545;
+}
+
+#tests tr.summary-row td {
+  border-top: 1px solid #454545;
+}
+
+#tests tr.summary-row td:first-child {
+  text-align: right;
+}
+
+#tests tr.summary-row td.final {
+  font-weight: bold;
+}
+
+#tests > tbody > tr > td {
+  font-weight: normal;
+}
+
+#tests > tbody > tr > td.name {
+  padding-left: 2ex;
+}
+
+#tests > thead > tr > td.number, #tests > tbody > tr > td.number {
+  width: 10ex;
+}
+
+#tests > tbody > tr > td.details {
+  border-top: none;
+  padding: 0.5ex 5ex 2ex 5ex;
+}
+
+#tests > tbody > tr.disabled {
+  color: rgba(69, 69, 69, 0.5);
+}
+
+#tests > tbody > tr td > span {
+  font-size: 16px;
+  margin-left: 16px;
+  font-weight: bold;
+  cursor: pointer;
+  position: relative;
+  padding-left: 19px;
+}
+
+#tests > tbody > tr td > span:hover {
+ color: black;
+}
+
+#tests > tbody > tr td > span::before {
+  content: "\25BA";
+  font-size: 10px;
+  margin-right: 10px;
+  position: absolute;
+  top: 4px;
+  left: 0;
+  cursor: pointer;
+  display: inline-block;
+}
+
+#tests > tbody > tr.expanded td > span::before {
+  -webkit-transform: rotate(90deg);
+  -moz-transform: rotate(90deg);
+  -ms-transform: rotate(90deg);
+  -o-transform: rotate(90deg);
+  transform: rotate(90deg);
+}
+
+div[id^=tags] {
+  text-align: center;
+}
+
+.details > div {
+  font-size: 14px;
+  border: 1px dotted #FCCF4D;
+  background: #FDE7A7;
+}
+
+tr.details td {
+  display: none;
+}
+
+tr.details.expanded td {
+  display: table-cell;
+}
+
+.details > div.disabled {
+  opacity: 0.5;
+}
+
+.runs {
+  margin-top: 0.5ex;
+  table-layout: fixed;
+}
+
+.runs > thead > tr > td.number, .runs > tbody > tr > td.number {
+  width: 10ex;
+}
+
+.runs > thead > tr > td.name, .runs > tbody > tr > td.name {
+  width: 30ex;
+}
+
+.runs > tbody > tr > td {
+  font-weight: normal;
+  border-top:1px dotted #FCCF4D;
+}
+
+td.number {
+  text-align: right;
+}
+
+.tag {
+  display: inline-block;
+  background-color: #FCCF4D;
+  color: #FEF1CA;
+  border-radius: 3px;
+  padding: 2px;
+  margin: 2px;
+  cursor: pointer;
+}
+
+.tag.technology {
+  background-color: #F6943D;
+}
+
+.tag.special {
+  background-color: #AAAAAA;
+}
+
+.tag.inactive {
+  opacity: 0.2;
+}
+
+.tag.partially-inactive {
+  opacity: 0.6;
+}
+
+.tag:hover {
+  opacity: 1.0;
+  color: #454545;
+}
+
+#console {
+  color: silver;
+  font-size: 10px;
+  overflow-y: auto;
+  min-height: 200px;
+}
+
+#testSection {
+  clear: both;
+}
+
+#testFrame {
+  width: 300px;
+  height: 300px;
+  border: 1px solid silver;
+}
+
+h2 {
+  font-size: 14px;
+  font-weight: normal;
+  margin: 0;
+}
+
+h1 {
+  font-size: 36px;
+  margin: 0 10px 0 0;
+  display:inline;
+}
+
+header {
+  border-bottom: 1px solid #454545;
+  margin-bottom: 15px;
+  padding:15px;
+  position: relative;
+}
+
+header .logo {
+  display: inline-block;
+}
+
+header .logo h1 {
+  /* Necessary for positioning of "ALPHA" tag */
+  position: relative;
+}
+
+header .logo h1::after {
+  content: "ALPHA";
+  font-size: 14px;
+  font-weight:normal;
+  position:absolute;
+  bottom: -10px;
+  right: 0px;
+  color: #F05A2A;
+}
+
+header img {
+  height: 40px;
+  width: 40px;
+}
+
+#progress {
+  position: absolute;
+  background-color: #F05A2A;
+  top: 0;
+  left: 0;
+  height: 100%;
+  width: 100%;
+  z-index: -1;
+  margin-left: -100%;
+  -webkit-transition: opacity 3s ease;
+  -moz-transition: opacity 3s ease;
+  -o-transition: opacity 3s ease;
+  transition: opacity 3s ease;
+}
+
+div.results {
+  margin: 120px 0;
+  text-align: center;
+  width: 100%;
+  -webkit-transition: .3s ease;
+  -moz-transition: .3s ease;
+  -o-transition: .3s ease;
+  transition: .3s ease;
+}
+
+.running div.results {
+  margin: 0;
+}
+
+header > div > div {
+
+}
+
+.results div {
+  color: #454545;
+  font-weight: bold;
+  font-size: 108px;
+  font-style: normal;
+}
+
+#index-prefix {
+  color: #FCCF4D;
+  font-weight: 100;
+}
+
+#index {
+  opacity: 0.1;
+  -webkit-transition: opacity 2s;
+  -moz-transition: opacity 2s;
+  -o-transition: opacity 2s;
+  transition: opacity 2s;
+}
+
+#index.final {
+  opacity: 1.0;
+}
+
+#runButton {
+  float: right;
+  margin: 0 130px 0 10px;
+  padding: 5px;
+  border-radius: 5px;
+  background-color: #F05A2A;
+  background-image: -webkit-linear-gradient(top, #F05A2A, #F6943D);
+  background-image: -moz-linear-gradient(top, #F05A2A, #F6943D);
+  background-image: -o-linear-gradient(top, #F05A2A, #F6943D);
+  background-image: linear-gradient(top, #F05A2A, #F6943D);
+  color: #FDE7A7;
+  font-size: 26px;
+  /* Large enough so the size doesn't change for any of the messages */
+  width:175px;
+  border: 1px solid #FCCF4D;
+  cursor: pointer;
+  box-shadow: 0px 2px 5px 0px #454545;
+  -webkit-animation-name: button-flourish;
+  -webkit-animation-duration: 2s;
+  -webkit-animation-iteration-count: 1;
+  -webkit-animation-timing-function: ease;
+  /* we ideally want to flourish just as the logo crosses on load. */
+  -webkit-animation-delay: 1s;
+
+  -moz-animation-name: button-flourish;
+  -moz-animation-duration: 2s;
+  -moz-animation-iteration-count: 1;
+  -moz-animation-timing-function: ease;
+  /* we ideally want to flourish just as the logo crosses on load. */
+  -moz-animation-delay: 1s;
+
+  -o-animation-name: button-flourish;
+  -o-animation-duration: 2s;
+  -o-animation-iteration-count: 1;
+  -o-animation-timing-function: ease;
+  /* we ideally want to flourish just as the logo crosses on load. */
+  -o-animation-delay: 1s;
+
+  animation-name: button-flourish;
+  animation-duration: 2s;
+  animation-iteration-count: 1;
+  animation-timing-function: ease;
+  /* we ideally want to flourish just as the logo crosses on load. */
+  animation-delay: 1s;
+}
+
+#runButton:active {
+  background-image: -webkit-linear-gradient(top, #F6943D, #F05A2A);
+  background-image: -moz-linear-gradient(top, #F6943D, #F05A2A);
+  background-image: -o-linear-gradient(top, #F6943D, #F05A2A);
+  background-image: linear-gradient(top, #F6943D, #F05A2A);
+}
+
+#runButton:hover {
+  color:#FCCF4D;
+}
+
+#runButton:disabled {
+  opacity: 0.6;
+  color: #FCCF4D;
+}
+
+#status {
+  font-size: 14px;
+  text-align: center;
+  color: #666666;
+  font-style: italic;
+  font-weight: normal;
+}
+
+#results > thead > tr > td {
+  border-bottom: 1px solid black;
+}
+
+header img {
+  -webkit-transform-origin: 30% 65%;
+  -moz-transform-origin: 30% 65%;
+  -o-transform-origin: 30% 65%;
+  transform-origin: 30% 65%;
+  /* Trigger compositing now so we don't get a flash when it does */
+  -webkit-transform: translate(0,0,0);
+  /* Show over top of the Run button */
+  z-index:1;
+}
+
+body.ready header img {
+  -webkit-animation-name: logo-ready;
+  -webkit-animation-duration: 4s;
+  -webkit-animation-iteration-count: 1;
+  -webkit-animation-timing-function: ease;
+
+  -moz-animation-name: logo-ready;
+  -moz-animation-duration: 4s;
+  -moz-animation-iteration-count: 1;
+  -moz-animation-timing-function: ease;
+}
+
+@-webkit-keyframes logo-ready {
+  from {
+    -webkit-transform: translate(0, 0);
+  }
+  10% {
+    -webkit-transform: translate(0, 0) rotate(34deg);
+  }
+  17% {
+    -webkit-transform: translate(-50%, 0) rotate(45deg);
+  }
+  50% {
+    -webkit-transform: translate(10000%, 0) rotate(45deg);
+    opacity: 1.0;
+  }
+  51% {
+    opacity: 0.0;
+  }
+  52% {
+    -webkit-transform: translate(0, 0);
+  }
+  to {
+    opacity: 1.0;
+  }
+}
+
+@-moz-keyframes logo-ready {
+  from {
+    -moz-transform: translate(0, 0);
+  }
+  10% {
+    -moz-transform: translate(0, 0) rotate(34deg);
+  }
+  17% {
+    -moz-transform: translate(-50%, 0) rotate(45deg);
+  }
+  50% {
+    -moz-transform: translate(10000%, 0) rotate(45deg);
+    opacity: 1.0;
+  }
+  51% {
+    opacity: 0.0;
+  }
+  52% {
+    -moz-transform: translate(0, 0);
+  }
+  to {
+    opacity: 1.0;
+  }
+}
+
+@-webkit-keyframes button-flourish {
+  from {
+    -webkit-transform: scale(1.0);
+  }
+  10% {
+    -webkit-transform: scale(1.15);
+  }
+  to {
+    -webkit-transform: scale(1.0);
+  }
+}
+
+@-moz-keyframes button-flourish {
+  from {
+    -moz-transform: scale(1.0);
+  }
+  10% {
+    -moz-transform: scale(1.15);
+  }
+  to {
+    -moz-transform: scale(1.0);
+  }
+}
+
+footer #fork {
+  -webkit-animation: button-flourish 2s ease 1.02s 1;
+     -moz-animation: button-flourish 2s ease 1.02s 1;
+       -o-animation: button-flourish 2s ease 1.02s 1;
+          animation: button-flourish 2s ease 1.02s 1;
+
+  position: absolute;
+  top: 0;
+  right: 0;
+  border: 0;
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.html
new file mode 100644
index 0000000..801714e
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.html
@@ -0,0 +1,84 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <title>RoboHornet</title>
+        <meta name="viewport" content="width=1024">
+        <script>
+          if (document.location.host.toLowerCase().indexOf('robohornet.com') != -1)
+            document.location.replace('http://www.robohornet.org');
+        </script>
+        <script src="third_party/benchmark.js/benchmark.js"></script>
+        <script src="third_party/classList.js/classList.min.js"></script>
+        <script src="robohornet.js"></script>
+        <script src="benchmarks.json"></script>
+        <link href="main.css" rel="stylesheet" type="text/css">
+        <link rel="icon" href="icon.png" type="image/png">
+    </head>
+    <body>
+        <header>
+          <div>
+            <div class='logo'>
+              <h1>RoboHornet</h1><img src='robohornet.png'>
+            </div>
+            <button id="runButton" onclick="__robohornet__.run()" disabled="true">Loading...</button>
+          </div>
+          <div class='results'>
+              <div>
+                  <span id="index-prefix">RH:</span><span id='index'>000.00</span>
+              </div>
+              <div id="status">
+                  RoboHornet is a benchmark designed around performance pain points real web developers care about.
+                  <br>
+                  <a href="javascript:__robohornet__.run()">Run the test</a> to see the index or <a href='https://github.com/robohornet/robohornet/blob/master/CONTRIBUTING.md' target="_blank">learn how to participate</a>.
+              </div>
+          </div>
+          <div id='progress'></div>
+        </header>
+
+        <footer>
+          <a href="https://github.com/robohornet/robohornet"><img id="fork" src="https://s3.amazonaws.com/github/ribbons/forkme_right_orange_ff7600.png" alt="Fork me on GitHub"></a>
+        </footer>
+
+        <section>
+            <div id="tags-special"><span>Suite: </span></div>
+            <div id="tags-technology"><span>Technology: </span></div>
+            <div id="tags-app"><span>Affects: </span></div>
+            <div id="test-container">
+              <div id="iframe-wrap">
+              </div>
+              <div id="tests-wrap">
+                <table id="tests">
+                  <thead>
+                      <td>Benchmark</td>
+                      <td>Status</td>
+                      <td class="number">Time</td>
+                      <td class="number">Baseline</td>
+                      <td class="number">Weight</td>
+                      <td class="number">Index</td>
+                  </thead>
+                  <tbody></tbody>
+                </table>
+              </div>
+            </div>
+        </section>
+
+        <script>
+            var __robohornet__ = new robohornet.Runner(ROBOHORNET_DATA);
+            //PTS, auto start
+            window.setTimeout(function() { __robohornet__.run(); }, 500);
+        </script>
+        <script type="text/javascript">
+
+          var _gaq = _gaq || [];
+          _gaq.push(['_setAccount', 'UA-34237734-1']);
+          _gaq.push(['_trackPageview']);
+
+          (function() {
+            var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
+            ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
+            var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
+          })();
+
+        </script>
+    </body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.js
new file mode 100644
index 0000000..b29fdea
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.js
@@ -0,0 +1,809 @@
+var robohornet = {};
+
+robohornet.Status = {
+  LOADING: 0,
+  READY: 1,
+  RUNNING: 2
+};
+
+// Captions for the run button based on the robohornet.Status enum.
+robohornet.RunButtonCaption = {
+  0: 'Loading...',
+  1: 'Run',
+  2: 'Running...'
+};
+
+robohornet.enabledBenchmarks = {
+  SUCCESS: 0,
+  LOADING: 1,
+  RUNNING: 2,
+  PENDING: 3,
+  LOAD_FAILED: 4,
+  RUN_FAILED: 5,
+  SKIPPED: 6,
+  POPUP_BLOCKED: 7,
+  ABORTED: 8
+};
+
+// Descriptions for the robohornet.enabledBenchmarks enum.
+robohornet.enabledBenchmarksDescription = {
+  0: 'Completed successfully',
+  1: 'Loading...',
+  2: 'Running...',
+  3: 'Pending',
+  4: 'Failed to load',
+  5: 'Failed to run',
+  6: 'Skipped',
+  7: 'Benchmark window blocked',
+  8: 'Aborted by user'
+};
+
+robohornet.TagType = {
+  SPECIAL: 'special',
+  TECHNOLOGY: 'technology',
+  APP: 'app'
+};
+
+
+/**
+ * Class representing a the RoboHornet test runner.
+ *
+ * @param {string} version String describing this version of the benchmark.
+ * @param {Array.<Object>} benchmarks Array of benchmark json objects.
+ * @constructor
+ */
+robohornet.Runner = function(data) {
+  this.testsContainer = document.getElementById('tests');
+  this.statusElement_ = document.getElementById('status');
+  this.runElement_ = document.getElementById('runButton');
+  this.progressElement_ = document.getElementById('progress');
+  this.indexElement_ = document.getElementById('index');
+  this.tagsSpecialElement_ = document.getElementById('tags-special');
+  this.tagsTechElement_ = document.getElementById('tags-technology');
+  this.tagsAppElement_ = document.getElementById('tags-app');
+
+  document.getElementById('index-prefix').textContent = data.version + ':';
+
+  this.hasExtendedBenchmark_ = false;
+
+  this.initTags_(data.technologyTags, data.productTags);
+  this.initBenchmarks_(data.benchmarks);
+  this.initTagUi_();
+  this.initBenchmarkUi_();
+  this.digestHash_();
+
+  this.setStatus_(robohornet.Status.READY);
+
+  this.progressCallback_ = bind(this.progressTransitionDone_, this);
+
+  window.addEventListener('unload', bind(this.onWindowUnload_, this), false);
+};
+
+(function() {
+
+  var IS_WEBKIT = window.navigator.userAgent.indexOf('WebKit') != -1;
+
+  var TRANSITION_END_EVENT = IS_WEBKIT ? 'webkitTransitionEnd' : 'transitionend';
+
+  var requestAnimationFrameFunction = window.mozRequestAnimationFrame ||
+      window.msRequestAnimationFrame ||
+      window.webkitRequestAnimationFrame ||
+      window.oRequestAnimationFrame;
+
+  function formatNumber(n, minWidth, decimalPlaces) {
+    return Array(Math.max((minWidth + 1) - n.toFixed(0).length, 0)).join('0') +
+            n.toFixed(decimalPlaces);
+  }
+
+  function enableClass(element, className, enable) {
+    if (enable)
+      element.classList.add(className);
+    else
+      element.classList.remove(className);
+  }
+
+  function createElement(tagName, textContent, opt_className) {
+    var element = document.createElement(tagName);
+    if (opt_className)
+      element.className = opt_className;
+    element.appendChild(document.createTextNode(textContent));
+    return element;
+  }
+
+  function addChildren(element, children) {
+    for (var i = 0; i < children.length; i++) {
+      element.appendChild(children[i]);
+    }
+  }
+
+  function addToTag(benchmark, tag) {
+    benchmark.tags.push(tag);
+    tag.benchmarks.push(benchmark);
+  }
+
+  var _p = robohornet.Runner.prototype;
+
+  _p.initTagUi_ = function() {
+    for (var tag, i = 0; tag = this.tags_[i]; i++) {
+      tag.element = this.makeTagElement_(tag);
+      if (tag.type == robohornet.TagType.TECHNOLOGY) {
+        this.tagsTechElement_.appendChild(tag.element);
+      }
+      else if (tag.type == robohornet.TagType.APP) {
+        this.tagsAppElement_.appendChild(tag.element);
+      }
+      else {
+        if (tag.id == 'EXTENDED' && !this.hasExtendedBenchmark_)
+          continue;
+        this.tagsSpecialElement_.appendChild(tag.element);
+      }
+    }
+  };
+
+  _p.run = function() {
+    this.setStatus_(robohornet.Status.RUNNING);
+    this.currentIndex_ = -1;
+    this.score_ = 0;
+    this.rawScore_ = 0;
+    this.progressElement_.style.opacity = '0.1';
+    this.statusElement_.textContent = 'Please wait while the benchmark runs. For best results, close all other programs and pages while the test is running.';
+    window.setTimeout(bind(this.next_, this), 1000);
+  };
+
+  _p.next_ = function() {
+    var benchmark;
+    while (!benchmark) {
+      benchmark = this.benchmarks_[++this.currentIndex_];
+      if (!benchmark)
+        break;
+      if (!benchmark.enabled) {
+        this.setBenchmarkStatus_(benchmark, robohornet.enabledBenchmarks.SKIPPED);
+        benchmark = null;
+      }
+    }
+
+    var progressAmount = (this.currentIndex_ / this.benchmarks_.length) * 100;
+    this.progressElement_.style.marginLeft = '-' + (100 - progressAmount).toString() + '%';
+
+    this.activeBenchmark_ = benchmark;
+    if (benchmark) {
+      this.loadBenchmark_(benchmark);
+    } else {
+      this.done_();
+    }
+  };
+
+  _p.done_ = function() {
+    this.progressElement_.addEventListener(TRANSITION_END_EVENT, this.progressCallback_, false);
+    this.progressElement_.style.opacity = '0.0';
+    var skippedExtendedRuns = 0;
+
+    var successfulRuns = 0, failedRuns = 0, blockedRuns = 0;
+    for (var benchmark, i = 0; benchmark = this.benchmarks_[i]; i++) {
+      if (benchmark.status == robohornet.enabledBenchmarks.SUCCESS)
+        successfulRuns++;
+      else if (benchmark.extended)
+        skippedExtendedRuns++;
+      else if (benchmark.status == robohornet.enabledBenchmarks.POPUP_BLOCKED)
+        blockedRuns++;
+      else if (benchmark.status != robohornet.enabledBenchmarks.SKIPPED)
+        failedRuns++;
+    }
+
+    if (successfulRuns + skippedExtendedRuns == this.benchmarks_.length) {
+      this.setScore_(true /* opt_finalScore */);
+      this.statusElement_.innerHTML = 'The RoboHornet index is normalized to 100 and roughly shows your browser\'s performance compared to other modern browsers on reference hardware. <a href="https://github.com/robohornet/robohornet/wiki/Benchmark-Scoring" target="_blank">Learn more</a>';
+    } else if (blockedRuns) {
+      this.statusElement_.textContent = "Your browser's popup blocker prevented some of the benchmarks from running. Disable your popup blocker and run the test again to see the index.";
+    } else if (failedRuns) {
+      this.statusElement_.textContent = failedRuns + ' out of ' + this.benchmarks_.length + ' benchmark(s) failed.';
+    } else {
+      this.statusElement_.textContent = 'Ran ' + successfulRuns + ' out of ' + this.benchmarks_.length + ' benchmarks. Enable all benchmarks to compute the index.';
+    }
+    this.setStatus_(robohornet.Status.READY);
+  };
+
+  _p.progressTransitionDone_ = function() {
+    // Wait until the progress bar fades out to put it back to the left.
+    this.progressElement_.style.marginLeft = '-100%';
+    this.progressElement_.removeEventListener(TRANSITION_END_EVENT, this.progressCallback_, false);
+  };
+
+  _p.benchmarkLoaded = function() {
+    var benchmark = this.activeBenchmark_;
+    if (!benchmark)
+      return;
+
+    var self = this;
+    var suite = new Benchmark.Suite(this.name, {
+      onComplete: function() { self.onBenchmarkComplete_(this, benchmark); },
+      onAbort: function() { self.onBenchmarkAbort_(this, benchmark); }
+    });
+
+    var callFunction = function(win, fn, arg, deferred) {
+      win[fn] && win[fn].call(win, arg);
+      if (fn == 'setUp' && win['resetMathRandom'])
+        win['resetMathRandom']();
+      if (deferred)
+        deferred.resolve();
+    };
+
+    var callTest = function(win, arg, deferred) {
+      if (win['testAsync']) {
+        win['testAsync'].call(win, deferred, arg);
+      }
+      else if (win['test']) {
+        win['test'].call(win, arg);
+        if (deferred)
+          deferred.resolve();
+      }
+      else
+        this.abort();
+    };
+
+    var win = this.benchmarkWindow_;
+    for (var run, i = 0; run = benchmark.runs[i]; i++) {
+      var arg = run[1];
+      suite.add(run[0], {
+        defer: true,
+        fn: bind(callTest, suite, win, arg),
+        setup: bind(callFunction, suite, win, 'setUp', arg),
+        teardown: bind(callFunction, suite, win, 'tearDown', arg),
+        reset: bind(callFunction, suite, win, "reset", arg)
+      });
+    }
+
+    this.setBenchmarkStatus_(benchmark, robohornet.enabledBenchmarks.RUNNING);
+    suite.run(true);
+  };
+
+  _p.initTags_ = function(technologyTags, productTags) {
+    this.tags_ = [];
+    this.tagsById_ = {};
+
+    var f = function(tags, type) {
+      for (var id in tags) {
+        var tag = tags[id];
+        tag.id = id;
+        tag.type = type;
+        tag.benchmarks = [];
+        this.tags_.push(tag);
+        this.tagsById_[id] = tag;
+      }
+    };
+
+    var specialTags = {
+      CORE: { name: 'Core' },
+      EXTENDED: { name: 'Extended' },
+      NONE: { name: 'None' }
+    }
+
+    f.call(this, technologyTags, robohornet.TagType.TECHNOLOGY);
+    f.call(this, productTags, robohornet.TagType.APP);
+    f.call(this, specialTags, robohornet.TagType.SPECIAL);
+  };
+
+  _p.initBenchmarks_ = function(benchmarks) {
+    var totalWeight = 0;
+    var benchmark;
+
+    for (var details, i = 0; details = benchmarks[i]; i++) {
+      totalWeight += details.weight;
+    }
+
+    this.benchmarks_ = [];
+    this.benchmarksById_ = {};
+    for (var details, i = 0; details = benchmarks[i]; i++) {
+      benchmark = new robohornet.Benchmark(details);
+      benchmark.index = i;
+      benchmark.computedWeight = (benchmark.weight / totalWeight) * 100;
+
+      for (var tagId, j = 0; tagId = details.tags[j]; j++) {
+        addToTag(benchmark, this.tagsById_[tagId]);
+      }
+      if (!benchmark.extended)
+        addToTag(benchmark, this.tagsById_['CORE']);
+      addToTag(benchmark, this.tagsById_['EXTENDED']);
+
+      this.benchmarks_.push(benchmark);
+      this.benchmarksById_[benchmark.id] = benchmark;
+
+      if (benchmark.extended)
+        this.hasExtendedBenchmark_ = true;
+    }
+  };
+
+  _p.initBenchmarkUi_ = function() {
+    for (var benchmark, i = 0; benchmark = this.benchmarks_[i]; i++) {
+      this.registerBenchmark_(benchmark);
+    }
+    var finalRow = document.createElement("tr");
+    finalRow.className = "summary-row";
+    var cell = document.createElement("td");
+    cell.colSpan = 5;
+    cell.innerHTML = "<em>Raw score</em>";
+    finalRow.appendChild(cell);
+    cell = document.createElement("td");
+    cell.className = "number";
+    cell.textContent = "-";
+    finalRow.appendChild(cell);
+    this.rawScoreElement_ = cell;
+    this.testsContainer.tBodies[0].appendChild(finalRow);
+  };
+
+  _p.loadBenchmark_ = function(benchmark) {
+    if (this.benchmarkWindow_) {
+      this.benchmarkWindow_.close();
+      this.benchmarkWindow_ = null
+    }
+
+    this.setBenchmarkStatus_(benchmark, robohornet.enabledBenchmarks.LOADING);
+    this.activeBenchmark_ = benchmark;
+
+    // delay each execution to yield for UI
+    window.setTimeout(bind(function() {
+
+      // clear old test iframes
+      _p.clearTestIframes_();
+
+      // create fresh test environment iframe
+      var iframe = document.createElement('iframe');
+      document.getElementById('iframe-wrap').appendChild(iframe);
+      iframe.src = benchmark.filename + '?use_test_runner';
+      this.benchmarkWindow_ = iframe.contentWindow;
+
+      if (this.benchmarkWindow_) {
+        this.benchmarkWindow_.onload = function(){
+          if (this.benchmarkWindow_.innerHeight <= 0)
+            this.onPopupBlock_();
+        };
+      } else {
+          this.onPopupBlock_();
+      }
+    }, this), 25);
+  };
+
+  _p.clearTestIframes_ = function() {
+    var iframes = document.querySelectorAll('iframe');
+    [].forEach.call(iframes, function(elem){
+      elem.parentNode.removeChild(elem);
+    });
+  };
+
+
+  _p.onPopupBlock_ = function() {
+      // Reclaim window's name so we can use it again
+      this.benchmarkWindow_ && this.benchmarkWindow_.close();
+
+      this.setBenchmarkStatus_(this.activeBenchmark_, robohornet.enabledBenchmarks.POPUP_BLOCKED);
+      this.activeBenchmark_ = null;
+      window.setTimeout(bind(this.next_, this), 25);
+  };
+
+  _p.onBenchmarkAbort_ = function(suite, benchmark) {
+      if (benchmark.status == robohornet.enabledBenchmarks.ABORTED)
+        return;
+
+      this.setBenchmarkStatus_(benchmark, robohornet.enabledBenchmarks.ABORTED);
+      if (this.benchmarkWindow_)
+        this.benchmarkWindow_.close();
+      this.benchmarkWindow_ = null;
+      window.setTimeout(bind(this.next_, this), 250);
+  };
+
+  _p.onBenchmarkComplete_ = function(suite, benchmark) {
+    if (!this.benchmarkWindow_) {
+      this.onBenchmarkAbort_(suite, benchmark);
+      return;
+    }
+
+    _p.clearTestIframes_();
+
+    this.benchmarkWindow_ = null;
+
+    var results = [];
+    for (var run, i = 0; run = suite[i]; i++) {
+      results.push({
+        name: run.name,
+        mean: run.stats.mean * 1000,
+        rme: run.stats.rme,
+        runs: run.stats.sample.length
+      });
+    }
+    benchmark.results = results;
+    this.setBenchmarkStatus_(benchmark, robohornet.enabledBenchmarks.SUCCESS);
+    this.showBenchmarkResults_(benchmark);
+    window.setTimeout(bind(this.next_, this), 25);
+  };
+
+  _p.onBenchmarkToggle_ = function(benchmark, event) {
+    this.setBenchmarkEnabled_(benchmark, benchmark.toggleElement_.checked);
+    this.updateHash_();
+  }
+
+  _p.setBenchmarkEnabled_ = function(benchmark, enabled) {
+    benchmark.toggleElement_.checked = enabled;
+    benchmark.enabled = enabled;
+    enableClass(benchmark.detailsElement_, 'disabled', !benchmark.enabled);
+    enableClass(benchmark.summaryRow_, 'disabled', !benchmark.enabled);
+  }
+
+  _p.registerBenchmark_ = function(benchmark) {
+    var identifier = 'benchmark-' + benchmark.index;
+
+    // Append summary row.
+    var row = document.createElement('tr');
+    row.id = identifier;
+
+    var cell = document.createElement('td');
+    var checkbox = document.createElement('input');
+    checkbox.type = 'checkbox';
+    checkbox.id = identifier + '-toggle';
+    checkbox.checked = true;
+    checkbox.addEventListener('click', bind(this.onBenchmarkToggle_, this, benchmark), false);
+    cell.appendChild(checkbox);
+    benchmark.toggleElement_ = checkbox;
+
+    var label = document.createElement('span');
+    label.appendChild(document.createTextNode(benchmark.name));
+    label.addEventListener('click', bind(this.toggleBenchmarkDetails_, this, benchmark), false);
+    cell.appendChild(label);
+
+    row.appendChild(cell);
+
+    addChildren(row, [
+      createElement('td', '-'),
+      createElement('td', '-', 'number'),
+      createElement('td', benchmark.baselineTime.toFixed(2) + 'ms', 'number'),
+      createElement('td', benchmark.computedWeight.toFixed(2) + '%', 'number'),
+      createElement('td', '-', 'number')
+    ]);
+    this.testsContainer.tBodies[0].appendChild(row);
+    benchmark.summaryRow_ = row;
+
+    // Append details row.
+    row = document.createElement('tr');
+    cell = document.createElement('td');
+    cell.className = 'details';
+    cell.colSpan = 7;
+
+    var detailsElement = document.createElement('div');
+    detailsElement.className = '';
+    cell.appendChild(detailsElement);
+    detailsElement.appendChild(document.createTextNode(benchmark.description));
+    detailsElement.appendChild(document.createElement("br"));
+
+    var issueLink = document.createElement("a");
+    issueLink.href = "http://github.com/robohornet/robohornet/issues/" + benchmark.issueNumber;
+    issueLink.target = "_blank";
+    issueLink.appendChild(document.createTextNode("View issue details on GitHub"));
+    detailsElement.appendChild(issueLink);
+    detailsElement.appendChild(document.createElement("br"));
+
+    if (benchmark.extended)
+      detailsElement.appendChild(this.makeTagElement_(this.tagsById_['EXTENDED']));
+
+    for (var tag, i = 0; tag = benchmark.tags[i]; i++) {
+      if (tag.type != robohornet.TagType.SPECIAL)
+      detailsElement.appendChild(this.makeTagElement_(tag));
+    }
+
+    // Append list of runs/parameters.
+    var runsTable = document.createElement('table');
+    runsTable.id = identifier + '-runs';
+    runsTable.className = 'runs';
+    runsTable.appendChild(document.createElement('thead'));
+
+    var headerRow = document.createElement('tr');
+    addChildren(headerRow, [
+      createElement('td', 'Parameters'),
+      createElement('td', 'Runs', 'number'),
+      createElement('td', 'Error', 'number'),
+      createElement('td', 'Mean', 'number')
+    ]);
+    runsTable.tHead.appendChild(headerRow);
+
+    runsTable.appendChild(document.createElement('tbody'));
+    for (i = 0; i < benchmark.runs.length; i++) {
+      var runsRow = document.createElement('tr');
+      addChildren(runsRow, [
+        createElement('td', benchmark.runs[i][0], 'name'),
+        createElement('td', '0', 'number'),
+        createElement('td', '0', 'number'),
+        createElement('td', '0', 'number')
+      ]);
+      runsTable.tBodies[0].appendChild(runsRow);
+    }
+    detailsElement.appendChild(runsTable);
+    var linkElement = document.createElement('a');
+    linkElement.target = '_new';
+    linkElement.href = benchmark.filename;
+    linkElement.appendChild(document.createTextNode('Open test in new window'));
+    detailsElement.appendChild(linkElement);
+    benchmark.detailsElement_ = detailsElement;
+
+    row.appendChild(cell);
+    this.testsContainer.tBodies[0].appendChild(row);
+    row.className = 'details';
+  };
+
+  _p.showBenchmarkResults_ = function(benchmark) {
+    var results = benchmark.results;
+
+    var row = benchmark.summaryRow_;
+    row.cells[1].textContent = 'Computing Index...';
+
+    var accumulatedMean = 0;
+    var runsTable = document.getElementById(row.id + '-runs');
+    for (var result, i = 0; result = results[i]; i++) {
+      var runCells = runsTable.tBodies[0].rows[i].cells;
+      runCells[1].textContent = result.runs;
+      runCells[2].textContent = String.fromCharCode(177) +
+          result.rme.toFixed(2) + '%';
+      runCells[3].textContent = result.mean.toFixed(2) + 'ms';
+      accumulatedMean += result.mean;
+    }
+
+    var diff = accumulatedMean - benchmark.baselineTime;
+    var score = benchmark.baselineTime * benchmark.computedWeight / accumulatedMean;
+    this.score_ += score;
+    var rawScore = accumulatedMean * benchmark.computedWeight;
+    this.rawScore_ += rawScore;
+
+    this.setScore_();
+
+    row.cells[1].textContent = 'Completed';
+    row.cells[2].textContent = accumulatedMean.toFixed(2) + 'ms';
+    row.cells[5].textContent = score.toFixed(2);
+    //PTS
+    console.log(row.cells[0].textContent + ':' + row.cells[2].textContent);
+  };
+
+  _p.setBenchmarkStatus_ = function(benchmark, status) {
+    benchmark.status = status;
+    var caption = robohornet.enabledBenchmarksDescription[benchmark.status] ||
+            'Unknown failure';
+    benchmark.summaryRow_.cells[1].textContent = caption;
+  };
+
+  _p.setStatus_ = function(status) {
+    this.status_ = status;
+
+    var isRunning = status == robohornet.Status.RUNNING;
+    for (var benchmark, i = 0; benchmark = this.benchmarks_[i]; i++) {
+      benchmark.toggleElement_.disabled = isRunning;
+      if (isRunning)
+        this.setBenchmarkStatus_(benchmark, robohornet.enabledBenchmarks.PENDING);
+    }
+
+    document.body.className = this.status_ == robohornet.Status.READY ? 'ready' : 'running';
+    this.runElement_.textContent = robohornet.RunButtonCaption[status];
+    this.runElement_.disabled = this.status_ != robohornet.Status.READY;
+  };
+
+  _p.setScore_ = function(opt_finalScore) {
+    // Ensure that we have 4 digits in front of the dot and 2 after.
+    this.indexElement_.textContent = formatNumber(this.score_, 4, 2);
+    this.indexElement_.className = opt_finalScore ? 'final' : '';
+    this.rawScoreElement_.textContent = this.rawScore_.toFixed(2);
+
+    // PTS, print the result
+    if (opt_finalScore) {
+        console.log('RoboHornet Score:' + this.score_);
+    }
+    if (this.rawScoreElement_.classList.contains('final') != opt_finalScore)
+        this.rawScoreElement_.classList.toggle('final');
+  }
+
+  _p.toggleBenchmarkDetails_ = function(benchmark, e) {
+    var rowEle = benchmark.detailsElement_.parentElement.parentElement;
+    rowEle.classList.toggle("expanded");
+    benchmark.summaryRow_.classList.toggle("expanded");
+  };
+
+  _p.makeTagElement_ = function(tag) {
+    var tagElement = createElement('span', tag.name, 'tag ' + tag.type);
+    var callback = function(tag, event) {
+      if (event.shiftKey) {
+        this.addBenchmarksToSelectionByTag(tag);
+      } else {
+        this.selectBenchmarksByTag(tag);
+      }
+      // Undo the text selection from a shift-click.
+      window.getSelection().removeAllRanges();
+    };
+    tagElement.addEventListener('click', bind(callback, this, tag), false);
+    return tagElement;
+  }
+
+  _p.selectBenchmarksByTag = function(tag) {
+    for (var benchmark, i = 0; benchmark = this.benchmarks_[i]; i++) {
+      this.setBenchmarkEnabled_(benchmark, false);
+    }
+    this.addBenchmarksToSelectionByTag(tag);
+  }
+
+  _p.addBenchmarksToSelectionByTag = function(tag) {
+    for (var benchmark, i = 0; benchmark = tag.benchmarks[i]; i++) {
+      this.setBenchmarkEnabled_(benchmark, true);
+    }
+    this.updateHash_();
+  }
+
+  _p.updateTagSelection_ = function() {
+    // Get number of enabled benchmarks per tag.
+    var enabledBenchmarksByTag = [];
+    for (var benchmark, i = 0; benchmark = this.benchmarks_[i]; i++) {
+      if (!benchmark.enabled)
+        continue;
+      for (var tag, j = 0; tag = benchmark.tags[j]; j++) {
+        enabledBenchmarksByTag[tag.id] = (enabledBenchmarksByTag[tag.id] || 0) + 1;
+      }
+    }
+
+    // Highlight tags based on selection.
+    for (var identifier in this.tagsById_) {
+      var tag = this.tagsById_[identifier];
+      var n = enabledBenchmarksByTag[identifier] || 0;
+      enableClass(tag.element, 'partially-inactive', n && n != tag.benchmarks.length);
+      enableClass(tag.element, 'inactive', n == 0);
+    }
+  };
+
+  _p.updateHash_ = function() {
+    // We'll keep track of how many benchmarks each of the tag has enabled.
+    var enabledTagCount = {};
+    var enabledBenchmarkIDs = [];
+    var disabledBenchmarkIDs = [];
+    for (var benchmark, i = 0; benchmark = this.benchmarks_[i]; i++) {
+      if (benchmark.enabled) {
+        enabledBenchmarkIDs.push(benchmark.id);
+        for (var tag, k = 0; tag = benchmark.tags[k]; k++) {
+          enabledTagCount[tag.id] = (enabledTagCount[tag.id] || 0) + 1;
+        }
+      }
+      else {
+        disabledBenchmarkIDs.push(benchmark.id);
+      }
+    }
+
+    if (enabledBenchmarkIDs.length == 0) {
+      window.location.hash = '#et=none';
+      this.updateTagSelection_();
+      return;
+    }
+
+    var maxTagId = 'NONE'
+    var maxTagCount = 0;
+
+    // See which of the tags has the most coverage.
+    for (var tagId in enabledTagCount) {
+      if (enabledTagCount[tagId] < this.tagsById_[tagId].benchmarks.length) {
+        continue;
+      }
+      if (enabledTagCount[tagId] > maxTagCount) {
+        maxTagCount = enabledTagCount[tagId];
+        maxTagId = tagId;
+      }
+    }
+
+    // Check if that maxTagId has coverage of all enabled benchmarks.
+    if (maxTagCount == enabledBenchmarkIDs.length) {
+      if (maxTagId == 'CORE') {
+        // All is the default.
+        window.location.hash = '';
+      } else {
+        window.location.hash = '#et=' + maxTagId.toLowerCase();
+      }
+    } else {
+      // Fall back on covering the benchmarks one by one.
+      // We want to encode as few IDs as possible in the hash.
+      // This also gives us a good default to follow for new benchmarks.
+      if (disabledBenchmarkIDs.length) {
+        if (disabledBenchmarkIDs.length < enabledBenchmarkIDs.length) {
+          window.location.hash = '#d=' + disabledBenchmarkIDs.join(',');
+        } else {
+          window.location.hash = '#e=' + enabledBenchmarkIDs.join(',');
+        }
+      } else {
+        window.location.hash = '';
+      }
+    }
+
+    this.updateTagSelection_();
+  }
+
+  _p.digestHash_ = function() {
+    var hash = window.location.hash;
+
+    if (!hash) {
+      //The core set should be selected by default.
+      this.selectBenchmarksByTag(this.tagsById_['CORE']);
+      return;
+    }
+    hash = hash.replace('#', '').toLowerCase().split('&');
+    var enableBenchmarks;
+    var benchmark;
+    var segment;
+
+    //First, checkx if "enabled-tags" is in, because we do special processing if it is.
+    for (segment, i = 0; segment = hash[i]; i++) {
+      hash[i] = hash[i].split('=');
+      if (hash[i][0] == "et") {
+        var tag = this.tagsById_[hash[i][1].toUpperCase()];
+        if (!tag) continue;
+        this.selectBenchmarksByTag(tag);
+        return;
+      }
+    }
+
+    // There wasn't a single enabled tag. Let's see if there are any individual enabled/disabled benchmarks.
+    for (var segment, i = 0; segment = hash[i]; i++) {
+      enableBenchmarks = false;
+      switch (hash[i][0]) {
+        case 'e':
+          enableBenchmarks = true;
+          // We set all benchmarks to disable and then only enable some.
+          for (var k = 0; benchmark = this.benchmarks_[k]; k++) {
+            this.setBenchmarkEnabled_(benchmark, false, true);
+          }
+        case 'd':
+          var ids = hash[i][1].split(',');
+          for (var benchmarkID, j = 0; benchmarkID = ids[j]; j++) {
+            benchmark = this.benchmarksById_[benchmarkID];
+            if (!benchmark)
+              continue;
+            this.setBenchmarkEnabled_(benchmark, enableBenchmarks, true);
+          }
+          break;
+      }
+    }
+
+    this.updateTagSelection_();
+  }
+
+  _p.onWindowUnload_ = function() {
+    if (this.benchmarkWindow_)
+      this.benchmarkWindow_.close();
+  }
+
+})();
+
+
+/**
+ * Class representing a single benchmark.
+ *
+ * @param {Object} details Benchmarks details as json object.
+ * @constructor
+ */
+robohornet.Benchmark = function(details) {
+  if (!details)
+    details = {};
+  this.name = details.name;
+  this.description = details.description;
+  this.filename = details.filename;
+  this.runs = details.runs;
+  this.weight = details.weight;
+  this.baselineTime = details.baselineTime;
+  this.tags = details.tags;
+  this.issueNumber = details.issueNumber;
+  this.extended = details.extended;
+  this.enabled = true;
+  this.tags = [];
+
+  this.id = this.filename.match(/\/([A-z]+)\./)[1].toLowerCase();
+};
+
+function bind(fn, opt_scope, var_args) {
+  var scope = opt_scope || window;
+  var len = arguments.length;
+  var args = [];
+  for (var i = 2; i < len; i++) {
+    args.push(arguments[i]);
+  }
+  return function(arguments) {
+    var a = args.slice();
+    a.push.call(a, arguments);
+    fn.apply(scope, a);
+  };
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.png b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.png
new file mode 100644
index 0000000..f989ee2
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.png
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.rb b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.rb
new file mode 100644
index 0000000..54a2fc7
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/robohornet.rb
@@ -0,0 +1,10 @@
+#!/usr/bin/env ruby
+require 'rubygems'
+require 'sinatra'
+
+set :root, File.dirname(__FILE__)
+set :public_folder, Proc.new { root }
+
+get '/' do
+  redirect '/robohornet.html'
+end
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/test.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/test.js
new file mode 100644
index 0000000..801bdc8
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/test.js
@@ -0,0 +1,57 @@
+var requestAnimationFrameFunction =
+    window.requestAnimationFrame ||
+    window.mozRequestAnimationFrame ||
+    window.msRequestAnimationFrame ||
+    window.webkitRequestAnimationFrame ||
+    window.oRequestAnimationFrame ||
+    function(callback) {
+      window.setTimeout(callback, 1000 / 60);
+    };
+
+if (window.parent && window.parent.__robohornet__ && window.location.search == '?use_test_runner') {
+  document.body.appendChild(document.createTextNode('Running test using test runner...'));
+  window.parent.__robohornet__.benchmarkLoaded();
+} else {
+
+  document.body.appendChild(document.createTextNode('Running test standalone...'));
+
+  resetMathRandom();
+  setUp();
+  window.setTimeout(function() {
+    if (window['testAsync']) {
+      var startTime = new Date().getTime();
+      var deferred = {
+        startTime: 0,
+        resolve: function() {
+          var endTime = new Date().getTime();
+          document.body.appendChild(document.createTextNode('Ran test in ' + (endTime - startTime) + ' ms.'));
+        }
+      };
+      window['testAsync'](deferred);
+    } else {
+      var startTime = new Date().getTime();
+      window['test']();
+      var endTime = new Date().getTime();
+      document.body.appendChild(document.createTextNode('Ran test in ' + (endTime - startTime) + ' ms.'));
+    }
+  }, 0);
+}
+
+// To make the benchmark results predictable, we replace Math.random with a
+// 100% deterministic alternative.
+function resetMathRandom() {
+  Math.random = (function() {
+    var seed = 49734321;
+    return function() {
+      // Robert Jenkins' 32 bit integer hash function.
+      seed = ((seed + 0x7ed55d16) + (seed << 12))  & 0xffffffff;
+      seed = ((seed ^ 0xc761c23c) ^ (seed >>> 19)) & 0xffffffff;
+      seed = ((seed + 0x165667b1) + (seed << 5))   & 0xffffffff;
+      seed = ((seed + 0xd3a2646c) ^ (seed << 9))   & 0xffffffff;
+      seed = ((seed + 0xfd7046c5) + (seed << 3))   & 0xffffffff;
+      seed = ((seed ^ 0xb55a4f09) ^ (seed >>> 16)) & 0xffffffff;
+      return (seed & 0xfffffff) / 0x10000000;
+    };
+  })();
+}
+
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/addcol.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/addcol.html
new file mode 100644
index 0000000..c11fff4
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/addcol.html
@@ -0,0 +1,63 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Adding columns to table</title>
+<style>
+    td { border: 1px; margin: 5px; }
+</style>
+</head>
+<body>
+<script>
+var DEFAULT_ROW_COUNT = 1000;
+var tableElement;
+
+function setUp(opt_rowCount) {
+  var rowCount = opt_rowCount || DEFAULT_ROW_COUNT;
+  var tableBody = document.createElement('tbody');
+  for (var i = 0; i < rowCount; i++) {
+    var tableRow = document.createElement('tr');
+    tableBody.appendChild(tableRow);
+    var tableCell = document.createElement('td');
+    tableCell.appendChild(document.createTextNode(i));
+    tableRow.appendChild(tableCell);
+  }
+
+  tableElement = document.createElement('table');
+  tableElement.appendChild(tableBody);
+  document.body.appendChild(tableElement);
+}
+
+function tearDown() {
+  tableElement.parentNode.removeChild(tableElement);
+  tableElement = null;
+}
+
+function reset() {
+  if (!tableElement) return;
+  for (var i = 0; i < tableElement.rows.length; i++) {
+    var tableRow = tableElement.rows[i];
+    if (tableRow.lastChild != tableRow.firstChild) {
+        tableRow.removeChild(tableRow.lastChild);
+    }
+  }
+}
+
+function test(opt_rowCount) {
+  var rowCount = opt_rowCount || DEFAULT_ROW_COUNT;
+  for (var i = 0; i < tableElement.rows.length; i++) {
+      var tableRow = tableElement.rows[i];
+      var tableCell = document.createElement('td');
+      tableCell.appendChild(document.createTextNode(i));
+      tableRow.appendChild(tableCell);
+  }
+  var tableRow = document.createElement('tr');
+  tableElement.tBodies[0].appendChild(tableRow);
+  var tableCell = document.createElement('td');
+  tableCell.appendChild(document.createTextNode(tableElement.offsetHeight));
+    tableRow.appendChild(tableCell);
+  }
+
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/addrow.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/addrow.html
new file mode 100644
index 0000000..27cb356
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/addrow.html
@@ -0,0 +1,44 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Adding rows to table</title>
+</head>
+<body>
+<script>
+var DEFAULT_ROW_COUNT = 1000;
+var tableElement;
+
+function setUp() {
+  var tableBody = document.createElement('tbody');
+  tableElement = document.createElement('table');
+  tableElement.appendChild(tableBody);
+  document.body.appendChild(tableElement);
+}
+
+function tearDown() {
+  tableElement.parentNode.removeChild(tableElement);
+  tableElement = null;
+}
+
+function test(opt_rowCount) {
+  var rowCount = opt_rowCount || DEFAULT_ROW_COUNT;
+  var tableBody = tableElement.tBodies[0];
+  for (var i = 0; i < rowCount; i++) {
+    var tableRow = document.createElement('tr');
+    tableBody.appendChild(tableRow);
+    var tableCell = document.createElement('td');
+    tableCell.appendChild(document.createTextNode(i));
+    tableRow.appendChild(tableCell);
+  }
+
+  var tableRow = document.createElement('tr');
+  tableElement.tBodies[0].appendChild(tableRow);
+
+  var tableCell = document.createElement('td');
+  tableCell.appendChild(document.createTextNode(tableElement.offsetHeight));
+  tableRow.appendChild(tableCell);
+  }
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/animated_gifs.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/animated_gifs.html
new file mode 100644
index 0000000..c8bcf06
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/animated_gifs.html
@@ -0,0 +1,89 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Animated gifs.</title>
+
+<!--
+  GIF being used is in public domain.
+  See: http://commons.wikimedia.org/wiki/File:Throbber_allbackgrounds_circledots_32.gif
+-->
+
+<style>
+#container { height: 300px; width: 300px; overflow: auto; }
+</style>
+</head>
+<body>
+<div id="container"></div>
+<script>
+var img_src = 'data:image/gif;base64,' +
+    'R0lGODlhIAAgALMJAAAAAHt7e4SEhJSUlKWlpa2trb29vcbGxtbW1gAAAAAAAAAAAAAAAAAAAAAAAAAA' +
+    'ACH/C05FVFNDQVBFMi4wAwEAAAAh+QQFCgAJACwAAAAAIAAgAAAEsDDJSam4ourNE8ZdqH2faHrkdVZD' +
+    'O0ipmgR0ILoums413eE4HajX4wBzQgmxZjy+NkvfxvnURG3NYGd5Q4aKG4KYsNIgzojJeFyeoNGJ9br9' +
+    'fsvZ5Tr6LqbrEXxkeX9xcm0Semp4hwlvHAWQBSYHlAcikZEhlZUdmJgcm5uPnpCgoZSjpBIGrAYSp6gb' +
+    'pKWtrQmwlqmZtbW3oZeZCby2vrFtw6yMGsiuyhXDzhu9JxEAACH5BAUKAAkALAAAAAAgACAAAASwMMlJ' +
+    '6bij6s0Txl2ofZ9oeuR1VkRLSKmaCLQgui6azjXd4TgdqNfjAHNCCbFmPL42S9/G+dREbc1gZ3lDhoqb' +
+    'griw0gTOgcl4XJ6g0Yn1uv1+y9nlOvoupusDfGR5f3FybRJ6aniHCW8cBpAGJgiUCCKRkSGVlR2YmByb' +
+    'm4+ekKChlKOkEgesBxKnqBukpa2tCbCWqZm1tbehl5kJvLa+sW3DrIwayK7KFcPOG70nEQAAIfkEBQoA' +
+    'CQAsAAAAACAAIAAABLAwyUkpuaTqzRPGXah9n2h65HVWRVtIqZoM9CC6LprONd3hOB2o1+MAc0IJsWY8' +
+    'vjZL38b51ERtzWBneUOGihuD2LDSCM6CyXhcnqDRifW6/X7L2eU6+i6m6wV8ZHl/cXJtEnpqeIcJbxwH' +
+    'kAcmAZQBIpGRIZWVHZiYHJubj56QoKGUo6QSCKwIEqeoG6Slra0JsJapmbW1t6GXmQm8tr6xbcOsjBrI' +
+    'rsoVw84bvScRAAAh+QQFCgAJACwAAAAAIAAgAAAEsDDJSWm5perNE8ZdqH2faHrkdVZGa0ipmhA0Ibou' +
+    'ms413eE4HajX4wBzQgmxZjy+NkvfxvnURG3NYGd5Q4aKm4P4sNIMzoPJeFyeoNGJ9br9fsvZ5Tr6Lqbr' +
+    'B3xkeX9xcm0Semp4hwlvHAiQCCYClAIikZEhlZUdmJgcm5uPnpCgoZSjpBIBrAESp6gbpKWtrQmwlqmZ' +
+    'tbW3oZeZCby2vrFtw6yMGsiuyhXDzhu9JxEAACH5BAUKAAkALAAAAAAgACAAAASwMMlJqbmm6s0Txl2o' +
+    'fZ9oeuR1Vkd7SKmaFHQhui6azjXd4TgdqNfjAHNCCbFmPL42S9/G+dREbc1gZ3lDhoobhBix0hDOhMl4' +
+    'XJ6g0Yn1uv1+y9nlOvoupusJfGR5f3FybRJ6aniHCW8cAZABJgOUAyKRkSGVlR2YmBybm4+ekKChlKOk' +
+    'EgKsAhKnqBukpa2tCbCWqZm1tbehl5kJvLa+sW3DrIwayK7KFcPOG70nEQAAIfkEBQoACQAsAAAAACAA' +
+    'IAAABLAwyUnpuafqzRPGXah9n2h65HVWSItIqZoYtCG6LprONd3hOB2o1+MAc0IJsWY8vjZL38b51ERt' +
+    'zWBneUOGipuAOLDSFM6FyXhcnqDRifW6/X7L2eU6+i6m6wt8ZHl/cXJtEnpqeIcJbxwCkAImBJQEIpGR' +
+    'IZWVHZiYHJubj56QoKGUo6QSA6wDEqeoG6Slra0JsJapmbW1t6GXmQm8tr6xbcOsjBrIrsoVw84bvScR' +
+    'AAAh+QQFCgAJACwAAAAAIAAgAAAEsDDJSSm6qOrNE8ZdqH2faHrkdVZBG0ipmhz0Iboums413eE4HajX' +
+    '4wBzQgmxZjy+NkvfxvnURG3NYGd5Q4aKG4FYsNIYzobJeFyeoNGJ9br9fsvZ5Tr6LqbrDXxkeX9xcm0S' +
+    'emp4hwlvHAOQAyYFlAUikZEhlZUdmJgcm5uPnpCgoZSjpBIErAQSp6gbpKWtrQmwlqmZtbW3oZeZCby2' +
+    'vrFtw6yMGsiuyhXDzhu9JxEAACH5BAUKAAkALAAAAAAgACAAAASwMMlJabih6s0Txl2ofZ9oeuR1VkIr' +
+    'SKmaIDQiui6azjXd4TgdqNfjAHNCCbFmPL42S9/G+dREbc1gZ3lDhoqbgXiw0hzOh8l4XJ6g0Yn1uv1+' +
+    'y9nlOvoupusPfGR5f3FybRJ6aniHCW8cBJAEJgaUBiKRkSGVlR2YmBybm4+ekKChlKOkEgWsBRKnqBuk' +
+    'pa2tCbCWqZm1tbehl5kJvLa+sW3DrIwayK7KFcPOG70nEQAAOw==';
+
+var container;
+var num;
+var DEFAULT_LINES = 50;
+var DEFAULT_COLUMNS = 10;
+
+function setUp(opt_size) {
+  var size = opt_size || [DEFAULT_LINES, DEFAULT_COLUMNS];
+  var lines = size[0];
+  var columns = size[1];
+  container = document.getElementById('container');
+  container.appendChild(document.createElement('div'));
+  var subContainer = container.firstChild;
+  for (var i = 0; i < lines; i++) {
+    for (var j = 0; j < columns; j++) {
+      var image = document.createElement('img');
+      image.src = img_src;
+      subContainer.appendChild(image);
+    }
+    subContainer.appendChild(document.createElement('br'));
+  }
+}
+
+function reset() {
+  if (!container) return;
+  container.innerHTML = '';
+  container.scrollTop = 0;
+}
+
+function tearDown() {
+  container.innerHTML = '';
+  container.scrollTop = 0;
+}
+
+function test() {
+  while (container.scrollTop < (container.firstChild.offsetHeight - container.offsetHeight)) {
+    container.scrollTop += 1000;
+  }
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvasclear.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvasclear.html
new file mode 100644
index 0000000..294c734
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvasclear.html
@@ -0,0 +1,53 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Canvas clearing benchmark</title>
+<script>
+
+var canvas = null, context = null, size = 0;
+var DEFAULT_SIZE = 512;
+
+function setUp(canvasSize) {
+  var container = null, canvasStyle = null;
+
+  // ensure we have a canvas size
+  size = canvasSize || DEFAULT_SIZE;
+
+  // set the container
+  container = document.getElementById('container');
+  container.style.width = container.style.height = size + 'px';
+
+  // create a canvas
+  canvas = document.createElement('canvas');
+  context = canvas.getContext('2d');
+  context.fillStyle = "#000000";
+  canvasStyle = canvas.style;
+
+  // set its width and height using CSS
+  canvasStyle.position = 'absolute';
+  canvasStyle.left = '0px';
+  canvasStyle.top = '0px';
+
+  // and then set the backing store
+  canvas.width = canvas.height = size;
+  canvasStyle.width = canvasStyle.height = size + 'px';
+  container.appendChild(canvas);
+}
+
+function tearDown() {
+  var container = document.getElementById('container');
+  while (container.firstChild) { container.removeChild(container.lastChild); }
+}
+
+function test() {
+  context.fillRect(0, 0, 1, 1);
+  context.clearRect(0, 0, size, size);
+}
+
+</script>
+</head>
+<body>
+<div id="container" style="position: absolute; width: 256px; height: 256px; overflow:hidden; border: 1px solid black;"></div>
+</body>
+<script src="../test.js"></script>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvasdrawline.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvasdrawline.html
new file mode 100644
index 0000000..0d4c04d
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvasdrawline.html
@@ -0,0 +1,68 @@
+<!DOCTYPE html> 
+<html> 
+<head> 
+<title>Canvas painting benchmark</title> 
+<script> 
+
+var canvas, vertices;
+var DEFAULT_NUMBER_OF_VERTICES = 2500;
+
+function setUp(opt_numberOfVertices) {
+  var numberOfVertices = opt_numberOfVertices || DEFAULT_NUMBER_OF_VERTICES;
+  var container = document.getElementById('container');
+  container.style.overflow = 'hidden';
+
+  vertices = makeRandomPath(512, 512, numberOfVertices);
+
+  canvas = document.createElement('canvas');
+  var canvasStyle = canvas.style;
+  canvasStyle.position = 'absolute';
+  canvasStyle.left = '0px';
+  canvasStyle.top = '0px';
+  canvas.width = canvas.height = 512;
+  canvasStyle.width = canvasStyle.height = '512px';
+
+  container.appendChild(canvas);
+}
+
+function tearDown() {
+  var container = document.getElementById('container');
+  while (container.firstChild) { container.removeChild(container.lastChild); }
+}
+ 
+function test(opt_numberOfVertices) {
+  var numberOfVertices = opt_numberOfVertices || DEFAULT_NUMBER_OF_VERTICES;
+  var path = vertices;
+  var context = canvas.getContext('2d');
+
+  context.lineCap = context.lineJoin = 'round';
+  context.strokeStyle = 'rgba(255, 170, 85, 0.2)';
+  context.lineWidth = 1;
+  
+  var x = path[0];
+  var y = path[1]
+  for (var i = 2, I = path.length; i < I;) {
+    context.moveTo(x, y);
+    x = path[i];
+    y = path[i + 1];
+    context.lineTo(x, y);
+    context.stroke();
+    i += 2;
+  }
+}
+
+function makeRandomPath(width, height, numVerts) {
+  var path = [];
+  for (var i = 0; i < numVerts; ++i) {
+    path.push(Math.round(Math.random() * width),
+              Math.round(Math.random() * height));
+  }
+  return path;
+}
+</script> 
+</head> 
+<body>
+<div id="container" style="position: absolute; width:512px; height: 512px; overflow:hidden; border: 1px solid black;"></div>
+</body> 
+<script src="../test.js"></script>
+</html> 
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvastodataurl.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvastodataurl.html
new file mode 100644
index 0000000..b381bc0
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/canvastodataurl.html
@@ -0,0 +1,66 @@
+<!DOCTYPE html> 
+<html> 
+<head> 
+<title>Canvas painting benchmark</title> 
+<script> 
+
+var canvas;
+var DEFAULT_NUMBER_OF_VERTICES = 1000;
+var DEFAULT_HEIGHT = 512;
+
+function setUp(opt_sizes) {
+  var opt_sizes = opt_sizes || [];
+  var numberOfVertices = opt_sizes[0] || DEFAULT_NUMBER_OF_VERTICES;
+  var height = opt_sizes[1] || DEFAULT_HEIGHT;
+
+  var container = document.getElementById('container');
+  container.style.width = container.style.height = height;
+  container.width = container.height = height;
+  //container.style.overflow = 'hidden';
+
+  canvas = document.createElement('canvas');
+  var canvasStyle = canvas.style;
+  canvasStyle.position = 'absolute';
+  canvasStyle.left = '0px';
+  canvasStyle.top = '0px';
+  canvas.width = canvas.height = height;
+  canvasStyle.width = canvasStyle.height = height + 'px';
+  container.appendChild(canvas);
+
+  draw(height, height, numberOfVertices);
+}
+
+function tearDown() {
+  var container = document.getElementById('container');
+  while (container.firstChild) { container.removeChild(container.lastChild); }
+}
+ 
+function test(opt_numberOfVertices) {
+  canvas.toDataURL("image/png");
+}
+
+function draw(width, height, numVerts) {
+  var path = [];
+  for (var i = 0; i < numVerts; ++i) {
+    path.push(Math.round(Math.random() * width),
+              Math.round(Math.random() * height));
+  }
+
+  var context = canvas.getContext('2d');
+  context.lineCap = context.lineJoin = 'round';
+
+  context.moveTo(path[0], path[1]);
+  for (var i = 2, I = path.length; i < I; ) {
+    context.lineTo(path[i++], path[i++]);
+  }
+  context.strokeStyle = 'rgba(255, 170, 85, 0.2)';
+  context.lineWidth = 1;
+  context.stroke();
+}
+</script> 
+</head> 
+<body>
+<div id="container" style="position: absolute; overflow:hidden; border: 1px solid black;"></div>
+</body> 
+<script src="../test.js"></script>
+</html> 
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/createtable.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/createtable.html
new file mode 100644
index 0000000..012b474
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/createtable.html
@@ -0,0 +1,60 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Adding a large table</title>
+<style>
+    td { border: 1px; margin: 5px; overflow:hidden; }
+</style>
+</head>
+<body>
+<div id="table-container"></div>
+<script>
+
+var tableContainer;
+var tableString;
+var DEFAULT_SIZE = [200, 100];
+
+function setUp(opt_size) {
+  var size = opt_size || DEFAULT_SIZE;
+  var rowCount = size[0];
+  var colCount = size[1];
+
+  tableContainer = document.getElementById('table-container');
+  var htmlBuffer = ['<table><tbody>'];
+  htmlBuffer.push('<tr>');
+  for (var col = 0; col < colCount; col++) {
+    // Build table headers
+    htmlBuffer.push('<th style="width:200px;">H</th>');
+  }
+  htmlBuffer.push('</tr>');
+  for (var i = 0; i < rowCount; i++) {
+    htmlBuffer.push('<tr>');
+    for (var j = 0; j < colCount; j++) {
+        htmlBuffer.push('<td>');
+        htmlBuffer.push(i + ', ' + j);
+        htmlBuffer.push('</td>');
+    }
+    htmlBuffer.push('</tr>');
+  }
+  htmlBuffer.push('</tbody></table>');
+  tableString = htmlBuffer.join('');
+}
+
+function reset() {
+  if (!tableContainer) return;
+  tableContainer.innerHTML = '';
+}
+
+function tearDown() {
+  tableContainer.innerHTML = '';
+  tableContainer = null;
+  tableString = null;
+}
+
+function test() {
+  tableContainer.innerHTML = tableString;
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/descendantselector.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/descendantselector.html
new file mode 100644
index 0000000..a927d91
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/descendantselector.html
@@ -0,0 +1,47 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Descendant selectors</title>
+<style>
+div { padding: 1px; }
+.test div { padding: 2px; }
+.test2 div { padding: 3px; }
+.test div div div { background: orange; }
+.test div div div div { background: white; }
+</style>
+</head>
+<body>
+<script>
+
+var rootElement;
+var totalHeight;
+var DEFAULT_DEPTH = 1000;
+
+function setUp(opt_depth) {
+  var depth = opt_depth || DEFAULT_DEPTH;
+  rootElement = document.createElement('div');
+  var parentElement = rootElement;
+  for (var i = 0; i < depth; i++) {
+    var element = document.createElement('div');
+    element.appendChild(document.createTextNode(i));
+    parentElement.appendChild(element);
+    parentElement = element;
+  }
+  document.body.appendChild(rootElement);
+  totalHeight = 0
+}
+
+function tearDown() {
+  rootElement.parentNode.removeChild(rootElement);
+  rootElement = null;
+}
+
+function test(opt_depth) {
+  var depth = opt_depth || DEFAULT_DEPTH;
+  rootElement.className = rootElement.className == 'test' ? 'test2' : 'test';
+  totalHeight += rootElement.offsetHeight;
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/localstorage_read.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/localstorage_read.html
new file mode 100644
index 0000000..2cfe328
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/localstorage_read.html
@@ -0,0 +1,41 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Reading from localStorage</title>
+<style>
+</style>
+</head>
+<body>
+<script>
+
+var DEFAULT_ITEMS = 100;
+var keySet = {};
+
+function setUp(opt_items) {
+  var items = opt_items || DEFAULT_ITEMS;
+  for (var i = 0; i < items; i++) {
+    var key = 'key' + i;
+    keySet[i] = key;
+    localStorage.setItem(key, i);
+  }
+}
+
+function reset() {
+  keySet = {};
+  localStorage.clear();
+}
+
+function tearDown() {
+  localStorage.clear();
+}
+
+function test(opt_items) {
+  var items = opt_items || DEFAULT_ITEMS;
+  for (var i = 0; i < items; i++) {
+    localStorage.getItem(keySet[i]);
+  }
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/localstorage_write.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/localstorage_write.html
new file mode 100644
index 0000000..f7a7f88
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/localstorage_write.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Writing to localStorage</title>
+<style>
+</style>
+</head>
+<body>
+<script>
+
+var DEFAULT_ITEMS = 100;
+var keySet = {};
+
+function setUp(opt_items) {
+  var items = opt_items || DEFAULT_ITEMS;
+  for (var i = 0; i < items; i++) {
+    keySet[i] = 'key' + i;
+  }
+}
+
+function reset() {
+  keySet = {};
+  localStorage.clear();
+}
+
+function tearDown() {
+  keySet = {};
+  localStorage.clear();
+}
+
+function test(opt_items) {
+  var items = opt_items || DEFAULT_ITEMS;
+  for (var i = 0; i < items; i++) {
+    localStorage.setItem(keySet[i], i);
+  }
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/object_scope.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/object_scope.html
new file mode 100644
index 0000000..53159f7
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/object_scope.html
@@ -0,0 +1,123 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Object Scope Access Test</title>
+<script>
+
+// This test is pretty slow in Chrome, a lot faster in Opera and even faster in Firefox.
+function ClassWithThisEverywhere(n) {
+  this.a = 1, this.sum = 0, this.count = n || 10000000;
+  this.unnamed = function() {
+    for (var i = 0; i < this.count; i++) this.sum += this.a;
+  }
+  this.named = function calcWithThisEverywhere() {
+    for (var i = 0; i < this.count; i++) this.sum += this.a;
+  }
+}
+
+// This test runs relatively fast in Chrome and Firefox. This is the only test with a difference between the
+// unnamed and named versions. The named version is horribly slow in Opera.
+function ClassWithoutThis(n) {
+  var a = 1, sum = 0, count = n || 10000000;
+  this.unnamed = function() {
+    for (var i = 0; i < count; i++) sum += a;
+  }
+  this.named = function calcWithoutThis() {
+    for (var i = 0; i < count; i++) sum += a;
+  }
+}
+
+// This is the fastest of the three tests in all browsers.
+function ClassLocal(n) {
+  this.unnamed = function() {
+    var a = 1, sum = 0, count = n || 10000000;
+    for (var i = 0; i < count; i++) sum += a;
+  }
+  this.named = function calcLocal() {
+    var a = 1, sum = 0, count = n || 10000000;
+    for (var i = 0; i < count; i++) sum += a;
+  }
+}
+
+// Benchmark stuff and a bit eye candy...
+function setUp(n) {
+  objectWithThisEverywhere = new ClassWithThisEverywhere(n);
+  objectWithoutThis = new ClassWithoutThis(n);
+  objectLocal = new ClassLocal(n);
+}
+
+function prettyTest(id, t) {
+  var t1 = new Date().getTime();
+  if (!this.times) {
+    this.times = [];
+  }
+
+  if (typeof id !== 'undefined') {
+    this.times[id] = t1 - t;
+  }
+  var min = -1, max = -1;
+  for (var i = this.times.length; i--; ) {
+    min = min < 0 ? this.times[i] : Math.min(min, this.times[i]);
+    max = Math.max(max, this.times[i]);
+  }
+  for (var i = this.times.length; i--; ) {
+    var e = document.getElementById('id' + i);
+    if (this.times.length < 6) {
+      e.firstChild.data = this.times[i] ? this.times[i] + ' ms' : '?';
+    } else {
+      e.firstChild.data = this.times[i] ? Math.round(this.times[i] * 10 / min) * 10 + ' percent of the time' : '?';
+    }
+
+    if (this.times[i] > min * 6) {
+      e.className = 'bad';
+    } else if (this.times[i] < min * 2) {
+      e.className = 'top';
+    } else {
+      e.className = '';
+    }
+  }
+  return new Date().getTime();
+}
+
+function test() {
+  var t = prettyTest();
+  objectWithThisEverywhere.unnamed();
+  t = prettyTest(0, t);
+  objectWithThisEverywhere.named();
+  t = prettyTest(1, t);
+  objectWithoutThis.unnamed();
+  t = prettyTest(2, t);
+  objectWithoutThis.named();
+  t = prettyTest(3, t);
+  objectLocal.unnamed();
+  t = prettyTest(4, t);
+  objectLocal.named();
+  prettyTest(5, t);
+}
+</script>
+
+<style>
+  body { font-family: sans-serif; line-height: 1.4em; }
+  sup { color: #666; font-weight: bold; line-height: 0; }
+  var { background: #DDD; border-radius: .2em; font-style: normal; font-weight: bold; padding: 0 .5em; }
+  .top { background: #6D0; }
+  .bad { background: #D60; color: #FFF; }
+</style>
+</head>
+<body>
+  <ul>
+    <li><var id="id0">?</var> for accessing class variables with <code>this</code> added everywhere<sup>[w]</sup></li>
+    <li><var id="id1">?</var> for accessing class variables with <code>this</code> added everywhere (using a named function)<sup>[w]</sup></li>
+    <li><var id="id2">?</var> for accessing class variables without using <code>this</code><sup>[m]</sup></li>
+    <li><var id="id3">?</var> for accessing class variables without using <code>this</code> (using a named function)<sup>[m]</sup><sup>[o]</sup></li>
+    <li><var id="id4">?</var> for accessing local variables</li>
+    <li><var id="id5">?</var> for accessing local variables (using a named function)</li>
+  </ul>
+  <p>
+    <sup>[m]</sup> Mozilla Firefox loses here (about 4 times slower than the fastest method).<br>
+    <sup>[o]</sup> Triggers a bug in Opera (100 times slower than the fastest method).<br>
+    <sup>[w]</sup> WebKit loses here.<br>
+  </p>
+</body>
+<script src="../test.js"></script>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/offsetreflow.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/offsetreflow.html
new file mode 100644
index 0000000..1e16dbc
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/offsetreflow.html
@@ -0,0 +1,38 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Force layout with offsetHeight</title>
+<style>
+    #container { width: 200px; height: 800px; overflow-y: scroll; }
+</style>
+</head>
+<body>
+<div id="container">
+</div>
+<script>
+
+var container;
+var DEFAULT_RUNS = 10000;
+
+function setUp(opt_numResizes) {
+  container = document.getElementById('container');
+}
+
+function reset() {
+  if (!container) return;
+  container.style.width = 200 + 'px';
+  container.style.height = 800 + 'px';
+}
+
+function test(opt_numResizes) {
+  var runs = opt_numResizes || DEFAULT_RUNS;
+  for (var i = 0; i < runs; i++) {
+    container.style.width = Math.random() * 500 + 'px';
+    container.style.height = Math.random() * 500 + 'px';
+    container.offsetHeight;
+  }
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/property_accessors.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/property_accessors.html
new file mode 100644
index 0000000..365c308
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/property_accessors.html
@@ -0,0 +1,64 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>ES5 Property Accessors</title>
+<script>
+
+"use strict"
+var TIMES = 1e5;
+var OBJ;
+
+function setUp() {
+  OBJ = {
+    prop: 1,
+    get bar() {
+      return this.prop;
+    },
+    set bar(value) {
+      this.prop = value;
+    }
+  };
+}
+
+function reset() {
+  if (!OBJ) return;
+  OBJ.prop = 1;
+}
+
+function test_get() {
+  for (var i = 0; i < TIMES; i++) {
+    var x = OBJ.bar + 1;
+  }
+}
+
+function test_set() {
+  for (var i = 0; i < TIMES; i++) {
+    OBJ.bar = 42;
+  }
+}
+
+function test_combined() {
+  for (var i = 0; i < TIMES; i++) {
+    OBJ.bar = OBJ.bar + 1;
+  }
+}
+
+function test(opt_mode) {
+  switch(opt_mode) {
+    case 'GET':
+      test_get();
+      break;
+    case 'SET':
+      test_set();
+      break;
+    default:
+      test_combined();
+      break;
+  }
+}
+
+</script>
+</head>
+<body></body>
+<script src="../test.js"></script>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/range.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/range.html
new file mode 100644
index 0000000..4c64e02
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/range.html
@@ -0,0 +1,46 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>DOM Range API</title>
+<script src="../third_party/rangy/rangy-core.js"></script>
+<script>
+
+var ITERATIONS = 100;
+var MIN_NODES = 50;
+var BODY_HTML = generateHtml(MIN_NODES, true /** opt_withIds */);
+var REPLACEMENT_HTML = generateHtml(MIN_NODES);
+
+function reset() { document.body.innerHTML = BODY_HTML; }
+function setUp() { reset(); }
+
+function test(opt_nodes) {
+  // Replace a section of the DOM using the Range API.
+  // Inspired by metamorph.js.
+  var nodes = opt_nodes || MIN_NODES;
+  var start = document.getElementById('node' + Math.floor(MIN_NODES * 0.1));
+  var end = document.getElementById('node' + Math.floor(MIN_NODES * 0.9));
+  var r = rangy.createRange(); // Use Rangy as a workaround for IE <= 9
+  for (var i = 0; i < ITERATIONS; i++) {
+    r.setStartAfter(start);
+    r.setEndBefore(end);
+    r.deleteContents();
+    r.insertNode(r.createContextualFragment(REPLACEMENT_HTML));
+  }
+}
+
+function generateHtml(nodes, opt_withIds) {
+  var lorem =
+      'Cupcake ipsum dolor sit amet icing carrot cake gummi ' +
+      'bears jelly. Sugar plum sesame snaps candy canes.';
+  var s = '';
+  for (var i = 0; i < nodes; i++) {
+    s += '<p id=node' + (opt_withIds ? i : '') + '>' + lorem + '</p>';
+  }
+  return s;
+}
+
+</script>
+</head>
+<body></body>
+<script src="../test.js"></script>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/resizecol.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/resizecol.html
new file mode 100644
index 0000000..fc5ecba
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/resizecol.html
@@ -0,0 +1,71 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Resizing columns in a table</title>
+<style>
+  td { border: 1px; margin: 5px; }
+</style>
+</head>
+<body>
+<script>
+
+var container;
+var DEFAULT_SIZE = [500, 100];
+
+function setUp(opt_size) {
+  var size = opt_size || DEFAULT_SIZE;
+  var rowCount = size[0];
+  var colCount = size[1];
+
+  var buff = [];
+  buff.push('<table><tbody>');
+  buff.push('<tr>');
+  for (var col = 0; col < colCount; col++) {
+    // Build table headers
+    buff.push('<th style="width:200px;">H</th>');
+  }
+  buff.push('</tr>');
+  for (var i = 0; i < rowCount; i++) {
+    buff.push('<tr>');
+    for (var j = 0; j < colCount; j++) {
+      buff.push('<td>');
+      buff.push(i + ', ' + j);
+      buff.push('</td>');
+    }
+    buff.push('</tr>');
+  }
+  buff.push('</tbody></table>');
+
+  container = document.createElement('div');
+  container.innerHTML = buff.join('');
+  document.body.appendChild(container);
+}
+
+function tearDown() {
+  container.parentNode.removeChild(container);
+  container = null;
+}
+
+function reset() {
+  if (!container) return;
+  var tbody = container.firstChild.firstChild;
+  var firstRow = tbody.firstChild;
+  for (var i = 0; i < size[1] /** colCount */; i++) {
+    firstRow.cells[i].style.width = '200px';
+    var forceLayout = tbody.offsetTop;
+  }
+}
+
+function test(opt_size) {
+  var size = opt_size || DEFAULT_SIZE;
+  var tbody = container.firstChild.firstChild;
+  var firstRow = tbody.firstChild;
+  for (var i = 0; i < size[1] /** colCount */; i++) {
+    firstRow.cells[i].style.width = '50px';
+    var forceLayout = tbody.offsetTop;
+  }
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/svgresize.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/svgresize.html
new file mode 100644
index 0000000..d855223
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/svgresize.html
@@ -0,0 +1,125 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Resize an SVG</title>
+<style>
+  #container { width: 200px; height: 800px; overflow-y: scroll; }
+  .slide { width: 180px; height: 180px; }
+  td { border: 1px; margin: 5px; overflow:hidden; }
+</style>
+</head>
+<body>
+<div id="container">
+  <svg version="1.1" fill-rule="evenodd" fill="none" stroke="none"
+      stroke-linecap="square" stroke-miterlimit="10" overflow="hidden" preserveAspectRatio="none"
+      width="100%" height="100%" shape-rendering="optimize-speed">
+    <g id="content"></g>
+  </svg>
+</div>
+<svg>
+  <g class="slide">
+    <g class="content"></g>
+    <g class="info"></g>
+  </g>
+</svg>
+<script>
+
+var COLORS = [
+  '#fa8',
+  '#f8a',
+  '#af8',
+  '#a8f',
+  '#8af',
+  '#8fa'
+];
+var NUM_ELEMENTS = 500;
+var DEFAULT_NUM_SVGS = 500;
+
+var container;
+var contentContainer;
+var template;
+
+function setUp(opt_numSvgs) {
+  var numSvgs = opt_numSvgs || DEFAULT_NUM_SVGS;
+  container = document.getElementById('container');
+  contentContainer = document.getElementById('content');
+  template = document.getElementsByClassName('slide')[0];
+  for (var i = 0; i < numSvgs; i++) {
+    render(i);
+  }
+}
+
+function reset() {
+  if (!contentContainer) return;
+  contentContainer.innerHTML = '';
+}
+
+function tearDown() {
+  while(contentContainer.firstChild) {
+    contentContainer.firstChild.parentNode.removeChild(contentContainer.firstChild);
+  }
+  container = null;
+  contentContainer = null;
+  template = null;
+}
+
+function test(opt_numSvgs) {
+  var numSvgs = opt_numSvgs || DEFAULT_NUM_SVGS;
+  for (var i = 0; i < 10; i++) {
+    var size = Math.random() * 300 + 100;
+    container.style.width = size + 'px';
+    contentContainer.setAttribute('transform', 'scale(' + (size / 180) + ')');
+  }
+}
+
+// Utility functions below
+function render(index) {
+  var copy = template.cloneNode(true);
+  // The number being multipled by the index should match the height in the .slide class above.
+  copy.setAttribute('transform', 'translate(0,' + (180 * index) + ')');
+  contentContainer.appendChild(copy);
+  var newSvg = makeSvg(NUM_ELEMENTS, index);
+  var svgContainer = copy.querySelectorAll('.content')[0];
+  svgContainer.innerHTML = '';
+  svgContainer.appendChild(newSvg);
+}
+
+function makeSvg(numElements, index) {
+  var points = [[71, 0], [100, 0]];
+  var str = '<svg width="100%" height="100%" xmlns="http://www.w3.org/2000/svg">';
+  var elemCount = numElements;
+  while (elemCount > 0) {
+    var p = points.shift();
+    var q = points.shift();
+    var next = nextPoints(p, q);
+    str += createPath(COLORS[index & COLORS.length], p, q, next[2], next[0]);
+    points.push(next[0], next[1], next[1], next[2]);
+    elemCount--;
+  }
+  str += "</svg>"
+  var svgDoc = new DOMParser().parseFromString(str, 'text/xml');
+  return svgDoc.removeChild(svgDoc.firstChild);
+}
+
+function nextPoints(p, q) {
+  var d = [p[1] - q[1], q[0] - p[0]];
+  var pPrime = [p[0] + d[0], p[1] + d[1]];
+  var qPrime = [q[0] + d[0], q[1] + d[1]];
+  var t = [(pPrime[0] + qPrime[0] + d[0]) / 2, (pPrime[1] + qPrime[1] + d[1]) / 2];
+  return [pPrime, t, qPrime];
+}
+
+function createPath(color, a, b, c, d) {
+  var id = Math.random();
+  return '<path id="' + id + '" stroke="#000" stroke-width="1" stroke-opacity=".5" fill-opacity=".5" ' +
+      'fill="' + color + '" d="' +
+      'M' + a[0] + ' ' + a[1] +
+      'L' + b[0] + ' ' + b[1] +
+      'L' + c[0] + ' ' + c[1] +
+      'L' + d[0] + ' ' + d[1] +
+      'Z"></path>';
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/table_scrolltop.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/table_scrolltop.html
new file mode 100644
index 0000000..e41f4b8
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/table_scrolltop.html
@@ -0,0 +1,67 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Scrolling a large table with scrollTop</title>
+<style>
+    td { border: 1px; margin: 5px; overflow:hidden; }
+    #container { height: 284px; width: 284px; overflow: auto; }
+    #main-table { table-layout: fixed; width: 0; }
+</style>
+</head>
+<body>
+<div id="container"></div>
+<script>
+
+var container;
+
+var DEFAULT_SIZE = [1000, 100];
+
+function setUp(opt_size) {
+  var size = opt_size || DEFAULT_SIZE;
+  var rowCount = size[0];
+  var colCount = size[1];
+
+  container = document.getElementById('container')
+  var htmlBuffer = ['<table id="main-table"><tbody>'];
+  htmlBuffer.push('<tr>');
+  for (var col = 0; col < colCount; col++) {
+    // Build table headers
+    htmlBuffer.push('<th style="width:200px;">H</th>');
+  }
+  htmlBuffer.push('</tr>');
+  for (var i = 0; i < rowCount; i++) {
+    htmlBuffer.push('<tr>');
+    for (var j = 0; j < colCount; j++) {
+      htmlBuffer.push('<td>');
+      htmlBuffer.push(i + ', ' + j);
+      htmlBuffer.push('</td>');
+    }
+    htmlBuffer.push('</tr>');
+  }
+  htmlBuffer.push('</tbody></table>');
+  container.innerHTML = htmlBuffer.join('');
+}
+
+function reset() {
+  if (!container) return;
+  container.innerHTML = '';
+  container.scrollTop = 0;
+}
+
+function tearDown() {
+  container.innerHTML = '';
+  container.scrollTop = 0;
+}
+
+function testAsync(deferred, opt_size) {
+  var size = opt_size || DEFAULT_SIZE;
+  var maxScroll = container.firstChild.offsetHeight - container.offsetHeight;
+  while (container.scrollTop < maxScroll) {
+    container.scrollTop = container.scrollTop + 1000;
+  }
+  requestAnimationFrameFunction(function() { deferred.resolve(); });
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/template.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/template.html
new file mode 100644
index 0000000..9c0d741
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/template.html
@@ -0,0 +1,15 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>Test Title</title>
+<script>
+function setUp() {}
+function tearDown() {}
+function test() {}
+function reset() {}
+</script>
+</head>
+<body>
+</body>
+<script src="../test.js"></script>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/varargs.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/varargs.html
new file mode 100644
index 0000000..0dcd483
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/tests/varargs.html
@@ -0,0 +1,40 @@
+<!DOCTYPE html>
+<html>
+<head>
+<title>arguments instantiation is slow</title>
+<style>
+</style>
+</head>
+<body>
+<script>
+
+var args;
+var runs;
+var DEFAULT_RUNS = 100000;
+
+function setUp(opt_runs) {
+  runs = opt_runs || DEFAULT_RUNS;
+  args = new Array(runs);
+}
+
+function reset() {
+  args = new Array(runs);
+}
+
+function tearDown() {
+  delete args;
+}
+
+function test() {
+  for (var i = 0; i < runs; i++) {
+    checkArgs(args[i], args[i], args[i]);
+  }
+}
+
+function checkArgs() {
+  return Array.prototype.slice.call(arguments);
+}
+</script>
+<script src="../test.js"></script>
+</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/LICENSE.txt b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/LICENSE.txt
new file mode 100644
index 0000000..e33e63f
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/LICENSE.txt
@@ -0,0 +1,22 @@
+Copyright 2010-2012 Mathias Bynens <http://mathiasbynens.be/>
+Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
+Modified by John-David Dalton <http://allyoucanleet.com/>
+
+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.
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/README.md
new file mode 100644
index 0000000..62a2d1a
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/README.md
@@ -0,0 +1,135 @@
+# Benchmark.js <sup>v1.0.0</sup>
+
+A [robust](http://calendar.perfplanet.com/2010/bulletproof-javascript-benchmarks/ "Bulletproof JavaScript benchmarks") benchmarking library that works on nearly all JavaScript platforms<sup><a name="fnref1" href="#fn1">1</a></sup>, supports high-resolution timers, and returns statistically significant results. As seen on [jsPerf](http://jsperf.com/).
+
+## Download
+
+ * [Development source](https://raw.github.com/bestiejs/benchmark.js/v1.0.0/benchmark.js)
+
+## Dive in
+
+We’ve got [API docs](http://benchmarkjs.com/docs) and [unit tests](http://benchmarkjs.com/tests).
+
+For a list of upcoming features, check out our [roadmap](https://github.com/bestiejs/benchmark.js/wiki/Roadmap).
+
+## Support
+
+Benchmark.js has been tested in at least Adobe AIR 3.1, Chrome 5-21, Firefox 1.5-13, IE 6-9, Opera 9.25-12.01, Safari 3-6, Node.js 0.8.6, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
+
+## Installation and usage
+
+In a browser or Adobe AIR:
+
+```html
+<script src="benchmark.js"></script>
+```
+
+Optionally, expose Java’s nanosecond timer by adding the `nano` applet to the `<body>`:
+
+```html
+<applet code="nano" archive="nano.jar"></applet>
+```
+
+Or enable Chrome’s microsecond timer by using the [command line switch](http://peter.sh/experiments/chromium-command-line-switches/#enable-benchmarking):
+
+    --enable-benchmarking
+
+Via [npm](http://npmjs.org/):
+
+```bash
+npm install benchmark
+```
+
+In [Node.js](http://nodejs.org/) and [RingoJS v0.8.0+](http://ringojs.org/):
+
+```js
+var Benchmark = require('benchmark');
+```
+
+Optionally, use the [microtime module](https://github.com/wadey/node-microtime) by Wade Simmons:
+
+```bash
+npm install microtime
+```
+
+In [RingoJS v0.7.0-](http://ringojs.org/):
+
+```js
+var Benchmark = require('benchmark').Benchmark;
+```
+
+In [Rhino](http://www.mozilla.org/rhino/):
+
+```js
+load('benchmark.js');
+```
+
+In an AMD loader like [RequireJS](http://requirejs.org/):
+
+```js
+require({
+  'paths': {
+    'benchmark': 'path/to/benchmark'
+  }
+},
+['benchmark'], function(Benchmark) {
+  console.log(Benchmark.version);
+});
+
+// or with platform.js
+// https://github.com/bestiejs/platform.js
+require({
+  'paths': {
+    'benchmark': 'path/to/benchmark',
+    'platform': 'path/to/platform'
+  }
+},
+['benchmark', 'platform'], function(Benchmark, platform) {
+  Benchmark.platform = platform;
+  console.log(Benchmark.platform.name);
+});
+```
+
+Usage example:
+
+```js
+var suite = new Benchmark.Suite;
+
+// add tests
+suite.add('RegExp#test', function() {
+  /o/.test('Hello World!');
+})
+.add('String#indexOf', function() {
+  'Hello World!'.indexOf('o') > -1;
+})
+// add listeners
+.on('cycle', function(event) {
+  console.log(String(event.target));
+})
+.on('complete', function() {
+  console.log('Fastest is ' + this.filter('fastest').pluck('name'));
+})
+// run async
+.run({ 'async': true });
+
+// logs:
+// > RegExp#test x 4,161,532 +-0.99% (59 cycles)
+// > String#indexOf x 6,139,623 +-1.00% (131 cycles)
+// > Fastest is String#indexOf
+```
+
+## BestieJS
+
+Benchmark.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation.
+
+## Authors
+
+* [Mathias Bynens](http://mathiasbynens.be/)
+  [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter")
+* [John-David Dalton](http://allyoucanleet.com/)
+  [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter")
+
+## Contributors
+
+* [Kit Cambridge](http://kitcambridge.github.com/)
+  [![twitter/kitcambridge](http://gravatar.com/avatar/6662a1d02f351b5ef2f8b4d815804661?s=70)](https://twitter.com/kitcambridge "Follow @kitcambridge on Twitter")
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/benchmark.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/benchmark.js
new file mode 100644
index 0000000..db2c995
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/benchmark.js
@@ -0,0 +1,3919 @@
+/*!
+ * Benchmark.js v1.0.0 <http://benchmarkjs.com/>
+ * Copyright 2010-2012 Mathias Bynens <http://mths.be/>
+ * Based on JSLitmus.js, copyright Robert Kieffer <http://broofa.com/>
+ * Modified by John-David Dalton <http://allyoucanleet.com/>
+ * Available under MIT license <http://mths.be/mit>
+ */
+;(function(window, undefined) {
+  'use strict';
+
+  /** Used to assign each benchmark an incrimented id */
+  var counter = 0;
+
+  /** Detect DOM document object */
+  var doc = isHostType(window, 'document') && document;
+
+  /** Detect free variable `define` */
+  var freeDefine = typeof define == 'function' &&
+    typeof define.amd == 'object' && define.amd && define;
+
+  /** Detect free variable `exports` */
+  var freeExports = typeof exports == 'object' && exports &&
+    (typeof global == 'object' && global && global == global.global && (window = global), exports);
+
+  /** Detect free variable `require` */
+  var freeRequire = typeof require == 'function' && require;
+
+  /** Used to crawl all properties regardless of enumerability */
+  var getAllKeys = Object.getOwnPropertyNames;
+
+  /** Used to get property descriptors */
+  var getDescriptor = Object.getOwnPropertyDescriptor;
+
+  /** Used in case an object doesn't have its own method */
+  var hasOwnProperty = {}.hasOwnProperty;
+
+  /** Used to check if an object is extensible */
+  var isExtensible = Object.isExtensible || function() { return true; };
+
+  /** Used to access Wade Simmons' Node microtime module */
+  var microtimeObject = req('microtime');
+
+  /** Used to access the browser's high resolution timer */
+  var perfObject = isHostType(window, 'performance') && performance;
+
+  /** Used to call the browser's high resolution timer */
+  var perfName = perfObject && (
+    perfObject.now && 'now' ||
+    perfObject.webkitNow && 'webkitNow'
+  );
+
+  /** Used to access Node's high resolution timer */
+  var processObject = isHostType(window, 'process') && process;
+
+  /** Used to check if an own property is enumerable */
+  var propertyIsEnumerable = {}.propertyIsEnumerable;
+
+  /** Used to set property descriptors */
+  var setDescriptor = Object.defineProperty;
+
+  /** Used to resolve a value's internal [[Class]] */
+  var toString = {}.toString;
+
+  /** Used to prevent a `removeChild` memory leak in IE < 9 */
+  var trash = doc && doc.createElement('div');
+
+  /** Used to integrity check compiled tests */
+  var uid = 'uid' + (+new Date);
+
+  /** Used to avoid infinite recursion when methods call each other */
+  var calledBy = {};
+
+  /** Used to avoid hz of Infinity */
+  var divisors = {
+    '1': 4096,
+    '2': 512,
+    '3': 64,
+    '4': 8,
+    '5': 0
+  };
+
+  /**
+   * T-Distribution two-tailed critical values for 95% confidence
+   * http://www.itl.nist.gov/div898/handbook/eda/section3/eda3672.htm
+   */
+  var tTable = {
+    '1':  12.706,'2':  4.303, '3':  3.182, '4':  2.776, '5':  2.571, '6':  2.447,
+    '7':  2.365, '8':  2.306, '9':  2.262, '10': 2.228, '11': 2.201, '12': 2.179,
+    '13': 2.16,  '14': 2.145, '15': 2.131, '16': 2.12,  '17': 2.11,  '18': 2.101,
+    '19': 2.093, '20': 2.086, '21': 2.08,  '22': 2.074, '23': 2.069, '24': 2.064,
+    '25': 2.06,  '26': 2.056, '27': 2.052, '28': 2.048, '29': 2.045, '30': 2.042,
+    'infinity': 1.96
+  };
+
+  /**
+   * Critical Mann-Whitney U-values for 95% confidence
+   * http://www.saburchill.com/IBbiology/stats/003.html
+   */
+  var uTable = {
+    '5':  [0, 1, 2],
+    '6':  [1, 2, 3, 5],
+    '7':  [1, 3, 5, 6, 8],
+    '8':  [2, 4, 6, 8, 10, 13],
+    '9':  [2, 4, 7, 10, 12, 15, 17],
+    '10': [3, 5, 8, 11, 14, 17, 20, 23],
+    '11': [3, 6, 9, 13, 16, 19, 23, 26, 30],
+    '12': [4, 7, 11, 14, 18, 22, 26, 29, 33, 37],
+    '13': [4, 8, 12, 16, 20, 24, 28, 33, 37, 41, 45],
+    '14': [5, 9, 13, 17, 22, 26, 31, 36, 40, 45, 50, 55],
+    '15': [5, 10, 14, 19, 24, 29, 34, 39, 44, 49, 54, 59, 64],
+    '16': [6, 11, 15, 21, 26, 31, 37, 42, 47, 53, 59, 64, 70, 75],
+    '17': [6, 11, 17, 22, 28, 34, 39, 45, 51, 57, 63, 67, 75, 81, 87],
+    '18': [7, 12, 18, 24, 30, 36, 42, 48, 55, 61, 67, 74, 80, 86, 93, 99],
+    '19': [7, 13, 19, 25, 32, 38, 45, 52, 58, 65, 72, 78, 85, 92, 99, 106, 113],
+    '20': [8, 14, 20, 27, 34, 41, 48, 55, 62, 69, 76, 83, 90, 98, 105, 112, 119, 127],
+    '21': [8, 15, 22, 29, 36, 43, 50, 58, 65, 73, 80, 88, 96, 103, 111, 119, 126, 134, 142],
+    '22': [9, 16, 23, 30, 38, 45, 53, 61, 69, 77, 85, 93, 101, 109, 117, 125, 133, 141, 150, 158],
+    '23': [9, 17, 24, 32, 40, 48, 56, 64, 73, 81, 89, 98, 106, 115, 123, 132, 140, 149, 157, 166, 175],
+    '24': [10, 17, 25, 33, 42, 50, 59, 67, 76, 85, 94, 102, 111, 120, 129, 138, 147, 156, 165, 174, 183, 192],
+    '25': [10, 18, 27, 35, 44, 53, 62, 71, 80, 89, 98, 107, 117, 126, 135, 145, 154, 163, 173, 182, 192, 201, 211],
+    '26': [11, 19, 28, 37, 46, 55, 64, 74, 83, 93, 102, 112, 122, 132, 141, 151, 161, 171, 181, 191, 200, 210, 220, 230],
+    '27': [11, 20, 29, 38, 48, 57, 67, 77, 87, 97, 107, 118, 125, 138, 147, 158, 168, 178, 188, 199, 209, 219, 230, 240, 250],
+    '28': [12, 21, 30, 40, 50, 60, 70, 80, 90, 101, 111, 122, 132, 143, 154, 164, 175, 186, 196, 207, 218, 228, 239, 250, 261, 272],
+    '29': [13, 22, 32, 42, 52, 62, 73, 83, 94, 105, 116, 127, 138, 149, 160, 171, 182, 193, 204, 215, 226, 238, 249, 260, 271, 282, 294],
+    '30': [13, 23, 33, 43, 54, 65, 76, 87, 98, 109, 120, 131, 143, 154, 166, 177, 189, 200, 212, 223, 235, 247, 258, 270, 282, 293, 305, 317]
+  };
+
+  /**
+   * An object used to flag environments/features.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @type Object
+   */
+  var support = {};
+
+  (function() {
+
+    /**
+     * Detect Adobe AIR.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.air = isClassOf(window.runtime, 'ScriptBridgingProxyObject');
+
+    /**
+     * Detect if `arguments` objects have the correct internal [[Class]] value.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.argumentsClass = isClassOf(arguments, 'Arguments');
+
+    /**
+     * Detect if in a browser environment.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.browser = doc && isHostType(window, 'navigator');
+
+    /**
+     * Detect if strings support accessing characters by index.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.charByIndex =
+      // IE 8 supports indexes on string literals but not string objects
+      ('x'[0] + Object('x')[0]) == 'xx';
+
+    /**
+     * Detect if strings have indexes as own properties.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.charByOwnIndex =
+      // Narwhal, Rhino, RingoJS, IE 8, and Opera < 10.52 support indexes on
+      // strings but don't detect them as own properties
+      support.charByIndex && hasKey('x', '0');
+
+    /**
+     * Detect if Java is enabled/exposed.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.java = isClassOf(window.java, 'JavaPackage');
+
+    /**
+     * Detect if the Timers API exists.
+     *
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.timeout = isHostType(window, 'setTimeout') && isHostType(window, 'clearTimeout');
+
+    /**
+     * Detect if functions support decompilation.
+     *
+     * @name decompilation
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      // Safari 2.x removes commas in object literals
+      // from Function#toString results
+      // http://webk.it/11609
+      // Firefox 3.6 and Opera 9.25 strip grouping
+      // parentheses from Function#toString results
+      // http://bugzil.la/559438
+      support.decompilation = Function(
+        'return (' + (function(x) { return { 'x': '' + (1 + x) + '', 'y': 0 }; }) + ')'
+      )()(0).x === '1';
+    } catch(e) {
+      support.decompilation = false;
+    }
+
+    /**
+     * Detect ES5+ property descriptor API.
+     *
+     * @name descriptors
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      var o = {};
+      support.descriptors = (setDescriptor(o, o, o), 'value' in getDescriptor(o, o));
+    } catch(e) {
+      support.descriptors = false;
+    }
+
+    /**
+     * Detect ES5+ Object.getOwnPropertyNames().
+     *
+     * @name getAllKeys
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      support.getAllKeys = /\bvalueOf\b/.test(getAllKeys(Object.prototype));
+    } catch(e) {
+      support.getAllKeys = false;
+    }
+
+    /**
+     * Detect if own properties are iterated before inherited properties (all but IE < 9).
+     *
+     * @name iteratesOwnLast
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    support.iteratesOwnFirst = (function() {
+      var props = [];
+      function ctor() { this.x = 1; }
+      ctor.prototype = { 'y': 1 };
+      for (var prop in new ctor) { props.push(prop); }
+      return props[0] == 'x';
+    }());
+
+    /**
+     * Detect if a node's [[Class]] is resolvable (all but IE < 9)
+     * and that the JS engine errors when attempting to coerce an object to a
+     * string without a `toString` property value of `typeof` "function".
+     *
+     * @name nodeClass
+     * @memberOf Benchmark.support
+     * @type Boolean
+     */
+    try {
+      support.nodeClass = ({ 'toString': 0 } + '', toString.call(doc || 0) != '[object Object]');
+    } catch(e) {
+      support.nodeClass = true;
+    }
+  }());
+
+  /**
+   * Timer object used by `clock()` and `Deferred#resolve`.
+   *
+   * @private
+   * @type Object
+   */
+  var timer = {
+
+   /**
+    * The timer namespace object or constructor.
+    *
+    * @private
+    * @memberOf timer
+    * @type Function|Object
+    */
+    'ns': Date,
+
+   /**
+    * Starts the deferred timer.
+    *
+    * @private
+    * @memberOf timer
+    * @param {Object} deferred The deferred instance.
+    */
+    'start': null, // lazy defined in `clock()`
+
+   /**
+    * Stops the deferred timer.
+    *
+    * @private
+    * @memberOf timer
+    * @param {Object} deferred The deferred instance.
+    */
+    'stop': null // lazy defined in `clock()`
+  };
+
+  /** Shortcut for inverse results */
+  var noArgumentsClass = !support.argumentsClass,
+      noCharByIndex = !support.charByIndex,
+      noCharByOwnIndex = !support.charByOwnIndex;
+
+  /** Math shortcuts */
+  var abs   = Math.abs,
+      floor = Math.floor,
+      max   = Math.max,
+      min   = Math.min,
+      pow   = Math.pow,
+      sqrt  = Math.sqrt;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The Benchmark constructor.
+   *
+   * @constructor
+   * @param {String} name A name to identify the benchmark.
+   * @param {Function|String} fn The test to benchmark.
+   * @param {Object} [options={}] Options object.
+   * @example
+   *
+   * // basic usage (the `new` operator is optional)
+   * var bench = new Benchmark(fn);
+   *
+   * // or using a name first
+   * var bench = new Benchmark('foo', fn);
+   *
+   * // or with options
+   * var bench = new Benchmark('foo', fn, {
+   *
+   *   // displayed by Benchmark#toString if `name` is not available
+   *   'id': 'xyz',
+   *
+   *   // called when the benchmark starts running
+   *   'onStart': onStart,
+   *
+   *   // called after each run cycle
+   *   'onCycle': onCycle,
+   *
+   *   // called when aborted
+   *   'onAbort': onAbort,
+   *
+   *   // called when a test errors
+   *   'onError': onError,
+   *
+   *   // called when reset
+   *   'onReset': onReset,
+   *
+   *   // called when the benchmark completes running
+   *   'onComplete': onComplete,
+   *
+   *   // compiled/called before the test loop
+   *   'setup': setup,
+   *
+   *   // compiled/called after the test loop
+   *   'teardown': teardown
+   * });
+   *
+   * // or name and options
+   * var bench = new Benchmark('foo', {
+   *
+   *   // a flag to indicate the benchmark is deferred
+   *   'defer': true,
+   *
+   *   // benchmark test function
+   *   'fn': function(deferred) {
+   *     // call resolve() when the deferred test is finished
+   *     deferred.resolve();
+   *   }
+   * });
+   *
+   * // or options only
+   * var bench = new Benchmark({
+   *
+   *   // benchmark name
+   *   'name': 'foo',
+   *
+   *   // benchmark test as a string
+   *   'fn': '[1,2,3,4].sort()'
+   * });
+   *
+   * // a test's `this` binding is set to the benchmark instance
+   * var bench = new Benchmark('foo', function() {
+   *   'My name is '.concat(this.name); // My name is foo
+   * });
+   */
+  function Benchmark(name, fn, options) {
+    var me = this;
+
+    // allow instance creation without the `new` operator
+    if (me == null || me.constructor != Benchmark) {
+      return new Benchmark(name, fn, options);
+    }
+    // juggle arguments
+    if (isClassOf(name, 'Object')) {
+      // 1 argument (options)
+      options = name;
+    }
+    else if (isClassOf(name, 'Function')) {
+      // 2 arguments (fn, options)
+      options = fn;
+      fn = name;
+    }
+    else if (isClassOf(fn, 'Object')) {
+      // 2 arguments (name, options)
+      options = fn;
+      fn = null;
+      me.name = name;
+    }
+    else {
+      // 3 arguments (name, fn [, options])
+      me.name = name;
+    }
+    setOptions(me, options);
+    me.id || (me.id = ++counter);
+    me.fn == null && (me.fn = fn);
+    me.stats = deepClone(me.stats);
+    me.times = deepClone(me.times);
+  }
+
+  /**
+   * The Deferred constructor.
+   *
+   * @constructor
+   * @memberOf Benchmark
+   * @param {Object} clone The cloned benchmark instance.
+   */
+  function Deferred(clone) {
+    var me = this;
+    if (me == null || me.constructor != Deferred) {
+      return new Deferred(clone);
+    }
+    me.benchmark = clone;
+    clock(me);
+  }
+
+  /**
+   * The Event constructor.
+   *
+   * @constructor
+   * @memberOf Benchmark
+   * @param {String|Object} type The event type.
+   */
+  function Event(type) {
+    var me = this;
+    return (me == null || me.constructor != Event)
+      ? new Event(type)
+      : (type instanceof Event)
+          ? type
+          : extend(me, { 'timeStamp': +new Date }, typeof type == 'string' ? { 'type': type } : type);
+  }
+
+  /**
+   * The Suite constructor.
+   *
+   * @constructor
+   * @memberOf Benchmark
+   * @param {String} name A name to identify the suite.
+   * @param {Object} [options={}] Options object.
+   * @example
+   *
+   * // basic usage (the `new` operator is optional)
+   * var suite = new Benchmark.Suite;
+   *
+   * // or using a name first
+   * var suite = new Benchmark.Suite('foo');
+   *
+   * // or with options
+   * var suite = new Benchmark.Suite('foo', {
+   *
+   *   // called when the suite starts running
+   *   'onStart': onStart,
+   *
+   *   // called between running benchmarks
+   *   'onCycle': onCycle,
+   *
+   *   // called when aborted
+   *   'onAbort': onAbort,
+   *
+   *   // called when a test errors
+   *   'onError': onError,
+   *
+   *   // called when reset
+   *   'onReset': onReset,
+   *
+   *   // called when the suite completes running
+   *   'onComplete': onComplete
+   * });
+   */
+  function Suite(name, options) {
+    var me = this;
+
+    // allow instance creation without the `new` operator
+    if (me == null || me.constructor != Suite) {
+      return new Suite(name, options);
+    }
+    // juggle arguments
+    if (isClassOf(name, 'Object')) {
+      // 1 argument (options)
+      options = name;
+    } else {
+      // 2 arguments (name [, options])
+      me.name = name;
+    }
+    setOptions(me, options);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Note: Some array methods have been implemented in plain JavaScript to avoid
+   * bugs in IE, Opera, Rhino, and Mobile Safari.
+   *
+   * IE compatibility mode and IE < 9 have buggy Array `shift()` and `splice()`
+   * functions that fail to remove the last element, `object[0]`, of
+   * array-like-objects even though the `length` property is set to `0`.
+   * The `shift()` method is buggy in IE 8 compatibility mode, while `splice()`
+   * is buggy regardless of mode in IE < 9 and buggy in compatibility mode in IE 9.
+   *
+   * In Opera < 9.50 and some older/beta Mobile Safari versions using `unshift()`
+   * generically to augment the `arguments` object will pave the value at index 0
+   * without incrimenting the other values's indexes.
+   * https://github.com/documentcloud/underscore/issues/9
+   *
+   * Rhino and environments it powers, like Narwhal and RingoJS, may have
+   * buggy Array `concat()`, `reverse()`, `shift()`, `slice()`, `splice()` and
+   * `unshift()` functions that make sparse arrays non-sparse by assigning the
+   * undefined indexes a value of undefined.
+   * https://github.com/mozilla/rhino/commit/702abfed3f8ca043b2636efd31c14ba7552603dd
+   */
+
+  /**
+   * Creates an array containing the elements of the host array followed by the
+   * elements of each argument in order.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Array} The new array.
+   */
+  function concat() {
+    var value,
+        j = -1,
+        length = arguments.length,
+        result = slice.call(this),
+        index = result.length;
+
+    while (++j < length) {
+      value = arguments[j];
+      if (isClassOf(value, 'Array')) {
+        for (var k = 0, l = value.length; k < l; k++, index++) {
+          if (k in value) {
+            result[index] = value[k];
+          }
+        }
+      } else {
+        result[index++] = value;
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Utility function used by `shift()`, `splice()`, and `unshift()`.
+   *
+   * @private
+   * @param {Number} start The index to start inserting elements.
+   * @param {Number} deleteCount The number of elements to delete from the insert point.
+   * @param {Array} elements The elements to insert.
+   * @returns {Array} An array of deleted elements.
+   */
+  function insert(start, deleteCount, elements) {
+    // `result` should have its length set to the `deleteCount`
+    // see https://bugs.ecmascript.org/show_bug.cgi?id=332
+    var deleteEnd = start + deleteCount,
+        elementCount = elements ? elements.length : 0,
+        index = start - 1,
+        length = start + elementCount,
+        object = this,
+        result = Array(deleteCount),
+        tail = slice.call(object, deleteEnd);
+
+    // delete elements from the array
+    while (++index < deleteEnd) {
+      if (index in object) {
+        result[index - start] = object[index];
+        delete object[index];
+      }
+    }
+    // insert elements
+    index = start - 1;
+    while (++index < length) {
+      object[index] = elements[index - start];
+    }
+    // append tail elements
+    start = index--;
+    length = max(0, (object.length >>> 0) - deleteCount + elementCount);
+    while (++index < length) {
+      if ((index - start) in tail) {
+        object[index] = tail[index - start];
+      } else if (index in object) {
+        delete object[index];
+      }
+    }
+    // delete excess elements
+    deleteCount = deleteCount > elementCount ? deleteCount - elementCount : 0;
+    while (deleteCount--) {
+      index = length + deleteCount;
+      if (index in object) {
+        delete object[index];
+      }
+    }
+    object.length = length;
+    return result;
+  }
+
+  /**
+   * Rearrange the host array's elements in reverse order.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Array} The reversed array.
+   */
+  function reverse() {
+    var upperIndex,
+        value,
+        index = -1,
+        object = Object(this),
+        length = object.length >>> 0,
+        middle = floor(length / 2);
+
+    if (length > 1) {
+      while (++index < middle) {
+        upperIndex = length - index - 1;
+        value = upperIndex in object ? object[upperIndex] : uid;
+        if (index in object) {
+          object[upperIndex] = object[index];
+        } else {
+          delete object[upperIndex];
+        }
+        if (value != uid) {
+          object[index] = value;
+        } else {
+          delete object[index];
+        }
+      }
+    }
+    return object;
+  }
+
+  /**
+   * Removes the first element of the host array and returns it.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Mixed} The first element of the array.
+   */
+  function shift() {
+    return insert.call(this, 0, 1)[0];
+  }
+
+  /**
+   * Creates an array of the host array's elements from the start index up to,
+   * but not including, the end index.
+   *
+   * @memberOf Benchmark.Suite
+   * @param {Number} start The starting index.
+   * @param {Number} end The end index.
+   * @returns {Array} The new array.
+   */
+  function slice(start, end) {
+    var index = -1,
+        object = Object(this),
+        length = object.length >>> 0,
+        result = [];
+
+    start = toInteger(start);
+    start = start < 0 ? max(length + start, 0) : min(start, length);
+    start--;
+    end = end == null ? length : toInteger(end);
+    end = end < 0 ? max(length + end, 0) : min(end, length);
+
+    while ((++index, ++start) < end) {
+      if (start in object) {
+        result[index] = object[start];
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Allows removing a range of elements and/or inserting elements into the
+   * host array.
+   *
+   * @memberOf Benchmark.Suite
+   * @param {Number} start The start index.
+   * @param {Number} deleteCount The number of elements to delete.
+   * @param {Mixed} [val1, val2, ...] values to insert at the `start` index.
+   * @returns {Array} An array of removed elements.
+   */
+  function splice(start, deleteCount) {
+    var object = Object(this),
+        length = object.length >>> 0;
+
+    start = toInteger(start);
+    start = start < 0 ? max(length + start, 0) : min(start, length);
+
+    // support the de-facto SpiderMonkey extension
+    // https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Array/splice#Parameters
+    // https://bugs.ecmascript.org/show_bug.cgi?id=429
+    deleteCount = arguments.length == 1
+      ? length - start
+      : min(max(toInteger(deleteCount), 0), length - start);
+
+    return insert.call(object, start, deleteCount, slice.call(arguments, 2));
+  }
+
+  /**
+   * Converts the specified `value` to an integer.
+   *
+   * @private
+   * @param {Mixed} value The value to convert.
+   * @returns {Number} The resulting integer.
+   */
+  function toInteger(value) {
+    value = +value;
+    return value === 0 || !isFinite(value) ? value || 0 : value - (value % 1);
+  }
+
+  /**
+   * Appends arguments to the host array.
+   *
+   * @memberOf Benchmark.Suite
+   * @returns {Number} The new length.
+   */
+  function unshift() {
+    var object = Object(this);
+    insert.call(object, 0, 0, arguments);
+    return object.length;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * A generic `Function#bind` like method.
+   *
+   * @private
+   * @param {Function} fn The function to be bound to `thisArg`.
+   * @param {Mixed} thisArg The `this` binding for the given function.
+   * @returns {Function} The bound function.
+   */
+  function bind(fn, thisArg) {
+    return function() { fn.apply(thisArg, arguments); };
+  }
+
+  /**
+   * Creates a function from the given arguments string and body.
+   *
+   * @private
+   * @param {String} args The comma separated function arguments.
+   * @param {String} body The function body.
+   * @returns {Function} The new function.
+   */
+  function createFunction() {
+    // lazy define
+    createFunction = function(args, body) {
+      var result,
+          anchor = freeDefine ? define.amd : Benchmark,
+          prop = uid + 'createFunction';
+
+      runScript((freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '=function(' + args + '){' + body + '}');
+      result = anchor[prop];
+      delete anchor[prop];
+      return result;
+    };
+    // fix JaegerMonkey bug
+    // http://bugzil.la/639720
+    createFunction = support.browser && (createFunction('', 'return"' + uid + '"') || noop)() == uid ? createFunction : Function;
+    return createFunction.apply(null, arguments);
+  }
+
+  /**
+   * Delay the execution of a function based on the benchmark's `delay` property.
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @param {Object} fn The function to execute.
+   */
+  function delay(bench, fn) {
+    bench._timerId = setTimeout(fn, bench.delay * 1e3);
+  }
+
+  /**
+   * Destroys the given element.
+   *
+   * @private
+   * @param {Element} element The element to destroy.
+   */
+  function destroyElement(element) {
+    trash.appendChild(element);
+    trash.innerHTML = '';
+  }
+
+  /**
+   * Iterates over an object's properties, executing the `callback` for each.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @private
+   * @param {Object} object The object to iterate over.
+   * @param {Function} callback The function executed per own property.
+   * @param {Object} options The options object.
+   * @returns {Object} Returns the object iterated over.
+   */
+  function forProps() {
+    var forShadowed,
+        skipSeen,
+        forArgs = true,
+        shadowed = ['constructor', 'hasOwnProperty', 'isPrototypeOf', 'propertyIsEnumerable', 'toLocaleString', 'toString', 'valueOf'];
+
+    (function(enumFlag, key) {
+      // must use a non-native constructor to catch the Safari 2 issue
+      function Klass() { this.valueOf = 0; };
+      Klass.prototype.valueOf = 0;
+      // check various for-in bugs
+      for (key in new Klass) {
+        enumFlag += key == 'valueOf' ? 1 : 0;
+      }
+      // check if `arguments` objects have non-enumerable indexes
+      for (key in arguments) {
+        key == '0' && (forArgs = false);
+      }
+      // Safari 2 iterates over shadowed properties twice
+      // http://replay.waybackmachine.org/20090428222941/http://tobielangel.com/2007/1/29/for-in-loop-broken-in-safari/
+      skipSeen = enumFlag == 2;
+      // IE < 9 incorrectly makes an object's properties non-enumerable if they have
+      // the same name as other non-enumerable properties in its prototype chain.
+      forShadowed = !enumFlag;
+    }(0));
+
+    // lazy define
+    forProps = function(object, callback, options) {
+      options || (options = {});
+
+      var result = object;
+      object = Object(object);
+
+      var ctor,
+          key,
+          keys,
+          skipCtor,
+          done = !result,
+          which = options.which,
+          allFlag = which == 'all',
+          index = -1,
+          iteratee = object,
+          length = object.length,
+          ownFlag = allFlag || which == 'own',
+          seen = {},
+          skipProto = isClassOf(object, 'Function'),
+          thisArg = options.bind;
+
+      if (thisArg !== undefined) {
+        callback = bind(callback, thisArg);
+      }
+      // iterate all properties
+      if (allFlag && support.getAllKeys) {
+        for (index = 0, keys = getAllKeys(object), length = keys.length; index < length; index++) {
+          key = keys[index];
+          if (callback(object[key], key, object) === false) {
+            break;
+          }
+        }
+      }
+      // else iterate only enumerable properties
+      else {
+        for (key in object) {
+          // Firefox < 3.6, Opera > 9.50 - Opera < 11.60, and Safari < 5.1
+          // (if the prototype or a property on the prototype has been set)
+          // incorrectly set a function's `prototype` property [[Enumerable]] value
+          // to `true`. Because of this we standardize on skipping the `prototype`
+          // property of functions regardless of their [[Enumerable]] value.
+          if ((done =
+              !(skipProto && key == 'prototype') &&
+              !(skipSeen && (hasKey(seen, key) || !(seen[key] = true))) &&
+              (!ownFlag || ownFlag && hasKey(object, key)) &&
+              callback(object[key], key, object) === false)) {
+            break;
+          }
+        }
+        // in IE < 9 strings don't support accessing characters by index
+        if (!done && (forArgs && isArguments(object) ||
+            ((noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String') &&
+              (iteratee = noCharByIndex ? object.split('') : object)))) {
+          while (++index < length) {
+            if ((done =
+                callback(iteratee[index], String(index), object) === false)) {
+              break;
+            }
+          }
+        }
+        if (!done && forShadowed) {
+          // Because IE < 9 can't set the `[[Enumerable]]` attribute of an existing
+          // property and the `constructor` property of a prototype defaults to
+          // non-enumerable, we manually skip the `constructor` property when we
+          // think we are iterating over a `prototype` object.
+          ctor = object.constructor;
+          skipCtor = ctor && ctor.prototype && ctor.prototype.constructor === ctor;
+          for (index = 0; index < 7; index++) {
+            key = shadowed[index];
+            if (!(skipCtor && key == 'constructor') &&
+                hasKey(object, key) &&
+                callback(object[key], key, object) === false) {
+              break;
+            }
+          }
+        }
+      }
+      return result;
+    };
+    return forProps.apply(null, arguments);
+  }
+
+  /**
+   * Gets the name of the first argument from a function's source.
+   *
+   * @private
+   * @param {Function} fn The function.
+   * @returns {String} The argument name.
+   */
+  function getFirstArgument(fn) {
+    return (!hasKey(fn, 'toString') &&
+      (/^[\s(]*function[^(]*\(([^\s,)]+)/.exec(fn) || 0)[1]) || '';
+  }
+
+  /**
+   * Computes the arithmetic mean of a sample.
+   *
+   * @private
+   * @param {Array} sample The sample.
+   * @returns {Number} The mean.
+   */
+  function getMean(sample) {
+    return reduce(sample, function(sum, x) {
+      return sum + x;
+    }) / sample.length || 0;
+  }
+
+  /**
+   * Gets the source code of a function.
+   *
+   * @private
+   * @param {Function} fn The function.
+   * @param {String} altSource A string used when a function's source code is unretrievable.
+   * @returns {String} The function's source code.
+   */
+  function getSource(fn, altSource) {
+    var result = altSource;
+    if (isStringable(fn)) {
+      result = String(fn);
+    } else if (support.decompilation) {
+      // escape the `{` for Firefox 1
+      result = (/^[^{]+\{([\s\S]*)}\s*$/.exec(fn) || 0)[1];
+    }
+    // trim string
+    result = (result || '').replace(/^\s+|\s+$/g, '');
+
+    // detect strings containing only the "use strict" directive
+    return /^(?:\/\*+[\w|\W]*?\*\/|\/\/.*?[\n\r\u2028\u2029]|\s)*(["'])use strict\1;?$/.test(result)
+      ? ''
+      : result;
+  }
+
+  /**
+   * Checks if a value is an `arguments` object.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the value is an `arguments` object, else `false`.
+   */
+  function isArguments() {
+    // lazy define
+    isArguments = function(value) {
+      return toString.call(value) == '[object Arguments]';
+    };
+    if (noArgumentsClass) {
+      isArguments = function(value) {
+        return hasKey(value, 'callee') &&
+          !(propertyIsEnumerable && propertyIsEnumerable.call(value, 'callee'));
+      };
+    }
+    return isArguments(arguments[0]);
+  }
+
+  /**
+   * Checks if an object is of the specified class.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @param {String} name The name of the class.
+   * @returns {Boolean} Returns `true` if the value is of the specified class, else `false`.
+   */
+  function isClassOf(value, name) {
+    return value != null && toString.call(value) == '[object ' + name + ']';
+  }
+
+  /**
+   * Host objects can return type values that are different from their actual
+   * data type. The objects we are concerned with usually return non-primitive
+   * types of object, function, or unknown.
+   *
+   * @private
+   * @param {Mixed} object The owner of the property.
+   * @param {String} property The property to check.
+   * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`.
+   */
+  function isHostType(object, property) {
+    var type = object != null ? typeof object[property] : 'number';
+    return !/^(?:boolean|number|string|undefined)$/.test(type) &&
+      (type == 'object' ? !!object[property] : true);
+  }
+
+  /**
+   * Checks if a given `value` is an object created by the `Object` constructor
+   * assuming objects created by the `Object` constructor have no inherited
+   * enumerable properties and that there are no `Object.prototype` extensions.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the `value` is a plain `Object` object, else `false`.
+   */
+  function isPlainObject(value) {
+    // avoid non-objects and false positives for `arguments` objects in IE < 9
+    var result = false;
+    if (!(value && typeof value == 'object') || isArguments(value)) {
+      return result;
+    }
+    // IE < 9 presents DOM nodes as `Object` objects except they have `toString`
+    // methods that are `typeof` "string" and still can coerce nodes to strings.
+    // Also check that the constructor is `Object` (i.e. `Object instanceof Object`)
+    var ctor = value.constructor;
+    if ((support.nodeClass || !(typeof value.toString != 'function' && typeof (value + '') == 'string')) &&
+        (!isClassOf(ctor, 'Function') || ctor instanceof ctor)) {
+      // In most environments an object's own properties are iterated before
+      // its inherited properties. If the last iterated property is an object's
+      // own property then there are no inherited enumerable properties.
+      if (support.iteratesOwnFirst) {
+        forProps(value, function(subValue, subKey) {
+          result = subKey;
+        });
+        return result === false || hasKey(value, result);
+      }
+      // IE < 9 iterates inherited properties before own properties. If the first
+      // iterated property is an object's own property then there are no inherited
+      // enumerable properties.
+      forProps(value, function(subValue, subKey) {
+        result = !hasKey(value, subKey);
+        return false;
+      });
+      return result === false;
+    }
+    return result;
+  }
+
+  /**
+   * Checks if a value can be safely coerced to a string.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the value can be coerced, else `false`.
+   */
+  function isStringable(value) {
+    return hasKey(value, 'toString') || isClassOf(value, 'String');
+  }
+
+  /**
+   * Wraps a function and passes `this` to the original function as the
+   * first argument.
+   *
+   * @private
+   * @param {Function} fn The function to be wrapped.
+   * @returns {Function} The new function.
+   */
+  function methodize(fn) {
+    return function() {
+      var args = [this];
+      args.push.apply(args, arguments);
+      return fn.apply(null, args);
+    };
+  }
+
+  /**
+   * A no-operation function.
+   *
+   * @private
+   */
+  function noop() {
+    // no operation performed
+  }
+
+  /**
+   * A wrapper around require() to suppress `module missing` errors.
+   *
+   * @private
+   * @param {String} id The module id.
+   * @returns {Mixed} The exported module or `null`.
+   */
+  function req(id) {
+    try {
+      var result = freeExports && freeRequire(id);
+    } catch(e) { }
+    return result || null;
+  }
+
+  /**
+   * Runs a snippet of JavaScript via script injection.
+   *
+   * @private
+   * @param {String} code The code to run.
+   */
+  function runScript(code) {
+    var anchor = freeDefine ? define.amd : Benchmark,
+        script = doc.createElement('script'),
+        sibling = doc.getElementsByTagName('script')[0],
+        parent = sibling.parentNode,
+        prop = uid + 'runScript',
+        prefix = '(' + (freeDefine ? 'define.amd.' : 'Benchmark.') + prop + '||function(){})();';
+
+    // Firefox 2.0.0.2 cannot use script injection as intended because it executes
+    // asynchronously, but that's OK because script injection is only used to avoid
+    // the previously commented JaegerMonkey bug.
+    try {
+      // remove the inserted script *before* running the code to avoid differences
+      // in the expected script element count/order of the document.
+      script.appendChild(doc.createTextNode(prefix + code));
+      anchor[prop] = function() { destroyElement(script); };
+    } catch(e) {
+      parent = parent.cloneNode(false);
+      sibling = null;
+      script.text = code;
+    }
+    parent.insertBefore(script, sibling);
+    delete anchor[prop];
+  }
+
+  /**
+   * A helper function for setting options/event handlers.
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @param {Object} [options={}] Options object.
+   */
+  function setOptions(bench, options) {
+    options = extend({}, bench.constructor.options, options);
+    bench.options = forOwn(options, function(value, key) {
+      if (value != null) {
+        // add event listeners
+        if (/^on[A-Z]/.test(key)) {
+          forEach(key.split(' '), function(key) {
+            bench.on(key.slice(2).toLowerCase(), value);
+          });
+        } else if (!hasKey(bench, key)) {
+          bench[key] = deepClone(value);
+        }
+      }
+    });
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Handles cycling/completing the deferred benchmark.
+   *
+   * @memberOf Benchmark.Deferred
+   */
+  function resolve() {
+    var me = this,
+        clone = me.benchmark,
+        bench = clone._original;
+
+    if (bench.aborted) {
+      // cycle() -> clone cycle/complete event -> compute()'s invoked bench.run() cycle/complete
+      me.teardown();
+      clone.running = false;
+      cycle(me);
+    }
+    else if (++me.cycles < clone.count) {
+      // continue the test loop
+      if (support.timeout) {
+        // use setTimeout to avoid a call stack overflow if called recursively
+        setTimeout(function() { clone.compiled.call(me, timer); }, 0);
+      } else {
+        clone.compiled.call(me, timer);
+      }
+    }
+    else {
+      timer.stop(me);
+      me.teardown();
+      delay(clone, function() { cycle(me); });
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * A deep clone utility.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Mixed} value The value to clone.
+   * @returns {Mixed} The cloned value.
+   */
+  function deepClone(value) {
+    var accessor,
+        circular,
+        clone,
+        ctor,
+        descriptor,
+        extensible,
+        key,
+        length,
+        markerKey,
+        parent,
+        result,
+        source,
+        subIndex,
+        data = { 'value': value },
+        index = 0,
+        marked = [],
+        queue = { 'length': 0 },
+        unmarked = [];
+
+    /**
+     * An easily detectable decorator for cloned values.
+     */
+    function Marker(object) {
+      this.raw = object;
+    }
+
+    /**
+     * The callback used by `forProps()`.
+     */
+    function forPropsCallback(subValue, subKey) {
+      // exit early to avoid cloning the marker
+      if (subValue && subValue.constructor == Marker) {
+        return;
+      }
+      // add objects to the queue
+      if (subValue === Object(subValue)) {
+        queue[queue.length++] = { 'key': subKey, 'parent': clone, 'source': value };
+      }
+      // assign non-objects
+      else {
+        try {
+          // will throw an error in strict mode if the property is read-only
+          clone[subKey] = subValue;
+        } catch(e) { }
+      }
+    }
+
+    /**
+     * Gets an available marker key for the given object.
+     */
+    function getMarkerKey(object) {
+      // avoid collisions with existing keys
+      var result = uid;
+      while (object[result] && object[result].constructor != Marker) {
+        result += 1;
+      }
+      return result;
+    }
+
+    do {
+      key = data.key;
+      parent = data.parent;
+      source = data.source;
+      clone = value = source ? source[key] : data.value;
+      accessor = circular = descriptor = false;
+
+      // create a basic clone to filter out functions, DOM elements, and
+      // other non `Object` objects
+      if (value === Object(value)) {
+        // use custom deep clone function if available
+        if (isClassOf(value.deepClone, 'Function')) {
+          clone = value.deepClone();
+        } else {
+          ctor = value.constructor;
+          switch (toString.call(value)) {
+            case '[object Array]':
+              clone = new ctor(value.length);
+              break;
+
+            case '[object Boolean]':
+              clone = new ctor(value == true);
+              break;
+
+            case '[object Date]':
+              clone = new ctor(+value);
+              break;
+
+            case '[object Object]':
+              isPlainObject(value) && (clone = {});
+              break;
+
+            case '[object Number]':
+            case '[object String]':
+              clone = new ctor(value);
+              break;
+
+            case '[object RegExp]':
+              clone = ctor(value.source,
+                (value.global     ? 'g' : '') +
+                (value.ignoreCase ? 'i' : '') +
+                (value.multiline  ? 'm' : ''));
+          }
+        }
+        // continue clone if `value` doesn't have an accessor descriptor
+        // http://es5.github.com/#x8.10.1
+        if (clone && clone != value &&
+            !(descriptor = source && support.descriptors && getDescriptor(source, key),
+              accessor = descriptor && (descriptor.get || descriptor.set))) {
+          // use an existing clone (circular reference)
+          if ((extensible = isExtensible(value))) {
+            markerKey = getMarkerKey(value);
+            if (value[markerKey]) {
+              circular = clone = value[markerKey].raw;
+            }
+          } else {
+            // for frozen/sealed objects
+            for (subIndex = 0, length = unmarked.length; subIndex < length; subIndex++) {
+              data = unmarked[subIndex];
+              if (data.object === value) {
+                circular = clone = data.clone;
+                break;
+              }
+            }
+          }
+          if (!circular) {
+            // mark object to allow quickly detecting circular references and tie it to its clone
+            if (extensible) {
+              value[markerKey] = new Marker(clone);
+              marked.push({ 'key': markerKey, 'object': value });
+            } else {
+              // for frozen/sealed objects
+              unmarked.push({ 'clone': clone, 'object': value });
+            }
+            // iterate over object properties
+            forProps(value, forPropsCallback, { 'which': 'all' });
+          }
+        }
+      }
+      if (parent) {
+        // for custom property descriptors
+        if (accessor || (descriptor && !(descriptor.configurable && descriptor.enumerable && descriptor.writable))) {
+          if ('value' in descriptor) {
+            descriptor.value = clone;
+          }
+          setDescriptor(parent, key, descriptor);
+        }
+        // for default property descriptors
+        else {
+          parent[key] = clone;
+        }
+      } else {
+        result = clone;
+      }
+    } while ((data = queue[index++]));
+
+    // remove markers
+    for (index = 0, length = marked.length; index < length; index++) {
+      data = marked[index];
+      delete data.object[data.key];
+    }
+    return result;
+  }
+
+  /**
+   * An iteration utility for arrays and objects.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array|Object} object The object to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array|Object} Returns the object iterated over.
+   */
+  function each(object, callback, thisArg) {
+    var result = object;
+    object = Object(object);
+
+    var fn = callback,
+        index = -1,
+        length = object.length,
+        isSnapshot = !!(object.snapshotItem && (length = object.snapshotLength)),
+        isSplittable = (noCharByIndex || noCharByOwnIndex) && isClassOf(object, 'String'),
+        isConvertable = isSnapshot || isSplittable || 'item' in object,
+        origObject = object;
+
+    // in Opera < 10.5 `hasKey(object, 'length')` returns `false` for NodeLists
+    if (length === length >>> 0) {
+      if (isConvertable) {
+        // the third argument of the callback is the original non-array object
+        callback = function(value, index) {
+          return fn.call(this, value, index, origObject);
+        };
+        // in IE < 9 strings don't support accessing characters by index
+        if (isSplittable) {
+          object = object.split('');
+        } else {
+          object = [];
+          while (++index < length) {
+            // in Safari 2 `index in object` is always `false` for NodeLists
+            object[index] = isSnapshot ? result.snapshotItem(index) : result[index];
+          }
+        }
+      }
+      forEach(object, callback, thisArg);
+    } else {
+      forOwn(object, callback, thisArg);
+    }
+    return result;
+  }
+
+  /**
+   * Copies enumerable properties from the source(s) object to the destination object.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Object} destination The destination object.
+   * @param {Object} [source={}] The source object.
+   * @returns {Object} The destination object.
+   */
+  function extend(destination, source) {
+    // Chrome < 14 incorrectly sets `destination` to `undefined` when we `delete arguments[0]`
+    // http://code.google.com/p/v8/issues/detail?id=839
+    var result = destination;
+    delete arguments[0];
+
+    forEach(arguments, function(source) {
+      forProps(source, function(value, key) {
+        result[key] = value;
+      });
+    });
+    return result;
+  }
+
+  /**
+   * A generic `Array#filter` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function|String} callback The function/alias called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array} A new array of values that passed callback filter.
+   * @example
+   *
+   * // get odd numbers
+   * Benchmark.filter([1, 2, 3, 4, 5], function(n) {
+   *   return n % 2;
+   * }); // -> [1, 3, 5];
+   *
+   * // get fastest benchmarks
+   * Benchmark.filter(benches, 'fastest');
+   *
+   * // get slowest benchmarks
+   * Benchmark.filter(benches, 'slowest');
+   *
+   * // get benchmarks that completed without erroring
+   * Benchmark.filter(benches, 'successful');
+   */
+  function filter(array, callback, thisArg) {
+    var result;
+
+    if (callback == 'successful') {
+      // callback to exclude those that are errored, unrun, or have hz of Infinity
+      callback = function(bench) { return bench.cycles && isFinite(bench.hz); };
+    }
+    else if (callback == 'fastest' || callback == 'slowest') {
+      // get successful, sort by period + margin of error, and filter fastest/slowest
+      result = filter(array, 'successful').sort(function(a, b) {
+        a = a.stats; b = b.stats;
+        return (a.mean + a.moe > b.mean + b.moe ? 1 : -1) * (callback == 'fastest' ? 1 : -1);
+      });
+      result = filter(result, function(bench) {
+        return result[0].compare(bench) == 0;
+      });
+    }
+    return result || reduce(array, function(result, value, index) {
+      return callback.call(thisArg, value, index, array) ? (result.push(value), result) : result;
+    }, []);
+  }
+
+  /**
+   * A generic `Array#forEach` like method.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array} Returns the array iterated over.
+   */
+  function forEach(array, callback, thisArg) {
+    var index = -1,
+        length = (array = Object(array)).length >>> 0;
+
+    if (thisArg !== undefined) {
+      callback = bind(callback, thisArg);
+    }
+    while (++index < length) {
+      if (index in array &&
+          callback(array[index], index, array) === false) {
+        break;
+      }
+    }
+    return array;
+  }
+
+  /**
+   * Iterates over an object's own properties, executing the `callback` for each.
+   * Callbacks may terminate the loop by explicitly returning `false`.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Object} object The object to iterate over.
+   * @param {Function} callback The function executed per own property.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Object} Returns the object iterated over.
+   */
+  function forOwn(object, callback, thisArg) {
+    return forProps(object, callback, { 'bind': thisArg, 'which': 'own' });
+  }
+
+  /**
+   * Converts a number to a more readable comma-separated string representation.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Number} number The number to convert.
+   * @returns {String} The more readable string representation.
+   */
+  function formatNumber(number) {
+    number = String(number).split('.');
+    return number[0].replace(/(?=(?:\d{3})+$)(?!\b)/g, ',') +
+      (number[1] ? '.' + number[1] : '');
+  }
+
+  /**
+   * Checks if an object has the specified key as a direct property.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Object} object The object to check.
+   * @param {String} key The key to check for.
+   * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+   */
+  function hasKey() {
+    // lazy define for worst case fallback (not as accurate)
+    hasKey = function(object, key) {
+      var parent = object != null && (object.constructor || Object).prototype;
+      return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]);
+    };
+    // for modern browsers
+    if (isClassOf(hasOwnProperty, 'Function')) {
+      hasKey = function(object, key) {
+        return object != null && hasOwnProperty.call(object, key);
+      };
+    }
+    // for Safari 2
+    else if ({}.__proto__ == Object.prototype) {
+      hasKey = function(object, key) {
+        var result = false;
+        if (object != null) {
+          object = Object(object);
+          object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0];
+        }
+        return result;
+      };
+    }
+    return hasKey.apply(this, arguments);
+  }
+
+  /**
+   * A generic `Array#indexOf` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Mixed} value The value to search for.
+   * @param {Number} [fromIndex=0] The index to start searching from.
+   * @returns {Number} The index of the matched value or `-1`.
+   */
+  function indexOf(array, value, fromIndex) {
+    var index = toInteger(fromIndex),
+        length = (array = Object(array)).length >>> 0;
+
+    index = (index < 0 ? max(0, length + index) : index) - 1;
+    while (++index < length) {
+      if (index in array && value === array[index]) {
+        return index;
+      }
+    }
+    return -1;
+  }
+
+  /**
+   * Modify a string by replacing named tokens with matching object property values.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {String} string The string to modify.
+   * @param {Object} object The template object.
+   * @returns {String} The modified string.
+   */
+  function interpolate(string, object) {
+    forOwn(object, function(value, key) {
+      // escape regexp special characters in `key`
+      string = string.replace(RegExp('#\\{' + key.replace(/([.*+?^=!:${}()|[\]\/\\])/g, '\\$1') + '\\}', 'g'), value);
+    });
+    return string;
+  }
+
+  /**
+   * Invokes a method on all items in an array.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} benches Array of benchmarks to iterate over.
+   * @param {String|Object} name The name of the method to invoke OR options object.
+   * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+   * @returns {Array} A new array of values returned from each method invoked.
+   * @example
+   *
+   * // invoke `reset` on all benchmarks
+   * Benchmark.invoke(benches, 'reset');
+   *
+   * // invoke `emit` with arguments
+   * Benchmark.invoke(benches, 'emit', 'complete', listener);
+   *
+   * // invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks
+   * Benchmark.invoke(benches, {
+   *
+   *   // invoke the `run` method
+   *   'name': 'run',
+   *
+   *   // pass a single argument
+   *   'args': true,
+   *
+   *   // treat as queue, removing benchmarks from front of `benches` until empty
+   *   'queued': true,
+   *
+   *   // called before any benchmarks have been invoked.
+   *   'onStart': onStart,
+   *
+   *   // called between invoking benchmarks
+   *   'onCycle': onCycle,
+   *
+   *   // called after all benchmarks have been invoked.
+   *   'onComplete': onComplete
+   * });
+   */
+  function invoke(benches, name) {
+    var args,
+        bench,
+        queued,
+        index = -1,
+        eventProps = { 'currentTarget': benches },
+        options = { 'onStart': noop, 'onCycle': noop, 'onComplete': noop },
+        result = map(benches, function(bench) { return bench; });
+
+    /**
+     * Invokes the method of the current object and if synchronous, fetches the next.
+     */
+    function execute() {
+      var listeners,
+          async = isAsync(bench);
+
+      if (async) {
+        // use `getNext` as the first listener
+        bench.on('complete', getNext);
+        listeners = bench.events.complete;
+        listeners.splice(0, 0, listeners.pop());
+      }
+      // execute method
+      result[index] = isClassOf(bench && bench[name], 'Function') ? bench[name].apply(bench, args) : undefined;
+      // if synchronous return true until finished
+      return !async && getNext();
+    }
+
+    /**
+     * Fetches the next bench or executes `onComplete` callback.
+     */
+    function getNext(event) {
+      var cycleEvent,
+          last = bench,
+          async = isAsync(last);
+
+      if (async) {
+        last.off('complete', getNext);
+        last.emit('complete');
+      }
+      // emit "cycle" event
+      eventProps.type = 'cycle';
+      eventProps.target = last;
+      cycleEvent = Event(eventProps);
+      options.onCycle.call(benches, cycleEvent);
+
+      // choose next benchmark if not exiting early
+      if (!cycleEvent.aborted && raiseIndex() !== false) {
+        bench = queued ? benches[0] : result[index];
+        if (isAsync(bench)) {
+          delay(bench, execute);
+        }
+        else if (async) {
+          // resume execution if previously asynchronous but now synchronous
+          while (execute()) { }
+        }
+        else {
+          // continue synchronous execution
+          return true;
+        }
+      } else {
+        // emit "complete" event
+        eventProps.type = 'complete';
+        options.onComplete.call(benches, Event(eventProps));
+      }
+      // When used as a listener `event.aborted = true` will cancel the rest of
+      // the "complete" listeners because they were already called above and when
+      // used as part of `getNext` the `return false` will exit the execution while-loop.
+      if (event) {
+        event.aborted = true;
+      } else {
+        return false;
+      }
+    }
+
+    /**
+     * Checks if invoking `Benchmark#run` with asynchronous cycles.
+     */
+    function isAsync(object) {
+      // avoid using `instanceof` here because of IE memory leak issues with host objects
+      var async = args[0] && args[0].async;
+      return Object(object).constructor == Benchmark && name == 'run' &&
+        ((async == null ? object.options.async : async) && support.timeout || object.defer);
+    }
+
+    /**
+     * Raises `index` to the next defined index or returns `false`.
+     */
+    function raiseIndex() {
+      var length = result.length;
+      if (queued) {
+        // if queued remove the previous bench and subsequent skipped non-entries
+        do {
+          ++index > 0 && shift.call(benches);
+        } while ((length = benches.length) && !('0' in benches));
+      }
+      else {
+        while (++index < length && !(index in result)) { }
+      }
+      // if we reached the last index then return `false`
+      return (queued ? length : index < length) ? index : (index = false);
+    }
+
+    // juggle arguments
+    if (isClassOf(name, 'String')) {
+      // 2 arguments (array, name)
+      args = slice.call(arguments, 2);
+    } else {
+      // 2 arguments (array, options)
+      options = extend(options, name);
+      name = options.name;
+      args = isClassOf(args = 'args' in options ? options.args : [], 'Array') ? args : [args];
+      queued = options.queued;
+    }
+
+    // start iterating over the array
+    if (raiseIndex() !== false) {
+      // emit "start" event
+      bench = result[index];
+      eventProps.type = 'start';
+      eventProps.target = bench;
+      options.onStart.call(benches, Event(eventProps));
+
+      // end early if the suite was aborted in an "onStart" listener
+      if (benches.aborted && benches.constructor == Suite && name == 'run') {
+        // emit "cycle" event
+        eventProps.type = 'cycle';
+        options.onCycle.call(benches, Event(eventProps));
+        // emit "complete" event
+        eventProps.type = 'complete';
+        options.onComplete.call(benches, Event(eventProps));
+      }
+      // else start
+      else {
+        if (isAsync(bench)) {
+          delay(bench, execute);
+        } else {
+          while (execute()) { }
+        }
+      }
+    }
+    return result;
+  }
+
+  /**
+   * Creates a string of joined array values or object key-value pairs.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array|Object} object The object to operate on.
+   * @param {String} [separator1=','] The separator used between key-value pairs.
+   * @param {String} [separator2=': '] The separator used between keys and values.
+   * @returns {String} The joined result.
+   */
+  function join(object, separator1, separator2) {
+    var result = [],
+        length = (object = Object(object)).length,
+        arrayLike = length === length >>> 0;
+
+    separator2 || (separator2 = ': ');
+    each(object, function(value, key) {
+      result.push(arrayLike ? value : key + separator2 + value);
+    });
+    return result.join(separator1 || ',');
+  }
+
+  /**
+   * A generic `Array#map` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} thisArg The `this` binding for the callback.
+   * @returns {Array} A new array of values returned by the callback.
+   */
+  function map(array, callback, thisArg) {
+    return reduce(array, function(result, value, index) {
+      result[index] = callback.call(thisArg, value, index, array);
+      return result;
+    }, Array(Object(array).length >>> 0));
+  }
+
+  /**
+   * Retrieves the value of a specified property from all items in an array.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {String} property The property to pluck.
+   * @returns {Array} A new array of property values.
+   */
+  function pluck(array, property) {
+    return map(array, function(object) {
+      return object == null ? undefined : object[property];
+    });
+  }
+
+  /**
+   * A generic `Array#reduce` like method.
+   *
+   * @static
+   * @memberOf Benchmark
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} accumulator Initial value of the accumulator.
+   * @returns {Mixed} The accumulator.
+   */
+  function reduce(array, callback, accumulator) {
+    var noaccum = arguments.length < 3;
+    forEach(array, function(value, index) {
+      accumulator = noaccum ? (noaccum = false, value) : callback(accumulator, value, index, array);
+    });
+    return accumulator;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Aborts all benchmarks in the suite.
+   *
+   * @name abort
+   * @memberOf Benchmark.Suite
+   * @returns {Object} The suite instance.
+   */
+  function abortSuite() {
+    var event,
+        me = this,
+        resetting = calledBy.resetSuite;
+
+    if (me.running) {
+      event = Event('abort');
+      me.emit(event);
+      if (!event.cancelled || resetting) {
+        // avoid infinite recursion
+        calledBy.abortSuite = true;
+        me.reset();
+        delete calledBy.abortSuite;
+
+        if (!resetting) {
+          me.aborted = true;
+          invoke(me, 'abort');
+        }
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Adds a test to the benchmark suite.
+   *
+   * @memberOf Benchmark.Suite
+   * @param {String} name A name to identify the benchmark.
+   * @param {Function|String} fn The test to benchmark.
+   * @param {Object} [options={}] Options object.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // basic usage
+   * suite.add(fn);
+   *
+   * // or using a name first
+   * suite.add('foo', fn);
+   *
+   * // or with options
+   * suite.add('foo', fn, {
+   *   'onCycle': onCycle,
+   *   'onComplete': onComplete
+   * });
+   *
+   * // or name and options
+   * suite.add('foo', {
+   *   'fn': fn,
+   *   'onCycle': onCycle,
+   *   'onComplete': onComplete
+   * });
+   *
+   * // or options only
+   * suite.add({
+   *   'name': 'foo',
+   *   'fn': fn,
+   *   'onCycle': onCycle,
+   *   'onComplete': onComplete
+   * });
+   */
+  function add(name, fn, options) {
+    var me = this,
+        bench = Benchmark(name, fn, options),
+        event = Event({ 'type': 'add', 'target': bench });
+
+    if (me.emit(event), !event.cancelled) {
+      me.push(bench);
+    }
+    return me;
+  }
+
+  /**
+   * Creates a new suite with cloned benchmarks.
+   *
+   * @name clone
+   * @memberOf Benchmark.Suite
+   * @param {Object} options Options object to overwrite cloned options.
+   * @returns {Object} The new suite instance.
+   */
+  function cloneSuite(options) {
+    var me = this,
+        result = new me.constructor(extend({}, me.options, options));
+
+    // copy own properties
+    forOwn(me, function(value, key) {
+      if (!hasKey(result, key)) {
+        result[key] = value && isClassOf(value.clone, 'Function')
+          ? value.clone()
+          : deepClone(value);
+      }
+    });
+    return result;
+  }
+
+  /**
+   * An `Array#filter` like method.
+   *
+   * @name filter
+   * @memberOf Benchmark.Suite
+   * @param {Function|String} callback The function/alias called per iteration.
+   * @returns {Object} A new suite of benchmarks that passed callback filter.
+   */
+  function filterSuite(callback) {
+    var me = this,
+        result = new me.constructor;
+
+    result.push.apply(result, filter(me, callback));
+    return result;
+  }
+
+  /**
+   * Resets all benchmarks in the suite.
+   *
+   * @name reset
+   * @memberOf Benchmark.Suite
+   * @returns {Object} The suite instance.
+   */
+  function resetSuite() {
+    var event,
+        me = this,
+        aborting = calledBy.abortSuite;
+
+    if (me.running && !aborting) {
+      // no worries, `resetSuite()` is called within `abortSuite()`
+      calledBy.resetSuite = true;
+      me.abort();
+      delete calledBy.resetSuite;
+    }
+    // reset if the state has changed
+    else if ((me.aborted || me.running) &&
+        (me.emit(event = Event('reset')), !event.cancelled)) {
+      me.running = false;
+      if (!aborting) {
+        invoke(me, 'reset');
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Runs the suite.
+   *
+   * @name run
+   * @memberOf Benchmark.Suite
+   * @param {Object} [options={}] Options object.
+   * @returns {Object} The suite instance.
+   * @example
+   *
+   * // basic usage
+   * suite.run();
+   *
+   * // or with options
+   * suite.run({ 'async': true, 'queued': true });
+   */
+  function runSuite(options) {
+    var me = this;
+
+    me.reset();
+    me.running = true;
+    options || (options = {});
+
+    invoke(me, {
+      'name': 'run',
+      'args': options,
+      'queued': options.queued,
+      'onStart': function(event) {
+        me.emit(event);
+      },
+      'onCycle': function(event) {
+        var bench = event.target;
+        if (bench.error) {
+          me.emit({ 'type': 'error', 'target': bench });
+        }
+        me.emit(event);
+        event.aborted = me.aborted;
+      },
+      'onComplete': function(event) {
+        me.running = false;
+        me.emit(event);
+      }
+    });
+    return me;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Executes all registered listeners of the specified event type.
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String|Object} type The event type or object.
+   * @returns {Mixed} Returns the return value of the last listener executed.
+   */
+  function emit(type) {
+    var listeners,
+        me = this,
+        event = Event(type),
+        events = me.events,
+        args = (arguments[0] = event, arguments);
+
+    event.currentTarget || (event.currentTarget = me);
+    event.target || (event.target = me);
+    delete event.result;
+
+    if (events && (listeners = hasKey(events, event.type) && events[event.type])) {
+      forEach(listeners.slice(), function(listener) {
+        if ((event.result = listener.apply(me, args)) === false) {
+          event.cancelled = true;
+        }
+        return !event.aborted;
+      });
+    }
+    return event.result;
+  }
+
+  /**
+   * Returns an array of event listeners for a given type that can be manipulated
+   * to add or remove listeners.
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String} type The event type.
+   * @returns {Array} The listeners array.
+   */
+  function listeners(type) {
+    var me = this,
+        events = me.events || (me.events = {});
+
+    return hasKey(events, type) ? events[type] : (events[type] = []);
+  }
+
+  /**
+   * Unregisters a listener for the specified event type(s),
+   * or unregisters all listeners for the specified event type(s),
+   * or unregisters all listeners for all event types.
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String} [type] The event type.
+   * @param {Function} [listener] The function to unregister.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // unregister a listener for an event type
+   * bench.off('cycle', listener);
+   *
+   * // unregister a listener for multiple event types
+   * bench.off('start cycle', listener);
+   *
+   * // unregister all listeners for an event type
+   * bench.off('cycle');
+   *
+   * // unregister all listeners for multiple event types
+   * bench.off('start cycle complete');
+   *
+   * // unregister all listeners for all event types
+   * bench.off();
+   */
+  function off(type, listener) {
+    var me = this,
+        events = me.events;
+
+    events && each(type ? type.split(' ') : events, function(listeners, type) {
+      var index;
+      if (typeof listeners == 'string') {
+        type = listeners;
+        listeners = hasKey(events, type) && events[type];
+      }
+      if (listeners) {
+        if (listener) {
+          index = indexOf(listeners, listener);
+          if (index > -1) {
+            listeners.splice(index, 1);
+          }
+        } else {
+          listeners.length = 0;
+        }
+      }
+    });
+    return me;
+  }
+
+  /**
+   * Registers a listener for the specified event type(s).
+   *
+   * @memberOf Benchmark, Benchmark.Suite
+   * @param {String} type The event type.
+   * @param {Function} listener The function to register.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // register a listener for an event type
+   * bench.on('cycle', listener);
+   *
+   * // register a listener for multiple event types
+   * bench.on('start cycle', listener);
+   */
+  function on(type, listener) {
+    var me = this,
+        events = me.events || (me.events = {});
+
+    forEach(type.split(' '), function(type) {
+      (hasKey(events, type)
+        ? events[type]
+        : (events[type] = [])
+      ).push(listener);
+    });
+    return me;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Aborts the benchmark without recording times.
+   *
+   * @memberOf Benchmark
+   * @returns {Object} The benchmark instance.
+   */
+  function abort() {
+    var event,
+        me = this,
+        resetting = calledBy.reset;
+
+    if (me.running) {
+      event = Event('abort');
+      me.emit(event);
+      if (!event.cancelled || resetting) {
+        // avoid infinite recursion
+        calledBy.abort = true;
+        me.reset();
+        delete calledBy.abort;
+
+        if (support.timeout) {
+          clearTimeout(me._timerId);
+          delete me._timerId;
+        }
+        if (!resetting) {
+          me.aborted = true;
+          me.running = false;
+        }
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Creates a new benchmark using the same test and options.
+   *
+   * @memberOf Benchmark
+   * @param {Object} options Options object to overwrite cloned options.
+   * @returns {Object} The new benchmark instance.
+   * @example
+   *
+   * var bizarro = bench.clone({
+   *   'name': 'doppelganger'
+   * });
+   */
+  function clone(options) {
+    var me = this,
+        result = new me.constructor(extend({}, me, options));
+
+    // correct the `options` object
+    result.options = extend({}, me.options, options);
+
+    // copy own custom properties
+    forOwn(me, function(value, key) {
+      if (!hasKey(result, key)) {
+        result[key] = deepClone(value);
+      }
+    });
+    return result;
+  }
+
+  /**
+   * Determines if a benchmark is faster than another.
+   *
+   * @memberOf Benchmark
+   * @param {Object} other The benchmark to compare.
+   * @returns {Number} Returns `-1` if slower, `1` if faster, and `0` if indeterminate.
+   */
+  function compare(other) {
+    var critical,
+        zStat,
+        me = this,
+        sample1 = me.stats.sample,
+        sample2 = other.stats.sample,
+        size1 = sample1.length,
+        size2 = sample2.length,
+        maxSize = max(size1, size2),
+        minSize = min(size1, size2),
+        u1 = getU(sample1, sample2),
+        u2 = getU(sample2, sample1),
+        u = min(u1, u2);
+
+    function getScore(xA, sampleB) {
+      return reduce(sampleB, function(total, xB) {
+        return total + (xB > xA ? 0 : xB < xA ? 1 : 0.5);
+      }, 0);
+    }
+
+    function getU(sampleA, sampleB) {
+      return reduce(sampleA, function(total, xA) {
+        return total + getScore(xA, sampleB);
+      }, 0);
+    }
+
+    function getZ(u) {
+      return (u - ((size1 * size2) / 2)) / sqrt((size1 * size2 * (size1 + size2 + 1)) / 12);
+    }
+
+    // exit early if comparing the same benchmark
+    if (me == other) {
+      return 0;
+    }
+    // reject the null hyphothesis the two samples come from the
+    // same population (i.e. have the same median) if...
+    if (size1 + size2 > 30) {
+      // ...the z-stat is greater than 1.96 or less than -1.96
+      // http://www.statisticslectures.com/topics/mannwhitneyu/
+      zStat = getZ(u);
+      return abs(zStat) > 1.96 ? (zStat > 0 ? -1 : 1) : 0;
+    }
+    // ...the U value is less than or equal the critical U value
+    // http://www.geoib.com/mann-whitney-u-test.html
+    critical = maxSize < 5 || minSize < 3 ? 0 : uTable[maxSize][minSize - 3];
+    return u <= critical ? (u == u1 ? 1 : -1) : 0;
+  }
+
+  /**
+   * Reset properties and abort if running.
+   *
+   * @memberOf Benchmark
+   * @returns {Object} The benchmark instance.
+   */
+  function reset() {
+    var data,
+        event,
+        me = this,
+        index = 0,
+        changes = { 'length': 0 },
+        queue = { 'length': 0 };
+
+    if (me.running && !calledBy.abort) {
+      // no worries, `reset()` is called within `abort()`
+      calledBy.reset = true;
+      me.abort();
+      delete calledBy.reset;
+    }
+    else {
+      // a non-recursive solution to check if properties have changed
+      // http://www.jslab.dk/articles/non.recursive.preorder.traversal.part4
+      data = { 'destination': me, 'source': extend({}, me.constructor.prototype, me.options) };
+      do {
+        forOwn(data.source, function(value, key) {
+          var changed,
+              destination = data.destination,
+              currValue = destination[key];
+
+          if (value && typeof value == 'object') {
+            if (isClassOf(value, 'Array')) {
+              // check if an array value has changed to a non-array value
+              if (!isClassOf(currValue, 'Array')) {
+                changed = currValue = [];
+              }
+              // or has changed its length
+              if (currValue.length != value.length) {
+                changed = currValue = currValue.slice(0, value.length);
+                currValue.length = value.length;
+              }
+            }
+            // check if an object has changed to a non-object value
+            else if (!currValue || typeof currValue != 'object') {
+              changed = currValue = {};
+            }
+            // register a changed object
+            if (changed) {
+              changes[changes.length++] = { 'destination': destination, 'key': key, 'value': currValue };
+            }
+            queue[queue.length++] = { 'destination': currValue, 'source': value };
+          }
+          // register a changed primitive
+          else if (value !== currValue && !(value == null || isClassOf(value, 'Function'))) {
+            changes[changes.length++] = { 'destination': destination, 'key': key, 'value': value };
+          }
+        });
+      }
+      while ((data = queue[index++]));
+
+      // if changed emit the `reset` event and if it isn't cancelled reset the benchmark
+      if (changes.length && (me.emit(event = Event('reset')), !event.cancelled)) {
+        forEach(changes, function(data) {
+          data.destination[data.key] = data.value;
+        });
+      }
+    }
+    return me;
+  }
+
+  /**
+   * Displays relevant benchmark information when coerced to a string.
+   *
+   * @name toString
+   * @memberOf Benchmark
+   * @returns {String} A string representation of the benchmark instance.
+   */
+  function toStringBench() {
+    var me = this,
+        error = me.error,
+        hz = me.hz,
+        id = me.id,
+        stats = me.stats,
+        size = stats.sample.length,
+        pm = support.java ? '+/-' : '\xb1',
+        result = me.name || (isNaN(id) ? id : '<Test #' + id + '>');
+
+    if (error) {
+      result += ': ' + join(error);
+    } else {
+      result += ' x ' + formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) + ' ops/sec ' + pm +
+        stats.rme.toFixed(2) + '% (' + size + ' run' + (size == 1 ? '' : 's') + ' sampled)';
+    }
+    return result;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Clocks the time taken to execute a test per cycle (secs).
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @returns {Number} The time taken.
+   */
+  function clock() {
+    var applet,
+        options = Benchmark.options,
+        template = { 'begin': 's$=new n$', 'end': 'r$=(new n$-s$)/1e3', 'uid': uid },
+        timers = [{ 'ns': timer.ns, 'res': max(0.0015, getRes('ms')), 'unit': 'ms' }];
+
+    // lazy define for hi-res timers
+    clock = function(clone) {
+      var deferred;
+      if (clone instanceof Deferred) {
+        deferred = clone;
+        clone = deferred.benchmark;
+      }
+
+      var bench = clone._original,
+          fn = bench.fn,
+          fnArg = deferred ? getFirstArgument(fn) || 'deferred' : '',
+          stringable = isStringable(fn);
+
+      var source = {
+        'setup': getSource(bench.setup, preprocess('m$.setup()')),
+        'fn': getSource(fn, preprocess('m$.fn(' + fnArg + ')')),
+        'fnArg': fnArg,
+        'teardown': getSource(bench.teardown, preprocess('m$.teardown()'))
+      };
+
+      var count = bench.count = clone.count,
+          decompilable = support.decompilation || stringable,
+          id = bench.id,
+          isEmpty = !(source.fn || stringable),
+          name = bench.name || (typeof id == 'number' ? '<Test #' + id + '>' : id),
+          ns = timer.ns,
+          result = 0;
+
+      // init `minTime` if needed
+      clone.minTime = bench.minTime || (bench.minTime = bench.options.minTime = options.minTime);
+
+      // repair nanosecond timer
+      // (some Chrome builds erase the `ns` variable after millions of executions)
+      if (applet) {
+        try {
+          ns.nanoTime();
+        } catch(e) {
+          // use non-element to avoid issues with libs that augment them
+          ns = timer.ns = new applet.Packages.nano;
+        }
+      }
+
+      // Compile in setup/teardown functions and the test loop.
+      // Create a new compiled test, instead of using the cached `bench.compiled`,
+      // to avoid potential engine optimizations enabled over the life of the test.
+      var compiled = bench.compiled = createFunction(preprocess('t$'), interpolate(
+        preprocess(deferred
+          ? 'var d$=this,#{fnArg}=d$,m$=d$.benchmark._original,f$=m$.fn,su$=m$.setup,td$=m$.teardown;' +
+            // when `deferred.cycles` is `0` then...
+            'if(!d$.cycles){' +
+            // set `deferred.fn`
+            'd$.fn=function(){var #{fnArg}=d$;if(typeof f$=="function"){try{#{fn}\n}catch(e$){f$(d$)}}else{#{fn}\n}};' +
+            // set `deferred.teardown`
+            'd$.teardown=function(){d$.cycles=0;if(typeof td$=="function"){try{#{teardown}\n}catch(e$){td$()}}else{#{teardown}\n}};' +
+            // execute the benchmark's `setup`
+            'if(typeof su$=="function"){try{#{setup}\n}catch(e$){su$()}}else{#{setup}\n};' +
+            // start timer
+            't$.start(d$);' +
+            // execute `deferred.fn` and return a dummy object
+            '}d$.fn();return{}'
+
+          : 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count,n$=t$.ns;#{setup}\n#{begin};' +
+            'while(i$--){#{fn}\n}#{end};#{teardown}\nreturn{elapsed:r$,uid:"#{uid}"}'),
+        source
+      ));
+
+      try {
+        if (isEmpty) {
+          // Firefox may remove dead code from Function#toString results
+          // http://bugzil.la/536085
+          throw new Error('The test "' + name + '" is empty. This may be the result of dead code removal.');
+        }
+        else if (!deferred) {
+          // pretest to determine if compiled code is exits early, usually by a
+          // rogue `return` statement, by checking for a return object with the uid
+          bench.count = 1;
+          compiled = (compiled.call(bench, timer) || {}).uid == uid && compiled;
+          bench.count = count;
+        }
+      } catch(e) {
+        compiled = null;
+        clone.error = e || new Error(String(e));
+        bench.count = count;
+      }
+      // fallback when a test exits early or errors during pretest
+      if (decompilable && !compiled && !deferred && !isEmpty) {
+        compiled = createFunction(preprocess('t$'), interpolate(
+          preprocess(
+            (clone.error && !stringable
+              ? 'var r$,s$,m$=this,f$=m$.fn,i$=m$.count'
+              : 'function f$(){#{fn}\n}var r$,s$,m$=this,i$=m$.count'
+            ) +
+            ',n$=t$.ns;#{setup}\n#{begin};m$.f$=f$;while(i$--){m$.f$()}#{end};' +
+            'delete m$.f$;#{teardown}\nreturn{elapsed:r$}'
+          ),
+          source
+        ));
+
+        try {
+          // pretest one more time to check for errors
+          bench.count = 1;
+          compiled.call(bench, timer);
+          bench.compiled = compiled;
+          bench.count = count;
+          delete clone.error;
+        }
+        catch(e) {
+          bench.count = count;
+          if (clone.error) {
+            compiled = null;
+          } else {
+            bench.compiled = compiled;
+            clone.error = e || new Error(String(e));
+          }
+        }
+      }
+      // assign `compiled` to `clone` before calling in case a deferred benchmark
+      // immediately calls `deferred.resolve()`
+      clone.compiled = compiled;
+      // if no errors run the full test loop
+      if (!clone.error) {
+        result = compiled.call(deferred || bench, timer).elapsed;
+      }
+      return result;
+    };
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Gets the current timer's minimum resolution (secs).
+     */
+    function getRes(unit) {
+      var measured,
+          begin,
+          count = 30,
+          divisor = 1e3,
+          ns = timer.ns,
+          sample = [];
+
+      // get average smallest measurable time
+      while (count--) {
+        if (unit == 'us') {
+          divisor = 1e6;
+          if (ns.stop) {
+            ns.start();
+            while (!(measured = ns.microseconds())) { }
+          } else if (ns[perfName]) {
+            divisor = 1e3;
+            measured = Function('n', 'var r,s=n.' + perfName + '();while(!(r=n.' + perfName + '()-s)){};return r')(ns);
+          } else {
+            begin = ns();
+            while (!(measured = ns() - begin)) { }
+          }
+        }
+        else if (unit == 'ns') {
+          divisor = 1e9;
+          if (ns.nanoTime) {
+            begin = ns.nanoTime();
+            while (!(measured = ns.nanoTime() - begin)) { }
+          } else {
+            begin = (begin = ns())[0] + (begin[1] / divisor);
+            while (!(measured = ((measured = ns())[0] + (measured[1] / divisor)) - begin)) { }
+            divisor = 1;
+          }
+        }
+        else {
+          begin = new ns;
+          while (!(measured = new ns - begin)) { }
+        }
+        // check for broken timers (nanoTime may have issues)
+        // http://alivebutsleepy.srnet.cz/unreliable-system-nanotime/
+        if (measured > 0) {
+          sample.push(measured);
+        } else {
+          sample.push(Infinity);
+          break;
+        }
+      }
+      // convert to seconds
+      return getMean(sample) / divisor;
+    }
+
+    /**
+     * Replaces all occurrences of `$` with a unique number and
+     * template tokens with content.
+     */
+    function preprocess(code) {
+      return interpolate(code, template).replace(/\$/g, /\d+/.exec(uid));
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // detect nanosecond support from a Java applet
+    each(doc && doc.applets || [], function(element) {
+      return !(timer.ns = applet = 'nanoTime' in element && element);
+    });
+
+    // check type in case Safari returns an object instead of a number
+    try {
+      if (typeof timer.ns.nanoTime() == 'number') {
+        timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
+      }
+    } catch(e) { }
+
+    // detect Chrome's microsecond timer:
+    // enable benchmarking via the --enable-benchmarking command
+    // line switch in at least Chrome 7 to use chrome.Interval
+    try {
+      if ((timer.ns = new (window.chrome || window.chromium).Interval)) {
+        timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+      }
+    } catch(e) { }
+
+    // detect `performance.now` microsecond resolution timer
+    if ((timer.ns = perfName && perfObject)) {
+      timers.push({ 'ns': timer.ns, 'res': getRes('us'), 'unit': 'us' });
+    }
+
+    // detect Node's nanosecond resolution timer available in Node >= 0.8
+    if (processObject && typeof (timer.ns = processObject.hrtime) == 'function') {
+      timers.push({ 'ns': timer.ns, 'res': getRes('ns'), 'unit': 'ns' });
+    }
+
+    // detect Wade Simmons' Node microtime module
+    if (microtimeObject && typeof (timer.ns = microtimeObject.now) == 'function') {
+      timers.push({ 'ns': timer.ns,  'res': getRes('us'), 'unit': 'us' });
+    }
+
+    // pick timer with highest resolution
+    timer = reduce(timers, function(timer, other) {
+      return other.res < timer.res ? other : timer;
+    });
+
+    // remove unused applet
+    if (timer.unit != 'ns' && applet) {
+      applet = destroyElement(applet);
+    }
+    // error if there are no working timers
+    if (timer.res == Infinity) {
+      throw new Error('Benchmark.js was unable to find a working timer.');
+    }
+    // use API of chosen timer
+    if (timer.unit == 'ns') {
+      if (timer.ns.nanoTime) {
+        extend(template, {
+          'begin': 's$=n$.nanoTime()',
+          'end': 'r$=(n$.nanoTime()-s$)/1e9'
+        });
+      } else {
+        extend(template, {
+          'begin': 's$=n$()',
+          'end': 'r$=n$(s$);r$=r$[0]+(r$[1]/1e9)'
+        });
+      }
+    }
+    else if (timer.unit == 'us') {
+      if (timer.ns.stop) {
+        extend(template, {
+          'begin': 's$=n$.start()',
+          'end': 'r$=n$.microseconds()/1e6'
+        });
+      } else if (perfName) {
+        extend(template, {
+          'begin': 's$=n$.' + perfName + '()',
+          'end': 'r$=(n$.' + perfName + '()-s$)/1e3'
+        });
+      } else {
+        extend(template, {
+          'begin': 's$=n$()',
+          'end': 'r$=(n$()-s$)/1e6'
+        });
+      }
+    }
+
+    // define `timer` methods
+    timer.start = createFunction(preprocess('o$'),
+      preprocess('var n$=this.ns,#{begin};o$.elapsed=0;o$.timeStamp=s$'));
+
+    timer.stop = createFunction(preprocess('o$'),
+      preprocess('var n$=this.ns,s$=o$.timeStamp,#{end};o$.elapsed=r$'));
+
+    // resolve time span required to achieve a percent uncertainty of at most 1%
+    // http://spiff.rit.edu/classes/phys273/uncert/uncert.html
+    options.minTime || (options.minTime = max(timer.res / 2 / 0.01, 0.05));
+    return clock.apply(null, arguments);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Computes stats on benchmark results.
+   *
+   * @private
+   * @param {Object} bench The benchmark instance.
+   * @param {Object} options The options object.
+   */
+  function compute(bench, options) {
+    options || (options = {});
+
+    var async = options.async,
+        elapsed = 0,
+        initCount = bench.initCount,
+        minSamples = bench.minSamples,
+        queue = [],
+        sample = bench.stats.sample;
+
+    /**
+     * Adds a clone to the queue.
+     */
+    function enqueue() {
+      queue.push(bench.clone({
+        '_original': bench,
+        'events': {
+          'abort': [update],
+          'cycle': [update],
+          'error': [update],
+          'start': [update]
+        }
+      }));
+    }
+
+    /**
+     * Updates the clone/original benchmarks to keep their data in sync.
+     */
+    function update(event) {
+      var clone = this,
+          type = event.type;
+
+      if (bench.running) {
+        if (type == 'start') {
+          // Note: `clone.minTime` prop is inited in `clock()`
+          clone.count = bench.initCount;
+        }
+        else {
+          if (type == 'error') {
+            bench.error = clone.error;
+          }
+          if (type == 'abort') {
+            bench.abort();
+            bench.emit('cycle');
+          } else {
+            event.currentTarget = event.target = bench;
+            bench.emit(event);
+          }
+        }
+      } else if (bench.aborted) {
+        // clear abort listeners to avoid triggering bench's abort/cycle again
+        clone.events.abort.length = 0;
+        clone.abort();
+      }
+    }
+
+    /**
+     * Determines if more clones should be queued or if cycling should stop.
+     */
+    function evaluate(event) {
+      var critical,
+          df,
+          mean,
+          moe,
+          rme,
+          sd,
+          sem,
+          variance,
+          clone = event.target,
+          done = bench.aborted,
+          now = +new Date,
+          size = sample.push(clone.times.period),
+          maxedOut = size >= minSamples && (elapsed += now - clone.times.timeStamp) / 1e3 > bench.maxTime,
+          times = bench.times,
+          varOf = function(sum, x) { return sum + pow(x - mean, 2); };
+
+      // exit early for aborted or unclockable tests
+      if (done || clone.hz == Infinity) {
+        maxedOut = !(size = sample.length = queue.length = 0);
+      }
+
+      if (!done) {
+        // sample mean (estimate of the population mean)
+        mean = getMean(sample);
+        // sample variance (estimate of the population variance)
+        variance = reduce(sample, varOf, 0) / (size - 1) || 0;
+        // sample standard deviation (estimate of the population standard deviation)
+        sd = sqrt(variance);
+        // standard error of the mean (a.k.a. the standard deviation of the sampling distribution of the sample mean)
+        sem = sd / sqrt(size);
+        // degrees of freedom
+        df = size - 1;
+        // critical value
+        critical = tTable[Math.round(df) || 1] || tTable.infinity;
+        // margin of error
+        moe = sem * critical;
+        // relative margin of error
+        rme = (moe / mean) * 100 || 0;
+
+        extend(bench.stats, {
+          'deviation': sd,
+          'mean': mean,
+          'moe': moe,
+          'rme': rme,
+          'sem': sem,
+          'variance': variance
+        });
+
+        // Abort the cycle loop when the minimum sample size has been collected
+        // and the elapsed time exceeds the maximum time allowed per benchmark.
+        // We don't count cycle delays toward the max time because delays may be
+        // increased by browsers that clamp timeouts for inactive tabs.
+        // https://developer.mozilla.org/en/window.setTimeout#Inactive_tabs
+        if (maxedOut) {
+          // reset the `initCount` in case the benchmark is rerun
+          bench.initCount = initCount;
+          bench.running = false;
+          done = true;
+          times.elapsed = (now - times.timeStamp) / 1e3;
+        }
+        if (bench.hz != Infinity) {
+          bench.hz = 1 / mean;
+          times.cycle = mean * bench.count;
+          times.period = mean;
+        }
+      }
+      // if time permits, increase sample size to reduce the margin of error
+      if (queue.length < 2 && !maxedOut) {
+        enqueue();
+      }
+      // abort the invoke cycle when done
+      event.aborted = done;
+    }
+
+    // init queue and begin
+    enqueue();
+    invoke(queue, {
+      'name': 'run',
+      'args': { 'async': async },
+      'queued': true,
+      'onCycle': evaluate,
+      'onComplete': function() { bench.emit('complete'); }
+    });
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Cycles a benchmark until a run `count` can be established.
+   *
+   * @private
+   * @param {Object} clone The cloned benchmark instance.
+   * @param {Object} options The options object.
+   */
+  function cycle(clone, options) {
+    options || (options = {});
+
+    var deferred;
+    if (clone instanceof Deferred) {
+      deferred = clone;
+      clone = clone.benchmark;
+    }
+
+    var clocked,
+        cycles,
+        divisor,
+        event,
+        minTime,
+        period,
+        async = options.async,
+        bench = clone._original,
+        count = clone.count,
+        times = clone.times;
+
+    // continue, if not aborted between cycles
+    if (clone.running) {
+      // `minTime` is set to `Benchmark.options.minTime` in `clock()`
+      cycles = ++clone.cycles;
+      clocked = deferred ? deferred.elapsed : clock(clone);
+      minTime = clone.minTime;
+
+      if (cycles > bench.cycles) {
+        bench.cycles = cycles;
+      }
+      if (clone.error) {
+        event = Event('error');
+        event.message = clone.error;
+        clone.emit(event);
+        if (!event.cancelled) {
+          clone.abort();
+        }
+      }
+    }
+
+    // continue, if not errored
+    if (clone.running) {
+      // time taken to complete last test cycle
+      bench.times.cycle = times.cycle = clocked;
+      // seconds per operation
+      period = bench.times.period = times.period = clocked / count;
+      // ops per second
+      bench.hz = clone.hz = 1 / period;
+      // avoid working our way up to this next time
+      bench.initCount = clone.initCount = count;
+      // do we need to do another cycle?
+      clone.running = clocked < minTime;
+
+      if (clone.running) {
+        // tests may clock at `0` when `initCount` is a small number,
+        // to avoid that we set its count to something a bit higher
+        if (!clocked && (divisor = divisors[clone.cycles]) != null) {
+          count = floor(4e6 / divisor);
+        }
+        // calculate how many more iterations it will take to achive the `minTime`
+        if (count <= clone.count) {
+          count += Math.ceil((minTime - clocked) / period);
+        }
+        clone.running = count != Infinity;
+      }
+    }
+    // should we exit early?
+    event = Event('cycle');
+    clone.emit(event);
+    if (event.aborted) {
+      clone.abort();
+    }
+    // figure out what to do next
+    if (clone.running) {
+      // start a new cycle
+      clone.count = count;
+      if (deferred) {
+        clone.compiled.call(deferred, timer);
+      } else if (async) {
+        delay(clone, function() { cycle(clone, options); });
+      } else {
+        cycle(clone);
+      }
+    }
+    else {
+      // fix TraceMonkey bug associated with clock fallbacks
+      // http://bugzil.la/509069
+      if (support.browser) {
+        runScript(uid + '=1;delete ' + uid);
+      }
+      // done
+      clone.emit('complete');
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Runs the benchmark.
+   *
+   * @memberOf Benchmark
+   * @param {Object} [options={}] Options object.
+   * @returns {Object} The benchmark instance.
+   * @example
+   *
+   * // basic usage
+   * bench.run();
+   *
+   * // or with options
+   * bench.run({ 'async': true });
+   */
+  function run(options) {
+    var me = this,
+        event = Event('start');
+
+    // set `running` to `false` so `reset()` won't call `abort()`
+    me.running = false;
+    me.reset();
+    me.running = true;
+
+    me.count = me.initCount;
+    me.times.timeStamp = +new Date;
+    me.emit(event);
+
+    if (!event.cancelled) {
+      options = { 'async': ((options = options && options.async) == null ? me.async : options) && support.timeout };
+
+      // for clones created within `compute()`
+      if (me._original) {
+        if (me.defer) {
+          Deferred(me);
+        } else {
+          cycle(me, options);
+        }
+      }
+      // for original benchmarks
+      else {
+        compute(me, options);
+      }
+    }
+    return me;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // Firefox 1 erroneously defines variable and argument names of functions on
+  // the function itself as non-configurable properties with `undefined` values.
+  // The bugginess continues as the `Benchmark` constructor has an argument
+  // named `options` and Firefox 1 will not assign a value to `Benchmark.options`,
+  // making it non-writable in the process, unless it is the first property
+  // assigned by for-in loop of `extend()`.
+  extend(Benchmark, {
+
+    /**
+     * The default options copied by benchmark instances.
+     *
+     * @static
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'options': {
+
+      /**
+       * A flag to indicate that benchmark cycles will execute asynchronously
+       * by default.
+       *
+       * @memberOf Benchmark.options
+       * @type Boolean
+       */
+      'async': false,
+
+      /**
+       * A flag to indicate that the benchmark clock is deferred.
+       *
+       * @memberOf Benchmark.options
+       * @type Boolean
+       */
+      'defer': false,
+
+      /**
+       * The delay between test cycles (secs).
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'delay': 0.005,
+
+      /**
+       * Displayed by Benchmark#toString when a `name` is not available
+       * (auto-generated if absent).
+       *
+       * @memberOf Benchmark.options
+       * @type String
+       */
+      'id': undefined,
+
+      /**
+       * The default number of times to execute a test on a benchmark's first cycle.
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'initCount': 1,
+
+      /**
+       * The maximum time a benchmark is allowed to run before finishing (secs).
+       *
+       * Note: Cycle delays aren't counted toward the maximum time.
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'maxTime': 5,
+
+      /**
+       * The minimum sample size required to perform statistical analysis.
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'minSamples': 5,
+
+      /**
+       * The time needed to reduce the percent uncertainty of measurement to 1% (secs).
+       *
+       * @memberOf Benchmark.options
+       * @type Number
+       */
+      'minTime': 0,
+
+      /**
+       * The name of the benchmark.
+       *
+       * @memberOf Benchmark.options
+       * @type String
+       */
+      'name': undefined,
+
+      /**
+       * An event listener called when the benchmark is aborted.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onAbort': undefined,
+
+      /**
+       * An event listener called when the benchmark completes running.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onComplete': undefined,
+
+      /**
+       * An event listener called after each run cycle.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onCycle': undefined,
+
+      /**
+       * An event listener called when a test errors.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onError': undefined,
+
+      /**
+       * An event listener called when the benchmark is reset.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onReset': undefined,
+
+      /**
+       * An event listener called when the benchmark starts running.
+       *
+       * @memberOf Benchmark.options
+       * @type Function
+       */
+      'onStart': undefined
+    },
+
+    /**
+     * Platform object with properties describing things like browser name,
+     * version, and operating system.
+     *
+     * @static
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'platform': req('platform') || window.platform || {
+
+      /**
+       * The platform description.
+       *
+       * @memberOf Benchmark.platform
+       * @type String
+       */
+      'description': window.navigator && navigator.userAgent || null,
+
+      /**
+       * The name of the browser layout engine.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'layout': null,
+
+      /**
+       * The name of the product hosting the browser.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'product': null,
+
+      /**
+       * The name of the browser/environment.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'name': null,
+
+      /**
+       * The name of the product's manufacturer.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'manufacturer': null,
+
+      /**
+       * The name of the operating system.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'os': null,
+
+      /**
+       * The alpha/beta release indicator.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'prerelease': null,
+
+      /**
+       * The browser/environment version.
+       *
+       * @memberOf Benchmark.platform
+       * @type String|Null
+       */
+      'version': null,
+
+      /**
+       * Return platform description when the platform object is coerced to a string.
+       *
+       * @memberOf Benchmark.platform
+       * @type Function
+       * @returns {String} The platform description.
+       */
+      'toString': function() {
+        return this.description || '';
+      }
+    },
+
+    /**
+     * The semantic version number.
+     *
+     * @static
+     * @memberOf Benchmark
+     * @type String
+     */
+    'version': '1.0.0',
+
+    // an object of environment/feature detection flags
+    'support': support,
+
+    // clone objects
+    'deepClone': deepClone,
+
+    // iteration utility
+    'each': each,
+
+    // augment objects
+    'extend': extend,
+
+    // generic Array#filter
+    'filter': filter,
+
+    // generic Array#forEach
+    'forEach': forEach,
+
+    // generic own property iteration utility
+    'forOwn': forOwn,
+
+    // converts a number to a comma-separated string
+    'formatNumber': formatNumber,
+
+    // generic Object#hasOwnProperty
+    // (trigger hasKey's lazy define before assigning it to Benchmark)
+    'hasKey': (hasKey(Benchmark, ''), hasKey),
+
+    // generic Array#indexOf
+    'indexOf': indexOf,
+
+    // template utility
+    'interpolate': interpolate,
+
+    // invokes a method on each item in an array
+    'invoke': invoke,
+
+    // generic Array#join for arrays and objects
+    'join': join,
+
+    // generic Array#map
+    'map': map,
+
+    // retrieves a property value from each item in an array
+    'pluck': pluck,
+
+    // generic Array#reduce
+    'reduce': reduce
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Benchmark.prototype, {
+
+    /**
+     * The number of times a test was executed.
+     *
+     * @memberOf Benchmark
+     * @type Number
+     */
+    'count': 0,
+
+    /**
+     * The number of cycles performed while benchmarking.
+     *
+     * @memberOf Benchmark
+     * @type Number
+     */
+    'cycles': 0,
+
+    /**
+     * The number of executions per second.
+     *
+     * @memberOf Benchmark
+     * @type Number
+     */
+    'hz': 0,
+
+    /**
+     * The compiled test function.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     */
+    'compiled': undefined,
+
+    /**
+     * The error object if the test failed.
+     *
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'error': undefined,
+
+    /**
+     * The test to benchmark.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     */
+    'fn': undefined,
+
+    /**
+     * A flag to indicate if the benchmark is aborted.
+     *
+     * @memberOf Benchmark
+     * @type Boolean
+     */
+    'aborted': false,
+
+    /**
+     * A flag to indicate if the benchmark is running.
+     *
+     * @memberOf Benchmark
+     * @type Boolean
+     */
+    'running': false,
+
+    /**
+     * Compiled into the test and executed immediately **before** the test loop.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     * @example
+     *
+     * // basic usage
+     * var bench = Benchmark({
+     *   'setup': function() {
+     *     var c = this.count,
+     *         element = document.getElementById('container');
+     *     while (c--) {
+     *       element.appendChild(document.createElement('div'));
+     *     }
+     *   },
+     *   'fn': function() {
+     *     element.removeChild(element.lastChild);
+     *   }
+     * });
+     *
+     * // compiles to something like:
+     * var c = this.count,
+     *     element = document.getElementById('container');
+     * while (c--) {
+     *   element.appendChild(document.createElement('div'));
+     * }
+     * var start = new Date;
+     * while (count--) {
+     *   element.removeChild(element.lastChild);
+     * }
+     * var end = new Date - start;
+     *
+     * // or using strings
+     * var bench = Benchmark({
+     *   'setup': '\
+     *     var a = 0;\n\
+     *     (function() {\n\
+     *       (function() {\n\
+     *         (function() {',
+     *   'fn': 'a += 1;',
+     *   'teardown': '\
+     *          }())\n\
+     *        }())\n\
+     *      }())'
+     * });
+     *
+     * // compiles to something like:
+     * var a = 0;
+     * (function() {
+     *   (function() {
+     *     (function() {
+     *       var start = new Date;
+     *       while (count--) {
+     *         a += 1;
+     *       }
+     *       var end = new Date - start;
+     *     }())
+     *   }())
+     * }())
+     */
+    'setup': noop,
+
+    /**
+     * Compiled into the test and executed immediately **after** the test loop.
+     *
+     * @memberOf Benchmark
+     * @type Function|String
+     */
+    'teardown': noop,
+
+    /**
+     * An object of stats including mean, margin or error, and standard deviation.
+     *
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'stats': {
+
+      /**
+       * The margin of error.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'moe': 0,
+
+      /**
+       * The relative margin of error (expressed as a percentage of the mean).
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'rme': 0,
+
+      /**
+       * The standard error of the mean.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'sem': 0,
+
+      /**
+       * The sample standard deviation.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'deviation': 0,
+
+      /**
+       * The sample arithmetic mean.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'mean': 0,
+
+      /**
+       * The array of sampled periods.
+       *
+       * @memberOf Benchmark#stats
+       * @type Array
+       */
+      'sample': [],
+
+      /**
+       * The sample variance.
+       *
+       * @memberOf Benchmark#stats
+       * @type Number
+       */
+      'variance': 0
+    },
+
+    /**
+     * An object of timing data including cycle, elapsed, period, start, and stop.
+     *
+     * @memberOf Benchmark
+     * @type Object
+     */
+    'times': {
+
+      /**
+       * The time taken to complete the last cycle (secs).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'cycle': 0,
+
+      /**
+       * The time taken to complete the benchmark (secs).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'elapsed': 0,
+
+      /**
+       * The time taken to execute the test once (secs).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'period': 0,
+
+      /**
+       * A timestamp of when the benchmark started (ms).
+       *
+       * @memberOf Benchmark#times
+       * @type Number
+       */
+      'timeStamp': 0
+    },
+
+    // aborts benchmark (does not record times)
+    'abort': abort,
+
+    // creates a new benchmark using the same test and options
+    'clone': clone,
+
+    // compares benchmark's hertz with another
+    'compare': compare,
+
+    // executes listeners
+    'emit': emit,
+
+    // get listeners
+    'listeners': listeners,
+
+    // unregister listeners
+    'off': off,
+
+    // register listeners
+    'on': on,
+
+    // reset benchmark properties
+    'reset': reset,
+
+    // runs the benchmark
+    'run': run,
+
+    // pretty print benchmark info
+    'toString': toStringBench
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Deferred.prototype, {
+
+    /**
+     * The deferred benchmark instance.
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Object
+     */
+    'benchmark': null,
+
+    /**
+     * The number of deferred cycles performed while benchmarking.
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Number
+     */
+    'cycles': 0,
+
+    /**
+     * The time taken to complete the deferred benchmark (secs).
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Number
+     */
+    'elapsed': 0,
+
+    /**
+     * A timestamp of when the deferred benchmark started (ms).
+     *
+     * @memberOf Benchmark.Deferred
+     * @type Number
+     */
+    'timeStamp': 0,
+
+    // cycles/completes the deferred benchmark
+    'resolve': resolve
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Event.prototype, {
+
+    /**
+     * A flag to indicate if the emitters listener iteration is aborted.
+     *
+     * @memberOf Benchmark.Event
+     * @type Boolean
+     */
+    'aborted': false,
+
+    /**
+     * A flag to indicate if the default action is cancelled.
+     *
+     * @memberOf Benchmark.Event
+     * @type Boolean
+     */
+    'cancelled': false,
+
+    /**
+     * The object whose listeners are currently being processed.
+     *
+     * @memberOf Benchmark.Event
+     * @type Object
+     */
+    'currentTarget': undefined,
+
+    /**
+     * The return value of the last executed listener.
+     *
+     * @memberOf Benchmark.Event
+     * @type Mixed
+     */
+    'result': undefined,
+
+    /**
+     * The object to which the event was originally emitted.
+     *
+     * @memberOf Benchmark.Event
+     * @type Object
+     */
+    'target': undefined,
+
+    /**
+     * A timestamp of when the event was created (ms).
+     *
+     * @memberOf Benchmark.Event
+     * @type Number
+     */
+    'timeStamp': 0,
+
+    /**
+     * The event type.
+     *
+     * @memberOf Benchmark.Event
+     * @type String
+     */
+    'type': ''
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The default options copied by suite instances.
+   *
+   * @static
+   * @memberOf Benchmark.Suite
+   * @type Object
+   */
+  Suite.options = {
+
+    /**
+     * The name of the suite.
+     *
+     * @memberOf Benchmark.Suite.options
+     * @type String
+     */
+    'name': undefined
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  extend(Suite.prototype, {
+
+    /**
+     * The number of benchmarks in the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @type Number
+     */
+    'length': 0,
+
+    /**
+     * A flag to indicate if the suite is aborted.
+     *
+     * @memberOf Benchmark.Suite
+     * @type Boolean
+     */
+    'aborted': false,
+
+    /**
+     * A flag to indicate if the suite is running.
+     *
+     * @memberOf Benchmark.Suite
+     * @type Boolean
+     */
+    'running': false,
+
+    /**
+     * An `Array#forEach` like method.
+     * Callbacks may terminate the loop by explicitly returning `false`.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} callback The function called per iteration.
+     * @returns {Object} The suite iterated over.
+     */
+    'forEach': methodize(forEach),
+
+    /**
+     * An `Array#indexOf` like method.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Mixed} value The value to search for.
+     * @returns {Number} The index of the matched value or `-1`.
+     */
+    'indexOf': methodize(indexOf),
+
+    /**
+     * Invokes a method on all benchmarks in the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {String|Object} name The name of the method to invoke OR options object.
+     * @param {Mixed} [arg1, arg2, ...] Arguments to invoke the method with.
+     * @returns {Array} A new array of values returned from each method invoked.
+     */
+    'invoke': methodize(invoke),
+
+    /**
+     * Converts the suite of benchmarks to a string.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {String} [separator=','] A string to separate each element of the array.
+     * @returns {String} The string.
+     */
+    'join': [].join,
+
+    /**
+     * An `Array#map` like method.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} callback The function called per iteration.
+     * @returns {Array} A new array of values returned by the callback.
+     */
+    'map': methodize(map),
+
+    /**
+     * Retrieves the value of a specified property from all benchmarks in the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {String} property The property to pluck.
+     * @returns {Array} A new array of property values.
+     */
+    'pluck': methodize(pluck),
+
+    /**
+     * Removes the last benchmark from the suite and returns it.
+     *
+     * @memberOf Benchmark.Suite
+     * @returns {Mixed} The removed benchmark.
+     */
+    'pop': [].pop,
+
+    /**
+     * Appends benchmarks to the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @returns {Number} The suite's new length.
+     */
+    'push': [].push,
+
+    /**
+     * Sorts the benchmarks of the suite.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} [compareFn=null] A function that defines the sort order.
+     * @returns {Object} The sorted suite.
+     */
+    'sort': [].sort,
+
+    /**
+     * An `Array#reduce` like method.
+     *
+     * @memberOf Benchmark.Suite
+     * @param {Function} callback The function called per iteration.
+     * @param {Mixed} accumulator Initial value of the accumulator.
+     * @returns {Mixed} The accumulator.
+     */
+    'reduce': methodize(reduce),
+
+    // aborts all benchmarks in the suite
+    'abort': abortSuite,
+
+    // adds a benchmark to the suite
+    'add': add,
+
+    // creates a new suite with cloned benchmarks
+    'clone': cloneSuite,
+
+    // executes listeners of a specified type
+    'emit': emit,
+
+    // creates a new suite of filtered benchmarks
+    'filter': filterSuite,
+
+    // get listeners
+    'listeners': listeners,
+
+    // unregister listeners
+    'off': off,
+
+   // register listeners
+    'on': on,
+
+    // resets all benchmarks in the suite
+    'reset': resetSuite,
+
+    // runs all benchmarks in the suite
+    'run': runSuite,
+
+    // array methods
+    'concat': concat,
+
+    'reverse': reverse,
+
+    'shift': shift,
+
+    'slice': slice,
+
+    'splice': splice,
+
+    'unshift': unshift
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose Deferred, Event and Suite
+  extend(Benchmark, {
+    'Deferred': Deferred,
+    'Event': Event,
+    'Suite': Suite
+  });
+
+  // expose Benchmark
+  // some AMD build optimizers, like r.js, check for specific condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // define as an anonymous module so, through path mapping, it can be aliased
+    define(function() {
+      return Benchmark;
+    });
+  }
+  // check for `exports` after `define` in case a build optimizer adds an `exports` object
+  else if (freeExports) {
+    // in Node.js or RingoJS v0.8.0+
+    if (typeof module == 'object' && module && module.exports == freeExports) {
+      (module.exports = Benchmark).Benchmark = Benchmark;
+    }
+    // in Narwhal or RingoJS v0.7.0-
+    else {
+      freeExports.Benchmark = Benchmark;
+    }
+  }
+  // in a browser or Rhino
+  else {
+    // use square bracket notation so Closure Compiler won't munge `Benchmark`
+    // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
+    window['Benchmark'] = Benchmark;
+  }
+
+  // trigger clock's lazy define early to avoid a security error
+  if (support.air) {
+    clock({ '_original': { 'fn': noop, 'count': 1, 'options': {} } });
+  }
+}(this));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/doc/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/doc/README.md
new file mode 100644
index 0000000..5ac99e3
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/doc/README.md
@@ -0,0 +1,2629 @@
+# Benchmark.js <sup>v1.0.0</sup>
+
+<!-- div -->
+
+
+<!-- div -->
+
+## <a id="Benchmark"></a>`Benchmark`
+* [`Benchmark`](#benchmarkname-fn--options)
+* [`Benchmark.version`](#benchmarkversion)
+* [`Benchmark.deepClone`](#benchmarkdeepclonevalue)
+* [`Benchmark.each`](#benchmarkeachobject-callback-thisarg)
+* [`Benchmark.extend`](#benchmarkextenddestination--source)
+* [`Benchmark.filter`](#benchmarkfilterarray-callback-thisarg)
+* [`Benchmark.forEach`](#benchmarkforeacharray-callback-thisarg)
+* [`Benchmark.formatNumber`](#benchmarkformatnumbernumber)
+* [`Benchmark.forOwn`](#benchmarkforownobject-callback-thisarg)
+* [`Benchmark.hasKey`](#benchmarkhaskeyobject-key)
+* [`Benchmark.indexOf`](#benchmarkindexofarray-value--fromindex0)
+* [`Benchmark.interpolate`](#benchmarkinterpolatestring-object)
+* [`Benchmark.invoke`](#benchmarkinvokebenches-name--arg1-arg2-)
+* [`Benchmark.join`](#benchmarkjoinobject--separator1--separator2:)
+* [`Benchmark.map`](#benchmarkmaparray-callback-thisarg)
+* [`Benchmark.pluck`](#benchmarkpluckarray-property)
+* [`Benchmark.reduce`](#benchmarkreducearray-callback-accumulator)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype`
+* [`Benchmark.prototype.aborted`](#benchmarkprototypeaborted)
+* [`Benchmark.prototype.compiled`](#benchmarkprototypecompiled)
+* [`Benchmark.prototype.count`](#benchmarkprototypecount)
+* [`Benchmark.prototype.cycles`](#benchmarkprototypecycles)
+* [`Benchmark.prototype.fn`](#benchmarkprototypefn)
+* [`Benchmark.prototype.hz`](#benchmarkprototypehz)
+* [`Benchmark.prototype.running`](#benchmarkprototyperunning)
+* [`Benchmark.prototype.setup`](#benchmarkprototypesetup)
+* [`Benchmark.prototype.teardown`](#benchmarkprototypeteardown)
+* [`Benchmark.prototype.abort`](#benchmarkprototypeabort)
+* [`Benchmark.prototype.clone`](#benchmarkprototypecloneoptions)
+* [`Benchmark.prototype.compare`](#benchmarkprototypecompareother)
+* [`Benchmark.prototype.emit`](#benchmarkprototypeemittype)
+* [`Benchmark.prototype.listeners`](#benchmarkprototypelistenerstype)
+* [`Benchmark.prototype.off`](#benchmarkprototypeofftype-listener)
+* [`Benchmark.prototype.on`](#benchmarkprototypeontype-listener)
+* [`Benchmark.prototype.reset`](#benchmarkprototypereset)
+* [`Benchmark.prototype.run`](#benchmarkprototyperunoptions)
+* [`Benchmark.prototype.toString`](#benchmarkprototypetostring)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.options`
+* [`Benchmark.options`](#benchmarkoptions)
+* [`Benchmark.options.async`](#benchmarkoptionsasync)
+* [`Benchmark.options.defer`](#benchmarkoptionsdefer)
+* [`Benchmark.options.delay`](#benchmarkoptionsdelay)
+* [`Benchmark.options.id`](#benchmarkoptionsid)
+* [`Benchmark.options.initCount`](#benchmarkoptionsinitcount)
+* [`Benchmark.options.maxTime`](#benchmarkoptionsmaxtime)
+* [`Benchmark.options.minSamples`](#benchmarkoptionsminsamples)
+* [`Benchmark.options.minTime`](#benchmarkoptionsmintime)
+* [`Benchmark.options.name`](#benchmarkoptionsname)
+* [`Benchmark.options.onAbort`](#benchmarkoptionsonabort)
+* [`Benchmark.options.onComplete`](#benchmarkoptionsoncomplete)
+* [`Benchmark.options.onCycle`](#benchmarkoptionsoncycle)
+* [`Benchmark.options.onError`](#benchmarkoptionsonerror)
+* [`Benchmark.options.onReset`](#benchmarkoptionsonreset)
+* [`Benchmark.options.onStart`](#benchmarkoptionsonstart)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.platform`
+* [`Benchmark.platform`](#benchmarkplatform)
+* [`Benchmark.platform.description`](#benchmarkplatformdescription)
+* [`Benchmark.platform.layout`](#benchmarkplatformlayout)
+* [`Benchmark.platform.manufacturer`](#benchmarkplatformmanufacturer)
+* [`Benchmark.platform.name`](#benchmarkplatformname)
+* [`Benchmark.platform.os`](#benchmarkplatformos)
+* [`Benchmark.platform.prerelease`](#benchmarkplatformprerelease)
+* [`Benchmark.platform.product`](#benchmarkplatformproduct)
+* [`Benchmark.platform.version`](#benchmarkplatformversion)
+* [`Benchmark.platform.toString`](#benchmarkplatformtostring)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.support`
+* [`Benchmark.support`](#benchmarksupport)
+* [`Benchmark.support.air`](#benchmarksupportair)
+* [`Benchmark.support.argumentsClass`](#benchmarksupportargumentsclass)
+* [`Benchmark.support.browser`](#benchmarksupportbrowser)
+* [`Benchmark.support.charByIndex`](#benchmarksupportcharbyindex)
+* [`Benchmark.support.charByOwnIndex`](#benchmarksupportcharbyownindex)
+* [`Benchmark.support.decompilation`](#benchmarksupportdecompilation)
+* [`Benchmark.support.descriptors`](#benchmarksupportdescriptors)
+* [`Benchmark.support.getAllKeys`](#benchmarksupportgetallkeys)
+* [`Benchmark.support.iteratesOwnLast`](#benchmarksupportiteratesownfirst)
+* [`Benchmark.support.java`](#benchmarksupportjava)
+* [`Benchmark.support.nodeClass`](#benchmarksupportnodeclass)
+* [`Benchmark.support.timeout`](#benchmarksupporttimeout)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype.error`
+* [`Benchmark.prototype.error`](#benchmarkprototypeerror)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype.stats`
+* [`Benchmark.prototype.stats`](#benchmarkprototypestats)
+* [`Benchmark.prototype.stats.deviation`](#benchmark-statsdeviation)
+* [`Benchmark.prototype.stats.mean`](#benchmark-statsmean)
+* [`Benchmark.prototype.stats.moe`](#benchmark-statsmoe)
+* [`Benchmark.prototype.stats.rme`](#benchmark-statsrme)
+* [`Benchmark.prototype.stats.sample`](#benchmark-statssample)
+* [`Benchmark.prototype.stats.sem`](#benchmark-statssem)
+* [`Benchmark.prototype.stats.variance`](#benchmark-statsvariance)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype.times`
+* [`Benchmark.prototype.times`](#benchmarkprototypetimes)
+* [`Benchmark.prototype.times.cycle`](#benchmark-timescycle)
+* [`Benchmark.prototype.times.elapsed`](#benchmark-timeselapsed)
+* [`Benchmark.prototype.times.period`](#benchmark-timesperiod)
+* [`Benchmark.prototype.times.timeStamp`](#benchmark-timestimestamp)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Deferred`
+* [`Benchmark.Deferred`](#benchmarkdeferredclone)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Deferred.prototype`
+* [`Benchmark.Deferred.prototype.benchmark`](#benchmarkdeferredprototypebenchmark)
+* [`Benchmark.Deferred.prototype.cycles`](#benchmarkdeferredprototypecycles)
+* [`Benchmark.Deferred.prototype.elapsed`](#benchmarkdeferredprototypeelapsed)
+* [`Benchmark.Deferred.prototype.resolve`](#benchmarkdeferredprototyperesolve)
+* [`Benchmark.Deferred.prototype.timeStamp`](#benchmarkdeferredprototypetimestamp)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event`
+* [`Benchmark.Event`](#benchmarkeventtype)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event.prototype`
+* [`Benchmark.Event.prototype.aborted`](#benchmarkeventprototypeaborted)
+* [`Benchmark.Event.prototype.cancelled`](#benchmarkeventprototypecancelled)
+* [`Benchmark.Event.prototype.result`](#benchmarkeventprototyperesult)
+* [`Benchmark.Event.prototype.timeStamp`](#benchmarkeventprototypetimestamp)
+* [`Benchmark.Event.prototype.type`](#benchmarkeventprototypetype)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event.prototype.currentTarget`
+* [`Benchmark.Event.prototype.currentTarget`](#benchmarkeventprototypecurrenttarget)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event.prototype.target`
+* [`Benchmark.Event.prototype.target`](#benchmarkeventprototypetarget)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Suite`
+* [`Benchmark.Suite`](#benchmarksuitename--options)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Suite.prototype`
+* [`Benchmark.Suite.prototype.aborted`](#benchmarksuiteprototypeaborted)
+* [`Benchmark.Suite.prototype.length`](#benchmarksuiteprototypelength)
+* [`Benchmark.Suite.prototype.running`](#benchmarksuiteprototyperunning)
+* [`Benchmark.Suite.prototype.abort`](#benchmarksuiteprototypeabort)
+* [`Benchmark.Suite.prototype.add`](#benchmarksuiteprototypeaddname-fn--options)
+* [`Benchmark.Suite.prototype.clone`](#benchmarksuiteprototypecloneoptions)
+* [`Benchmark.Suite.prototype.emit`](#benchmarkprototypeemittype)
+* [`Benchmark.Suite.prototype.filter`](#benchmarksuiteprototypefiltercallback)
+* [`Benchmark.Suite.prototype.forEach`](#benchmarksuiteprototypeforeachcallback)
+* [`Benchmark.Suite.prototype.indexOf`](#benchmarksuiteprototypeindexofvalue)
+* [`Benchmark.Suite.prototype.invoke`](#benchmarksuiteprototypeinvokename--arg1-arg2-)
+* [`Benchmark.Suite.prototype.join`](#benchmarksuiteprototypejoinseparator-)
+* [`Benchmark.Suite.prototype.listeners`](#benchmarkprototypelistenerstype)
+* [`Benchmark.Suite.prototype.map`](#benchmarksuiteprototypemapcallback)
+* [`Benchmark.Suite.prototype.off`](#benchmarkprototypeofftype-listener)
+* [`Benchmark.Suite.prototype.on`](#benchmarkprototypeontype-listener)
+* [`Benchmark.Suite.prototype.pluck`](#benchmarksuiteprototypepluckproperty)
+* [`Benchmark.Suite.prototype.pop`](#benchmarksuiteprototypepop)
+* [`Benchmark.Suite.prototype.push`](#benchmarksuiteprototypepush)
+* [`Benchmark.Suite.prototype.reduce`](#benchmarksuiteprototypereducecallback-accumulator)
+* [`Benchmark.Suite.prototype.reset`](#benchmarksuiteprototypereset)
+* [`Benchmark.Suite.prototype.reverse`](#benchmarksuiteprototypereverse)
+* [`Benchmark.Suite.prototype.run`](#benchmarksuiteprototyperunoptions)
+* [`Benchmark.Suite.prototype.shift`](#benchmarksuiteprototypeshift)
+* [`Benchmark.Suite.prototype.slice`](#benchmarksuiteprototypeslicestart-end)
+* [`Benchmark.Suite.prototype.sort`](#benchmarksuiteprototypesortcomparefnnull)
+* [`Benchmark.Suite.prototype.splice`](#benchmarksuiteprototypesplicestart-deletecount--val1-val2-)
+* [`Benchmark.Suite.prototype.unshift`](#benchmarksuiteprototypeunshift)
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Suite.options`
+* [`Benchmark.Suite.options`](#benchmarksuiteoptions)
+* [`Benchmark.Suite.options.name`](#benchmarksuiteoptionsname)
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+
+<!-- div -->
+
+## `Benchmark`
+
+<!-- div -->
+
+### <a id="benchmarkname-fn--options"></a>`Benchmark(name, fn [, options={}])`
+<a href="#benchmarkname-fn--options">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L404 "View in source") [&#x24C9;][1]
+
+The Benchmark constructor.
+
+#### Arguments
+1. `name` *(String)*: A name to identify the benchmark.
+2. `fn` *(Function|String)*: The test to benchmark.
+3. `[options={}]` *(Object)*: Options object.
+
+#### Example
+```js
+// basic usage (the `new` operator is optional)
+var bench = new Benchmark(fn);
+
+// or using a name first
+var bench = new Benchmark('foo', fn);
+
+// or with options
+var bench = new Benchmark('foo', fn, {
+
+  // displayed by Benchmark#toString if `name` is not available
+  'id': 'xyz',
+
+  // called when the benchmark starts running
+  'onStart': onStart,
+
+  // called after each run cycle
+  'onCycle': onCycle,
+
+  // called when aborted
+  'onAbort': onAbort,
+
+  // called when a test errors
+  'onError': onError,
+
+  // called when reset
+  'onReset': onReset,
+
+  // called when the benchmark completes running
+  'onComplete': onComplete,
+
+  // compiled/called before the test loop
+  'setup': setup,
+
+  // compiled/called after the test loop
+  'teardown': teardown
+});
+
+// or name and options
+var bench = new Benchmark('foo', {
+
+  // a flag to indicate the benchmark is deferred
+  'defer': true,
+
+  // benchmark test function
+  'fn': function(deferred) {
+    // call resolve() when the deferred test is finished
+    deferred.resolve();
+  }
+});
+
+// or options only
+var bench = new Benchmark({
+
+  // benchmark name
+  'name': 'foo',
+
+  // benchmark test as a string
+  'fn': '[1,2,3,4].sort()'
+});
+
+// a test's `this` binding is set to the benchmark instance
+var bench = new Benchmark('foo', function() {
+  'My name is '.concat(this.name); // My name is foo
+});
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkversion"></a>`Benchmark.version`
+<a href="#benchmarkversion">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3268 "View in source") [&#x24C9;][1]
+
+*(String)*: The semantic version number.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkdeepclonevalue"></a>`Benchmark.deepClone(value)`
+<a href="#benchmarkdeepclonevalue">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1225 "View in source") [&#x24C9;][1]
+
+A deep clone utility.
+
+#### Arguments
+1. `value` *(Mixed)*: The value to clone.
+
+#### Returns
+*(Mixed)*: The cloned value.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkeachobject-callback-thisarg"></a>`Benchmark.each(object, callback, thisArg)`
+<a href="#benchmarkeachobject-callback-thisarg">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1400 "View in source") [&#x24C9;][1]
+
+An iteration utility for arrays and objects. Callbacks may terminate the loop by explicitly returning `false`.
+
+#### Arguments
+1. `object` *(Array|Object)*: The object to iterate over.
+2. `callback` *(Function)*: The function called per iteration.
+3. `thisArg` *(Mixed)*: The `this` binding for the callback.
+
+#### Returns
+*(Array, Object)*: Returns the object iterated over.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkextenddestination--source"></a>`Benchmark.extend(destination [, source={}])`
+<a href="#benchmarkextenddestination--source">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1446 "View in source") [&#x24C9;][1]
+
+Copies enumerable properties from the source(s) object to the destination object.
+
+#### Arguments
+1. `destination` *(Object)*: The destination object.
+2. `[source={}]` *(Object)*: The source object.
+
+#### Returns
+*(Object)*: The destination object.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkfilterarray-callback-thisarg"></a>`Benchmark.filter(array, callback, thisArg)`
+<a href="#benchmarkfilterarray-callback-thisarg">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1485 "View in source") [&#x24C9;][1]
+
+A generic `Array#filter` like method.
+
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `callback` *(Function|String)*: The function/alias called per iteration.
+3. `thisArg` *(Mixed)*: The `this` binding for the callback.
+
+#### Returns
+*(Array)*: A new array of values that passed callback filter.
+
+#### Example
+```js
+// get odd numbers
+Benchmark.filter([1, 2, 3, 4, 5], function(n) {
+  return n % 2;
+}); // -> [1, 3, 5];
+
+// get fastest benchmarks
+Benchmark.filter(benches, 'fastest');
+
+// get slowest benchmarks
+Benchmark.filter(benches, 'slowest');
+
+// get benchmarks that completed without erroring
+Benchmark.filter(benches, 'successful');
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkforeacharray-callback-thisarg"></a>`Benchmark.forEach(array, callback, thisArg)`
+<a href="#benchmarkforeacharray-callback-thisarg">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1518 "View in source") [&#x24C9;][1]
+
+A generic `Array#forEach` like method. Callbacks may terminate the loop by explicitly returning `false`.
+
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `callback` *(Function)*: The function called per iteration.
+3. `thisArg` *(Mixed)*: The `this` binding for the callback.
+
+#### Returns
+*(Array)*: Returns the array iterated over.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkformatnumbernumber"></a>`Benchmark.formatNumber(number)`
+<a href="#benchmarkformatnumbernumber">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1557 "View in source") [&#x24C9;][1]
+
+Converts a number to a more readable comma-separated string representation.
+
+#### Arguments
+1. `number` *(Number)*: The number to convert.
+
+#### Returns
+*(String)*: The more readable string representation.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkforownobject-callback-thisarg"></a>`Benchmark.forOwn(object, callback, thisArg)`
+<a href="#benchmarkforownobject-callback-thisarg">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1545 "View in source") [&#x24C9;][1]
+
+Iterates over an object's own properties, executing the `callback` for each. Callbacks may terminate the loop by explicitly returning `false`.
+
+#### Arguments
+1. `object` *(Object)*: The object to iterate over.
+2. `callback` *(Function)*: The function executed per own property.
+3. `thisArg` *(Mixed)*: The `this` binding for the callback.
+
+#### Returns
+*(Object)*: Returns the object iterated over.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkhaskeyobject-key"></a>`Benchmark.hasKey(object, key)`
+<a href="#benchmarkhaskeyobject-key">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1572 "View in source") [&#x24C9;][1]
+
+Checks if an object has the specified key as a direct property.
+
+#### Arguments
+1. `object` *(Object)*: The object to check.
+2. `key` *(String)*: The key to check for.
+
+#### Returns
+*(Boolean)*: Returns `true` if key is a direct property, else `false`.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkindexofarray-value--fromindex0"></a>`Benchmark.indexOf(array, value [, fromIndex=0])`
+<a href="#benchmarkindexofarray-value--fromindex0">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1608 "View in source") [&#x24C9;][1]
+
+A generic `Array#indexOf` like method.
+
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `value` *(Mixed)*: The value to search for.
+3. `[fromIndex=0]` *(Number)*: The index to start searching from.
+
+#### Returns
+*(Number)*: The index of the matched value or `-1`.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkinterpolatestring-object"></a>`Benchmark.interpolate(string, object)`
+<a href="#benchmarkinterpolatestring-object">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1630 "View in source") [&#x24C9;][1]
+
+Modify a string by replacing named tokens with matching object property values.
+
+#### Arguments
+1. `string` *(String)*: The string to modify.
+2. `object` *(Object)*: The template object.
+
+#### Returns
+*(String)*: The modified string.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkinvokebenches-name--arg1-arg2-"></a>`Benchmark.invoke(benches, name [, arg1, arg2, ...])`
+<a href="#benchmarkinvokebenches-name--arg1-arg2-">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1677 "View in source") [&#x24C9;][1]
+
+Invokes a method on all items in an array.
+
+#### Arguments
+1. `benches` *(Array)*: Array of benchmarks to iterate over.
+2. `name` *(String|Object)*: The name of the method to invoke OR options object.
+3. `[arg1, arg2, ...]` *(Mixed)*: Arguments to invoke the method with.
+
+#### Returns
+*(Array)*: A new array of values returned from each method invoked.
+
+#### Example
+```js
+// invoke `reset` on all benchmarks
+Benchmark.invoke(benches, 'reset');
+
+// invoke `emit` with arguments
+Benchmark.invoke(benches, 'emit', 'complete', listener);
+
+// invoke `run(true)`, treat benchmarks as a queue, and register invoke callbacks
+Benchmark.invoke(benches, {
+
+  // invoke the `run` method
+  'name': 'run',
+
+  // pass a single argument
+  'args': true,
+
+  // treat as queue, removing benchmarks from front of `benches` until empty
+  'queued': true,
+
+  // called before any benchmarks have been invoked.
+  'onStart': onStart,
+
+  // called between invoking benchmarks
+  'onCycle': onCycle,
+
+  // called after all benchmarks have been invoked.
+  'onComplete': onComplete
+});
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkjoinobject--separator1--separator2:"></a>`Benchmark.join(object [, separator1=',', separator2=': '])`
+<a href="#benchmarkjoinobject--separator1--separator2:">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1831 "View in source") [&#x24C9;][1]
+
+Creates a string of joined array values or object key-value pairs.
+
+#### Arguments
+1. `object` *(Array|Object)*: The object to operate on.
+2. `[separator1=',']` *(String)*: The separator used between key-value pairs.
+3. `[separator2=': ']` *(String)*: The separator used between keys and values.
+
+#### Returns
+*(String)*: The joined result.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkmaparray-callback-thisarg"></a>`Benchmark.map(array, callback, thisArg)`
+<a href="#benchmarkmaparray-callback-thisarg">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1853 "View in source") [&#x24C9;][1]
+
+A generic `Array#map` like method.
+
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `callback` *(Function)*: The function called per iteration.
+3. `thisArg` *(Mixed)*: The `this` binding for the callback.
+
+#### Returns
+*(Array)*: A new array of values returned by the callback.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkpluckarray-property"></a>`Benchmark.pluck(array, property)`
+<a href="#benchmarkpluckarray-property">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1869 "View in source") [&#x24C9;][1]
+
+Retrieves the value of a specified property from all items in an array.
+
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `property` *(String)*: The property to pluck.
+
+#### Returns
+*(Array)*: A new array of property values.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkreducearray-callback-accumulator"></a>`Benchmark.reduce(array, callback, accumulator)`
+<a href="#benchmarkreducearray-callback-accumulator">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1885 "View in source") [&#x24C9;][1]
+
+A generic `Array#reduce` like method.
+
+#### Arguments
+1. `array` *(Array)*: The array to iterate over.
+2. `callback` *(Function)*: The function called per iteration.
+3. `accumulator` *(Mixed)*: Initial value of the accumulator.
+
+#### Returns
+*(Mixed)*: The accumulator.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype`
+
+<!-- div -->
+
+### <a id="benchmarkprototypeaborted"></a>`Benchmark.prototype.aborted`
+<a href="#benchmarkprototypeaborted">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3378 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate if the benchmark is aborted.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypecompiled"></a>`Benchmark.prototype.compiled`
+<a href="#benchmarkprototypecompiled">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3354 "View in source") [&#x24C9;][1]
+
+*(Function, String)*: The compiled test function.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypecount"></a>`Benchmark.prototype.count`
+<a href="#benchmarkprototypecount">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3330 "View in source") [&#x24C9;][1]
+
+*(Number)*: The number of times a test was executed.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypecycles"></a>`Benchmark.prototype.cycles`
+<a href="#benchmarkprototypecycles">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3338 "View in source") [&#x24C9;][1]
+
+*(Number)*: The number of cycles performed while benchmarking.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypefn"></a>`Benchmark.prototype.fn`
+<a href="#benchmarkprototypefn">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3370 "View in source") [&#x24C9;][1]
+
+*(Function, String)*: The test to benchmark.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypehz"></a>`Benchmark.prototype.hz`
+<a href="#benchmarkprototypehz">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3346 "View in source") [&#x24C9;][1]
+
+*(Number)*: The number of executions per second.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototyperunning"></a>`Benchmark.prototype.running`
+<a href="#benchmarkprototyperunning">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3386 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate if the benchmark is running.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypesetup"></a>`Benchmark.prototype.setup`
+<a href="#benchmarkprototypesetup">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3449 "View in source") [&#x24C9;][1]
+
+*(Function, String)*: Compiled into the test and executed immediately **before** the test loop.
+
+#### Example
+```js
+// basic usage
+var bench = Benchmark({
+  'setup': function() {
+    var c = this.count,
+        element = document.getElementById('container');
+    while (c--) {
+      element.appendChild(document.createElement('div'));
+    }
+  },
+  'fn': function() {
+    element.removeChild(element.lastChild);
+  }
+});
+
+// compiles to something like:
+var c = this.count,
+    element = document.getElementById('container');
+while (c--) {
+  element.appendChild(document.createElement('div'));
+}
+var start = new Date;
+while (count--) {
+  element.removeChild(element.lastChild);
+}
+var end = new Date - start;
+
+// or using strings
+var bench = Benchmark({
+  'setup': '\
+    var a = 0;\n\
+    (function() {\n\
+      (function() {\n\
+        (function() {',
+  'fn': 'a += 1;',
+  'teardown': '\
+         }())\n\
+       }())\n\
+     }())'
+});
+
+// compiles to something like:
+var a = 0;
+(function() {
+  (function() {
+    (function() {
+      var start = new Date;
+      while (count--) {
+        a += 1;
+      }
+      var end = new Date - start;
+    }())
+  }())
+}())
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeteardown"></a>`Benchmark.prototype.teardown`
+<a href="#benchmarkprototypeteardown">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3457 "View in source") [&#x24C9;][1]
+
+*(Function, String)*: Compiled into the test and executed immediately **after** the test loop.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeabort"></a>`Benchmark.prototype.abort()`
+<a href="#benchmarkprototypeabort">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2218 "View in source") [&#x24C9;][1]
+
+Aborts the benchmark without recording times.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypecloneoptions"></a>`Benchmark.prototype.clone(options)`
+<a href="#benchmarkprototypecloneoptions">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2257 "View in source") [&#x24C9;][1]
+
+Creates a new benchmark using the same test and options.
+
+#### Arguments
+1. `options` *(Object)*: Options object to overwrite cloned options.
+
+#### Returns
+*(Object)*: The new benchmark instance.
+
+#### Example
+```js
+var bizarro = bench.clone({
+  'name': 'doppelganger'
+});
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypecompareother"></a>`Benchmark.prototype.compare(other)`
+<a href="#benchmarkprototypecompareother">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2280 "View in source") [&#x24C9;][1]
+
+Determines if a benchmark is faster than another.
+
+#### Arguments
+1. `other` *(Object)*: The benchmark to compare.
+
+#### Returns
+*(Number)*: Returns `-1` if slower, `1` if faster, and `0` if indeterminate.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeemittype"></a>`Benchmark.Suite.prototype.emit(type)`
+<a href="#benchmarkprototypeemittype">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2095 "View in source") [&#x24C9;][1]
+
+Executes all registered listeners of the specified event type.
+
+#### Arguments
+1. `type` *(String|Object)*: The event type or object.
+
+#### Returns
+*(Mixed)*: Returns the return value of the last listener executed.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypelistenerstype"></a>`Benchmark.Suite.prototype.listeners(type)`
+<a href="#benchmarkprototypelistenerstype">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2125 "View in source") [&#x24C9;][1]
+
+Returns an array of event listeners for a given type that can be manipulated to add or remove listeners.
+
+#### Arguments
+1. `type` *(String)*: The event type.
+
+#### Returns
+*(Array)*: The listeners array.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeofftype-listener"></a>`Benchmark.Suite.prototype.off([type, listener])`
+<a href="#benchmarkprototypeofftype-listener">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2158 "View in source") [&#x24C9;][1]
+
+Unregisters a listener for the specified event type(s), or unregisters all listeners for the specified event type(s), or unregisters all listeners for all event types.
+
+#### Arguments
+1. `[type]` *(String)*: The event type.
+2. `[listener]` *(Function)*: The function to unregister.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+#### Example
+```js
+// unregister a listener for an event type
+bench.off('cycle', listener);
+
+// unregister a listener for multiple event types
+bench.off('start cycle', listener);
+
+// unregister all listeners for an event type
+bench.off('cycle');
+
+// unregister all listeners for multiple event types
+bench.off('start cycle complete');
+
+// unregister all listeners for all event types
+bench.off();
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeontype-listener"></a>`Benchmark.Suite.prototype.on(type, listener)`
+<a href="#benchmarkprototypeontype-listener">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2197 "View in source") [&#x24C9;][1]
+
+Registers a listener for the specified event type(s).
+
+#### Arguments
+1. `type` *(String)*: The event type.
+2. `listener` *(Function)*: The function to register.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+#### Example
+```js
+// register a listener for an event type
+bench.on('cycle', listener);
+
+// register a listener for multiple event types
+bench.on('start cycle', listener);
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypereset"></a>`Benchmark.prototype.reset()`
+<a href="#benchmarkprototypereset">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2334 "View in source") [&#x24C9;][1]
+
+Reset properties and abort if running.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototyperunoptions"></a>`Benchmark.prototype.run([options={}])`
+<a href="#benchmarkprototyperunoptions">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3000 "View in source") [&#x24C9;][1]
+
+Runs the benchmark.
+
+#### Arguments
+1. `[options={}]` *(Object)*: Options object.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+#### Example
+```js
+// basic usage
+bench.run();
+
+// or with options
+bench.run({ 'async': true });
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypetostring"></a>`Benchmark.prototype.toString()`
+<a href="#benchmarkprototypetostring">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2405 "View in source") [&#x24C9;][1]
+
+Displays relevant benchmark information when coerced to a string.
+
+#### Returns
+*(String)*: A string representation of the benchmark instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.options`
+
+<!-- div -->
+
+### <a id="benchmarkoptions"></a>`Benchmark.options`
+<a href="#benchmarkoptions">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3049 "View in source") [&#x24C9;][1]
+
+*(Object)*: The default options copied by benchmark instances.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsasync"></a>`Benchmark.options.async`
+<a href="#benchmarkoptionsasync">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3058 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate that benchmark cycles will execute asynchronously by default.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsdefer"></a>`Benchmark.options.defer`
+<a href="#benchmarkoptionsdefer">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3066 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate that the benchmark clock is deferred.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsdelay"></a>`Benchmark.options.delay`
+<a href="#benchmarkoptionsdelay">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3073 "View in source") [&#x24C9;][1]
+
+*(Number)*: The delay between test cycles *(secs)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsid"></a>`Benchmark.options.id`
+<a href="#benchmarkoptionsid">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3082 "View in source") [&#x24C9;][1]
+
+*(String)*: Displayed by Benchmark#toString when a `name` is not available *(auto-generated if absent)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsinitcount"></a>`Benchmark.options.initCount`
+<a href="#benchmarkoptionsinitcount">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3090 "View in source") [&#x24C9;][1]
+
+*(Number)*: The default number of times to execute a test on a benchmark's first cycle.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsmaxtime"></a>`Benchmark.options.maxTime`
+<a href="#benchmarkoptionsmaxtime">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3100 "View in source") [&#x24C9;][1]
+
+*(Number)*: The maximum time a benchmark is allowed to run before finishing *(secs)*.  Note: Cycle delays aren't counted toward the maximum time.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsminsamples"></a>`Benchmark.options.minSamples`
+<a href="#benchmarkoptionsminsamples">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3108 "View in source") [&#x24C9;][1]
+
+*(Number)*: The minimum sample size required to perform statistical analysis.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsmintime"></a>`Benchmark.options.minTime`
+<a href="#benchmarkoptionsmintime">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3116 "View in source") [&#x24C9;][1]
+
+*(Number)*: The time needed to reduce the percent uncertainty of measurement to `1`% *(secs)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsname"></a>`Benchmark.options.name`
+<a href="#benchmarkoptionsname">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3124 "View in source") [&#x24C9;][1]
+
+*(String)*: The name of the benchmark.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsonabort"></a>`Benchmark.options.onAbort`
+<a href="#benchmarkoptionsonabort">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3132 "View in source") [&#x24C9;][1]
+
+An event listener called when the benchmark is aborted.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsoncomplete"></a>`Benchmark.options.onComplete`
+<a href="#benchmarkoptionsoncomplete">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3140 "View in source") [&#x24C9;][1]
+
+An event listener called when the benchmark completes running.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsoncycle"></a>`Benchmark.options.onCycle`
+<a href="#benchmarkoptionsoncycle">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3148 "View in source") [&#x24C9;][1]
+
+An event listener called after each run cycle.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsonerror"></a>`Benchmark.options.onError`
+<a href="#benchmarkoptionsonerror">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3156 "View in source") [&#x24C9;][1]
+
+An event listener called when a test errors.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsonreset"></a>`Benchmark.options.onReset`
+<a href="#benchmarkoptionsonreset">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3164 "View in source") [&#x24C9;][1]
+
+An event listener called when the benchmark is reset.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkoptionsonstart"></a>`Benchmark.options.onStart`
+<a href="#benchmarkoptionsonstart">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3172 "View in source") [&#x24C9;][1]
+
+An event listener called when the benchmark starts running.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.platform`
+
+<!-- div -->
+
+### <a id="benchmarkplatform"></a>`Benchmark.platform`
+<a href="#benchmarkplatform">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3183 "View in source") [&#x24C9;][1]
+
+*(Object)*: Platform object with properties describing things like browser name, version, and operating system.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformdescription"></a>`Benchmark.platform.description`
+<a href="#benchmarkplatformdescription">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3191 "View in source") [&#x24C9;][1]
+
+*(String)*: The platform description.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformlayout"></a>`Benchmark.platform.layout`
+<a href="#benchmarkplatformlayout">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3199 "View in source") [&#x24C9;][1]
+
+*(String, Null)*: The name of the browser layout engine.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformmanufacturer"></a>`Benchmark.platform.manufacturer`
+<a href="#benchmarkplatformmanufacturer">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3223 "View in source") [&#x24C9;][1]
+
+*(String, Null)*: The name of the product's manufacturer.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformname"></a>`Benchmark.platform.name`
+<a href="#benchmarkplatformname">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3215 "View in source") [&#x24C9;][1]
+
+*(String, Null)*: The name of the browser/environment.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformos"></a>`Benchmark.platform.os`
+<a href="#benchmarkplatformos">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3231 "View in source") [&#x24C9;][1]
+
+*(String, Null)*: The name of the operating system.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformprerelease"></a>`Benchmark.platform.prerelease`
+<a href="#benchmarkplatformprerelease">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3239 "View in source") [&#x24C9;][1]
+
+*(String, Null)*: The alpha/beta release indicator.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformproduct"></a>`Benchmark.platform.product`
+<a href="#benchmarkplatformproduct">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3207 "View in source") [&#x24C9;][1]
+
+*(String, Null)*: The name of the product hosting the browser.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformversion"></a>`Benchmark.platform.version`
+<a href="#benchmarkplatformversion">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3247 "View in source") [&#x24C9;][1]
+
+*(String, Null)*: The browser/environment version.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkplatformtostring"></a>`Benchmark.platform.toString()`
+<a href="#benchmarkplatformtostring">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3256 "View in source") [&#x24C9;][1]
+
+Return platform description when the platform object is coerced to a string.
+
+#### Returns
+*(String)*: The platform description.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.support`
+
+<!-- div -->
+
+### <a id="benchmarksupport"></a>`Benchmark.support`
+<a href="#benchmarksupport">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L135 "View in source") [&#x24C9;][1]
+
+*(Object)*: An object used to flag environments/features.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportair"></a>`Benchmark.support.air`
+<a href="#benchmarksupportair">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L145 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect Adobe AIR.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportargumentsclass"></a>`Benchmark.support.argumentsClass`
+<a href="#benchmarksupportargumentsclass">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L153 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if `arguments` objects have the correct internal [[Class]] value.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportbrowser"></a>`Benchmark.support.browser`
+<a href="#benchmarksupportbrowser">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L161 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if in a browser environment.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportcharbyindex"></a>`Benchmark.support.charByIndex`
+<a href="#benchmarksupportcharbyindex">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L169 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if strings support accessing characters by index.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportcharbyownindex"></a>`Benchmark.support.charByOwnIndex`
+<a href="#benchmarksupportcharbyownindex">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L179 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if strings have indexes as own properties.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportdecompilation"></a>`Benchmark.support.decompilation`
+<a href="#benchmarksupportdecompilation">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L207 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if functions support decompilation.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportdescriptors"></a>`Benchmark.support.descriptors`
+<a href="#benchmarksupportdescriptors">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L228 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect ES5+ property descriptor API.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportgetallkeys"></a>`Benchmark.support.getAllKeys`
+<a href="#benchmarksupportgetallkeys">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L242 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect ES5+ Object.getOwnPropertyNames().
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportiteratesownfirst"></a>`Benchmark.support.iteratesOwnFirst`
+<a href="#benchmarksupportiteratesownfirst">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L255 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if own properties are iterated before inherited properties *(all but IE < `9`)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportjava"></a>`Benchmark.support.java`
+<a href="#benchmarksupportjava">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L190 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if Java is enabled/exposed.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupportnodeclass"></a>`Benchmark.support.nodeClass`
+<a href="#benchmarksupportnodeclass">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L272 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if a node's [[Class]] is resolvable *(all but IE < `9`)* and that the JS engine errors when attempting to coerce an object to a string without a `toString` property value of `typeof` "function".
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksupporttimeout"></a>`Benchmark.support.timeout`
+<a href="#benchmarksupporttimeout">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L198 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: Detect if the Timers API exists.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype.error`
+
+<!-- div -->
+
+### <a id="benchmarkprototypeerror"></a>`Benchmark.prototype.error`
+<a href="#benchmarkprototypeerror">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3362 "View in source") [&#x24C9;][1]
+
+*(Object)*: The error object if the test failed.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype.stats`
+
+<!-- div -->
+
+### <a id="benchmarkprototypestats"></a>`Benchmark.prototype.stats`
+<a href="#benchmarkprototypestats">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3465 "View in source") [&#x24C9;][1]
+
+*(Object)*: An object of stats including mean, margin or error, and standard deviation.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-statsdeviation"></a>`Benchmark.prototype.stats.deviation`
+<a href="#benchmark-statsdeviation">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3497 "View in source") [&#x24C9;][1]
+
+*(Number)*: The sample standard deviation.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-statsmean"></a>`Benchmark.prototype.stats.mean`
+<a href="#benchmark-statsmean">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3505 "View in source") [&#x24C9;][1]
+
+*(Number)*: The sample arithmetic mean.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-statsmoe"></a>`Benchmark.prototype.stats.moe`
+<a href="#benchmark-statsmoe">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3473 "View in source") [&#x24C9;][1]
+
+*(Number)*: The margin of error.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-statsrme"></a>`Benchmark.prototype.stats.rme`
+<a href="#benchmark-statsrme">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3481 "View in source") [&#x24C9;][1]
+
+*(Number)*: The relative margin of error *(expressed as a percentage of the mean)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-statssample"></a>`Benchmark.prototype.stats.sample`
+<a href="#benchmark-statssample">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3513 "View in source") [&#x24C9;][1]
+
+*(Array)*: The array of sampled periods.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-statssem"></a>`Benchmark.prototype.stats.sem`
+<a href="#benchmark-statssem">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3489 "View in source") [&#x24C9;][1]
+
+*(Number)*: The standard error of the mean.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-statsvariance"></a>`Benchmark.prototype.stats.variance`
+<a href="#benchmark-statsvariance">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3521 "View in source") [&#x24C9;][1]
+
+*(Number)*: The sample variance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.prototype.times`
+
+<!-- div -->
+
+### <a id="benchmarkprototypetimes"></a>`Benchmark.prototype.times`
+<a href="#benchmarkprototypetimes">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3530 "View in source") [&#x24C9;][1]
+
+*(Object)*: An object of timing data including cycle, elapsed, period, start, and stop.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-timescycle"></a>`Benchmark.prototype.times.cycle`
+<a href="#benchmark-timescycle">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3538 "View in source") [&#x24C9;][1]
+
+*(Number)*: The time taken to complete the last cycle *(secs)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-timeselapsed"></a>`Benchmark.prototype.times.elapsed`
+<a href="#benchmark-timeselapsed">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3546 "View in source") [&#x24C9;][1]
+
+*(Number)*: The time taken to complete the benchmark *(secs)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-timesperiod"></a>`Benchmark.prototype.times.period`
+<a href="#benchmark-timesperiod">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3554 "View in source") [&#x24C9;][1]
+
+*(Number)*: The time taken to execute the test once *(secs)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmark-timestimestamp"></a>`Benchmark.prototype.times.timeStamp`
+<a href="#benchmark-timestimestamp">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3562 "View in source") [&#x24C9;][1]
+
+*(Number)*: A timestamp of when the benchmark started *(ms)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Deferred`
+
+<!-- div -->
+
+### <a id="benchmarkdeferredclone"></a>`Benchmark.Deferred(clone)`
+<a href="#benchmarkdeferredclone">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L445 "View in source") [&#x24C9;][1]
+
+The Deferred constructor.
+
+#### Arguments
+1. `clone` *(Object)*: The cloned benchmark instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Deferred.prototype`
+
+<!-- div -->
+
+### <a id="benchmarkdeferredprototypebenchmark"></a>`Benchmark.Deferred.prototype.benchmark`
+<a href="#benchmarkdeferredprototypebenchmark">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3606 "View in source") [&#x24C9;][1]
+
+*(Object)*: The deferred benchmark instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkdeferredprototypecycles"></a>`Benchmark.Deferred.prototype.cycles`
+<a href="#benchmarkdeferredprototypecycles">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3614 "View in source") [&#x24C9;][1]
+
+*(Number)*: The number of deferred cycles performed while benchmarking.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkdeferredprototypeelapsed"></a>`Benchmark.Deferred.prototype.elapsed`
+<a href="#benchmarkdeferredprototypeelapsed">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3622 "View in source") [&#x24C9;][1]
+
+*(Number)*: The time taken to complete the deferred benchmark *(secs)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkdeferredprototyperesolve"></a>`Benchmark.Deferred.prototype.resolve`
+<a href="#benchmarkdeferredprototyperesolve">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1188 "View in source") [&#x24C9;][1]
+
+*(Unknown)*: Handles cycling/completing the deferred benchmark.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkdeferredprototypetimestamp"></a>`Benchmark.Deferred.prototype.timeStamp`
+<a href="#benchmarkdeferredprototypetimestamp">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3630 "View in source") [&#x24C9;][1]
+
+*(Number)*: A timestamp of when the deferred benchmark started *(ms)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event`
+
+<!-- div -->
+
+### <a id="benchmarkeventtype"></a>`Benchmark.Event(type)`
+<a href="#benchmarkeventtype">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L461 "View in source") [&#x24C9;][1]
+
+The Event constructor.
+
+#### Arguments
+1. `type` *(String|Object)*: The event type.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event.prototype`
+
+<!-- div -->
+
+### <a id="benchmarkeventprototypeaborted"></a>`Benchmark.Event.prototype.aborted`
+<a href="#benchmarkeventprototypeaborted">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3646 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate if the emitters listener iteration is aborted.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkeventprototypecancelled"></a>`Benchmark.Event.prototype.cancelled`
+<a href="#benchmarkeventprototypecancelled">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3654 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate if the default action is cancelled.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkeventprototyperesult"></a>`Benchmark.Event.prototype.result`
+<a href="#benchmarkeventprototyperesult">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3670 "View in source") [&#x24C9;][1]
+
+*(Mixed)*: The return value of the last executed listener.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkeventprototypetimestamp"></a>`Benchmark.Event.prototype.timeStamp`
+<a href="#benchmarkeventprototypetimestamp">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3686 "View in source") [&#x24C9;][1]
+
+*(Number)*: A timestamp of when the event was created *(ms)*.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkeventprototypetype"></a>`Benchmark.Event.prototype.type`
+<a href="#benchmarkeventprototypetype">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3694 "View in source") [&#x24C9;][1]
+
+*(String)*: The event type.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event.prototype.currentTarget`
+
+<!-- div -->
+
+### <a id="benchmarkeventprototypecurrenttarget"></a>`Benchmark.Event.prototype.currentTarget`
+<a href="#benchmarkeventprototypecurrenttarget">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3662 "View in source") [&#x24C9;][1]
+
+*(Object)*: The object whose listeners are currently being processed.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Event.prototype.target`
+
+<!-- div -->
+
+### <a id="benchmarkeventprototypetarget"></a>`Benchmark.Event.prototype.target`
+<a href="#benchmarkeventprototypetarget">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3678 "View in source") [&#x24C9;][1]
+
+*(Object)*: The object to which the event was originally emitted.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Suite`
+
+<!-- div -->
+
+### <a id="benchmarksuitename--options"></a>`Benchmark.Suite(name [, options={}])`
+<a href="#benchmarksuitename--options">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L507 "View in source") [&#x24C9;][1]
+
+The Suite constructor.
+
+#### Arguments
+1. `name` *(String)*: A name to identify the suite.
+2. `[options={}]` *(Object)*: Options object.
+
+#### Example
+```js
+// basic usage (the `new` operator is optional)
+var suite = new Benchmark.Suite;
+
+// or using a name first
+var suite = new Benchmark.Suite('foo');
+
+// or with options
+var suite = new Benchmark.Suite('foo', {
+
+  // called when the suite starts running
+  'onStart': onStart,
+
+  // called between running benchmarks
+  'onCycle': onCycle,
+
+  // called when aborted
+  'onAbort': onAbort,
+
+  // called when a test errors
+  'onError': onError,
+
+  // called when reset
+  'onReset': onReset,
+
+  // called when the suite completes running
+  'onComplete': onComplete
+});
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Suite.prototype`
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeaborted"></a>`Benchmark.Suite.prototype.aborted`
+<a href="#benchmarksuiteprototypeaborted">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3735 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate if the suite is aborted.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypelength"></a>`Benchmark.Suite.prototype.length`
+<a href="#benchmarksuiteprototypelength">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3727 "View in source") [&#x24C9;][1]
+
+*(Number)*: The number of benchmarks in the suite.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototyperunning"></a>`Benchmark.Suite.prototype.running`
+<a href="#benchmarksuiteprototyperunning">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3743 "View in source") [&#x24C9;][1]
+
+*(Boolean)*: A flag to indicate if the suite is running.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeabort"></a>`Benchmark.Suite.prototype.abort()`
+<a href="#benchmarksuiteprototypeabort">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1902 "View in source") [&#x24C9;][1]
+
+Aborts all benchmarks in the suite.
+
+#### Returns
+*(Object)*: The suite instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeaddname-fn--options"></a>`Benchmark.Suite.prototype.add(name, fn [, options={}])`
+<a href="#benchmarksuiteprototypeaddname-fn--options">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1962 "View in source") [&#x24C9;][1]
+
+Adds a test to the benchmark suite.
+
+#### Arguments
+1. `name` *(String)*: A name to identify the benchmark.
+2. `fn` *(Function|String)*: The test to benchmark.
+3. `[options={}]` *(Object)*: Options object.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+#### Example
+```js
+// basic usage
+suite.add(fn);
+
+// or using a name first
+suite.add('foo', fn);
+
+// or with options
+suite.add('foo', fn, {
+  'onCycle': onCycle,
+  'onComplete': onComplete
+});
+
+// or name and options
+suite.add('foo', {
+  'fn': fn,
+  'onCycle': onCycle,
+  'onComplete': onComplete
+});
+
+// or options only
+suite.add({
+  'name': 'foo',
+  'fn': fn,
+  'onCycle': onCycle,
+  'onComplete': onComplete
+});
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypecloneoptions"></a>`Benchmark.Suite.prototype.clone(options)`
+<a href="#benchmarksuiteprototypecloneoptions">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L1981 "View in source") [&#x24C9;][1]
+
+Creates a new suite with cloned benchmarks.
+
+#### Arguments
+1. `options` *(Object)*: Options object to overwrite cloned options.
+
+#### Returns
+*(Object)*: The new suite instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeemittype"></a>`Benchmark.Suite.prototype.emit(type)`
+<a href="#benchmarkprototypeemittype">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2095 "View in source") [&#x24C9;][1]
+
+Executes all registered listeners of the specified event type.
+
+#### Arguments
+1. `type` *(String|Object)*: The event type or object.
+
+#### Returns
+*(Mixed)*: Returns the return value of the last listener executed.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypefiltercallback"></a>`Benchmark.Suite.prototype.filter(callback)`
+<a href="#benchmarksuiteprototypefiltercallback">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2004 "View in source") [&#x24C9;][1]
+
+An `Array#filter` like method.
+
+#### Arguments
+1. `callback` *(Function|String)*: The function/alias called per iteration.
+
+#### Returns
+*(Object)*: A new suite of benchmarks that passed callback filter.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeforeachcallback"></a>`Benchmark.Suite.prototype.forEach(callback)`
+<a href="#benchmarksuiteprototypeforeachcallback">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3753 "View in source") [&#x24C9;][1]
+
+An `Array#forEach` like method. Callbacks may terminate the loop by explicitly returning `false`.
+
+#### Arguments
+1. `callback` *(Function)*: The function called per iteration.
+
+#### Returns
+*(Object)*: The suite iterated over.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeindexofvalue"></a>`Benchmark.Suite.prototype.indexOf(value)`
+<a href="#benchmarksuiteprototypeindexofvalue">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3762 "View in source") [&#x24C9;][1]
+
+An `Array#indexOf` like method.
+
+#### Arguments
+1. `value` *(Mixed)*: The value to search for.
+
+#### Returns
+*(Number)*: The index of the matched value or `-1`.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeinvokename--arg1-arg2-"></a>`Benchmark.Suite.prototype.invoke(name [, arg1, arg2, ...])`
+<a href="#benchmarksuiteprototypeinvokename--arg1-arg2-">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3772 "View in source") [&#x24C9;][1]
+
+Invokes a method on all benchmarks in the suite.
+
+#### Arguments
+1. `name` *(String|Object)*: The name of the method to invoke OR options object.
+2. `[arg1, arg2, ...]` *(Mixed)*: Arguments to invoke the method with.
+
+#### Returns
+*(Array)*: A new array of values returned from each method invoked.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypejoinseparator-"></a>`Benchmark.Suite.prototype.join([separator=','])`
+<a href="#benchmarksuiteprototypejoinseparator-">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3781 "View in source") [&#x24C9;][1]
+
+Converts the suite of benchmarks to a string.
+
+#### Arguments
+1. `[separator=',']` *(String)*: A string to separate each element of the array.
+
+#### Returns
+*(String)*: The string.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypelistenerstype"></a>`Benchmark.Suite.prototype.listeners(type)`
+<a href="#benchmarkprototypelistenerstype">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2125 "View in source") [&#x24C9;][1]
+
+Returns an array of event listeners for a given type that can be manipulated to add or remove listeners.
+
+#### Arguments
+1. `type` *(String)*: The event type.
+
+#### Returns
+*(Array)*: The listeners array.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypemapcallback"></a>`Benchmark.Suite.prototype.map(callback)`
+<a href="#benchmarksuiteprototypemapcallback">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3790 "View in source") [&#x24C9;][1]
+
+An `Array#map` like method.
+
+#### Arguments
+1. `callback` *(Function)*: The function called per iteration.
+
+#### Returns
+*(Array)*: A new array of values returned by the callback.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeofftype-listener"></a>`Benchmark.Suite.prototype.off([type, listener])`
+<a href="#benchmarkprototypeofftype-listener">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2158 "View in source") [&#x24C9;][1]
+
+Unregisters a listener for the specified event type(s), or unregisters all listeners for the specified event type(s), or unregisters all listeners for all event types.
+
+#### Arguments
+1. `[type]` *(String)*: The event type.
+2. `[listener]` *(Function)*: The function to unregister.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+#### Example
+```js
+// unregister a listener for an event type
+bench.off('cycle', listener);
+
+// unregister a listener for multiple event types
+bench.off('start cycle', listener);
+
+// unregister all listeners for an event type
+bench.off('cycle');
+
+// unregister all listeners for multiple event types
+bench.off('start cycle complete');
+
+// unregister all listeners for all event types
+bench.off();
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarkprototypeontype-listener"></a>`Benchmark.Suite.prototype.on(type, listener)`
+<a href="#benchmarkprototypeontype-listener">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2197 "View in source") [&#x24C9;][1]
+
+Registers a listener for the specified event type(s).
+
+#### Arguments
+1. `type` *(String)*: The event type.
+2. `listener` *(Function)*: The function to register.
+
+#### Returns
+*(Object)*: The benchmark instance.
+
+#### Example
+```js
+// register a listener for an event type
+bench.on('cycle', listener);
+
+// register a listener for multiple event types
+bench.on('start cycle', listener);
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypepluckproperty"></a>`Benchmark.Suite.prototype.pluck(property)`
+<a href="#benchmarksuiteprototypepluckproperty">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3799 "View in source") [&#x24C9;][1]
+
+Retrieves the value of a specified property from all benchmarks in the suite.
+
+#### Arguments
+1. `property` *(String)*: The property to pluck.
+
+#### Returns
+*(Array)*: A new array of property values.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypepop"></a>`Benchmark.Suite.prototype.pop()`
+<a href="#benchmarksuiteprototypepop">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3807 "View in source") [&#x24C9;][1]
+
+Removes the last benchmark from the suite and returns it.
+
+#### Returns
+*(Mixed)*: The removed benchmark.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypepush"></a>`Benchmark.Suite.prototype.push()`
+<a href="#benchmarksuiteprototypepush">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3815 "View in source") [&#x24C9;][1]
+
+Appends benchmarks to the suite.
+
+#### Returns
+*(Number)*: The suite's new length.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypereducecallback-accumulator"></a>`Benchmark.Suite.prototype.reduce(callback, accumulator)`
+<a href="#benchmarksuiteprototypereducecallback-accumulator">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3834 "View in source") [&#x24C9;][1]
+
+An `Array#reduce` like method.
+
+#### Arguments
+1. `callback` *(Function)*: The function called per iteration.
+2. `accumulator` *(Mixed)*: Initial value of the accumulator.
+
+#### Returns
+*(Mixed)*: The accumulator.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypereset"></a>`Benchmark.Suite.prototype.reset()`
+<a href="#benchmarksuiteprototypereset">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2019 "View in source") [&#x24C9;][1]
+
+Resets all benchmarks in the suite.
+
+#### Returns
+*(Object)*: The suite instance.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypereverse"></a>`Benchmark.Suite.prototype.reverse()`
+<a href="#benchmarksuiteprototypereverse">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L638 "View in source") [&#x24C9;][1]
+
+Rearrange the host array's elements in reverse order.
+
+#### Returns
+*(Array)*: The reversed array.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototyperunoptions"></a>`Benchmark.Suite.prototype.run([options={}])`
+<a href="#benchmarksuiteprototyperunoptions">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L2056 "View in source") [&#x24C9;][1]
+
+Runs the suite.
+
+#### Arguments
+1. `[options={}]` *(Object)*: Options object.
+
+#### Returns
+*(Object)*: The suite instance.
+
+#### Example
+```js
+// basic usage
+suite.run();
+
+// or with options
+suite.run({ 'async': true, 'queued': true });
+```
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeshift"></a>`Benchmark.Suite.prototype.shift()`
+<a href="#benchmarksuiteprototypeshift">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L671 "View in source") [&#x24C9;][1]
+
+Removes the first element of the host array and returns it.
+
+#### Returns
+*(Mixed)*: The first element of the array.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeslicestart-end"></a>`Benchmark.Suite.prototype.slice(start, end)`
+<a href="#benchmarksuiteprototypeslicestart-end">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L684 "View in source") [&#x24C9;][1]
+
+Creates an array of the host array's elements from the start index up to, but not including, the end index.
+
+#### Arguments
+1. `start` *(Number)*: The starting index.
+2. `end` *(Number)*: The end index.
+
+#### Returns
+*(Array)*: The new array.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypesortcomparefnnull"></a>`Benchmark.Suite.prototype.sort([compareFn=null])`
+<a href="#benchmarksuiteprototypesortcomparefnnull">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3824 "View in source") [&#x24C9;][1]
+
+Sorts the benchmarks of the suite.
+
+#### Arguments
+1. `[compareFn=null]` *(Function)*: A function that defines the sort order.
+
+#### Returns
+*(Object)*: The sorted suite.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypesplicestart-deletecount--val1-val2-"></a>`Benchmark.Suite.prototype.splice(start, deleteCount [, val1, val2, ...])`
+<a href="#benchmarksuiteprototypesplicestart-deletecount--val1-val2-">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L714 "View in source") [&#x24C9;][1]
+
+Allows removing a range of elements and/or inserting elements into the host array.
+
+#### Arguments
+1. `start` *(Number)*: The start index.
+2. `deleteCount` *(Number)*: The number of elements to delete.
+3. `[val1, val2, ...]` *(Mixed)*: values to insert at the `start` index.
+
+#### Returns
+*(Array)*: An array of removed elements.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteprototypeunshift"></a>`Benchmark.Suite.prototype.unshift()`
+<a href="#benchmarksuiteprototypeunshift">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L749 "View in source") [&#x24C9;][1]
+
+Appends arguments to the host array.
+
+#### Returns
+*(Number)*: The new length.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- div -->
+
+## `Benchmark.Suite.options`
+
+<!-- div -->
+
+### <a id="benchmarksuiteoptions"></a>`Benchmark.Suite.options`
+<a href="#benchmarksuiteoptions">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3706 "View in source") [&#x24C9;][1]
+
+*(Object)*: The default options copied by suite instances.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- div -->
+
+### <a id="benchmarksuiteoptionsname"></a>`Benchmark.Suite.options.name`
+<a href="#benchmarksuiteoptionsname">#</a> [&#x24C8;](https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js#L3714 "View in source") [&#x24C9;][1]
+
+*(String)*: The name of the suite.
+
+* * *
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+<!-- /div -->
+
+
+  [1]: #Benchmark "Jump back to the TOC."
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/doc/parse.php b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/doc/parse.php
new file mode 100644
index 0000000..565b78f
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/doc/parse.php
@@ -0,0 +1,35 @@
+<?php
+
+  // cleanup requested filepath
+  $file = isset($_GET['f']) ? $_GET['f'] : 'benchmark';
+  $file = preg_replace('#(\.*[\/])+#', '', $file);
+  $file .= preg_match('/\.[a-z]+$/', $file) ? '' : '.js';
+
+  // output filename
+  if (isset($_GET['o'])) {
+    $output = $_GET['o'];
+  } else if (isset($_SERVER['argv'][1])) {
+    $output = $_SERVER['argv'][1];
+  } else {
+    $output = basename($file);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  require('../vendor/docdown/docdown.php');
+
+  // generate Markdown
+  $markdown = docdown(array(
+    'path' => '../' . $file,
+    'title' => 'Benchmark.js <sup>v1.0.0</sup>',
+    'url'  => 'https://github.com/bestiejs/benchmark.js/blob/master/benchmark.js'
+  ));
+
+  // save to a .md file
+  file_put_contents($output . '.md', $markdown);
+
+  // print
+  header('Content-Type: text/plain;charset=utf-8');
+  echo $markdown . PHP_EOL;
+
+?>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/index.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/index.html
new file mode 100644
index 0000000..040ddb6
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/index.html
@@ -0,0 +1,287 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<meta name="description" content="This is just a test document for Benchmark.js.">
+		<title>Benchmark.js test page | jsPerf</title>
+		<!-- http://jsperf.com/benchmark-js-test-page -->
+		<link rel="stylesheet" href="main.css">
+		<script>document.documentElement.className = 'js'</script>
+		<!--[if lt IE 9]><script src="https://raw.github.com/bestiejs/html5.js/master/html5.js"></script><![endif]-->
+	</head>
+	<body>
+		<article>
+			<hgroup>
+				<h1>Benchmark.js test page</h1>
+				<h2>JavaScript performance comparison</h2>
+			</hgroup>
+
+			<p class="meta">
+				Test case created by <a href="http://mathiasbynens.be/">Mathias</a>
+				<time datetime="2010-08-02T18:45:23+02:00" pubdate>40 seconds ago</time>
+				and last updated <time datetime="2010-08-02T18:45:51+02:00">12 seconds ago</time>
+			</p>
+
+			<section>
+				<h2>Info</h2>
+				<p>
+					This is just a test document for Benchmark.js.
+				</p>
+			</section>
+
+			<section id="prep-code">
+				<h2>Preparation code</h2>
+				<pre><code><span
+					class="sc2">&lt;<span class="kw2">div</span>&gt;</span>Lorem ipsum<span
+					class="sc2">&lt;<span class="sy0">/</span><span class="kw2">div</span>&gt;</span><br><span
+					class="sc2">&lt;<span class="kw2">script</span>&gt;</span><br>&nbsp; <span
+					class="kw2">var</span> arr <span class="sy0">=</span> <span class="br0">&#91;</span><span
+					class="nu0">1</span><span class="sy0">,</span> <span class="nu0">5</span><span
+					class="sy0">,</span> <span class="nu0">4</span><span class="sy0">,</span> <span
+					class="nu0">2</span><span class="sy0">,</span> <span class="nu0">3</span><span
+					class="br0">&#93;</span><span class="sy0">;</span><br><br>&nbsp; <span
+					class="kw2">function</span> init<span class="br0">&#40;</span><span
+					class="br0">&#41;</span> <span class="br0">&#123;</span><br>&nbsp;&nbsp;&nbsp; window.<span
+					class="me1">console</span> <span class="sy0">&amp;&amp;</span> console.<span
+					class="me1">log</span><span class="br0">&#40;</span><span class="st0">'init called'</span><span
+					class="br0">&#41;</span><span class="sy0">;</span><br>&nbsp; <span
+					class="br0">&#125;</span><br><span class="sc2">&lt;<span class="sy0">/</span><span
+					class="kw2">script</span>&gt;</span><br><span class="sc2">&lt;<span
+					class="kw2">script</span>></span><br>Benchmark.<span class="me1">prototype</span>.<span
+					class="me1">setup</span> <span class="sy0">=</span> <span class="kw2">function</span><span
+					class="br0">(</span><span class="br0">)</span> <span class="br0">{</span><br>&nbsp; window.<span
+					class="me1">foo</span> <span class="sy0">=</span> <span class="nu0">42</span><span
+					class="sy0">;</span><br>&nbsp; <span class="kw2">var</span> x <span
+					class="sy0">=</span> arr<span class="sy0">;</span><br><span class="br0">}</span><span
+					class="sy0">;</span><br><br>Benchmark.<span class="me1">prototype</span>.<span
+					class="me1">teardown</span> <span class="sy0">=</span> <span class="kw2">function</span><span
+					class="br0">(</span><span class="br0">) </span><span class="br0">{</span><br>&nbsp; window.<span
+					class="me1">foo</span> <span class="sy0">=</span> <span class="nu0">24</span><span
+					class="sy0">;</span><br><span class="br0">}</span><span class="sy0">;</span><br><span
+					class="sc2">&lt;<span class="sy0">/</span><span class="kw2">script</span>></span></code></pre>
+			</section>
+
+			<section>
+				<h2>Preparation code output</h2>
+				<div class="user-output">
+					<div>Lorem ipsum</div>
+				</div>
+			</section>
+
+			<section id="runner">
+				<h2>Test runner</h2>
+				<p id="firebug">
+					<strong>Warning! For accurate results, please disable Firebug before running the tests. <a href="http://jsperf.com/faq#firebug-warning">(Why?)</a></strong>
+				</p>
+				<p id="java">
+					<strong>Java applet disabled.</strong>
+				</p>
+				<p id="status">
+					<noscript>
+						<strong>To run the tests, please <a href="http://enable-javascript.com/">enable JavaScript</a> and reload the page.</strong>
+					</noscript>
+				</p>
+				<div id="controls">
+					<button id="run" type="button"></button>
+				</div>
+				<table id="test-table">
+					<caption>Testing in <span id="user-agent"></span></caption>
+					<thead>
+					<tr>
+						<th colspan="2">Test</th>
+						<th title="Operations per second (higher is better)">Ops/sec</th>
+					</tr>
+					</thead>
+					<tbody>
+					<tr>
+						<th scope="row" id="title-1">
+							<div>Normal</div>
+						</th>
+						<td class="code">
+							<pre><code>x.<span class="me1">sort</span><span class="br0">&#40;</span><span
+								class="kw2">function</span><span class="br0">&#40;</span>a<span
+								class="sy0">,</span> b<span class="br0">&#41;</span> <span
+								class="br0">&#123;</span><br>&nbsp; <span class="kw1">return</span> a <span
+								class="sy0">-</span> b<span class="sy0">;</span><br><span
+								class="br0">&#125;</span><span class="br0">&#41;</span><span
+								class="sy0">;</span></code></pre>
+						</td>
+						<td id="results-1" class="results"></td>
+					</tr>
+					<tr>
+						<th scope="row" id="title-2">
+							<div>Exit Early</div>
+						</th>
+						<td class="code">
+							<pre><code>x.<span class="me1">sort</span><span class="br0">&#40;</span><span
+								class="kw2">function</span><span class="br0">&#40;</span>a<span
+								class="sy0">,</span> b<span class="br0">&#41;</span> <span
+								class="br0">&#123;</span><br>&nbsp; <span class="kw1">return</span> a <span
+								class="sy0">-</span> b<span class="sy0">;</span><br><span
+								class="br0">&#125;</span><span class="br0">&#41;</span><span
+								class="sy0">;</span><br><span class="kw1">return</span><span
+								class="sy0">;</span></code></pre>
+						</td>
+						<td id="results-2" class="results"></td>
+					</tr>
+					<tr>
+						<th scope="row" id="title-3">
+							<div>Async</div>
+						</th>
+						<td class="code">
+							<pre><code><strong class="co1">// async test</strong><br>setTimeout<span
+								class="br0">&#40;</span><span class="kw2">function</span><span
+								class="br0">&#40;</span><span class="br0">&#41;</span> <span
+								class="br0">&#123;</span><br>&nbsp; deferred.<span
+								class="me1">resolve</span><span class="br0">&#40;</span><span
+								class="br0">&#41;</span><span class="sy0">;</span><br><span
+								class="br0">&#125;</span><span class="sy0">,</span> <span
+								class="nu0">10</span><span class="br0">&#41;</span><span
+								class="sy0">;</span></code></pre>
+						</td>
+						<td id="results-3" class="results"></td>
+					</tr>
+					<tr>
+						<th scope="row" id="title-4">
+							<div>Error</div>
+						</th>
+						<td class="code">
+							<pre><code>x.<span class="me1">foo</span><span class="br0">&#40;</span><span
+								class="br0">&#41;</span><span class="sy0">;</span> <span
+								class="co1">// unknown method</span></code></pre>
+						</td>
+						<td id="results-4" class="results"></td>
+					</tr>
+					<tr>
+						<th scope="row" id="title-5">
+							<div>Comments</div>
+						</th>
+						<td class="code">
+							<pre><code><span class="co1">// comments at start</span><br>x.<span
+								class="me1">reverse</span><span class="br0">&#40;</span><span
+								class="br0">&#41;</span>.<span class="me1">sort</span><span
+								class="br0">&#40;</span><span class="kw2">function</span><span
+								class="br0">&#40;</span>a<span class="sy0">,</span> b<span
+								class="br0">&#41;</span> <span class="br0">&#123;</span><br>&nbsp; <span
+								class="kw1">return</span> a <span class="sy0">-</span> b<span
+								class="sy0">;</span><br><span class="br0">&#125;</span><span
+								class="br0">&#41;</span><span class="sy0">;</span><br><span
+								class="co1">// comments at end</span></code></pre>
+						</td>
+						<td id="results-5" class="results"></td>
+					</tr>
+					</tbody>
+				</table>
+
+				<p>
+					You can <a href="#" rel="nofollow">edit these tests or add even more tests to this page</a>
+					by appending <code>/edit</code> to the URL.
+				</p>
+			</section>
+
+			<section>
+				<h2 id="results">Compare results of other browsers</h2>
+				<div id="bs-results"></div>
+			</section>
+
+			<section id="comments">
+				<h1>0 comments</h1>
+				<div id="comments-wrapper">
+					<form action="#comment-form" method="post" id="comment-form">
+						<fieldset>
+							<h2>Add a comment</h2>
+							<div>
+								<label for="author">Name <em title="This field is required">*</em></label>
+								<input type="text" name="author" id="author" required>
+							</div>
+							<div>
+								<label for="author-email">Email <em title="This field is required">*</em></label>
+								<label class="inline">
+									<input type="email" name="author-email" id="author-email" required> (only used for Gravatar)
+								</label>
+							</div>
+							<div>
+								<label for="author-url">URL </label>
+								<input type="url" name="author-url" id="author-url">
+							</div>
+							<div>
+								<label for="message">Message <em title="This field is required">*</em><span>Markdown syntax is allowed</span></label>
+								<textarea name="message" id="message" required></textarea>
+							</div>
+							<div class="question">
+								<label for="question">Are you a spammer? <span>(just answer the question)</span></label>
+								<input type="text" name="question" id="question">
+							</div>
+							<div class="buttons">
+								<input type="submit" class="submit" value="Add comment">
+							</div>
+						</fieldset>
+					</form>
+				</div>
+			</section>
+		</article>
+
+		<footer>
+			&copy; 2011 <a href="http://jsperf.com/">jsPerf.com</a>
+			&middot; <a href="http://jsperf.com/browse">Browse</a>
+			&middot; <a href="http://jsperf.com/popular">Popular tests</a>
+			&middot; <a href="http://jsperf.com/faq">FAQ</a>
+			&middot; <a href="http://jsperf.com/faq#donate">Donate</a>
+			&middot; <a href="http://twitter.com/jsprf" rel="nofollow">twitter: @jsprf</a>
+			&middot; <a href="http://benchmarkjs.com/">Benchmark.js</a>
+			&middot; by <a href="http://mathiasbynens.be/" title="Mathias Bynens, front-end web developer">@mathias</a>
+		</footer>
+
+		<script src="../../vendor/platform.js/platform.js"></script>
+		<script src="../../benchmark.js"></script>
+		<script src="ui.js"></script>
+		<script src="../../plugin/ui.browserscope.js"></script>
+		<script>
+			var arr = [1, 5, 4, 2, 3];
+
+			function init() {
+				window.console && console.log('init called');
+			}
+		</script>
+		<script>
+			ui.browserscope.key = 'agt1YS1wcm9maWxlcnINCxIEVGVzdBjR6NELDA';
+			ui.browserscope.selector = '#bs-results';
+
+			ui.add('Normal', '\
+				x.sort(function(a, b) {\n\
+					return a - b;\n\
+				});'
+			)
+			.add('Exit Early', '\
+				x.sort(function(a, b) {\n\
+					return a - b;\n\
+				});\n\
+				return;'
+			)
+			.add('Async', {
+				'defer': true,
+				'fn': '\
+					setTimeout(function() {\n\
+						deferred.resolve();\n\
+					}, 10);'
+			})
+			.add('Error', '\
+				x.foo(); // unknown method'
+			)
+			.add('Comments', '\
+				// comments at start\n\
+				x.reverse().sort(function(a, b) {\n\
+					return a - b;\n\
+				});\n\
+				// comments at end'
+			);
+
+			Benchmark.prototype.setup = '\
+				window.foo = 42;\n\
+				var x = arr;';
+
+			Benchmark.prototype.teardown = '\
+				window.foo = 24;';
+		</script>
+	</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/main.css b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/main.css
new file mode 100644
index 0000000..52d42ef
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/main.css
@@ -0,0 +1,588 @@
+html, body, h1, h2, h3, fieldset, #faq, #faq dt, #faq dd {
+  margin: 0;
+  padding: 0;
+  border: 0;
+}
+
+table, p, ul, h1, h2, h3, #error-info, form div, #faq, .bs-rt {
+  margin-bottom: 1em;
+}
+
+button, input, textarea, a, .bs-rt {
+  -moz-border-radius: 4px;
+  -webkit-border-radius: 4px;
+  border-radius: 4px;
+}
+
+html, input, textarea, button {
+  font: 1em/1.5 sans-serif;
+}
+
+html {
+  background: #c4c4c4;
+  height: 100%;
+}
+
+body {
+  background: #fff;
+  border: solid #aaa;
+  border-width: 0 1px;
+  width: 60em;
+  padding: 0 2.5em;
+  margin: 0 auto;
+  min-height: 100%;
+}
+
+a {
+  color: #357ab0;
+  padding: .2em;
+}
+
+a:hover, a:focus {
+  text-decoration: none;
+}
+
+blockquote {
+  margin: 0 0 1em;
+  border-left: 5px solid #b4b4b4;
+  padding-left: .5em;
+}
+
+table {
+  width: 100%;
+  border-collapse: collapse;
+}
+
+thead th, button:hover, button:focus, .submit:hover, .submit:focus, a:hover, a:focus, #comments .meta a:hover, #comments .meta a:focus, li.current a:hover, li.current a:focus, form h3, #comments .owner .meta {
+  background: #1a6ab9;
+  background-image: -moz-linear-gradient(top, #6ca5dd, #1a6ab9);
+  background-image: -o-linear-gradient(top, #6ca5dd, #1a6ab9);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#6ca5dd), to(#1a6ab9));
+  background-image: -webkit-linear-gradient(#6ca5dd, #1a6ab9);
+  background-image: linear-gradient(top, #6ca5dd, #1a6ab9);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#6ca5dd', EndColorStr='#1a6ab9');
+  color: #fff;
+}
+
+caption, #comments .meta {
+  background: #bcbcbc;
+  background-image: -moz-linear-gradient(top, #d0d0d0, #a7a7a7);
+  background-image: -o-linear-gradient(top, #d0d0d0, #a7a7a7);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#d0d0d0), to(#a7a7a7));
+  background-image: -webkit-linear-gradient(#d0d0d0, #a7a7a7);
+  background-image: linear-gradient(top, #d0d0d0, #a7a7a7);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#d0d0d0', EndColorStr='#a7a7a7');
+  color: #555;
+}
+
+thead th, caption {
+  font-weight: bold;
+}
+
+.js tbody th:hover, .js tbody th:focus {
+  text-decoration: underline;
+  cursor: pointer;
+}
+
+tbody th, td {
+  border: solid #b4b4b4;
+  border-width: 0 1px 1px 0;
+}
+
+tbody th {
+  background: #dde4ea;
+  min-width: 100px;
+}
+
+tbody th div {
+  max-width: 200px;
+  word-wrap: break-word;
+  overflow: auto;
+}
+
+td.results {
+  text-align: center;
+  border-right: 0;
+}
+
+.results span, small {
+  display: block;
+  font-size: .8em;
+}
+
+td, th, caption {
+  padding: .2em .5em;
+}
+
+td.fastest {
+  background: #9cee82;
+}
+
+tr:last-child td, tr:last-child th {
+  border-bottom: 0;
+}
+
+td.slowest, td.error, .invalid {
+  background: pink;
+}
+
+/* needs its own rule because of buggy IE */
+:focus:invalid {
+  background: pink;
+}
+
+td.error {
+  text-transform: uppercase;
+  font-weight: bold;
+}
+
+button, .submit {
+  padding: .35em .5em;
+  cursor: pointer;
+  color: #000;
+  border: 1px solid #999;
+  background: #dadada;
+  background-image: -moz-linear-gradient(top, #ebebeb, #b8b8b8);
+  background-image: -o-linear-gradient(top, #ebebeb, #b8b8b8);
+  background-image: -webkit-gradient(linear, left top, left bottom, from(#ebebeb), to(#b8b8b8));
+  background-image: -webkit-linear-gradient(top, #ebebeb, #b8b8b8);
+  background-image: linear-gradient(top, #ebebeb, #b8b8b8);
+  filter: progid:DXImageTransform.Microsoft.gradient(startColorStr='#ebebeb', EndColorStr='#b8b8b8');
+}
+
+a:hover span, a:focus span, #comments .owner .meta a {
+  color: #fff;
+}
+
+#controls, #run, .buttons {
+  float: right;
+}
+
+button:hover, button:focus, .submit:hover, .submit:focus {
+  border-color: #357ab0
+}
+
+#add-buttons button {
+  padding: .15em .4em;
+  font-size: 11px;
+  font-weight: bold;
+}
+
+form label {
+  float: left;
+  width: 14em;
+  cursor: pointer;
+  text-align: right;
+  margin-right: 1em;
+  padding: .4em;
+}
+
+label.inline {
+  float: none;
+  padding: 0;
+  margin: 0;
+}
+
+label[for="visible"], label[for$="][defer]"] {
+  position: relative;
+  top: -.37em;
+}
+
+label span {
+  display: block;
+  font-size: 90%;
+  color: #b4b4b4;
+}
+
+label em {
+  color: red;
+  font-style: normal;
+}
+
+.js #advanced {
+  display: none;
+}
+
+#show-advanced {
+  display: none;
+}
+
+.js #show-advanced {
+  display: block;
+}
+
+section {
+  display: block;
+  border-top: 1px solid #ccc;
+  padding-top: 2em;
+  margin: 2em 0;
+}
+
+textarea {
+  resize: vertical;
+  height: 15em;
+  width: 42.6em;
+  *width: 42.4em; /* IE < 8 */
+}
+
+input, textarea {
+  border: 1px solid #b4b4b4;
+  padding: .4em;
+}
+
+#visible, #calibrate { /* checkboxes, for IE */
+  border: 0;
+  padding: 0;
+}
+
+form h2, form h3, form h4, p.error, .preview, #add-libraries, #add-buttons {
+  padding-left: 250px;
+  display: block;
+}
+
+.js .question, hgroup h2, #controls, #firebug, #java {
+  display: none;
+}
+
+pre {
+  width: 100%;
+  overflow: auto;
+}
+
+table #results-1 {
+  width: 100px;
+}
+
+table pre {
+  *padding: 1.5em 0; /* IE < 8 */
+  *overflow-y: hidden; /* IE < 8 */
+}
+
+table pre, table td.code {
+  width: 650px;
+}
+
+mark {
+  background: #ff9;
+  padding: .2em .1em;
+}
+
+h1, h2, h3, h4 {
+  font-weight: bold;
+  font-size: 1em;
+}
+
+h1 {
+  padding-top: 1em;
+  font-size: 1.4em;
+}
+
+form h3 {
+  padding-top: .2em;
+  padding-bottom: .2em;
+}
+
+h1 em {
+  font-style: normal;
+}
+
+h1 strong {
+  font-style: italic;
+  font-family: Monaco, 'Lucida Console', monospace;
+}
+
+li.current a {
+  background: #90ee85;
+}
+
+#donate {
+  display: block;
+  background: #ffffdc;
+  border: 1px solid #faa700;
+  padding: 1em;
+}
+
+#donate h1 {
+  padding-top: 0;
+  font-size: 16px;
+}
+
+#paypal {
+  text-align: center;
+}
+
+footer {
+  display: block;
+  margin-top: 2em;
+  padding: .5em 0 1.5em;
+  border-top: 2px solid #c4c4c4;
+}
+
+#add-test {
+  margin-right: .3em;
+}
+
+#bs-chart {
+  overflow: auto;
+}
+
+#bs-logo {
+  margin: 0;
+}
+
+#bs-logo span, applet {
+  position: absolute;
+  left: -9999em;
+}
+
+#bs-logo a {
+  display: block;
+  width: 232px;
+  height: 39px;
+  filter: none;
+  background: url(//www.browserscope.org/static/img/logo.png) 0 0 no-repeat;
+}
+
+#bs-ua {
+  padding: .5em .5em 0;
+  color: #555;
+}
+
+#bs-results .bs-rt {
+  font-size: 10pt;
+  padding: .5em;
+  background: #ddd;
+}
+
+#bs-results td {
+  border: 1px solid #ddd;
+  padding: .4em;
+  white-space: nowrap;
+}
+
+#bs-results .rt-ua-cur {
+  font-style: italic;
+  font-weight: bold;
+}
+
+#bs-results .bs-rt-message {
+  padding: 3em;
+  text-align: center;
+  font-weight: bold;
+  color: #555;
+}
+
+#bs-results .google-visualization-table-tr-head td {
+  white-space: normal;
+}
+
+#controls {
+  margin-top: -3.35em;
+}
+
+#comments h1 {
+  padding: 0;
+}
+
+#comments .meta img {
+  position: absolute;
+  left: 0;
+  top: 0;
+  margin: 0;
+}
+
+#comments .meta img {
+  top: 2px;
+  left: 2px;
+}
+
+#comments .meta {
+  padding-left: 35px;
+  margin-top: 0;
+  width: 923px;
+  line-height: 30px;
+}
+
+#comments .meta a {
+  font-weight: bold;
+  color: #555;
+}
+
+#comments article div {
+  padding: 0 1em 0;
+}
+
+#comments article {
+  display: block;
+  border: 1px solid #b4b4b4;
+  position: relative;
+  margin-bottom: 1em;
+}
+
+/* needs its own rule (cannot be grouped with `tbody th`) because of buggy IE */
+#comments article:target {
+  background: #dde4ea;
+}
+
+#error-info.show, .meta strong, #firebug strong, #java strong, #status strong {
+  background: pink;
+  border: 1px solid #b00b00;
+  padding: .4em;
+}
+
+#error-info.show {
+  padding: .5em 1em;
+}
+
+#error-info, code, samp, var, textarea, #slug {
+  font-family: Monaco, monospace;
+  font-size: .9em;
+}
+
+#java strong {
+  background: #ffffdc;
+  border: 1px solid #faa700;
+}
+
+#slug {
+  font-size: 1em;
+}
+
+#faq dt {
+  margin-top: 1em;
+  font-weight: bold;
+}
+
+#faq dt a {
+  display: none;
+}
+
+#faq dt:hover a {
+  display: inline;
+}
+
+#faq dt:target, #faq dt:target + dd {
+  background: #90ee85;
+  margin: 0 -.8em;
+  padding: 0 .8em;
+}
+
+#faq dt:target + dd {
+  padding-bottom: .5em;
+  margin-bottom: -.5em;
+}
+
+#faq dt:target {
+  margin-top: .5em;
+  padding-top: .5em;
+}
+
+#firebug, #java, #status {
+  margin: 0 0 1em;
+  padding: .3em 0;
+}
+
+#prep-code pre {
+  max-height: 500px;
+  overflow: auto;
+}
+
+#controls.show, #firebug.show, #java.show {
+  display: block;
+}
+
+.co1, .co2, .coMULTI {
+  font-style: italic;
+}
+
+.error {
+  color: #b00b00;
+}
+
+.imp {
+  color: red;
+}
+
+.kw1, .kw3 {
+  color: #006;
+}
+
+.kw2 {
+  color: #036;
+}
+
+.co1, .coMULTI {
+  color: #060;
+}
+
+.co2 {
+  color: #096;
+}
+
+.es0 {
+  color: #009;
+}
+
+.br0 {
+  color: #090;
+}
+
+.sy0 {
+  color: #393;
+}
+
+.st0 {
+  color: #36c;
+}
+
+.nu0 {
+  color: #c00;
+}
+
+.me1 {
+  color: #606;
+}
+
+/* < 1051px */
+@media (max-width: 1050px) {
+  table pre, table td.code {
+    width: 550px;
+  }
+}
+
+/* < 1041px */
+@media (max-width: 1040px) {
+  body {
+    margin: 0;
+    border: 0;
+  }
+
+  body, #comments .meta {
+    width: 100%;
+    -webkit-box-sizing: border-box;
+    -moz-box-sizing: border-box;
+    box-sizing: border-box;
+  }
+
+}
+
+/* < 801px */
+@media (max-width: 800px) {
+  table pre, table td.code {
+    width: 450px;
+  }
+}
+
+/* < 681px */
+@media (max-width: 680px) {
+  table pre, table td.code {
+    width: 350px;
+  }
+}
+
+/* < 651px */
+@media (max-width: 650px) {
+  table pre, table td.code {
+    width: 200px;
+  }
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/ui.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/ui.js
new file mode 100644
index 0000000..8270cd66
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/example/jsperf/ui.js
@@ -0,0 +1,745 @@
+/*!
+ * ui.js
+ * Copyright Mathias Bynens <http://mths.be/>
+ * Modified by John-David Dalton <http://allyoucanleet.com/>
+ * Available under MIT license <http://mths.be/mit>
+ */
+(function(window, document) {
+
+  /** Java applet archive path */
+  var archive = '../../nano.jar';
+
+  /** Cache of error messages */
+  var errors = [];
+
+  /** Google Analytics account id */
+  var gaId = '';
+
+  /** Cache of event handlers */
+  var handlers = {};
+
+  /** A flag to indicate that the page has loaded */
+  var pageLoaded = false;
+
+  /** Benchmark results element id prefix (e.g. `results-1`) */
+  var prefix = 'results-';
+
+  /** The element responsible for scrolling the page (assumes ui.js is just before </body>) */
+  var scrollEl = document.body;
+
+  /** Used to resolve a value's internal [[Class]] */
+  var toString = {}.toString;
+
+  /** Namespace */
+  var ui = new Benchmark.Suite;
+
+  /** Object containing various CSS class names */
+  var classNames = {
+    // used for error styles
+    'error': 'error',
+    // used to make content visible
+    'show': 'show',
+    // used to reset result styles
+    'results': 'results'
+  };
+
+  /** Used to flag environments/features */
+  var has = {
+    // used for pre-populating form fields
+    'localStorage': !!function() {
+      try {
+        return !localStorage.getItem(+new Date);
+      } catch(e) { }
+    }(),
+    // used to distinguish between a regular test page and an embedded chart
+    'runner': !!$('runner')
+  };
+
+  /** Object containing various text messages */
+  var texts = {
+    // inner text for the various run button states
+    'run': {
+      'again': 'Run again',
+      'ready': 'Run tests',
+      'running': 'Stop running'
+    },
+    // common status values
+    'status': {
+      'again': 'Done. Ready to run again.',
+      'ready': 'Ready to run.'
+    }
+  };
+
+  /** The options object for Benchmark.Suite#run */
+  var runOptions = {
+    'async': true,
+    'queued': true
+  };
+
+  /** API shortcuts */
+  var each = Benchmark.each,
+      extend = Benchmark.extend,
+      filter = Benchmark.filter,
+      forOwn = Benchmark.forOwn,
+      formatNumber = Benchmark.formatNumber,
+      indexOf = Benchmark.indexOf,
+      invoke = Benchmark.invoke,
+      join = Benchmark.join;
+
+  /*--------------------------------------------------------------------------*/
+
+  handlers.benchmark = {
+
+    /**
+     * The onCycle callback, used for onStart as well, assigned to new benchmarks.
+     *
+     * @private
+     */
+    'cycle': function() {
+      var bench = this,
+          size = bench.stats.sample.length;
+
+      if (!bench.aborted) {
+        setStatus(bench.name + ' &times; ' + formatNumber(bench.count) + ' (' +
+          size + ' sample' + (size == 1 ? '' : 's') + ')');
+      }
+    },
+
+    /**
+     * The onStart callback assigned to new benchmarks.
+     *
+     * @private
+     */
+    'start': function() {
+        // call user provided init() function
+        if (isFunction(window.init)) {
+          init();
+        }
+    }
+  };
+
+  handlers.button = {
+
+    /**
+     * The "run" button click event handler used to run or abort the benchmarks.
+     *
+     * @private
+     */
+    'run': function() {
+      var stopped = !ui.running;
+      ui.abort();
+      ui.length = 0;
+
+      if (stopped) {
+        logError({ 'clear': true });
+        ui.push.apply(ui, filter(ui.benchmarks, function(bench) {
+          return !bench.error && bench.reset();
+        }));
+        ui.run(runOptions);
+      }
+    }
+  };
+
+  handlers.title = {
+
+    /**
+     * The title table cell click event handler used to run the corresponding benchmark.
+     *
+     * @private
+     * @param {Object} event The event object.
+     */
+    'click': function(event) {
+      event || (event = window.event);
+
+      var id,
+          index,
+          target = event.target || event.srcElement;
+
+      while (target && !(id = target.id)) {
+        target = target.parentNode;
+      }
+      index = id && --id.split('-')[1] || 0;
+      ui.push(ui.benchmarks[index].reset());
+      ui.running ? ui.render(index) : ui.run(runOptions);
+    },
+
+    /**
+     * The title cell keyup event handler used to simulate a mouse click when hitting the ENTER key.
+     *
+     * @private
+     * @param {Object} event The event object.
+     */
+    'keyup': function(event) {
+      if (13 == (event || window.event).keyCode) {
+        handlers.title.click(event);
+      }
+    }
+  };
+
+  handlers.window = {
+
+    /**
+     * The window hashchange event handler supported by Chrome 5+, Firefox 3.6+, and IE8+.
+     *
+     * @private
+     */
+    'hashchange': function() {
+      ui.parseHash();
+
+      var scrollTop,
+          params = ui.params,
+          chart = params.chart,
+          filterBy = params.filterby;
+
+      if (pageLoaded) {
+        // configure posting
+        ui.browserscope.postable = has.runner && !('nopost' in params);
+
+        // configure chart renderer
+        if (chart || filterBy) {
+          scrollTop = $('results').offsetTop;
+          ui.browserscope.render({ 'chart': chart, 'filterBy': filterBy });
+        }
+        if (has.runner) {
+          // call user provided init() function
+          if (isFunction(window.init)) {
+            init();
+          }
+          // auto-run
+          if ('run' in params) {
+            scrollTop = $('runner').offsetTop;
+            setTimeout(handlers.button.run, 1);
+          }
+          // scroll to the relevant section
+          if (scrollTop) {
+            scrollEl.scrollTop = scrollTop;
+          }
+        }
+      }
+    },
+
+    /**
+     * The window load event handler used to initialize the UI.
+     *
+     * @private
+     */
+    'load': function() {
+      // only for pages with a comment form
+      if (has.runner) {
+        // init the ui
+        addClass('controls', classNames.show);
+        addListener('run', 'click', handlers.button.run);
+
+        setHTML('run', texts.run.ready);
+        setHTML('user-agent', Benchmark.platform);
+        setStatus(texts.status.ready);
+
+        // answer spammer question
+        $('question').value = 'no';
+
+        // prefill author details
+        if (has.localStorage) {
+          each([$('author'), $('author-email'), $('author-url')], function(element) {
+            element.value = localStorage[element.id] || '';
+            element.oninput = element.onkeydown = function(event) {
+              event && event.type < 'k' && (element.onkeydown = null);
+              localStorage[element.id] = element.value;
+            };
+          });
+        }
+        // show warning when Firebug is enabled (avoids showing for Firebug Lite)
+        try {
+          // Firebug 1.9 no longer has `console.firebug`
+          if (console.firebug || /firebug/i.test(console.table())) {
+            addClass('firebug', classNames.show);
+          }
+        } catch(e) { }
+      }
+      // evaluate hash values
+      // (delay in an attempt to ensure this is executed after all other window load handlers)
+      setTimeout(function() {
+        pageLoaded = true;
+        handlers.window.hashchange();
+      }, 1);
+    }
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Shortcut for document.getElementById().
+   *
+   * @private
+   * @param {Element|String} id The id of the element to retrieve.
+   * @returns {Element} The element, if found, or null.
+   */
+  function $(id) {
+    return typeof id == 'string' ? document.getElementById(id) : id;
+  }
+
+  /**
+   * Adds a CSS class name to an element's className property.
+   *
+   * @private
+   * @param {Element|String} element The element or id of the element.
+   * @param {String} className The class name.
+   * @returns {Element} The element.
+   */
+  function addClass(element, className) {
+    if ((element = $(element)) && !hasClass(element, className)) {
+      element.className += (element.className ? ' ' : '') + className;
+    }
+    return element;
+  }
+
+  /**
+   * Registers an event listener on an element.
+   *
+   * @private
+   * @param {Element|String} element The element or id of the element.
+   * @param {String} eventName The name of the event.
+   * @param {Function} handler The event handler.
+   * @returns {Element} The element.
+   */
+  function addListener(element, eventName, handler) {
+    if ((element = $(element))) {
+      if (typeof element.addEventListener != 'undefined') {
+        element.addEventListener(eventName, handler, false);
+      } else if (typeof element.attachEvent != 'undefined') {
+        element.attachEvent('on' + eventName, handler);
+      }
+    }
+    return element;
+  }
+
+  /**
+   * Appends to an element's innerHTML property.
+   *
+   * @private
+   * @param {Element|String} element The element or id of the element.
+   * @param {String} html The HTML to append.
+   * @returns {Element} The element.
+   */
+  function appendHTML(element, html) {
+    if ((element = $(element)) && html != null) {
+      element.innerHTML += html;
+    }
+    return element;
+  }
+
+  /**
+   * Shortcut for document.createElement().
+   *
+   * @private
+   * @param {String} tag The tag name of the element to create.
+   * @returns {Element} A new element of the given tag name.
+   */
+  function createElement(tagName) {
+    return document.createElement(tagName);
+  }
+
+  /**
+   * Checks if an element is assigned the given class name.
+   *
+   * @private
+   * @param {Element|String} element The element or id of the element.
+   * @param {String} className The class name.
+   * @returns {Boolean} If assigned the class name return true, else false.
+   */
+  function hasClass(element, className) {
+    return !!(element = $(element)) &&
+      (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
+  }
+
+  /**
+   * Set an element's innerHTML property.
+   *
+   * @private
+   * @param {Element|String} element The element or id of the element.
+   * @param {String} html The HTML to set.
+   * @returns {Element} The element.
+   */
+  function setHTML(element, html) {
+    if ((element = $(element))) {
+      element.innerHTML = html == null ? '' : html;
+    }
+    return element;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Gets the Hz, i.e. operations per second, of `bench` adjusted for the
+   * margin of error.
+   *
+   * @private
+   * @param {Object} bench The benchmark object.
+   * @returns {Number} Returns the adjusted Hz.
+   */
+  function getHz(bench) {
+    return 1 / (bench.stats.mean + bench.stats.moe);
+  }
+
+  /**
+   * Checks if a value has an internal [[Class]] of Function.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the value is a function, else `false`.
+   */
+  function isFunction(value) {
+    return toString.call(value) == '[object Function]';
+  }
+
+  /**
+   * Appends to or clears the error log.
+   *
+   * @private
+   * @param {String|Object} text The text to append or options object.
+   */
+  function logError(text) {
+    var table,
+        div = $('error-info'),
+        options = {};
+
+    // juggle arguments
+    if (typeof text == 'object' && text) {
+      options = text;
+      text = options.text;
+    }
+    else if (arguments.length) {
+      options.text = text;
+    }
+    if (!div) {
+      table = $('test-table');
+      div = createElement('div');
+      div.id = 'error-info';
+      table.parentNode.insertBefore(div, table.nextSibling);
+    }
+    if (options.clear) {
+      div.className = div.innerHTML = '';
+      errors.length = 0;
+    }
+    if ('text' in options && indexOf(errors, text) < 0) {
+      errors.push(text);
+      addClass(div, classNames.show);
+      appendHTML(div, text);
+    }
+  }
+
+  /**
+   * Sets the status text.
+   *
+   * @private
+   * @param {String} text The text to write to the status.
+   */
+  function setStatus(text) {
+    setHTML('status', text);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Parses the window.location.hash value into an object assigned to `ui.params`.
+   *
+   * @static
+   * @memberOf ui
+   * @returns {Object} The suite instance.
+   */
+  function parseHash() {
+    var me = this,
+        hashes = location.hash.slice(1).split('&'),
+        params = me.params || (me.params = {});
+
+    // remove old params
+    forOwn(params, function(value, key) {
+      delete params[key];
+    });
+
+    // add new params
+    each(hashes[0] && hashes, function(value) {
+      value = value.split('=');
+      params[value[0].toLowerCase()] = (value[1] || '').toLowerCase();
+    });
+    return me;
+  }
+
+  /**
+   * Renders the results table cell of the corresponding benchmark(s).
+   *
+   * @static
+   * @memberOf ui
+   * @param {Number} [index] The index of the benchmark to render.
+   * @returns {Object} The suite instance.
+   */
+  function render(index) {
+    each(index == null ? (index = 0, ui.benchmarks) : [ui.benchmarks[index]], function(bench) {
+      var parsed,
+          cell = $(prefix + (++index)),
+          error = bench.error,
+          hz = bench.hz;
+
+      // reset title and class
+      cell.title = '';
+      cell.className = classNames.results;
+
+      // status: error
+      if (error) {
+        setHTML(cell, 'Error');
+        addClass(cell, classNames.error);
+        parsed = join(error, '</li><li>');
+        logError('<p>' + error + '.</p>' + (parsed ? '<ul><li>' + parsed + '</li></ul>' : ''));
+      }
+      else {
+        // status: running
+        if (bench.running) {
+          setHTML(cell, 'running&hellip;');
+        }
+        // status: completed
+        else if (bench.cycles) {
+          // obscure details until the suite has completed
+          if (ui.running) {
+            setHTML(cell, 'completed');
+          }
+          else {
+            cell.title = 'Ran ' + formatNumber(bench.count) + ' times in ' +
+              bench.times.cycle.toFixed(3) + ' seconds.';
+            setHTML(cell, formatNumber(hz.toFixed(hz < 100 ? 2 : 0)) +
+              ' <small>&plusmn;' + bench.stats.rme.toFixed(2) + '%</small>');
+          }
+        }
+        else {
+          // status: pending
+          if (ui.running && ui.indexOf(bench) > -1) {
+            setHTML(cell, 'pending&hellip;');
+          }
+          // status: ready
+          else {
+            setHTML(cell, 'ready');
+          }
+        }
+      }
+    });
+    return ui;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  ui.on('add', function(event) {
+    var bench = event.target,
+        index = ui.benchmarks.length,
+        id = index + 1,
+        title = $('title-' + id);
+
+    delete ui[--ui.length];
+    ui.benchmarks.push(bench);
+
+    if (has.runner) {
+      title.tabIndex = 0;
+      title.title = 'Click to run this test again.';
+
+      addListener(title, 'click', handlers.title.click);
+      addListener(title, 'keyup', handlers.title.keyup);
+
+      bench.on('start', handlers.benchmark.start);
+      bench.on('start cycle', handlers.benchmark.cycle);
+      ui.render(index);
+    }
+  })
+  .on('start cycle', function() {
+    ui.render();
+    setHTML('run', texts.run.running);
+  })
+  .on('complete', function() {
+    var benches = filter(ui.benchmarks, 'successful'),
+        fastest = filter(benches, 'fastest'),
+        slowest = filter(benches, 'slowest');
+
+    ui.render();
+    setHTML('run', texts.run.again);
+    setStatus(texts.status.again);
+
+    // highlight result cells
+    each(benches, function(bench) {
+      var cell = $(prefix + (indexOf(ui.benchmarks, bench) + 1)),
+          fastestHz = getHz(fastest[0]),
+          hz = getHz(bench),
+          percent = (1 - (hz / fastestHz)) * 100,
+          span = cell.getElementsByTagName('span')[0],
+          text = 'fastest';
+
+      if (indexOf(fastest, bench) > -1) {
+        // mark fastest
+        addClass(cell, text);
+      }
+      else {
+        text = isFinite(hz)
+          ? formatNumber(percent < 1 ? percent.toFixed(2) : Math.round(percent)) + '% slower'
+          : '';
+
+        // mark slowest
+        if (indexOf(slowest, bench) > -1) {
+          addClass(cell, 'slowest');
+        }
+      }
+      // write ranking
+      if (span) {
+        setHTML(span, text);
+      } else {
+        appendHTML(cell, '<span>' + text + '</span>');
+      }
+    });
+
+    ui.browserscope.post();
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * An array of benchmarks created from test cases.
+   *
+   * @memberOf ui
+   * @type Array
+   */
+  ui.benchmarks = [];
+
+  /**
+   * The parsed query parameters of the pages url hash.
+   *
+   * @memberOf ui
+   * @type Object
+   */
+  ui.params = {};
+
+  // parse query params into ui.params hash
+  ui.parseHash = parseHash;
+
+  // (re)render the results of one or more benchmarks
+  ui.render = render;
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose
+  window.ui = ui;
+
+  // don't let users alert, confirm, prompt, or open new windows
+  window.alert = window.confirm = window.prompt = window.open = function() { };
+
+  // parse hash query params when it changes
+  addListener(window, 'hashchange', handlers.window.hashchange);
+
+  // bootstrap onload
+  addListener(window, 'load', handlers.window.load);
+
+  // parse location hash string
+  ui.parseHash();
+
+  // provide a simple UI for toggling between chart types and filtering results
+  // (assumes ui.js is just before </body>)
+  (function() {
+    var sibling = $('bs-results'),
+        p = createElement('p');
+
+    p.innerHTML =
+      '<span id=charts><strong>Chart type:</strong> <a href=#>bar</a>, ' +
+      '<a href=#>column</a>, <a href=#>line</a>, <a href=#>pie</a>, ' +
+      '<a href=#>table</a></span><br>' +
+      '<span id=filters><strong>Filter:</strong> <a href=#>popular</a>, ' +
+      '<a href=#>all</a>, <a href=#>desktop</a>, <a href=#>family</a>, ' +
+      '<a href=#>major</a>, <a href=#>minor</a>, <a href=#>mobile</a>, ' +
+      '<a href=#>prerelease</a></span>';
+
+    sibling.parentNode.insertBefore(p, sibling);
+
+    // use DOM0 event handler to simplify canceling the default action
+    $('charts').onclick =
+    $('filters').onclick = function(event) {
+      event || (event = window.event);
+      var target = event.target || event.srcElement;
+      if (target.href || (target = target.parentNode).href) {
+        ui.browserscope.render(
+          target.parentNode.id == 'charts'
+            ? { 'chart': target.innerHTML }
+            : { 'filterBy': target.innerHTML }
+        );
+      }
+      // cancel the default action
+      return false;
+    };
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  // fork for runner or embedded chart
+  if (has.runner) {
+    // detect the scroll element
+    (function() {
+      var scrollTop,
+          div = document.createElement('div'),
+          body = document.body,
+          bodyStyle = body.style,
+          bodyHeight = bodyStyle.height,
+          html = document.documentElement,
+          htmlStyle = html.style,
+          htmlHeight = htmlStyle.height;
+
+      bodyStyle.height  = htmlStyle.height = 'auto';
+      div.style.cssText = 'display:block;height:9001px;';
+      body.insertBefore(div, body.firstChild);
+      scrollTop = html.scrollTop;
+
+      // set `scrollEl` that's used in `handlers.window.hashchange()`
+      if (html.clientWidth !== 0 && ++html.scrollTop && html.scrollTop == scrollTop + 1) {
+        scrollEl = html;
+      }
+      body.removeChild(div);
+      bodyStyle.height = bodyHeight;
+      htmlStyle.height = htmlHeight;
+      html.scrollTop = scrollTop;
+    }());
+
+    // catch and display errors from the "preparation code"
+    window.onerror = function(message, fileName, lineNumber) {
+      logError('<p>' + message + '.</p><ul><li>' + join({
+        'message': message,
+        'fileName': fileName,
+        'lineNumber': lineNumber
+      }, '</li><li>') + '</li></ul>');
+      scrollEl.scrollTop = $('error-info').offsetTop;
+    };
+    // inject nano applet
+    // (assumes ui.js is just before </body>)
+    if ('nojava' in ui.params) {
+      addClass('java', classNames.show);
+    } else {
+      // using innerHTML avoids an alert in some versions of IE6
+      document.body.insertBefore(setHTML(createElement('div'),
+        '<applet code=nano archive=' + archive + '>').lastChild, document.body.firstChild);
+    }
+  }
+  else {
+    // short circuit unusable methods
+    ui.render = function() { };
+    ui.off('start cycle complete');
+    setTimeout(function() {
+      ui.off();
+      ui.browserscope.post = function() { };
+      invoke(ui.benchmarks, 'off');
+    }, 1);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // optimized asynchronous Google Analytics snippet based on
+  // http://mathiasbynens.be/notes/async-analytics-snippet
+  if (gaId) {
+    (function() {
+      var script = createElement('script'),
+          sibling = document.getElementsByTagName('script')[0];
+
+      window._gaq = [['_setAccount', gaId], ['_trackPageview']];
+      script.src = '//www.google-analytics.com/ga.js';
+      sibling.parentNode.insertBefore(script, sibling);
+    }());
+  }
+}(this, document));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/nano.jar b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/nano.jar
new file mode 100644
index 0000000..b577840
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/nano.jar
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/nano.java b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/nano.java
new file mode 100644
index 0000000..96fc224
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/nano.java
@@ -0,0 +1,23 @@
+/**
+ * Simple class to expose nanoTime() to JavaScript.
+ *
+ * Compile using
+ *   javac -g:none -target 1.5 nano.java
+ *   jar cfM nano.jar nano.class
+ *   java -jar proguard.jar @options.txt
+ *
+ * ProGuard (http://proguard.sourceforge.net)
+ * options.txt
+ *   -injars      nano.jar
+ *   -outjars     nano_s.jar
+ *   -libraryjars <java.home>/jre/lib/rt.jar
+ *   -keep public class nano {
+ *     public long nanoTime();
+ *   }
+ */
+import java.applet.Applet;
+public class nano extends Applet {
+  public long nanoTime() {
+    return System.nanoTime();
+  }
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/package.json b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/package.json
new file mode 100644
index 0000000..2be940c
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/package.json
@@ -0,0 +1,54 @@
+{
+  "name": "benchmark",
+  "version": "1.0.0",
+  "description": "A benchmarking library that works on nearly all JavaScript platforms, supports high-resolution timers, and returns statistically significant results.",
+  "homepage": "http://benchmarkjs.com/",
+  "main": "benchmark",
+  "keywords": [
+    "benchmark",
+    "narwhal",
+    "node",
+    "performance",
+    "ringo",
+    "speed"
+  ],
+  "licenses": [
+    {
+      "type": "MIT",
+      "url": "http://mths.be/mit"
+    }
+  ],
+  "author": {
+    "name": "Mathias Bynens",
+    "email": "mathias@benchmarkjs.com",
+    "web": "http://mathiasbynens.be/"
+  },
+  "maintainers": [
+    {
+      "name": "John-David Dalton",
+      "email": "john.david.dalton@gmail.com",
+      "web": "http://allyoucanleet.com/"
+    },
+    {
+      "name": "Mathias Bynens",
+      "email": "mathias@benchmarkjs.com",
+      "web": "http://mathiasbynens.be/"
+    }
+  ],
+  "bugs": {
+    "email": "bugs@benchmarkjs.com",
+    "url": "https://github.com/bestiejs/benchmark.js/issues"
+  },
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/bestiejs/benchmark.js.git"
+  },
+  "engines": [
+    "node",
+    "rhino"
+  ],
+  "directories": {
+    "doc": "./doc",
+    "test": "./test"
+  }
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/plugin/ui.browserscope.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/plugin/ui.browserscope.js
new file mode 100644
index 0000000..01905bd
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/plugin/ui.browserscope.js
@@ -0,0 +1,1052 @@
+(function(window, document) {
+
+  /** Cache used by various methods */
+  var cache = {
+    'counter': 0,
+    'lastAction': 'load',
+    'lastChart': 'bar',
+    'lastFilterBy': 'all',
+    'responses': { /* 'all': null, 'desktop': null, 'major': null, ... */ },
+    'timers': { /* 'cleanup': null, 'load': null, 'post': null, ... */ },
+    'trash': createElement('div')
+  };
+
+  /**
+   * Used to filter Browserscope results by browser category.
+   *
+   * @see http://www.browserscope.org/user/tests/howto#urlparams
+   */
+  var filterMap = {
+    'all': 3,
+    'desktop': 'top-d',
+    'family': 0,
+    'major': 1,
+    'minor': 2,
+    'mobile': 'top-m',
+    'popular': 'top',
+    'prerelease': 'top-d-e'
+  };
+
+  /** Used to resolve a value's internal [[Class]] */
+  var toString = {}.toString;
+
+  /**
+   * The `uaToken` is prepended to the value of the data cell of the Google
+   * visualization data table object that matches the user's browser name. After
+   * the chart is rendered the element containing the `uaToken` is assigned the
+   * `ui.browserscope.uaClass` class name to allow for the creation of a visual
+   * indicator to help the user more easily find their browser's results.
+   */
+  var uaToken = '\u2028';
+
+  /** Math shortcuts */
+  var floor = Math.floor,
+      max = Math.max,
+      min = Math.min;
+
+  /** Utility shortcuts */
+  var each = Benchmark.each,
+      extend = Benchmark.extend,
+      filter = Benchmark.filter,
+      forOwn = Benchmark.forOwn,
+      formatNumber = Benchmark.formatNumber,
+      hasKey = Benchmark.hasKey,
+      indexOf = Benchmark.indexOf,
+      interpolate = Benchmark.interpolate,
+      invoke = Benchmark.invoke,
+      map = Benchmark.map,
+      reduce = Benchmark.reduce;
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Registers an event listener.
+   *
+   * @private
+   * @param {Element} element The element.
+   * @param {String} eventName The name of the event to listen to.
+   * @param {Function} handler The event handler.
+   * @returns {Element} The element.
+   */
+  function addListener(element, eventName, handler) {
+    if ((element = typeof element == 'string' ? query(element)[0] : element)) {
+      if (typeof element.addEventListener != 'undefined') {
+        element.addEventListener(eventName, handler, false);
+      } else if (typeof element.attachEvent != 'undefined') {
+        element.attachEvent('on' + eventName, handler);
+      }
+    }
+    return element;
+  }
+
+  /**
+   * Shortcut for `document.createElement()`.
+   *
+   * @private
+   * @param {String} tagName The tag name of the element to create.
+   * @param {String} name A name to assign to the element.
+   * @param {Document|Element} context The document object used to create the element.
+   * @returns {Element} Returns a new element.
+   */
+  function createElement(tagName, name, context) {
+    var result;
+    name && name.nodeType && (context = name, name = 0);
+    context =  context ? context.ownerDocument || context : document;
+    name || (name = '');
+
+    try {
+      // set name attribute for IE6/7
+      result = context.createElement('<' + tagName + ' name="' + name + '">');
+    } catch(e) {
+      (result = context.createElement(tagName)).name = name;
+    }
+    return result;
+  }
+
+  /**
+   * Creates a new style element.
+   *
+   * @private
+   * @param {String} cssText The css text of the style element.
+   * @param {Document|Element} context The document object used to create the element.
+   * @returns {Element} Returns the new style element.
+   */
+  function createStyleSheet(cssText, context) {
+    // use a text node, "x", to work around innerHTML issues with style elements
+    // http://msdn.microsoft.com/en-us/library/ms533897(v=vs.85).aspx#1
+    var div = createElement('div', context);
+    div.innerHTML = 'x<style>' + cssText + '</style>';
+    return div.lastChild;
+  }
+
+  /**
+   * Gets the text content of an element.
+   *
+   * @private
+   * @param {Element} element The element.
+   * @returns {String} The text content of the element.
+   */
+  function getText(element) {
+    element = query(element)[0];
+    return element && (element.textContent || element.innerText) || '';
+  }
+
+  /**
+   * Injects a script into the document.
+   *
+   * @private
+   * @param {String} src The external script source.
+   * @param {Object} sibling The element to inject the script after.
+   * @param {Document} context The document object used to create the script element.
+   * @returns {Object} The new script element.
+   */
+  function loadScript(src, sibling, context) {
+    context = sibling ? sibling.ownerDocument || [sibling, sibling = 0][0] : context;
+    var script = createElement('script', context),
+        nextSibling = sibling ? sibling.nextSibling : query('script', context).pop();
+
+    script.src = src;
+    return (sibling || nextSibling).parentNode.insertBefore(script,  nextSibling);
+  }
+
+  /**
+   * Queries the document for elements by id or tagName.
+   *
+   * @private
+   * @param {String} selector The css selector to match.
+   * @param {Document|Element} context The element whose descendants are queried.
+   * @returns {Array} The array of results.
+   */
+  function query(selector, context) {
+    var result = [];
+    selector || (selector = '');
+    context = typeof context == 'string' ? query(context)[0] : context || document;
+
+    if (selector.nodeType) {
+      result = [selector];
+    }
+    else if (context) {
+      each(selector.split(','), function(selector) {
+        each(/^#/.test(selector)
+            ? [context.getElementById(selector.slice(1))]
+            : context.getElementsByTagName(selector), function(node) {
+          result.push(node);
+        });
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Set an element's innerHTML property.
+   *
+   * @private
+   * @param {Element} element The element.
+   * @param {String} html The HTML to set.
+   * @param {Object} object The template object used to modify the html.
+   * @returns {Element} The element.
+   */
+  function setHTML(element, html, object) {
+    if ((element = query(element)[0])) {
+      element.innerHTML = interpolate(html, object);
+    }
+    return element;
+  }
+
+  /**
+   * Displays a message in the "results" element.
+   *
+   * @private
+   * @param {String} text The text to display.
+   * @param {Object} object The template object used to modify the text.
+   */
+  function setMessage(text, object) {
+    var me = ui.browserscope,
+        cont = me.container;
+
+    if (cont) {
+      cont.className = 'bs-rt-message';
+      setHTML(cont, text, object);
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Adds a style sheet to the current chart and assigns the `ui.browserscope.uaClass`
+   * class name to the chart element containing the user's browser name.
+   *
+   * @private
+   * @returns {Boolean} Returns `true` if the operation succeeded, else `false`.
+   */
+  function addChartStyle() {
+    var me = ui.browserscope,
+        cssText = [],
+        context = frames[query('iframe', me.container)[0].name].document,
+        chartNodes = query('text,textpath', context),
+        uaClass = me.uaClass,
+        result = false;
+
+    if (chartNodes.length) {
+      // extract CSS rules for `uaClass`
+      each(query('link,style'), function(node) {
+        // avoid access denied errors on external style sheets
+        // outside the same origin policy
+        try {
+          var sheet = node.sheet || node.styleSheet;
+          each(sheet.cssRules || sheet.rules, function(rule) {
+            if ((rule.selectorText || rule.cssText).indexOf('.' + uaClass) > -1) {
+              cssText.push(rule.style && rule.style.cssText || /[^{}]*(?=})/.exec(rule.cssText) || '');
+            }
+          });
+        } catch(e) { }
+      });
+
+      // insert custom style sheet
+      query('head', context)[0].appendChild(
+        createStyleSheet('.' + uaClass + '{' + cssText.join(';') + '}', context));
+
+      // scan chart elements for a match
+      each(chartNodes, function(node) {
+        var nextSibling;
+        if ((node.string || getText(node)).charAt(0) == uaToken) {
+          // for VML
+          if (node.string) {
+            // IE requires reinserting the element to render correctly
+            node.className = uaClass;
+            nextSibling = node.nextSibling;
+            node.parentNode.insertBefore(node.removeNode(), nextSibling);
+          }
+          // for SVG
+          else {
+            node.setAttribute('class', uaClass);
+          }
+          result = true;
+        }
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Periodically executed callback that removes injected script and iframe elements.
+   *
+   * @private
+   */
+  function cleanup() {
+    var me = ui.browserscope,
+        timings = me.timings,
+        timers = cache.timers,
+        trash = cache.trash,
+        delay = timings.cleanup * 1e3;
+
+    // remove injected scripts and old iframes when benchmarks aren't running
+    if (timers.cleanup && !ui.running) {
+      // if expired, destroy the element to prevent pseudo memory leaks.
+      // http://dl.dropbox.com/u/513327/removechild_ie_leak.html
+      each(query('iframe,script'), function(element) {
+        var expire = +(/^browserscope-\d+-(\d+)$/.exec(element.name) || 0)[1] + max(delay, timings.timeout * 1e3);
+        if (new Date > expire || /browserscope\.org|google\.com/.test(element.src)) {
+          trash.appendChild(element);
+          trash.innerHTML = '';
+        }
+      });
+    }
+    // schedule another round
+    timers.cleanup = setTimeout(cleanup, delay);
+  }
+
+  /**
+   * A simple data object cloning utility.
+   *
+   * @private
+   * @param {Mixed} data The data object to clone.
+   * @returns {Mixed} The cloned data object.
+   */
+  function cloneData(data) {
+    var fn,
+        ctor,
+        result = data;
+
+    if (isArray(data)) {
+      result = map(data, cloneData);
+    }
+    else if (data === Object(data)) {
+      ctor = data.constructor;
+      result = ctor == Object ? {} : (fn = function(){}, fn.prototype = ctor.prototype, new fn);
+      forOwn(data, function(value, key) {
+        result[key] = cloneData(value);
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Creates a Browserscope results object.
+   *
+   * @private
+   * @returns {Object|Null} Browserscope results object or null.
+   */
+  function createSnapshot() {
+    // clone benches, exclude those that are errored, unrun, or have hz of Infinity
+    var benches = invoke(filter(ui.benchmarks, 'successful'), 'clone'),
+        fastest = filter(benches, 'fastest'),
+        slowest = filter(benches, 'slowest'),
+        neither = filter(benches, function(bench) {
+          return indexOf(fastest, bench) + indexOf(slowest, bench) == -2;
+        });
+
+    function merge(destination, source) {
+      destination.count = source.count;
+      destination.cycles = source.cycles;
+      destination.hz = source.hz;
+      destination.stats = extend({}, source.stats);
+    }
+
+    // normalize results on slowest in each category
+    each(fastest.concat(slowest), function(bench) {
+      merge(bench, indexOf(fastest, bench) > -1 ? fastest[fastest.length - 1] : slowest[0]);
+    });
+
+    // sort slowest to fastest
+    // (a larger `mean` indicates a slower benchmark)
+    neither.sort(function(a, b) {
+      a = a.stats; b = b.stats;
+      return (a.mean + a.moe > b.mean + b.moe) ? -1 : 1;
+    });
+
+    // normalize the leftover benchmarks
+    reduce(neither, function(prev, bench) {
+      // if the previous slower benchmark is indistinguishable from
+      // the current then use the previous benchmark's values
+      if (prev.compare(bench) == 0) {
+        merge(bench, prev);
+      }
+      return bench;
+    });
+
+    // append benchmark ids for duplicate names or names with no alphanumeric/space characters
+    // and use the upper limit of the confidence interval to compute a lower hz
+    // to avoid recording inflated results caused by a high margin or error
+    return reduce(benches, function(result, bench, key) {
+      var stats = bench.stats;
+      result || (result = {});
+      key = toLabel(bench.name);
+      result[key && !hasKey(result, key) ? key : key + bench.id ] = floor(1 / (stats.mean + stats.moe));
+      return result;
+    }, null);
+  }
+
+  /**
+   * Retrieves the "cells" array from a given Google visualization data row object.
+   *
+   * @private
+   * @param {Object} object The data row object.
+   * @returns {Array} An array of cell objects.
+   */
+  function getDataCells(object) {
+    // resolve cells by duck typing because of munged property names
+    var result = [];
+    forOwn(object, function(value) {
+      return !(isArray(value) && (result = value));
+    });
+    // remove empty entries which occur when not all the tests are recorded
+    return filter(result, Boolean);
+  }
+
+  /**
+   * Retrieves the "labels" array from a given Google visualization data table object.
+   *
+   * @private
+   * @param {Object} object The data table object.
+   * @returns {Array} An array of label objects.
+   */
+  function getDataLabels(object) {
+    var result = [],
+        labelMap = {};
+
+    // resolve labels by duck typing because of munged property names
+    forOwn(object, function(value) {
+      return !(isArray(value) && 0 in value && 'type' in value[0] && (result = value));
+    });
+    // create a data map of labels to names
+    each(ui.benchmarks, function(bench) {
+      var key = toLabel(bench.name);
+      labelMap[key && !hasKey(labelMap, key) ? key : key + bench.id ] = bench.name;
+    });
+    // replace Browserscope's basic labels with benchmark names
+    return each(result, function(cell) {
+      var name = labelMap[cell.label];
+      name && (cell.label = name);
+    });
+  }
+
+  /**
+   * Retrieves the "rows" array from a given Google visualization data table object.
+   *
+   * @private
+   * @param {Object} object The data table object.
+   * @returns {Array} An array of row objects.
+   */
+  function getDataRows(object) {
+    var name,
+        filterBy = cache.lastFilterBy,
+        browserName = toBrowserName(getText(query('strong', '#bs-ua')[0]), filterBy),
+        uaClass = ui.browserscope.uaClass,
+        result = [];
+
+    // resolve rows by duck typing because of munged property names
+    forOwn(object, function(value, key) {
+      return !(isArray(value) && 0 in value && !('type' in value[0]) && (name = key, result = value));
+    });
+    // remove empty rows and set the `p.className` on the browser
+    // name cell that matches the user's browser name
+    if (result.length) {
+      result = object[name] = filter(result, function(value) {
+        var cells = getDataCells(value),
+            first = cells[0],
+            second = cells[1];
+
+        // cells[0] is the browser name cell so instead we check cells[1]
+        // for the presence of ops/sec data to determine if a row is empty or not
+        if (first && second && second.f) {
+          delete first.p.className;
+          if (browserName == toBrowserName(first.f, filterBy)) {
+            first.p.className = uaClass;
+          }
+          return true;
+        }
+      });
+    }
+    return result;
+  }
+
+  /**
+   * Checks if a value has an internal [[Class]] of Array.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if the value has an internal [[Class]] of
+   *  Array, else `false`.
+   */
+  function isArray(value) {
+    return toString.call(value) == '[object Array]';
+  }
+
+  /**
+   * Executes a callback at a given delay interval until it returns `false`.
+   *
+   * @private
+   * @param {Function} callback The function called every poll interval.
+   * @param {Number} delay The delay between callback calls (secs).
+   */
+  function poll(callback, delay) {
+    function poller(init) {
+      if (init || callback() !== false) {
+        setTimeout(poller, delay * 1e3);
+      }
+    }
+    poller(true);
+  }
+
+  /**
+   * Cleans up the last action and sets the current action.
+   *
+   * @private
+   * @param {String} action The current action.
+   */
+  function setAction(action) {
+    clearTimeout(cache.timers[cache.lastAction]);
+    cache.lastAction = action;
+  }
+
+  /**
+   * Converts the browser name version number to the format allowed by the
+   * specified filter.
+   *
+   * @private
+   * @param {String} name The full browser name .
+   * @param {String} filterBy The filter formating rules to apply.
+   * @returns {String} The converted browser name.
+   */
+  function toBrowserName(name, filterBy) {
+    name || (name = '');
+    if (filterBy == 'all') {
+      // truncate something like 1.0.0 to 1
+      name = name.replace(/(\d+)[.0]+$/, '$1');
+    }
+    else if (filterBy == 'family') {
+      // truncate something like XYZ 1.2 to XYZ
+      name = name.replace(/[.\d\s]+$/, '');
+    }
+    else if (/minor|popular/.test(filterBy) && /\d+(?:\.[1-9])+$/.test(name)) {
+      // truncate something like 1.2.3 to 1.2
+      name = name.replace(/(\d+\.[1-9])(\.[.\d]+$)/, '$1');
+    }
+    else {
+      // truncate something like 1.0 to 1 or 1.2.3 to 1 but leave something like 1.2 alone
+      name = name.replace(/(\d+)(?:(\.[1-9]$)|(\.[.\d]+$))/, '$1$2');
+    }
+    return name;
+  }
+
+  /**
+   * Replaces non-alphanumeric characters with spaces because Browserscope labels
+   * can only contain alphanumeric characters and spaces.
+   *
+   * @private
+   * @param {String} text The text to be converted.
+   * @returns {String} The Browserscope safe label text.
+   * @see http://code.google.com/p/browserscope/issues/detail?id=271
+   */
+  function toLabel(text) {
+    return (text || '').replace(/[^a-z0-9]+/gi, ' ');
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Loads Browserscope's cumulative results table.
+   *
+   * @static
+   * @memberOf ui.browserscope
+   * @param {Object} options The options object.
+   */
+  function load(options) {
+    options || (options = {});
+
+    var fired,
+        me = ui.browserscope,
+        cont = me.container,
+        filterBy = cache.lastFilterBy = options.filterBy || cache.lastFilterBy,
+        responses = cache.responses,
+        response = cache.responses[filterBy],
+        visualization = window.google && google.visualization;
+
+    function onComplete(response) {
+      var lastResponse = responses[filterBy];
+      if (!fired) {
+        // set the fired flag to avoid Google's own timeout
+        fired = true;
+        // render if the filter is still the same, else cache the result
+        if (filterBy == cache.lastFilterBy) {
+          me.render({ 'force': true, 'response': lastResponse || response });
+        } else if(!lastResponse && response && !response.isError()) {
+          responses[filterBy] = response;
+        }
+      }
+    }
+
+    // set last action in case the load fails and a retry is needed
+    setAction('load');
+
+    // exit early if there is no container element or the response is cached
+    // and retry if the visualization library hasn't loaded yet
+    if (!cont || !visualization || !visualization.Query || response) {
+      cont && onComplete(response);
+    }
+    else if (!ui.running) {
+      // set our own load timeout to display an error message and retry loading
+      cache.timers.load = setTimeout(onComplete, me.timings.timeout * 1e3);
+      // set "loading" message and attempt to load Browserscope data
+      setMessage(me.texts.loading);
+      // request Browserscope pass chart data to `google.visualization.Query.setResponse()`
+      (new visualization.Query(
+        '//www.browserscope.org/gviz_table_data?category=usertest_' + me.key + '&v=' + filterMap[filterBy],
+        { 'sendMethod': 'scriptInjection' }
+      ))
+      .send(onComplete);
+    }
+  }
+
+  /**
+   * Creates a Browserscope beacon and posts the benchmark results.
+   *
+   * @static
+   * @memberOf ui.browserscope
+   */
+  function post() {
+    var idoc,
+        iframe,
+        body = document.body,
+        me = ui.browserscope,
+        key = me.key,
+        timings = me.timings,
+        name = 'browserscope-' + (cache.counter++) + '-' + (+new Date),
+        snapshot = createSnapshot();
+
+    // set last action in case the post fails and a retry is needed
+    setAction('post');
+
+    if (key && snapshot && me.postable && !ui.running && !/Simulator/i.test(Benchmark.platform)) {
+      // create new beacon
+      // (the name contains a timestamp so `cleanup()` can determine when to remove it)
+      iframe = createElement('iframe', name);
+      body.insertBefore(iframe, body.firstChild);
+      idoc = frames[name].document;
+      iframe.style.display = 'none';
+
+      // expose results snapshot
+      me.snapshot = snapshot;
+      // set "posting" message and attempt to post the results snapshot
+      setMessage(me.texts.post);
+      // Note: We originally created an iframe to avoid Browerscope's old limit
+      // of one beacon per page load. It's currently used to implement custom
+      // request timeout and retry routines.
+      idoc.write(interpolate(
+        // the doctype is required so Browserscope detects the correct IE compat mode
+        '#{doctype}<title></title><body><script>' +
+        'with(parent.ui.browserscope){' +
+        'var _bTestResults=snapshot,' +
+        '_bC=function(){clearTimeout(_bT);parent.setTimeout(function(){purge();load()},#{refresh}*1e3)},' +
+        '_bT=setTimeout(function(){_bC=function(){};render()},#{timeout}*1e3)' +
+        '}<\/script>' +
+        '<script src=//www.browserscope.org/user/beacon/#{key}?callback=_bC><\/script>',
+        {
+          'doctype': /css/i.test(document.compatMode) ? '<!doctype html>' : '',
+          'key': key,
+          'refresh': timings.refresh,
+          'timeout': timings.timeout
+        }
+      ));
+      // avoid the IE spinner of doom
+      // http://www.google.com/search?q=IE+throbber+of+doom
+      idoc.close();
+    }
+    else {
+      me.load();
+    }
+  }
+
+  /**
+   * Purges the Browserscope response cache.
+   *
+   * @static
+   * @memberOf ui.browserscope
+   * @param {String} key The key of a single cache entry to clear.
+   */
+  function purge(key) {
+    // we don't pave the cache object with a new one to preserve existing references
+    var responses = cache.responses;
+    if (key) {
+      delete responses[key];
+    } else {
+      forOwn(responses, function(value, key) {
+        delete responses[key];
+      });
+    }
+  }
+
+  /**
+   * Renders the cumulative results table.
+   * (tweak the dimensions and styles to best fit your environment)
+   *
+   * @static
+   * @memberOf ui.browserscope
+   * @param {Object} options The options object.
+   */
+  function render(options) {
+    options || (options = {});
+
+    // coordinates, dimensions, and sizes are in px
+    var areaHeight,
+        cellWidth,
+        data,
+        labels,
+        rowCount,
+        rows,
+        me = ui.browserscope,
+        cont = me.container,
+        responses = cache.responses,
+        visualization = window.google && google.visualization,
+        lastChart = cache.lastChart,
+        chart = cache.lastChart = options.chart || lastChart,
+        lastFilterBy = cache.lastFilterBy,
+        filterBy = cache.lastFilterBy = options.filterBy || lastFilterBy,
+        lastResponse = responses[filterBy],
+        response = responses[filterBy] = 'response' in options ? (response = options.response) && !response.isError() && response : lastResponse,
+        areaWidth = '100%',
+        cellHeight = 80,
+        fontSize = 13,
+        height = 'auto',
+        hTitle = 'operations per second (higher is better)',
+        hTitleHeight = 48,
+        left = 240,
+        legend = 'top',
+        maxChars = 0,
+        maxCharsLimit = 20,
+        maxOps = 0,
+        minHeight = 480,
+        minWidth = cont && cont.offsetWidth || 948,
+        title = '',
+        top = 50,
+        vTitle = '',
+        vTitleWidth = 48,
+        width = minWidth;
+
+    function retry(force) {
+      var action = cache.lastAction;
+      if (force || ui.running) {
+        cache.timers[action] = setTimeout(retry, me.timings.retry * 1e3);
+      } else {
+        me[action].apply(me, action == 'render' ? [options] : []);
+      }
+    }
+
+    // set action to clear any timeouts and prep for retries
+    setAction(response ? 'render' : cache.lastAction);
+
+    // exit early if there is no container element, the data filter has changed or nothing has changed
+    if (!cont || visualization && (filterBy != lastFilterBy ||
+        (!options.force && chart == lastChart && response == lastResponse))) {
+      cont && filterBy != lastFilterBy && load(options);
+    }
+    // retry if response data is empty/errored or the visualization library hasn't loaded yet
+    else if (!response || !visualization) {
+      // set error message for empty/errored response
+      !response && visualization && setMessage(me.texts.error);
+      retry(true);
+    }
+    // visualization chart gallary
+    // http://code.google.com/apis/chart/interactive/docs/gallery.html
+    else if (!ui.running) {
+      cont.className = '';
+      data = cloneData(response.getDataTable());
+      labels = getDataLabels(data);
+      rows = getDataRows(data);
+      rowCount = rows.length;
+      chart = chart.charAt(0).toUpperCase() + chart.slice(1).toLowerCase();
+
+      // adjust data for non-tabular displays
+      if (chart != 'Table') {
+        // remove "# Tests" run count label (without label data the row will be ignored)
+        labels.pop();
+
+        // modify row data
+        each(rows, function(row) {
+          each(getDataCells(row), function(cell, index, cells) {
+            var lastIndex = cells.length - 1;
+
+            // cells[1] through cells[lastIndex - 1] are ops/sec cells
+            if (/^[\d.,]+$/.test(cell.f)) {
+              // assign ops/sec as cell value
+              cell.v = +cell.f.replace(/,/g, '');
+              // add rate to the text
+              cell.f += ' ops/sec';
+              // capture highest ops value to use when computing the left coordinate
+              maxOps = max(maxOps, cell.v);
+            }
+            // cells[0] is the browser name cell
+            // cells[lastIndex] is the run count cell and has no `f` property
+            else if (cell.f) {
+              // add test run count to browser name
+              cell.f += chart == 'Pie' ? '' : ' (' + (cells[lastIndex].v || 1) + ')';
+              // capture longest char count to use when computing left coordinate/cell width
+              maxChars = min(maxCharsLimit, max(maxChars, cell.f.length));
+            }
+            // compute sum of all ops/sec for pie charts
+            if (chart == 'Pie') {
+              if (index == lastIndex) {
+                cells[1].f = formatNumber(cells[1].v) + ' total ops/sec';
+              } else if (index > 1 && typeof cell.v == 'number') {
+                cells[1].v += cell.v;
+              }
+            }
+            // if the browser name matches the user's browser then style it
+            if (cell.p && cell.p.className) {
+              // prefix the browser name with a line separator (\u2028) because it's not rendered
+              // (IE may render a negligible space in the tooltip of browser names truncated with ellipsis)
+              cell.f = uaToken + cell.f;
+              // poll until the chart elements exist and are styled
+              poll(function() { return !addChartStyle(); }, 0.01);
+            }
+          });
+        });
+
+        // adjust captions and chart dimensions
+        if (chart == 'Bar') {
+          // use minHeight to avoid sizing issues when there is only 1 bar
+          height = max(minHeight, top + (rowCount * cellHeight));
+          // compute left by adding the longest approximate vAxis text width and
+          // a right pad of 10px
+          left = (maxChars * (fontSize / 1.6)) + 10;
+          // get percentage of width left after subtracting the chart's left
+          // coordinate and room for the ops/sec number
+          areaWidth = (100 - (((left + 50) / width) * 100)) + '%';
+        }
+        else {
+          // swap captions (the browser list caption is blank to conserve space)
+          vTitle = [hTitle, hTitle = vTitle][0];
+          height = minHeight;
+
+          if (chart == 'Pie') {
+            legend = 'right';
+            title = 'Total operations per second by browser (higher is better)';
+          }
+          else {
+            hTitleHeight = 28;
+            // compute left by getting the sum of the horizontal space wanted
+            // for the vAxis title's width, the approximate vAxis text width, and
+            // the 13px gap between the chart and the right side of the vAxis text
+            left = vTitleWidth + (formatNumber(maxOps).length * (fontSize / 1.6)) + 13;
+            // compute cell width by adding the longest approximate hAxis text
+            // width and wiggle room of 26px
+            cellWidth = (maxChars * (fontSize / 2)) + 26;
+            // use minWidth to avoid clipping the key
+            width = max(minWidth, left + (rowCount * cellWidth));
+          }
+        }
+        // get percentage of height left after subtracting the vertical space wanted
+        // for the hAxis title's height, text size, the chart's top coordinate,
+        // and the 8px gap between the chart and the top of the hAxis text
+        areaHeight = (100 - (((hTitleHeight + fontSize + top + 8) / height) * 100)) + '%';
+        // make chart type recognizable
+        chart += 'Chart';
+      }
+
+      if (rowCount && visualization[chart]) {
+         new visualization[chart](cont).draw(data, {
+          'colors': ui.browserscope.colors,
+          'fontSize': fontSize,
+          'is3D': true,
+          'legend': legend,
+          'height': height,
+          'title': title,
+          'width': width,
+          'chartArea': { 'height': areaHeight, 'left': left, 'top': top, 'width': areaWidth },
+          'hAxis': { 'baseline': 0, 'title': hTitle },
+          'vAxis': { 'baseline': 0, 'title': vTitle }
+        });
+      } else {
+        setMessage(me.texts.empty);
+      }
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose
+  ui.browserscope = {
+
+    /**
+     * Your Browserscope API key.
+     *
+     * @memberOf ui.browserscope
+     * @type String
+     */
+    'key': '',
+
+    /**
+     * A flag to indicate if posting is enabled or disabled.
+     *
+     * @memberOf ui.browserscope
+     * @type Boolean
+     */
+    'postable': true,
+
+    /**
+     * The selector of the element to contain the entire Browserscope UI.
+     *
+     * @memberOf ui.browserscope
+     * @type String
+     */
+    'selector': '',
+
+    /**
+     * The class name used to style the user's browser name when it appears
+     * in charts.
+     *
+     * @memberOf ui.browserscope
+     * @type String
+     */
+    'uaClass': 'rt-ua-cur',
+
+    /**
+     * Object containing various timings settings.
+     *
+     * @memberOf ui.browserscope
+     * @type Object
+     */
+    'timings': {
+
+      /**
+       * The delay between removing abandoned script and iframe elements (secs).
+       *
+       * @memberOf ui.browserscope.timings
+       * @type Number
+       */
+      'cleanup': 10,
+
+      /**
+       * The delay before refreshing the cumulative results after posting (secs).
+       *
+       * @memberOf ui.browserscope.timings
+       * @type Number
+       */
+      'refresh': 3,
+
+      /**
+       * The delay between load attempts (secs).
+       *
+       * @memberOf ui.browserscope.timings
+       * @type Number
+       */
+      'retry': 5,
+
+      /**
+       * The time to wait for a request to finish (secs).
+       *
+       * @memberOf ui.browserscope.timings
+       * @type Number
+       */
+      'timeout': 10
+    },
+
+    /**
+     * Object containing various text messages.
+     *
+     * @memberOf ui.browserscope
+     * @type Object
+     */
+    'texts': {
+
+      /**
+       * The text shown when their is no recorded data available to report.
+       *
+       * @memberOf ui.browserscope.texts
+       * @type String
+       */
+      'empty': 'No data available',
+
+      /**
+       * The text shown when the cumulative results data cannot be retrieved.
+       *
+       * @memberOf ui.browserscope.texts
+       * @type String
+       */
+      'error': 'The get/post request has failed :(',
+
+      /**
+       * The text shown while waiting for the cumulative results data to load.
+       *
+       * @memberOf ui.browserscope.texts
+       * @type String
+       */
+      'loading': 'Loading cumulative results data&hellip;',
+
+      /**
+       * The text shown while posting the results snapshot to Browserscope.
+       *
+       * @memberOf ui.browserscope.texts
+       * @type String
+       */
+      'post': 'Posting results snapshot&hellip;',
+
+      /**
+       * The text shown while benchmarks are running.
+       *
+       * @memberOf ui.browserscope.texts
+       * @type String
+       */
+      'wait': 'Benchmarks running. Please wait&hellip;'
+    },
+
+    // loads cumulative results table
+    'load': load,
+
+    // posts benchmark snapshot to Browserscope
+    'post': post,
+
+    // purges the Browserscope response cache
+    'purge': purge,
+
+    // renders cumulative results table
+    'render': render
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  addListener(window, 'load', function() {
+    var me = ui.browserscope,
+        key = me.key,
+        placeholder = key && query(me.selector)[0];
+
+    // create results html
+    if (placeholder) {
+      setHTML(placeholder,
+        '<h1 id=bs-logo><a href=//www.browserscope.org/user/tests/table/#{key}>' +
+        '<span>Browserscope</span></a></h1>' +
+        '<div class=bs-rt><div id=bs-chart></div></div>',
+        { 'key': key });
+
+      // the element the charts are inserted into
+      me.container = query('#bs-chart')[0];
+
+      // Browserscope's UA div is inserted before an element with the id of "bs-ua-script"
+      loadScript('//www.browserscope.org/ua?o=js', me.container).id = 'bs-ua-script';
+
+      // the "autoload" string can be created with
+      // http://code.google.com/apis/loader/autoloader-wizard.html
+      loadScript('//www.google.com/jsapi?autoload=' + encodeURIComponent('{' +
+        'modules:[{' +
+          'name:"visualization",' +
+          'version:1,' +
+          'packages:["corechart","table"],' +
+          'callback:ui.browserscope.load' +
+        '}]' +
+      '}'));
+
+      // init garbage collector
+      cleanup();
+    }
+  });
+
+  // hide the chart while benchmarks are running
+  ui.on('start', function() {
+    setMessage(ui.browserscope.texts.wait);
+  })
+  .on('abort', function() {
+    ui.browserscope.render({ 'force': true });
+  });
+
+}(this, document));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/README.md
new file mode 100644
index 0000000..a304b6f
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/README.md
@@ -0,0 +1,6 @@
+# Instructions
+
+Before importing the Flash Builder project please perform the following steps:
+
+ 1. Copy/paste `benchmark.js` into the `src` folder.
+ 2. Copy/paste `AIRIntrospector.js` from some place like `C:\Program Files\Adobe\Adobe Flash Builder 4.5\sdks\4.5.0\frameworks\libs\air\AIRIntrospector.js` into the `src` folder.
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/bin-debug/air.swf b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/bin-debug/air.swf
new file mode 100644
index 0000000..5339954
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/bin-debug/air.swf
Binary files differ
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/air-app.xml b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/air-app.xml
new file mode 100644
index 0000000..0ef9707
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/air-app.xml
@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8" standalone="no"?>
+<application xmlns="http://ns.adobe.com/air/application/2.6">
+	<id>air</id>
+	<filename>air</filename>
+	<name>air</name>
+	<versionNumber>0.0.0</versionNumber>
+	<initialWindow>
+		<content>index.html</content>
+		<resizable>true</resizable>
+		<width>320</width>
+		<height>240</height>
+		<autoOrients>false</autoOrients>
+        <fullScreen>false</fullScreen>
+        <visible>true</visible>
+    </initialWindow>
+</application>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/air.mxml b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/air.mxml
new file mode 100644
index 0000000..c2c4660
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/air.mxml
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="utf-8"?>
+<s:WindowedApplication xmlns:fx="http://ns.adobe.com/mxml/2009"
+					   xmlns:s="library://ns.adobe.com/flex/spark"
+					   xmlns:mx="library://ns.adobe.com/flex/mx">
+	<fx:Declarations>
+	</fx:Declarations>
+</s:WindowedApplication>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/index.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/index.html
new file mode 100644
index 0000000..1a1eb19
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/benchmark.air/src/index.html
@@ -0,0 +1,37 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<title>Benchmark.js Adobe AIR Test Suite</title>
+		<script src="AIRIntrospector.js"></script>
+		<script>
+			var console = air.Introspector.Console;
+		</script>
+		<script src="benchmark.js"></script>
+	</head>
+	<body>
+		<script>
+			(function() {
+				var suite = new Benchmark.Suite;
+
+				// add tests
+				suite.add('RegExp#test', function() {
+					/o/.test('Hello World!');
+				})
+				.add('String#indexOf', function() {
+					'Hello World!'.indexOf('o') > -1;
+				})
+				// add listeners
+				.on('cycle', function(event) {
+					console.log(event.target);
+				})
+				.on('complete', function() {
+					console.log('Fastest is ' + this.filter('fastest').pluck('name'));
+				})
+				// don't run async to avoid JavaScript security errors
+				// http://help.adobe.com/en_US/AIR/1.5/devappshtml/WS5b3ccc516d4fbf351e63e3d118666ade46-7f0e.html
+				.run({ 'async': false });
+			}());
+		</script>
+	</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/index.html b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/index.html
new file mode 100644
index 0000000..9762fb6
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/index.html
@@ -0,0 +1,85 @@
+<!doctype html>
+<html lang="en">
+	<head>
+		<meta charset="utf-8">
+		<title>Benchmark.js Test Suite</title>
+		<link rel="stylesheet" href="../vendor/qunit/qunit/qunit.css">
+		<style>
+			applet {
+				position: absolute;
+				left: -9999em;
+			}
+		</style>
+	</head>
+	<body>
+		<div id="qunit"></div>
+		<script src="../benchmark.js"></script>
+		<script src="../vendor/platform.js/platform.js"></script>
+		<script>
+			(function() {
+				var hasOwnProperty = function hasOwnProperty(key) {
+					var parent = (this.constructor || Object).prototype;
+					return key in this && !(key in parent && this[key] === parent[key]);
+				};
+				if (typeof {}.hasOwnProperty != 'function') {
+					// redefine for Safari 2, else use the less accurate fallback for others
+					if ({}.__proto__ == Object.prototype) {
+						hasOwnProperty = function hasOwnProperty(key) {
+							var result;
+							this.__proto__ = [this.__proto__, this.__proto__ = null, result = key in this][0];
+							return result;
+						};
+					}
+					Object.prototype.hasOwnProperty = hasOwnProperty;
+				}
+			}());
+
+			// load the nanosecond timer
+			if (!/[?&]nojava=true(?:&|$)/.test(location.search)) {
+				document.write('<applet code="nano" archive="../nano.jar"></applet>');
+			}
+
+			// avoid syntax errors for `QUnit.throws` in older Firefoxes
+			document.write(platform.name == 'Firefox' && /^1\b/.test(platform.version)
+				? '<script src="../vendor/qunit/qunit/qunit-1.8.0.js"><\/script>'
+				: '<script src="../vendor/qunit/qunit/qunit.js"><\/script>'
+			);
+		</script>
+		<script>
+			// load test.js if not using require.js
+			document.write(/[?&]norequire=true(?:&|$)/.test(location.search)
+				? '<script src="test.js"><\/script>'
+				: '<script src="../vendor/requirejs/require.js"><\/script>'
+			);
+		</script>
+		<script>
+			// load Benchmark as a module
+			var Benchmark2;
+
+			window.require && require({
+				'baseUrl': '../vendor/requirejs/',
+				'urlArgs': 't=' + (+new Date),
+				'paths': {
+					'benchmark': '../../benchmark',
+					'platform': '../platform.js/platform'
+				}
+			},
+			['benchmark', 'platform'], function(Benchmark, platform) {
+				Benchmark2 = Benchmark;
+				Benchmark2.platform = platform;
+				require(['test.js']);
+			});
+
+			// set a more readable browser name
+			window.onload = function() {
+				var timeoutId = setInterval(function() {
+					var ua = document.getElementById('qunit-userAgent');
+					if (ua) {
+						ua.innerHTML = platform;
+						clearInterval(timeoutId);
+					}
+				}, 15);
+			};
+		</script>
+	</body>
+</html>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/run-test.sh b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/run-test.sh
new file mode 100755
index 0000000..43424e4
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/run-test.sh
@@ -0,0 +1,9 @@
+cd "$(dirname "$0")"
+for cmd in rhino ringo narwhal node; do
+	echo ""
+	echo "Testing in $cmd..."
+	$cmd test.js
+done
+echo ""
+echo "Testing in a browser..."
+open index.html
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/test.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/test.js
new file mode 100644
index 0000000..d694494
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/test/test.js
@@ -0,0 +1,2074 @@
+;(function(window, undefined) {
+  'use strict';
+
+  /** Use a single load function */
+  var load = typeof require == 'function' ? require : window.load;
+
+  /** The `platform` object to check */
+  var platform =
+    window.platform ||
+    load('../vendor/platform.js/platform.js') ||
+    window.platform;
+
+  /** The unit testing framework */
+  var QUnit =
+    window.QUnit || (
+      window.setTimeout || (window.addEventListener = window.setTimeout = / /),
+      window.QUnit = load('../vendor/qunit/qunit/qunit' + (platform.name == 'Narwhal' ? '-1.8.0' : '') + '.js') || window.QUnit,
+      load('../vendor/qunit-clib/qunit-clib.js'),
+      (window.addEventListener || 0).test && delete window.addEventListener,
+      window.QUnit
+    );
+
+  /** The `Benchmark` constructor to test */
+  var Benchmark =
+    window.Benchmark || (
+      Benchmark = load('../benchmark.js') || window.Benchmark,
+      Benchmark.Benchmark || Benchmark
+    );
+
+  /** API shortcut */
+  var forOwn = Benchmark.forOwn;
+
+  /** Used to get property descriptors */
+  var getDescriptor = Object.getOwnPropertyDescriptor;
+
+  /** Used to set property descriptors */
+  var setDescriptor = Object.defineProperty;
+
+  /** Shortcut used to convert array-like objects to arrays */
+  var slice = [].slice;
+
+  /** Used to resolve a value's internal [[Class]] */
+  var toString = {}.toString;
+
+  /** Used to check problem JScript properties (a.k.a. the [[DontEnum]] bug) */
+  var shadowed = {
+    'constructor': 1,
+    'hasOwnProperty': 2,
+    'isPrototypeOf': 3,
+    'propertyIsEnumerable': 4,
+    'toLocaleString': 5,
+    'toString': 6,
+    'valueOf': 7
+  };
+
+  /** Used to flag environments/features */
+  var support = {
+    'descriptors': !!function() {
+      try {
+        var o = {};
+        return (setDescriptor(o, o, o), 'value' in getDescriptor(o, o));
+      } catch(e) { }
+    }()
+  };
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Skips a given number of tests with a passing result.
+   *
+   * @private
+   * @param {Number} [count=1] The number of tests to skip.
+   */
+  function skipTest(count) {
+    count || (count = 1);
+    while (count--) {
+      ok(true, 'test skipped');
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // init Benchmark.options.minTime
+  Benchmark(function() { throw 0; }).run();
+
+  // set a shorter max time
+  Benchmark.options.maxTime = Benchmark.options.minTime * 5;
+
+  // explicitly call `QUnit.module()` instead of `module()`
+  // in case we are in a CLI environment
+  QUnit.module('Benchmark');
+
+  (function() {
+    test('has the default `Benchmark.platform` value', function() {
+      if (window.document) {
+        equal(String(Benchmark.platform), navigator.userAgent);
+      } else {
+        skipTest(1)
+      }
+    });
+
+    test('supports loading Benchmark.js as a module', function() {
+      if (window.document && window.require) {
+        equal((Benchmark2 || {}).version, Benchmark.version);
+      } else {
+        skipTest(1)
+      }
+    });
+
+    test('supports loading Platform.js as a module', function() {
+      if (window.document && window.require) {
+        var platform = (Benchmark2 || {}).platform || {};
+        equal(typeof platform.name, 'string');
+      } else {
+        skipTest(1)
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark constructor');
+
+  (function() {
+    test('creates a new instance when called without the `new` operator', function() {
+      ok(Benchmark() instanceof Benchmark);
+    });
+
+    test('supports passing an options object', function() {
+      var bench = Benchmark({ 'name': 'foo', 'fn': function() { } });
+      ok(bench.fn && bench.name == 'foo');
+    });
+
+    test('supports passing a "name" and "fn" argument', function() {
+      var bench = Benchmark('foo', function() { });
+      ok(bench.fn && bench.name == 'foo');
+    });
+
+    test('supports passing a "name" argument and an options object', function() {
+      var bench = Benchmark('foo', { 'fn': function() { } });
+      ok(bench.fn && bench.name == 'foo');
+    });
+
+    test('supports passing a "name" argument and an options object', function() {
+      var bench = Benchmark('foo', function() { }, { 'id': 'bar' });
+      ok(bench.fn && bench.name == 'foo' && bench.id == 'bar');
+    });
+
+    test('supports passing an empy string for the "fn" options property', function() {
+      var bench = Benchmark({ 'fn': '' }).run();
+      ok(!bench.error);
+    });
+
+    test('detects dead code', function() {
+      var bench = Benchmark(function() { }).run();
+      ok(/setup\(\)/.test(bench.compiled) ? !bench.error : bench.error);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark compilation');
+
+  (function() {
+    test('compiles using the default `Function#toString`', function() {
+      var bench = Benchmark({
+        'setup': function() { var a = 1; },
+        'fn': function() { throw a; },
+        'teardown': function() { a = 2; }
+      }).run();
+
+      var compiled = bench.compiled;
+      if (/setup\(\)/.test(compiled)) {
+        skipTest();
+      }
+      else {
+        ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
+      }
+    });
+
+    test('compiles using a custom "toString" method', function() {
+      var bench = Benchmark({
+        'setup': function() { },
+        'fn': function() { },
+        'teardown': function() { }
+      });
+
+      bench.setup.toString = function() { return 'var a = 1;' };
+      bench.fn.toString = function() { return 'throw a;' };
+      bench.teardown.toString = function() { return 'a = 2;' };
+      bench.run();
+
+      var compiled = bench.compiled;
+      if (/setup\(\)/.test(compiled)) {
+        skipTest();
+      }
+      else {
+        ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
+      }
+    });
+
+    test('compiles using a string value', function() {
+      var bench = Benchmark({
+        'setup': 'var a = 1;',
+        'fn': 'throw a;',
+        'teardown': 'a = 2;'
+      }).run();
+
+      var compiled = bench.compiled;
+      if (/setup\(\)/.test(compiled)) {
+        skipTest();
+      }
+      else {
+        ok(/var a\s*=\s*1/.test(compiled) && /throw a/.test(compiled) && /a\s*=\s*2/.test(compiled));
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark test binding');
+
+  (function() {
+    var count = 0;
+
+    var tests = {
+      'inlined "setup", "fn", and "teardown"': (
+        'if(/ops/.test(this))this._fn=true;'
+      ),
+      'called "fn" and inlined "setup"/"teardown" reached by error': function() {
+        count++;
+        if (/ops/.test(this)) {
+          this._fn = true;
+        }
+      },
+      'called "fn" and inlined "setup"/"teardown" reached by `return` statement': function() {
+        if (/ops/.test(this)) {
+          this._fn = true;
+        }
+        return;
+      }
+    };
+
+    forOwn(tests, function(fn, title) {
+      test('has correct binding for ' + title, function() {
+        var bench = Benchmark({
+          'setup': 'if(/ops/.test(this))this._setup=true;',
+          'fn': fn,
+          'teardown': 'if(/ops/.test(this))this._teardown=true;',
+          'onCycle': function() { this.abort(); }
+        }).run();
+
+        var compiled = bench.compiled;
+        if (/setup\(\)/.test(compiled)) {
+          skipTest(3);
+        }
+        else {
+          ok(bench._setup, 'correct binding for "setup"');
+          ok(bench._fn, 'correct binding for "fn"');
+          ok(bench._teardown, 'correct binding for "teardown"');
+        }
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.deepClone');
+
+  (function() {
+    function createCircularObject() {
+      var result = {
+        'foo': { 'b': { 'foo': { 'c': { } } } },
+        'bar': { }
+      };
+
+      result.foo.b.foo.c.foo = result;
+      result.bar.b = result.foo.b;
+      return result;
+    }
+
+    function Klass() {
+      this.a = 1;
+    }
+
+    Klass.prototype = { 'b': 1 };
+
+    var notCloneable = {
+      'an arguments object': arguments,
+      'an element': window.document && document.body,
+      'a function': Klass,
+      'a Klass instance': new Klass
+    };
+
+    var objects = {
+      'an array': ['a', 'b', 'c', ''],
+      'an array-like-object': { '0': 'a', '1': 'b', '2': 'c',  '3': '', 'length': 5 },
+      'boolean': false,
+      'boolean object': Object(false),
+      'an object': { 'a': 0, 'b': 1, 'c': 3 },
+      'an object with object values': { 'a': /a/, 'b': ['B'], 'c': { 'C': 1 } },
+      'null': null,
+      'a number': 3,
+      'a number object': Object(3),
+      'a regexp': /x/gim,
+      'a string': 'x',
+      'a string object': Object('x'),
+      'undefined': undefined
+    };
+
+    objects['an array'].length = 5;
+
+    forOwn(objects, function(object, key) {
+      test('clones ' + key + ' correctly', function() {
+        var kind = toString.call(object),
+            clone = Benchmark.deepClone(object);
+
+        if (object == null) {
+          equal(clone, object);
+        } else {
+          deepEqual(clone.valueOf(), object.valueOf());
+        }
+        if (object === Object(object)) {
+          ok(clone !== object);
+        } else {
+          skipTest();
+        }
+      });
+    });
+
+    forOwn(notCloneable, function(object, key) {
+      test('does not clone ' + key, function() {
+        ok(Benchmark.deepClone(object) === object);
+      });
+    });
+
+    test('clones using Klass#deepClone', function() {
+      var object = new Klass;
+      Klass.prototype.deepClone = function() { return new Klass; };
+
+      var clone = Benchmark.deepClone(object);
+      ok(clone !== object && clone instanceof Klass);
+
+      delete Klass.prototype.clone;
+    });
+
+    test('clones problem JScript properties', function() {
+      var clone = Benchmark.deepClone(shadowed);
+      deepEqual(clone, shadowed);
+    });
+
+    test('clones string object with custom property', function() {
+      var object = new String('x');
+      object.x = 1;
+
+      var clone = Benchmark.deepClone(object);
+      ok(clone == 'x' && typeof clone == 'object' && clone.x === 1 && toString.call(clone) == '[object String]');
+    });
+
+    test('clones objects with circular references', function() {
+      var object = createCircularObject(),
+          clone = Benchmark.deepClone(object);
+
+      ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
+    });
+
+    test('clones non-extensible objects with circular references', function() {
+      if (Object.preventExtensions) {
+        var object = Object.preventExtensions(createCircularObject());
+        Object.preventExtensions(object.bar.b);
+
+        var clone = Benchmark.deepClone(object);
+        ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
+      } else {
+        skipTest(1)
+      }
+    });
+
+    test('clones sealed objects with circular references', function() {
+      if (Object.seal) {
+        var object = Object.seal(createCircularObject());
+        Object.seal(object.bar.b);
+
+        var clone = Benchmark.deepClone(object);
+        ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
+      } else {
+        skipTest(1)
+      }
+    });
+
+    test('clones frozen objects with circular references', function() {
+      if (Object.freeze) {
+        var object = Object.freeze(createCircularObject());
+        Object.freeze(object.bar.b);
+
+        var clone = Benchmark.deepClone(object);
+        ok(clone.bar.b === clone.foo.b && clone === clone.foo.b.foo.c.foo && clone !== object);
+      } else {
+        skipTest(1)
+      }
+    });
+
+    test('clones objects with custom descriptors and circular references', function() {
+      var accessor,
+          descriptor;
+
+      if (support.descriptors) {
+        var object = setDescriptor({}, 'foo', {
+          'configurable': true,
+          'value': setDescriptor({}, 'b', {
+            'writable': true,
+            'value': setDescriptor({}, 'foo', {
+              'get': function() { return accessor; },
+              'set': function(value) { accessor = value; }
+            })
+          })
+        });
+
+        setDescriptor(object, 'bar', { 'value': {} });
+        object.foo.b.foo = { 'c': object };
+        object.bar.b = object.foo.b;
+
+        var clone = Benchmark.deepClone(object);
+        ok(clone !== object &&
+          clone.bar.b === clone.foo.b &&
+          clone !== clone.foo.b.foo.c.foo &&
+          (descriptor = getDescriptor(clone, 'foo')) &&
+          descriptor.configurable && !(descriptor.enumerable && descriptor.writable) &&
+          (descriptor = getDescriptor(clone.foo, 'b')) &&
+          descriptor.writable && !(descriptor.configurable && descriptor.enumerable) &&
+          (descriptor = getDescriptor(clone.foo.b, 'foo')) &&
+          descriptor.get && descriptor.set &&
+          (descriptor = getDescriptor(clone.foo.b, 'foo')) &&
+          !(descriptor.configurable && descriptor.enumerable && descriptor.writable) &&
+          (descriptor = getDescriptor(clone, 'bar')) &&
+          !(descriptor.configurable && descriptor.enumerable && descriptor.writable));
+      }
+      else {
+        skipTest(1)
+      }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.each');
+
+  (function() {
+    var xpathResult;
+
+    var objects = {
+      'array': ['a', 'b', 'c', ''],
+      'array-like-object': { '0': 'a', '1': 'b', '2': 'c',  '3': '', 'length': 5 },
+      'xpath snapshot': null
+    };
+
+    if (window.document && document.evaluate) {
+      xpathResult = [document.documentElement, document.getElementsByTagName('head')[0], document.body];
+      objects['xpath snapshot'] = document.evaluate('//*[self::html or self::head or self::body]', document, null, 7, null);
+    }
+
+    objects.array.length = 5;
+
+    forOwn(objects, function(object, key) {
+      test('passes the correct arguments when passing an ' + key, function() {
+        if (object) {
+          var args
+          Benchmark.each(object, function() {
+            args || (args = slice.call(arguments));
+          });
+
+          if (key == 'xpath snapshot') {
+            ok(args[0] === xpathResult[0]);
+          } else {
+            equal(args[0], 'a');
+          }
+          equal(args[1], 0);
+          ok(args[2] === object);
+        }
+        else {
+          skipTest(3);
+        }
+      });
+
+      test('returns the passed object when passing an ' + key, function() {
+        if (object) {
+          var actual = Benchmark.each(object, function() { });
+          ok(actual === object);
+        }
+        else {
+          skipTest();
+        }
+      });
+
+      test('iterates over all indexes when passing an ' + key, function() {
+        if (object) {
+          var values = [];
+          Benchmark.each(object, function(value) {
+            values.push(value);
+          });
+
+          deepEqual(values, key == 'xpath snapshot' ? xpathResult : ['a', 'b', 'c', '']);
+        }
+        else {
+          skipTest();
+        }
+      });
+
+      test('exits early when returning `false` when passing an ' + key, function() {
+        if (object) {
+          var values = [];
+          Benchmark.each(object, function(value) {
+            values.push(value);
+            return values.length < 2;
+          });
+
+          deepEqual(values, key == 'xpath snapshot' ? xpathResult.slice(0, 2) : ['a', 'b']);
+        }
+        else {
+          skipTest();
+        }
+      });
+    });
+
+    test('passes the third callback argument as an object', function() {
+      var thirdArg;
+      Benchmark.each('hello', function(value, index, object) {
+        thirdArg = object;
+      });
+
+      ok(thirdArg && typeof thirdArg == 'object');
+    });
+
+    test('iterates over strings by index', function() {
+      var values = [];
+      Benchmark.each('hello', function(value) {
+        values.push(value)
+      });
+
+      deepEqual(values, ['h', 'e', 'l', 'l', 'o']);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.extend');
+
+  (function() {
+    test('allows no source argument', function() {
+      var object = {};
+      equal(Benchmark.extend(object), object);
+    });
+
+    test('allows a single source argument', function() {
+      var source = { 'x': 1, 'y': 1 },
+          actual = Benchmark.extend({}, source);
+
+      deepEqual(Benchmark.extend({}, source), { 'x': 1, 'y': 1 });
+    });
+
+    test('allows multiple source arguments', function() {
+      var source1 = { 'x': 1, 'y': 1 },
+          source2 = { 'y': 2, 'z': 2 },
+          actual = Benchmark.extend({}, source1, source2);
+
+      deepEqual(actual, { 'x': 1, 'y': 2, 'z': 2 });
+    });
+
+    test('will add inherited source properties', function() {
+      function Source() { }
+      Source.prototype.x = 1;
+      deepEqual(Benchmark.extend({}, new Source), { 'x': 1 });
+    });
+
+    test('will add problem JScript properties', function() {
+      deepEqual(Benchmark.extend({}, shadowed), shadowed);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.filter');
+
+  (function() {
+    var objects = {
+      'array': ['a', 'b', 'c', ''],
+      'array-like-object': { '0': 'a', '1': 'b', '2': 'c',  '3': '', 'length': 5 }
+    };
+
+    objects.array.length = 5;
+
+    forOwn(objects, function(object, key) {
+      test('passes the correct arguments when passing an ' + key, function() {
+        var args;
+        Benchmark.filter(object, function() {
+          args || (args = slice.call(arguments));
+        });
+
+        deepEqual(args, ['a', 0, object]);
+      });
+
+      test('produces the correct result when passing an ' + key, function() {
+        var actual = Benchmark.filter(object, function(value, index) {
+          return index > 0;
+        });
+
+        deepEqual(actual, ['b', 'c', '']);
+      });
+
+      test('iterates over sparse ' + key + 's correctly', function() {
+        var actual = Benchmark.filter(object, function(value) {
+          return value === undefined;
+        });
+
+        deepEqual(actual, []);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.forOwn');
+
+  (function() {
+    function fn() {
+      // no-op
+    }
+
+    function KlassA() {
+      this.a = 1;
+      this.b = 2;
+      this.c = 3;
+    }
+
+    function KlassB() {
+      this.a = 1;
+      this.constructor = 2;
+      this.hasOwnProperty = 3;
+      this.isPrototypeOf = 4;
+      this.propertyIsEnumerable = 5;
+      this.toLocaleString = 6;
+      this.toString = 7;
+      this.valueOf = 8;
+    }
+
+    function KlassC() {
+      // no-op
+    }
+
+    fn.a = 1;
+    fn.b = 2;
+    fn.c = 3;
+
+    KlassC.prototype.a = 1;
+    KlassC.prototype.b = 2;
+    KlassC.prototype.c = 3;
+
+    var objects = {
+      'an arguments object': arguments,
+      'a function': fn,
+      'an object': new KlassA,
+      'an object shadowing properties on Object.prototype': new KlassB,
+      'a prototype object': KlassC.prototype,
+      'a string': 'abc'
+    };
+
+    forOwn(objects, function(object, key) {
+      test('passes the correct arguments when passing ' + key, function() {
+        var args;
+        Benchmark.forOwn(object, function() {
+          args || (args = slice.call(arguments));
+        });
+
+        equal(typeof args[0], key == 'a string' ? 'string' : 'number');
+        equal(typeof args[1], 'string');
+        equal(args[2] && typeof args[2], key == 'a function' ? 'function' : 'object');
+      });
+
+      test('returns the passed object when passing ' + key, function() {
+        var actual = Benchmark.forOwn(object, function() { });
+        deepEqual(actual, object);
+      });
+
+      test('iterates over own properties when passing ' + key, function() {
+        var values = [];
+        Benchmark.forOwn(object, function(value) {
+          values.push(value);
+        });
+
+        if (object instanceof KlassB) {
+          deepEqual(values.sort(), [1, 2, 3, 4, 5, 6, 7, 8]);
+        } else if (key == 'a string') {
+          deepEqual(values, ['a', 'b', 'c']);
+        } else {
+          deepEqual(values.sort(), [1, 2, 3]);
+        }
+      });
+
+      test('exits early when returning `false` when passing ' + key, function() {
+        var values = [];
+        Benchmark.forOwn(object, function(value) {
+          values.push(value);
+          return false;
+        });
+
+        equal(values.length, 1);
+      });
+
+      if (object instanceof KlassB) {
+        test('exits correctly when transitioning to the JScript [[DontEnum]] fix', function() {
+          var values = [];
+          Benchmark.forOwn(object, function(value) {
+            values.push(value);
+            return values.length < 2;
+          });
+
+          equal(values.length, 2);
+        });
+      }
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.formatNumber');
+
+  (function() {
+    test('formats a million correctly', function() {
+      equal(Benchmark.formatNumber(1e6), '1,000,000');
+    });
+
+    test('formats less than 100 correctly', function() {
+      equal(Benchmark.formatNumber(23), '23');
+    });
+
+    test('formats numbers with decimal values correctly', function() {
+      equal(Benchmark.formatNumber(1234.56), '1,234.56');
+    });
+
+    test('formats negative numbers correctly', function() {
+      equal(Benchmark.formatNumber(-1234.56), '-1,234.56');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.hasKey');
+
+  (function() {
+    test('returns `true` for own properties', function() {
+      var object = { 'x': 1 };
+      equal(Benchmark.hasKey(object, 'x'), true);
+    });
+
+    test('returns `false` for inherited properties', function() {
+      equal(Benchmark.hasKey({}, 'toString'), false);
+    });
+
+    test('doesn\'t use an object\'s `hasOwnProperty` method', function() {
+      var object = { 'hasOwnProperty': function() { return true; } };
+      equal(Benchmark.hasKey(object, 'x'), false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.indexOf');
+
+  (function() {
+    var objects = {
+      'array': ['a', 'b', 'c', ''],
+      'array-like-object': { '0': 'a', '1': 'b', '2': 'c',  '3': '', 'length': 5 }
+    };
+
+    objects.array.length = 5;
+
+    forOwn(objects, function(object, key) {
+      test('produces the correct result when passing an ' + key, function() {
+        equal(Benchmark.indexOf(object, 'b'), 1);
+      });
+
+      test('matches values by strict equality when passing an ' + key, function() {
+        equal(Benchmark.indexOf(object, new String('b')), -1);
+      });
+
+      test('iterates over sparse ' + key + 's correctly', function() {
+        equal(Benchmark.indexOf(object, undefined), -1);
+      });
+    });
+
+    test('searches from the given `fromIndex`', function() {
+      var array = ['a', 'b', 'c', 'a'];
+      equal(Benchmark.indexOf(array, 'a', 1), 3);
+    });
+
+    test('handles extreme negative `fromIndex` values correctly', function() {
+      var array = ['a'];
+      array['-1'] = 'z';
+      equal(Benchmark.indexOf(array, 'z', -2), -1);
+    });
+
+    test('handles extreme positive `fromIndex` values correctly', function() {
+      var object = { '0': 'a', '1': 'b', '2': 'c', 'length': 2 };
+      equal(Benchmark.indexOf(object, 'c', 2), -1);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.interpolate');
+
+  (function() {
+    test('replaces tokens correctly', function() {
+      var actual = Benchmark.interpolate('#{greeting} #{location}.', {
+        'greeting': 'Hello',
+        'location': 'world'
+      });
+
+      equal(actual, 'Hello world.');
+    });
+
+    test('ignores inherited object properties', function() {
+      var actual = Benchmark.interpolate('x#{toString}', {});
+      equal(actual, 'x#{toString}');
+    });
+
+    test('allows for no template object', function() {
+      var actual = Benchmark.interpolate('x');
+      equal(actual, 'x');
+    });
+
+    test('replaces duplicate tokens', function() {
+      var actual = Benchmark.interpolate('#{x}#{x}#{x}', { 'x': 'a' });
+      equal(actual, 'aaa');
+    });
+
+    test('handles keys containing RegExp special characters', function() {
+      var actual = Benchmark.interpolate('#{.*+?^=!:${}()|[]\\/}', { '.*+?^=!:${}()|[]\\/': 'x' });
+      equal(actual, 'x');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.invoke');
+
+  (function() {
+    var objects = {
+      'array': ['a', ['b'], 'c', null],
+      'array-like-object': { '0': 'a', '1': ['b'], '2': 'c',  '3': null, 'length': 5 }
+    };
+
+    objects.array.length = 5;
+
+    forOwn(objects, function(object, key) {
+      test('produces the correct result when passing an ' + key, function() {
+        var actual = Benchmark.invoke(object, 'concat');
+        deepEqual(actual, ['a', ['b'], 'c', undefined, undefined]);
+        equal('4' in actual, false);
+      });
+
+      test('passes the correct arguments to the invoked method when passing an ' + key, function() {
+        var actual = Benchmark.invoke(object, 'concat', 'x', 'y', 'z');
+        deepEqual(actual, ['axyz', ['b', 'x', 'y', 'z'], 'cxyz', undefined, undefined]);
+        equal('4' in actual, false);
+      });
+
+      test('handles options object with callbacks correctly when passing an ' + key, function() {
+        function callback() {
+          callbacks.push(slice.call(arguments));
+        }
+
+        var callbacks = [];
+        var actual = Benchmark.invoke(object, {
+          'name': 'concat',
+          'args': ['x', 'y', 'z'],
+          'onStart': callback,
+          'onCycle': callback,
+          'onComplete': callback
+        });
+
+        deepEqual(actual, ['axyz', ['b', 'x', 'y', 'z'], 'cxyz', undefined, undefined]);
+        equal('4' in actual, false);
+
+        equal(callbacks[0].length, 1);
+        equal(callbacks[0][0].target, 'a');
+        deepEqual(callbacks[0][0].currentTarget, object);
+        equal(callbacks[0][0].type, 'start');
+        equal(callbacks[1][0].type, 'cycle');
+        equal(callbacks[5][0].type, 'complete');
+      });
+
+      test('supports queuing when passing an ' + key, function() {
+        var lengths = [];
+        var actual = Benchmark.invoke(object, {
+          'name': 'concat',
+          'queued': true,
+          'args': 'x',
+          'onCycle': function() {
+            lengths.push(object.length);
+          }
+        });
+
+        deepEqual(lengths, [5, 4, 3, 2]);
+        deepEqual(actual, ['ax', ['b', 'x'], 'cx', undefined, undefined]);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.join');
+
+  (function() {
+    var objects = {
+      'array': ['a', 'b', ''],
+      'array-like-object': { '0': 'a', '1': 'b', '2': '', 'length': 4 },
+      'object': { 'a': '0', 'b': '1', '': '2' }
+    };
+
+    objects.array.length = 4;
+
+    forOwn(objects, function(object, key) {
+      test('joins correctly using the default separator when passing an ' + key, function() {
+        equal(Benchmark.join(object), key == 'object' ? 'a: 0,b: 1,: 2' : 'a,b,');
+      });
+
+      test('joins correctly using a custom separator when passing an ' + key, function() {
+        equal(Benchmark.join(object, '+', '@'), key == 'object' ? 'a@0+b@1+@2' :  'a+b+');
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.map');
+
+  (function() {
+    var objects = {
+      'array': ['a', 'b', 'c', ''],
+      'array-like-object': { '0': 'a', '1': 'b', '2': 'c',  '3': '', 'length': 5 }
+    };
+
+    objects.array.length = 5;
+
+    forOwn(objects, function(object, key) {
+      test('passes the correct arguments when passing an ' + key, function() {
+        var args;
+        Benchmark.map(object, function() {
+          args || (args = slice.call(arguments));
+        });
+
+        deepEqual(args, ['a', 0, object]);
+      });
+
+      test('produces the correct result when passing an ' + key, function() {
+        var actual = Benchmark.map(object, function(value, index) {
+          return value + index;
+        });
+
+        deepEqual(actual, ['a0', 'b1', 'c2', '3', undefined]);
+        equal('4' in actual, false);
+      });
+
+      test('produces an array of the correct length for sparse ' + key + 's', function() {
+        equal(Benchmark.map(object, function() { }).length, 5);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.pluck');
+
+  (function() {
+    var objects = {
+      'array': [{ '_': 'a' }, { '_': 'b' }, { '_': 'c' }, null],
+      'array-like-object': { '0': { '_': 'a' }, '1': { '_': 'b' }, '2': { '_': 'c' },  '3': null, 'length': 5 }
+    };
+
+    objects.array.length = 5;
+
+    forOwn(objects, function(object, key) {
+      test('produces the correct result when passing an ' + key, function() {
+        var actual = Benchmark.pluck(object, '_');
+        deepEqual(actual, ['a', 'b', 'c', undefined, undefined]);
+        equal('4' in actual, false);
+      });
+
+      test('produces the correct result for non-existent keys when passing an ' + key, function() {
+        var actual = Benchmark.pluck(object, 'non-existent');
+        deepEqual(actual, [undefined, undefined, undefined, undefined, undefined]);
+        equal('4' in actual, false);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.reduce');
+
+  (function() {
+    var objects = {
+      'array': ['b', 'c', ''],
+      'array-like-object': { '0': 'b', '1': 'c',  '2': '', 'length': 4 }
+    };
+
+    objects.array.length = 4;
+
+    forOwn(objects, function(object, key) {
+      test('passes the correct arguments when passing an ' + key, function() {
+        var args;
+        Benchmark.reduce(object, function() {
+          args || (args = slice.call(arguments));
+        }, 'a');
+
+        deepEqual(args, ['a', 'b', 0, object]);
+      });
+
+      test('accumulates correctly when passing an ' + key, function() {
+        var actual = Benchmark.reduce(object, function(string, value) {
+          return string + value;
+        }, 'a');
+
+        equal(actual, 'abc');
+      });
+
+      test('handles arguments with no initial value correctly when passing an ' + key, function() {
+        var args;
+        Benchmark.reduce(object, function() {
+          args || (args = slice.call(arguments));
+        });
+
+        deepEqual(args, ['b', 'c', 1, object]);
+      });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark#clone');
+
+  (function() {
+    var bench = Benchmark(function() { this.count += 0; }).run();
+
+    test('produces the correct result passing no arguments', function() {
+      var clone = bench.clone();
+      deepEqual(clone, bench);
+      ok(clone.stats != bench.stats && clone.times != bench.times && clone.options != bench.options);
+    });
+
+    test('produces the correct result passing a data object', function() {
+      var clone = bench.clone({ 'fn': '', 'name': 'foo' });
+      ok(clone.fn === '' && clone.options.fn === '');
+      ok(clone.name == 'foo' && clone.options.name == 'foo');
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark#run');
+
+  (function() {
+    var data = { 'onComplete': 0, 'onCycle': 0, 'onStart': 0 };
+
+    var bench = Benchmark({
+      'fn': function() {
+        this.count += 0;
+      },
+      'onStart': function() {
+        data.onStart++;
+      },
+      'onComplete': function() {
+        data.onComplete++;
+      }
+    })
+    .run();
+
+    test('onXYZ callbacks should not be triggered by internal benchmark clones', function() {
+      equal(data.onStart, 1);
+      equal(data.onComplete, 1);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  forOwn({
+    'Benchmark': Benchmark,
+    'Benchmark.Suite': Benchmark.Suite
+  },
+  function(Constructor, namespace) {
+
+    QUnit.module(namespace + '#emit');
+
+    (function() {
+      test('emits passed arguments', function() {
+        var args,
+            object = Constructor();
+
+        object.on('args', function() { args = slice.call(arguments, 1); });
+        object.emit('args', 'a', 'b', 'c');
+        deepEqual(args, ['a', 'b', 'c']);
+      });
+
+      test('emits with no listeners', function() {
+        var event = Benchmark.Event('empty'),
+            object = Constructor();
+
+        object.emit(event);
+        equal(event.cancelled, false);
+      });
+
+      test('emits with an event type of "toString"', function() {
+        var event = Benchmark.Event('toString'),
+            object = Constructor();
+
+        object.emit(event);
+        equal(event.cancelled, false);
+      });
+
+      test('returns the last listeners returned value', function() {
+        var event = Benchmark.Event('result'),
+            object = Constructor();
+
+        object.on('result', function() { return 'x'; });
+        object.on('result', function() { return 'y'; });
+        equal(object.emit(event), 'y');
+      });
+
+      test('aborts the emitters listener iteration when `event.aborted` is `true`', function() {
+        var event = Benchmark.Event('aborted'),
+            object = Constructor();
+
+        object.on('aborted', function(event) {
+          event.aborted = true;
+          return false;
+        });
+
+        object.on('aborted', function(event) {
+          // should not get here
+          event.aborted = false;
+          return true;
+        });
+
+        equal(object.emit(event), false);
+        equal(event.aborted, true);
+      });
+
+      test('cancels the event if a listener explicitly returns `false`', function() {
+        var event = Benchmark.Event('cancel'),
+            object = Constructor();
+
+        object.on('cancel', function() { return false; });
+        object.on('cancel', function() { return true; });
+        object.emit(event);
+        equal(event.cancelled, true);
+      });
+
+      test('uses a shallow clone of the listeners when emitting', function() {
+        var event,
+            listener2 = function(eventObject) { eventObject.listener2 = true },
+            object = Constructor();
+
+        object.on('shallowclone', function(eventObject) {
+          event = eventObject;
+          object.off(event.type, listener2);
+        })
+        .on('shallowclone', listener2)
+        .emit('shallowclone');
+
+        ok(event.listener2);
+      });
+
+      test('emits a custom event object', function() {
+        var event = Benchmark.Event('custom'),
+            object = Constructor();
+
+        object.on('custom', function(eventObject) { eventObject.touched = true; });
+        object.emit(event);
+        ok(event.touched);
+      });
+
+      test('sets `event.result` correctly', function() {
+        var event = Benchmark.Event('result'),
+            object = Constructor();
+
+        object.on('result', function() { return 'x'; });
+        object.emit(event);
+        equal(event.result, 'x');
+      });
+
+      test('sets `event.type` correctly', function() {
+        var event,
+            object = Constructor();
+
+        object.on('type', function(eventObj) {
+          event = eventObj;
+        });
+
+        object.emit('type');
+        equal(event.type, 'type');
+      });
+    }());
+
+    /*------------------------------------------------------------------------*/
+
+    QUnit.module(namespace + '#listeners');
+
+    (function() {
+      test('returns the correct listeners', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x', listener);
+        deepEqual(object.listeners('x'), [listener]);
+      });
+
+      test('returns an array and initializes previously uninitialized listeners', function() {
+        var object = Constructor();
+        deepEqual(object.listeners('x'), []);
+        deepEqual(object.events, { 'x': [] });
+      });
+    }());
+
+    /*------------------------------------------------------------------------*/
+
+    QUnit.module(namespace + '#off');
+
+    (function() {
+      test('returns the benchmark', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x', listener);
+        equal(object.off('x', listener), object);
+      });
+
+      test('will ignore inherited properties of the event cache', function() {
+        var Dummy = function() { },
+            listener = function() { },
+            object = Constructor();
+
+        Dummy.prototype.x = [listener];
+        object.events = new Dummy;
+
+        object.off('x', listener);
+        deepEqual(object.events.x, [listener]);
+      });
+
+      test('handles an event type and listener', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x', listener);
+        object.off('x', listener);
+        deepEqual(object.events.x, []);
+      });
+
+      test('handles unregistering duplicate listeners', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x', listener);
+        object.on('x', listener);
+
+        var events = object.events;
+        object.off('x', listener);
+        deepEqual(events.x, [listener]);
+
+        object.off('x', listener);
+        deepEqual(events.x, []);
+      });
+
+      test('handles a non-registered listener', function() {
+        var object = Constructor();
+        object.off('x', function() { });
+        equal(object.events, undefined);
+      });
+
+      test('handles space separated event type and listener', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x', listener);
+        object.on('y', listener);
+
+        var events = object.events;
+        object.off('x y', listener);
+        deepEqual(events.x, []);
+        deepEqual(events.y, []);
+      });
+
+      test('handles space separated event type and no listener', function() {
+        var listener1 = function() { },
+            listener2 = function() { },
+            object = Constructor();
+
+        object.on('x', listener1);
+        object.on('y', listener2);
+
+        var events = object.events;
+        object.off('x y');
+        deepEqual(events.x, []);
+        deepEqual(events.y, []);
+      });
+
+      test('handles no arguments', function() {
+        var listener1 = function() { },
+            listener2 = function() { },
+            listener3 = function() { },
+            object = Constructor();
+
+        object.on('x', listener1);
+        object.on('y', listener2);
+        object.on('z', listener3);
+
+        var events = object.events;
+        object.off();
+        deepEqual(events.x, []);
+        deepEqual(events.y, []);
+        deepEqual(events.z, []);
+      });
+    }());
+
+    /*------------------------------------------------------------------------*/
+
+    QUnit.module(namespace + '#on');
+
+    (function() {
+      test('returns the benchmark', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        equal(object.on('x', listener), object);
+      });
+
+      test('will ignore inherited properties of the event cache', function() {
+        var Dummy = function() { },
+            listener1 = function() { },
+            listener2 = function() { },
+            object = Constructor();
+
+        Dummy.prototype.x = [listener1];
+        object.events = new Dummy;
+
+        object.on('x', listener2);
+        deepEqual(object.events.x, [listener2]);
+      });
+
+      test('handles an event type and listener', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x', listener);
+        deepEqual(object.events.x, [listener]);
+      });
+
+      test('handles registering duplicate listeners', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x', listener);
+        object.on('x', listener);
+        deepEqual(object.events.x, [listener, listener]);
+      });
+
+      test('handles space separated event type and listener', function() {
+        var listener = function() { },
+            object = Constructor();
+
+        object.on('x y', listener);
+
+        var events = object.events;
+        deepEqual(events.x, [listener]);
+        deepEqual(events.y, [listener]);
+      });
+    }());
+  });
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite#abort');
+
+  (function() {
+    test('igores abort calls when the suite isn\'t running', function() {
+      var fired = false;
+      var suite = Benchmark.Suite('suite', {
+        'onAbort': function() { fired = true; }
+      });
+
+      suite.add('foo', function() { });
+      suite.abort();
+      equal(fired, false);
+    });
+
+    test('ignores abort calls from `Benchmark.Suite#reset` when the suite isn\'t running', function() {
+      var fired = false;
+      var suite = Benchmark.Suite('suite', {
+        'onAbort': function() { fired = true; }
+      });
+
+      suite.add('foo', function() { });
+      suite.reset();
+      equal(fired, false);
+    });
+
+    asyncTest('emits an abort event when running', function() {
+      var fired = false;
+
+      Benchmark.Suite({
+        'onAbort': function() { fired = true; }
+      })
+      .on('start', function() {
+        this.abort();
+      })
+      .on('complete', function() {
+        ok(fired);
+        QUnit.start();
+      })
+      .add(function(){ })
+      .run({ 'async': true });
+    });
+
+    asyncTest('emits an abort event after calling `Benchmark.Suite#reset`', function() {
+      var fired = false;
+
+      Benchmark.Suite({
+        'onAbort': function() { fired = true; }
+      })
+      .on('start', function() {
+        this.reset();
+      })
+      .on('complete', function() {
+        ok(fired);
+        QUnit.start();
+      })
+      .add(function(){ })
+      .run({ 'async': true });
+    });
+
+    asyncTest('should abort deferred benchmark', function() {
+      var fired = false,
+          suite = Benchmark.Suite();
+
+      suite.on('complete', function() {
+        equal(fired, false);
+        QUnit.start();
+      })
+      .add('a', {
+        'defer': true,
+        'fn': function(deferred) {
+          // avoid test inlining
+          suite.name;
+          // delay resolve
+          setTimeout(function() {
+            deferred.resolve();
+            suite.abort();
+          }, 10);
+        }
+      })
+      .add('b', {
+        'defer': true,
+        'fn': function(deferred) {
+          // avoid test inlining
+          suite.name;
+          // delay resolve
+          setTimeout(function() {
+            deferred.resolve();
+            fired = true;
+          }, 10);
+        }
+      })
+      .run();
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite#concat');
+
+  (function() {
+    var args = arguments;
+
+    test('doesn\'t treat an arguments object like an array', function() {
+      var suite = Benchmark.Suite();
+      deepEqual(suite.concat(args), [args]);
+    });
+
+    test('flattens array arguments', function() {
+      var suite = Benchmark.Suite();
+      deepEqual(suite.concat([1, 2], 3, [4, 5]), [1, 2, 3, 4, 5]);
+    });
+
+    test('supports concating sparse arrays', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[2] = 2;
+      suite.length = 3;
+
+      var actual = suite.concat(3);
+      deepEqual(actual, [0, undefined, 2, 3]);
+      equal('1' in actual, false);
+    });
+
+    test('supports sparse arrays as arguments', function() {
+      var suite = Benchmark.Suite(),
+          sparse = [];
+
+      sparse[0] = 0;
+      sparse[2] = 2;
+      sparse.length = 3;
+
+      var actual = suite.concat(sparse);
+      deepEqual(actual, [0, undefined, 2]);
+      equal('1' in actual, false);
+    });
+
+    test('creates a new array', function() {
+      var suite = Benchmark.Suite();
+      ok(suite.concat(1) !== suite);
+    });
+  }(1, 2, 3));
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite#reverse');
+
+  (function() {
+    test('reverses the element order', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 1;
+      suite.length = 2;
+
+      var actual = suite.reverse();
+      equal(actual, suite);
+      deepEqual(slice.call(actual), [1, 0]);
+    });
+
+    test('supports reversing sparse arrays', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[2] = 2;
+      suite.length = 3;
+
+      var actual = suite.reverse();
+      equal(actual, suite);
+      deepEqual(slice.call(actual), [2, undefined, 0]);
+      equal('1' in actual, false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite#shift');
+
+  (function() {
+    test('removes the first element', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 1;
+      suite.length = 2;
+
+      var actual = suite.shift();
+      equal(actual, 0);
+      deepEqual(slice.call(suite), [1]);
+    });
+
+    test('shifts an object with no elements', function() {
+      var suite = Benchmark.Suite(),
+          actual = suite.shift();
+
+      equal(actual, undefined);
+      deepEqual(slice.call(suite), []);
+    });
+
+    test('should have no elements when length is 0 after shift', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite.length = 1;
+      suite.shift();
+
+      // ensure element is removed
+      equal('0' in suite, false);
+      equal(suite.length, 0);
+    });
+
+    test('supports shifting sparse arrays', function() {
+      var suite = Benchmark.Suite();
+      suite[1] = 1;
+      suite[3] = 3;
+      suite.length = 4;
+
+      var actual = suite.shift();
+      equal(actual, undefined);
+      deepEqual(slice.call(suite), [1, undefined, 3]);
+      equal('1' in suite, false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite#slice');
+
+  (function() {
+    var suite = Benchmark.Suite();
+    suite[0] = 0;
+    suite[1] = 1;
+    suite[2] = 2;
+    suite[3] = 3;
+    suite.length = 4;
+
+    test('works with no arguments', function() {
+      var actual = suite.slice();
+      deepEqual(actual, [0, 1, 2, 3]);
+      ok(suite !== actual);
+    });
+
+    test('works with positive `start` argument', function() {
+      var actual = suite.slice(2);
+      deepEqual(actual, [2, 3]);
+      ok(suite !== actual);
+    });
+
+    test('works with positive `start` and `end` arguments', function() {
+      var actual = suite.slice(1, 3);
+      deepEqual(actual, [1, 2]);
+      ok(suite !== actual);
+    });
+
+    test('works with `end` values exceeding length', function() {
+      var actual = suite.slice(1, 10);
+      deepEqual(actual, [1, 2, 3]);
+      ok(suite !== actual);
+    });
+
+    test('works with negative `start` and `end` arguments', function() {
+      var actual = suite.slice(-3, -1);
+      deepEqual(actual, [1, 2]);
+      ok(suite !== actual);
+    });
+
+    test('works with an extreme negative `end` value', function() {
+      var actual = suite.slice(1, -10);
+      deepEqual(actual, []);
+      equal('-1' in actual, false);
+      ok(suite !== actual);
+    });
+
+    test('supports slicing sparse arrays', function() {
+      var sparse = Benchmark.Suite();
+      sparse[1] = 1;
+      sparse[3] = 3;
+      sparse.length = 4;
+
+      var actual = sparse.slice(0, 2);
+      deepEqual(actual, [undefined, 1]);
+      equal('0' in actual, false);
+
+      actual = sparse.slice(1);
+      deepEqual(actual, [1, undefined, 3]);
+      equal('1' in actual, false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite#splice');
+
+  (function() {
+    test('works with no arguments', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite.length = 1;
+
+      var actual = suite.splice();
+      deepEqual(actual, []);
+      deepEqual(slice.call(suite), [0]);
+    });
+
+    test('works with only the `start` argument', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 1;
+      suite.length = 2;
+
+      var actual = suite.splice(1);
+      deepEqual(actual, [1]);
+      deepEqual(slice.call(suite), [0]);
+    });
+
+    test('should have no elements when length is 0 after splice', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite.length = 1
+      suite.splice(0, 1);
+
+      // ensure element is removed
+      equal('0' in suite, false);
+      equal(suite.length, 0);
+    });
+
+    test('works with positive `start` argument', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 3;
+      suite.length = 2;
+
+      var actual = suite.splice(1, 0, 1, 2);
+      deepEqual(actual, []);
+      deepEqual(slice.call(suite), [0, 1, 2, 3]);
+    });
+
+    test('works with positive `start` and `deleteCount` arguments', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 3;
+      suite.length = 2;
+
+      var actual = suite.splice(1, 1, 1, 2);
+      deepEqual(actual, [3]);
+      deepEqual(slice.call(suite), [0, 1, 2]);
+    });
+
+    test('works with `deleteCount` values exceeding length', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 3;
+      suite.length = 2;
+
+      var actual = suite.splice(1, 10, 1, 2);
+      deepEqual(actual, [3]);
+      deepEqual(slice.call(suite), [0, 1, 2]);
+    });
+
+    test('works with negative `start` and `deleteCount` arguments', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 3;
+      suite.length = 2;
+
+      var actual = suite.splice(-1, -1, 1, 2);
+      deepEqual(actual, []);
+      deepEqual(slice.call(suite), [0, 1, 2, 3]);
+    });
+
+    test('works with an extreme negative `deleteCount` value', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 0;
+      suite[1] = 3;
+      suite.length = 2;
+
+      var actual = suite.splice(0, -10, 1, 2);
+      deepEqual(actual, []);
+      deepEqual(slice.call(suite), [1, 2, 0, 3]);
+    });
+
+    test('supports splicing sparse arrays', function() {
+      var suite = Benchmark.Suite();
+      suite[1] = 1;
+      suite[3] = 3;
+      suite.length = 4;
+
+      var actual = suite.splice(1, 2, 1, 2);
+      deepEqual(actual, [1, undefined]);
+      equal(actual.length, 2);
+      deepEqual(slice.call(suite), [undefined, 1, 2, 3]);
+      equal('0' in suite, false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite#unshift');
+
+  (function() {
+    test('adds a first element', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 1;
+      suite.length = 1;
+
+      var actual = suite.unshift(0);
+      equal(actual, 2);
+      deepEqual(slice.call(suite), [0, 1]);
+    });
+
+    test('adds multiple elements to the front', function() {
+      var suite = Benchmark.Suite();
+      suite[0] = 3;
+      suite.length = 1;
+
+      var actual = suite.unshift(0, 1, 2);
+      equal(actual, 4);
+      deepEqual(slice.call(suite), [0, 1, 2, 3]);
+    });
+
+    test('supports unshifting sparse arrays', function() {
+      var suite = Benchmark.Suite();
+      suite[1] = 2;
+      suite.length = 2;
+
+      var actual = suite.unshift(0);
+      equal(actual, 3);
+      deepEqual(slice.call(suite), [0, undefined, 2]);
+      equal('1' in suite, false);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite filtered results onComplete');
+
+  (function() {
+    var count = 0,
+        suite = Benchmark.Suite();
+
+    suite.add('a', function() {
+      count++;
+    })
+    .add('b', function() {
+      for (var i = 0; i < 1e6; i++) {
+        count++;
+      }
+    })
+    .add('c', function() {
+      throw new TypeError;
+    });
+
+    asyncTest('should filter by fastest', function() {
+      suite.on('complete', function() {
+        suite.off();
+        deepEqual(this.filter('fastest').pluck('name'), ['a']);
+        QUnit.start();
+      })
+      .run({ 'async': true });
+    });
+
+    asyncTest('should filter by slowest', function() {
+      suite.on('complete', function() {
+        suite.off();
+        deepEqual(this.filter('slowest').pluck('name'), ['b']);
+        QUnit.start();
+      })
+      .run({ 'async': true });
+    });
+
+    asyncTest('should filter by successful', function() {
+      suite.on('complete', function() {
+        suite.off();
+        deepEqual(this.filter('successful').pluck('name'), ['a', 'b']);
+        QUnit.start();
+      })
+      .run({ 'async': true });
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.Suite event flow');
+
+  (function() {
+    var events = [],
+        callback = function(event) { events.push(event); };
+
+    var suite = Benchmark.Suite('suite', {
+      'onAdd': callback,
+      'onAbort': callback,
+      'onClone': callback,
+      'onError': callback,
+      'onStart': callback,
+      'onCycle': callback,
+      'onComplete': callback,
+      'onReset': callback
+    })
+    .add('bench', function() {
+      throw null;
+    }, {
+      'onAbort': callback,
+      'onClone': callback,
+      'onError': callback,
+      'onStart': callback,
+      'onCycle': callback,
+      'onComplete': callback,
+      'onReset': callback
+    })
+    .run({ 'async': false });
+
+    // first Suite#onAdd
+    test('should emit the suite "add" event first', function() {
+      var event = events[0];
+      ok(event.type == 'add' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
+    });
+
+    // next we start the Suite because no reset was needed
+    test('should emit the suite "start" event', function() {
+      var event = events[1];
+      ok(event.type == 'start' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
+    });
+
+    // and so start the first benchmark
+    test('should emit the benchmark "start" event', function() {
+      var event = events[2];
+      ok(event.type == 'start' && event.currentTarget.name == 'bench');
+    });
+
+    // oh no! we abort because of an error
+    test('should emit the benchmark "error" event', function() {
+      var event = events[3];
+      ok(event.type == 'error' && event.currentTarget.name == 'bench');
+    });
+
+    // benchmark error triggered
+    test('should emit the benchmark "abort" event', function() {
+      var event = events[4];
+      ok(event.type == 'abort' && event.currentTarget.name == 'bench');
+    });
+
+    // we reset the benchmark as part of the abort
+    test('should emit the benchmark "reset" event', function() {
+      var event = events[5];
+      ok(event.type == 'reset' && event.currentTarget.name == 'bench');
+    });
+
+    // benchmark is cycle is finished
+    test('should emit the benchmark "cycle" event', function() {
+      var event = events[6];
+      ok(event.type == 'cycle' && event.currentTarget.name == 'bench');
+    });
+
+    // benchmark is complete
+    test('should emit the benchmark "complete" event', function() {
+      var event = events[7];
+      ok(event.type == 'complete' && event.currentTarget.name == 'bench');
+    });
+
+    // the benchmark error triggers a Suite error
+    test('should emit the suite "error" event', function() {
+      var event = events[8];
+      ok(event.type == 'error' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
+    });
+
+    // the Suite cycle finishes
+    test('should emit the suite "cycle" event', function() {
+      var event = events[9];
+      ok(event.type == 'cycle' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
+    });
+
+    // the Suite completes
+    test('finally it should emit the suite "complete" event', function() {
+      var event = events[10];
+      ok(event.type == 'complete' && event.currentTarget.name == 'suite' && event.target.name == 'bench');
+    });
+
+    test('emitted all expected events', function() {
+      ok(events.length == 11);
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Deferred benchmarks');
+
+  (function() {
+    asyncTest('should run a deferred benchmark correctly', function() {
+      Benchmark(function(deferred) {
+        setTimeout(function() { deferred.resolve(); }, 1e3);
+      }, {
+        'defer': true,
+        'onComplete': function() {
+          equal(this.hz.toFixed(0), 1);
+          QUnit.start();
+        }
+      })
+      .run();
+    });
+
+    asyncTest('should run with string values for "fn", "setup", and "teardown"', function() {
+      Benchmark({
+        'defer': true,
+        'setup': 'var x = [3, 2, 1];',
+        'fn': 'setTimeout(function() { x.sort(); deferred.resolve(); }, 10);',
+        'teardown': 'x.length = 0;',
+        'onComplete': function() {
+          ok(true);
+          QUnit.start();
+        }
+      })
+      .run();
+    });
+
+    asyncTest('should run recursively', function() {
+      Benchmark({
+        'defer': true,
+        'setup': 'var x = [3, 2, 1];',
+        'fn': 'for (var i = 0; i < 100; i++) x[ i % 2 ? "sort" : "reverse" ](); deferred.resolve();',
+        'teardown': 'x.length = 0;',
+        'onComplete': function() {
+          ok(true);
+          QUnit.start();
+        }
+      })
+      .run();
+    });
+
+    asyncTest('should execute "setup", "fn", and "teardown" in correct order', function() {
+      var fired = [];
+
+      Benchmark({
+        'defer': true,
+        'setup': function() {
+          fired.push('setup');
+        },
+        'fn': function(deferred) {
+          fired.push('fn');
+          setTimeout(function() { deferred.resolve(); }, 10);
+        },
+        'teardown': function() {
+          fired.push('teardown');
+        },
+        'onComplete': function() {
+          var actual = fired.join().replace(/(fn,)+/g, '$1').replace(/(setup,fn,teardown(?:,|$))+/, '$1');
+          equal(actual, 'setup,fn,teardown');
+          QUnit.start();
+        }
+      })
+      .run();
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  QUnit.module('Benchmark.deepClone');
+
+  (function() {
+    asyncTest('avoids call stack limits', function() {
+      var result,
+          count = 0,
+          object = {},
+          recurse = function() { count++; recurse(); };
+
+      setTimeout(function() {
+        ok(result, 'avoids call stack limits (stack limit is ' + (count - 1) + ')');
+        QUnit.start();
+      }, 15);
+
+      if (toString.call(window.java) == '[object JavaPackage]') {
+        // Java throws uncatchable errors on call stack overflows, so to avoid
+        // them I chose a number higher than Rhino's call stack limit without
+        // dynamically testing for the actual limit
+        count = 3e3;
+      } else {
+        try { recurse(); } catch(e) { }
+      }
+
+      // exceed limit
+      count++;
+      for (var i = 0, sub = object; i <= count; i++) {
+        sub = sub[i] = {};
+      }
+
+      try {
+        for (var i = 0, sub = Benchmark.deepClone(object); sub = sub[i]; i++) { }
+        result = --i == count;
+      } catch(e) { }
+    });
+  }());
+
+  /*--------------------------------------------------------------------------*/
+
+  // explicitly call `QUnit.start()` for Narwhal, Rhino, and RingoJS
+  if (!window.document) {
+    QUnit.start();
+  }
+}(typeof global == 'object' && global || this));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/LICENSE.txt b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/LICENSE.txt
new file mode 100644
index 0000000..dadad22
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
+
+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.
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/README.md
new file mode 100644
index 0000000..7e2665e
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/README.md
@@ -0,0 +1,33 @@
+# Docdown <sup>v1.0.0</sup>
+
+A simple JSDoc to Markdown documentation generator.
+
+## Documentation
+
+The documentation for Docdown can be viewed here: [/doc/README.md](https://github.com/jdalton/docdown/blob/master/doc/README.md#readme)
+
+For a list of upcoming features, check out our [roadmap](https://github.com/jdalton/docdown/wiki/Roadmap).
+
+## Installation and usage
+
+Usage example:
+
+```php
+require("docdown.php");
+
+// generate Markdown
+$markdown = docdown(array(
+  "path" => $filepath,
+  "url"  => "https://github.com/username/project/blob/master/my.js"
+));
+```
+
+## Author
+
+* [John-David Dalton](http://allyoucanleet.com/)
+  [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter")
+
+## Contributors
+
+* [Mathias Bynens](http://mathiasbynens.be/)
+  [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter")
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/docdown.php b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/docdown.php
new file mode 100644
index 0000000..6a960d4
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/docdown.php
@@ -0,0 +1,38 @@
+<?php
+/*!
+ * Docdown v1.0.0-pre
+ * Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
+ * Available under MIT license <http://mths.be/mit>
+ */
+require(dirname(__FILE__) . '/src/DocDown/Generator.php');
+
+/**
+ * Generates Markdown from JSDoc entries in a given file.
+ * 
+ * @param {Array} [$options=array()] The options array.
+ * @returns {String} The generated Markdown.
+ * @example
+ *
+ * // specify a file path
+ * $markdown = docdown(array(
+ *   // path to js file
+ *   'path' => $filepath,
+ *   // url used to reference line numbers in code
+ *   'url' => 'https://github.com/username/project/blob/master/my.js'
+ * ));
+ *
+ * // or pass raw js
+ * $markdown = docdown(array(
+ *   // raw JavaScript source
+ *   'source' => $rawJS,
+ *   // documentation title
+ *   'title' => 'My API Documentation',
+ *   // url used to reference line numbers in code
+ *   'url' => 'https://github.com/username/project/blob/master/my.js'
+ * ));
+ */
+function docdown( $options = array() ) {
+  $gen = new Generator($options);
+  return $gen->generate();
+}
+?>
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/src/DocDown/Entry.php b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/src/DocDown/Entry.php
new file mode 100644
index 0000000..e7924c0
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/src/DocDown/Entry.php
@@ -0,0 +1,304 @@
+<?php
+
+/**
+ * A class to simplify parsing a single JSDoc entry.
+ */
+class Entry {
+
+  /**
+   * The documentation entry.
+   *
+   * @memberOf Entry
+   * @type String
+   */
+  public $entry = '';
+
+  /**
+   * The language highlighter used for code examples.
+   *
+   * @memberOf Entry
+   * @type String
+   */
+  public $lang = '';
+
+  /**
+   * The source code.
+   *
+   * @memberOf Entry
+   * @type String
+   */
+  public $source = '';
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The Entry constructor.
+   *
+   * @constructor
+   * @param {String} $entry The documentation entry to analyse.
+   * @param {String} $source The source code.
+   * @param {String} $lang The language highlighter used for code examples.
+   */
+  public function __construct( $entry, $source, $lang = 'js' ) {
+    $this->entry = $entry;
+    $this->lang = $lang;
+    $this->source = str_replace(PHP_EOL, "\n", $source);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Extracts the documentation entries from source code.
+   *
+   * @static
+   * @memberOf Entry
+   * @param {String} $source The source code.
+   * @returns {Array} The array of entries.
+   */
+  public static function getEntries( $source ) {
+    preg_match_all('#/\*\*(?![-!])[\s\S]*?\*/\s*[^\n]+#', $source, $result);
+    return array_pop($result);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Checks if the entry is a function reference.
+   *
+   * @private
+   * @memberOf Entry
+   * @returns {Boolean} Returns `true` if the entry is a function reference, else `false`.
+   */
+  private function isFunction() {
+    return !!(
+      $this->isCtor() ||
+      count($this->getParams()) ||
+      count($this->getReturns()) ||
+      preg_match('/\*\s*@function\b/', $this->entry)
+    );
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Extracts the function call from the entry.
+   *
+   * @memberOf Entry
+   * @returns {String} The function call.
+   */
+  public function getCall() {
+    preg_match('#\*/\s*(?:function ([^(]*)|(.*?)(?=[:=,]|return\b))#', $this->entry, $result);
+    if ($result = array_pop($result)) {
+      $result = array_pop(explode('var ', trim(trim(array_pop(explode('.', $result))), "'")));
+    }
+    // resolve name
+    // avoid $this->getName() because it calls $this->getCall()
+    preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $name);
+    if (count($name)) {
+      $name = trim($name[1]);
+    } else {
+      $name = $result;
+    }
+    // compile function call syntax
+    if ($this->isFunction()) {
+      // compose parts
+      $result = array($result);
+      $params = $this->getParams();
+      foreach ($params as $param) {
+        $result[] = $param[1];
+      }
+      // format
+      $result = $name .'('. implode(array_slice($result, 1), ', ') .')';
+      $result = str_replace(', [', ' [, ', str_replace('], [', ', ', $result));
+    }
+    return $result ? $result : $name;
+  }
+
+  /**
+   * Extracts the entry description.
+   *
+   * @memberOf Entry
+   * @returns {String} The entry description.
+   */
+  public function getDesc() {
+    preg_match('#/\*\*(?:\s*\*)?([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
+    if (count($result)) {
+      $type = $this->getType();
+      $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+      $result = ($type == 'Function' ? '' : '(' . str_replace('|', ', ', trim($type, '{}')) . '): ') . $result;
+    }
+    return $result;
+  }
+
+  /**
+   * Extracts the entry `example` data.
+   *
+   * @memberOf Entry
+   * @returns {String} The entry `example` data.
+   */
+  public function getExample() {
+    preg_match('#\*\s*@example\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
+    if (count($result)) {
+      $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', "\n", $result[1]));
+      $result = '```' . $this->lang . "\n" . $result . "\n```";
+    }
+    return $result;
+  }
+
+  /**
+   * Resolves the line number of the entry.
+   *
+   * @memberOf Entry
+   * @returns {Number} The line number.
+   */
+  public function getLineNumber() {
+    preg_match_all('/\n/', substr($this->source, 0, strrpos($this->source, $this->entry) + strlen($this->entry)), $lines);
+    return count(array_pop($lines)) + 1;
+  }
+
+  /**
+   * Extracts the entry `member` data.
+   *
+   * @memberOf Entry
+   * @param {Number} $index The index of the array value to return.
+   * @returns {Array|String} The entry `member` data.
+   */
+  public function getMembers( $index = null ) {
+    preg_match('#\*\s*@member(?:Of)?\s+([^\n]+)#', $this->entry, $result);
+    if (count($result)) {
+      $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+      $result = preg_split('/,\s*/', $result);
+    }
+    return $index !== null ? @$result[$index] : $result;
+  }
+
+  /**
+   * Extracts the entry `name` data.
+   *
+   * @memberOf Entry
+   * @returns {String} The entry `name` data.
+   */
+  public function getName() {
+    preg_match('#\*\s*@name\s+([^\n]+)#', $this->entry, $result);
+    if (count($result)) {
+      $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+    } else {
+      $result = array_shift(explode('(', $this->getCall()));
+    }
+    return $result;
+  }
+
+  /**
+   * Extracts the entry `param` data.
+   *
+   * @memberOf Entry
+   * @param {Number} $index The index of the array value to return.
+   * @returns {Array} The entry `param` data.
+   */
+  public function getParams( $index = null ) {
+    preg_match_all('#\*\s*@param\s+\{([^}]+)\}\s+(\[.+\]|[$\w]+)\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#i', $this->entry, $result);
+    if (count($result = array_filter(array_slice($result, 1)))) {
+      // repurpose array
+      foreach ($result as $param) {
+        foreach ($param as $key => $value) {
+          if (!is_array($result[0][$key])) {
+            $result[0][$key] = array();
+          }
+          $result[0][$key][] = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $value));
+        }
+      }
+      $result = $result[0];
+    }
+    return $index !== null ? @$result[$index] : $result;
+  }
+
+  /**
+   * Extracts the entry `returns` data.
+   *
+   * @memberOf Entry
+   * @returns {String} The entry `returns` data.
+   */
+  public function getReturns() {
+    preg_match('#\*\s*@returns\s+\{([^}]+)\}\s+([\s\S]*?)(?=\*\s\@[a-z]|\*/)#', $this->entry, $result);
+    if (count($result)) {
+      $result = array_map('trim', array_slice($result, 1));
+      $result[0] = str_replace('|', ', ', $result[0]);
+      $result[1] = preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]);
+    }
+    return $result;
+  }
+
+  /**
+   * Extracts the entry `type` data.
+   *
+   * @memberOf Entry
+   * @returns {String} The entry `type` data.
+   */
+  public function getType() {
+    preg_match('#\*\s*@type\s+([^\n]+)#', $this->entry, $result);
+    if (count($result)) {
+      $result = trim(preg_replace('/(?:^|\n)\s*\* ?/', ' ', $result[1]));
+    } else {
+      $result = $this->isFunction() ? 'Function' : 'Unknown';
+    }
+    return $result;
+  }
+
+  /**
+   * Checks if an entry is a constructor.
+   *
+   * @memberOf Entry
+   * @returns {Boolean} Returns true if a constructor, else false.
+   */
+  public function isCtor() {
+    return !!preg_match('/\*\s*@constructor\b/', $this->entry);
+  }
+
+  /**
+   * Checks if an entry *is* assigned to a prototype.
+   *
+   * @memberOf Entry
+   * @returns {Boolean} Returns true if assigned to a prototype, else false.
+   */
+  public function isPlugin() {
+    return !$this->isCtor() && !$this->isPrivate() && !$this->isStatic();
+  }
+
+  /**
+   * Checks if an entry is private.
+   *
+   * @memberOf Entry
+   * @returns {Boolean} Returns true if private, else false.
+   */
+  public function isPrivate() {
+    return !!preg_match('/\*\s*@private\b/', $this->entry) || strrpos($this->entry, '@') === false;
+  }
+
+  /**
+   * Checks if an entry is *not* assigned to a prototype.
+   *
+   * @memberOf Entry
+   * @returns {Boolean} Returns true if not assigned to a prototype, else false.
+   */
+  public function isStatic() {
+    $public = !$this->isPrivate();
+    $result = $public && !!preg_match('/\*\s*@static\b/', $this->entry);
+
+    // set in cases where it isn't explicitly stated
+    if ($public && !$result) {
+      if ($parent = array_pop(preg_split('/[#.]/', $this->getMembers(0)))) {
+        foreach (Entry::getEntries($this->source) as $entry) {
+          $entry = new Entry($entry, $this->source);
+          if ($entry->getName() == $parent) {
+            $result = !$entry->isCtor();
+            break;
+          }
+        }
+      } else {
+        $result = true;
+      }
+    }
+    return $result;
+  }
+}
+?>
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/src/DocDown/Generator.php b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/src/DocDown/Generator.php
new file mode 100644
index 0000000..6c1873d
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/docdown/src/DocDown/Generator.php
@@ -0,0 +1,416 @@
+<?php
+
+require(dirname(__FILE__) . "/Entry.php");
+
+/**
+ * Generates Markdown from JSDoc entries.
+ */
+class Generator {
+
+  /**
+   * An array of JSDoc entries.
+   *
+   * @memberOf Generator
+   * @type Array
+   */
+  public $entries = array();
+
+  /**
+   * An options array used to configure the generator.
+   *
+   * @memberOf Generator
+   * @type Array
+   */
+  public $options = array();
+
+  /**
+   * The entire file's source code.
+   *
+   * @memberOf Generator
+   * @type String
+   */
+  public $source = '';
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * The Generator constructor.
+   *
+   * @constructor
+   * @param {String} $source The source code to parse.
+   * @param {Array} $options The options array.
+   */
+  public function __construct( $source, $options = array() ) {
+    // juggle arguments
+    if (is_array($source)) {
+      $options = $source;
+    } else {
+      $options['source'] = $source;
+    }
+    if (isset($options['source']) && realpath($options['source'])) {
+      $options['path'] = $options['source'];
+    }
+    if (isset($options['path'])) {
+      preg_match('/(?<=\.)[a-z]+$/', $options['path'], $ext);
+      $options['source'] = file_get_contents($options['path']);
+      $ext = array_pop($ext);
+
+      if (!isset($options['lang']) && $ext) {
+        $options['lang'] = $ext;
+      }
+      if (!isset($options['title'])) {
+        $options['title'] = ucfirst(basename($options['path'])) . ' API documentation';
+      }
+    }
+    if (!isset($options['lang'])) {
+      $options['lang'] = 'js';
+    }
+
+    $this->options = $options;
+    $this->source = str_replace(PHP_EOL, "\n", $options['source']);
+    $this->entries = Entry::getEntries($this->source);
+
+    foreach ($this->entries as $index => $value) {
+      $this->entries[$index] = new Entry($value, $this->source, $options['lang']);
+    }
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Performs common string formatting operations.
+   *
+   * @private
+   * @static
+   * @memberOf Generator
+   * @param {String} $string The string to format.
+   * @returns {String} The formatted string.
+   */
+  private static function format($string) {
+    $counter = 0;
+
+    // tokenize inline code snippets
+    preg_match_all('/`[^`]+`/', $string, $tokenized);
+    $tokenized = $tokenized[0];
+    foreach ($tokenized as $snippet) {
+      $string = str_replace($snippet, '__token' . ($counter++) .'__', $string);
+    }
+
+    // italicize parentheses
+    $string = preg_replace('/(^|\s)(\([^)]+\))/', '$1*$2*', $string);
+
+    // mark numbers as inline code
+    $string = preg_replace('/ (-?\d+(?:.\d+)?)(?!\.[^\n])/', ' `$1`', $string);
+
+    // detokenize inline code snippets
+    $counter = 0;
+    foreach ($tokenized as $snippet) {
+      $string = str_replace('__token' . ($counter++) . '__', $snippet, $string);
+    }
+
+    return trim($string);
+  }
+
+  /**
+   * Modify a string by replacing named tokens with matching assoc. array values.
+   *
+   * @private
+   * @static
+   * @memberOf Generator
+   * @param {String} $string The string to modify.
+   * @param {Array|Object} $object The template object.
+   * @returns {String} The modified string.
+   */
+  private static function interpolate($string, $object) {
+    preg_match_all('/#\{([^}]+)\}/', $string, $tokens);
+    $tokens = array_unique(array_pop($tokens));
+
+    foreach ($tokens as $token) {
+      $pattern = '/#\{' . $token . '\}/';
+      $replacement = '';
+
+      if (is_object($object)) {
+        preg_match('/\(([^)]+?)\)$/', $token, $args);
+        $args = preg_split('/,\s*/', array_pop($args));
+        $method = 'get' . ucfirst(str_replace('/\([^)]+?\)$/', '', $token));
+
+        if (method_exists($object, $method)) {
+          $replacement = (string) call_user_func_array(array($object, $method), $args);
+        } else if (isset($object->{$token})) {
+          $replacement = (string) $object->{$token};
+        }
+      } else if (isset($object[$token])) {
+        $replacement = (string) $object[$token];
+      }
+      $string = preg_replace($pattern, trim($replacement), $string);
+    }
+    return Generator::format($string);
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Resolves the entry's hash used to navigate the documentation.
+   *
+   * @private
+   * @memberOf Generator
+   * @param {Number|Object} $entry The entry object.
+   * @param {String} $member The name of the member.
+   * @returns {String} The url hash.
+   */
+  private function getHash( $entry, $member = '' ) {
+    $entry = is_numeric($entry) ? $this->entries[$entry] : $entry;
+    $member = !$member ? $entry->getMembers(0) : $member;
+    $result = ($member ? $member . ($entry->isPlugin() ? 'prototype' : '') : '') . $entry->getCall();
+    $result = preg_replace('/\(\[|\[\]/', '', $result);
+    $result = preg_replace('/[ =\'"{}.()\]]/', '', $result);
+    $result = preg_replace('/[[#,]/', '-', $result);
+    return strtolower($result);
+  }
+
+  /**
+   * Resolves the entry's url for the specific line number.
+   *
+   * @private
+   * @memberOf Generator
+   * @param {Number|Object} $entry The entry object.
+   * @returns {String} The url.
+   */
+  private function getLineUrl( $entry ) {
+    $entry = is_numeric($entry) ? $this->entries($entry) : $entry;
+    return $this->options['url'] . '#L' . $entry->getLineNumber();
+  }
+
+  /**
+   * Extracts the character used to separate the entry's name from its member.
+   *
+   * @private
+   * @memberOf Generator
+   * @param {Number|Object} $entry The entry object.
+   * @returns {String} The separator.
+   */
+  private function getSeparator( $entry ) {
+    $entry = is_numeric($entry) ? $this->entries($entry) : $entry;
+    return $entry->isPlugin() ? '.prototype.' : '.';
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Generates Markdown from JSDoc entries.
+   *
+   * @memberOf Generator
+   * @returns {String} The rendered Markdown.
+   */
+  public function generate() {
+    $api = array();
+    $compiling = false;
+    $openTag = "\n<!-- div -->\n";
+    $closeTag = "\n<!-- /div -->\n";
+    $result = array('# ' . $this->options['title']);
+    $toc = 'toc';
+
+    // initialize $api array
+    foreach ($this->entries as $entry) {
+
+      if (!$entry->isPrivate()) {
+        $name = $entry->getName();
+        $members = $entry->getMembers();
+        $members = count($members) ? $members : array('');
+
+        foreach ($members as $member) {
+          // create api category arrays
+          if (!isset($api[$member]) && $member) {
+            $api[$member] = new Entry('', '', $entry->lang);
+            $api[$member]->static = array();
+            $api[$member]->plugin = array();
+          }
+          // append entry to api category
+          if (!$member || $entry->isCtor() || ($entry->getType() == 'Object' &&
+              !preg_match('/[=:]\s*null\s*[,;]?$/', $entry->entry))) {
+            $member = ($member ? $member . ($entry->isPlugin() ? '#' : '.') : '') . $name;
+            $entry->static = @$api[$member] ? $api[$member]->static : array();
+            $entry->plugin = @$api[$member] ? $api[$member]->plugin : array();
+            $api[$member]  = $entry;
+          }
+          else if ($entry->isStatic()) {
+            $api[$member]->static[] = $entry;
+          } else if (!$entry->isCtor()) {
+            $api[$member]->plugin[] = $entry;
+          }
+        }
+      }
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // custom sort for root level entries
+    // TODO: see how well it handles deeper namespace traversal
+    function sortCompare($a, $b) {
+      $score = array( 'a' => 0, 'b' => 0);
+      foreach (array( 'a' => $a, 'b' => $b) as $key => $value) {
+        // capitalized keys that represent constructor properties are last
+        if (preg_match('/[#.][A-Z]/', $value)) {
+          $score[$key] = 0;
+        }
+        // lowercase keys with prototype properties are next to last
+        else if (preg_match('/#[a-z]/', $value)) {
+          $score[$key] = 1;
+        }
+        // lowercase keys with static properties next to first
+        else if (preg_match('/\.[a-z]/', $value)) {
+          $score[$key] = 2;
+        }
+        // lowercase keys with no properties are first
+        else if (preg_match('/^[^#.]+$/', $value)) {
+          $score[$key] = 3;
+        }
+      }
+      $score = $score['b'] - $score['a'];
+      return $score ? $score : strcasecmp($a, $b);
+    }
+
+    uksort($api, 'sortCompare');
+
+    // sort static and plugin sub-entries
+    foreach ($api as $entry) {
+      foreach (array('static', 'plugin') as $kind) {
+        $sortBy = array( 'a' => array(), 'b' => array(), 'c' => array() );
+        foreach ($entry->{$kind} as $subentry) {
+          $name = $subentry->getName();
+          // functions w/o ALL-CAPs names are last
+          $sortBy['a'][] = $subentry->getType() == 'Function' && !preg_match('/^[A-Z_]+$/', $name);
+          // ALL-CAPs properties first
+          $sortBy['b'][] = preg_match('/^[A-Z_]+$/', $name);
+          // lowercase alphanumeric sort
+          $sortBy['c'][] = strtolower($name);
+        }
+        array_multisort($sortBy['a'], SORT_ASC,  $sortBy['b'], SORT_DESC, $sortBy['c'], SORT_ASC, $entry->{$kind});
+      }
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // compile TOC
+    $result[] = $openTag;
+
+    foreach ($api as $key => $entry) {
+      $entry->hash = $this->getHash($entry);
+      $entry->href = $this->getLineUrl($entry);
+
+      $member = $entry->getMembers(0);
+      $member = ($member ? $member . ($entry->isPlugin() ? '.prototype.' : '.') : '') . $entry->getName();
+
+      $entry->member = preg_replace('/' . $entry->getName() . '$/', '', $member);
+
+      $compiling = $compiling ? ($result[] = $closeTag) : true;
+
+      // assign TOC hash
+      if (count($result) == 2) {
+        $toc = $member;
+      }
+
+      // add root entry
+      array_push(
+        $result,
+        $openTag, '## ' . (count($result) == 2 ? '<a id="' . $toc . '"></a>' : '') . '`' . $member . '`',
+        Generator::interpolate('* [`' . $member . '`](##{hash})', $entry)
+      );
+
+      // add static and plugin sub-entries
+      foreach (array('static', 'plugin') as $kind) {
+        if ($kind == 'plugin' && count($entry->plugin)) {
+          array_push(
+            $result,
+            $closeTag,
+            $openTag,
+            '## `' . $member . ($entry->isCtor() ? '.prototype`' : '`')
+          );
+        }
+        foreach ($entry->{$kind} as $subentry) {
+          $subentry->hash = $this->getHash($subentry);
+          $subentry->href = $this->getLineUrl($subentry);
+          $subentry->member = $member;
+          $subentry->separator = $this->getSeparator($subentry);
+          $result[] = Generator::interpolate('* [`#{member}#{separator}#{name}`](##{hash})', $subentry);
+        }
+      }
+    }
+
+    array_push($result, $closeTag, $closeTag);
+
+    /*------------------------------------------------------------------------*/
+
+    // compile content
+    $compiling = false;
+    $result[] = $openTag;
+
+    foreach ($api as $entry) {
+      // add root entry
+      $member = $entry->member . $entry->getName();
+      $compiling = $compiling ? ($result[] = $closeTag) : true;
+
+      array_push($result, $openTag, '## `' . $member . '`');
+
+      foreach (array($entry, 'static', 'plugin') as $kind) {
+        $subentries = is_string($kind) ? $entry->{$kind} : array($kind);
+
+        // title
+        if ($kind != 'static' && $entry->getType() != 'Object' &&
+              count($subentries) && $subentries[0] != $kind) {
+          if ($kind == 'plugin') {
+            $result[] = $closeTag;
+          }
+          array_push(
+            $result,
+            $openTag,
+            '## `' . $member . ($kind == 'plugin' ? '.prototype`' : '`')
+          );
+        }
+
+        // body
+        foreach ($subentries as $subentry) {
+          // description
+          array_push(
+            $result,
+            $openTag,
+            Generator::interpolate("### <a id=\"#{hash}\"></a>`#{member}#{separator}#{call}`\n<a href=\"##{hash}\">#</a> [&#x24C8;](#{href} \"View in source\") [&#x24C9;][1]\n\n#{desc}", $subentry)
+          );
+
+          // @param
+          if (count($params = $subentry->getParams())) {
+            array_push($result, '', '#### Arguments');
+            foreach ($params as $index => $param) {
+              $result[] = Generator::interpolate('#{num}. `#{name}` (#{type}): #{desc}', array(
+                'desc' => $param[2],
+                'name' => $param[1],
+                'num'  => $index + 1,
+                'type' => $param[0]
+              ));
+            }
+          }
+          // @returns
+          if (count($returns = $subentry->getReturns())) {
+            array_push(
+              $result, '',
+              '#### Returns',
+              Generator::interpolate('(#{type}): #{desc}', array('desc' => $returns[1], 'type' => $returns[0]))
+            );
+          }
+          // @example
+          if ($example = $subentry->getExample()) {
+            array_push($result, '', '#### Example', $example);
+          }
+          array_push($result, "\n* * *", $closeTag);
+        }
+      }
+    }
+
+    // close tags add TOC link reference
+    array_push($result, $closeTag, $closeTag, '', '  [1]: #' . $toc . ' "Jump back to the TOC."');
+
+    // cleanup whitespace
+    return trim(preg_replace('/ +\n/', "\n", join($result, "\n")));
+  }
+}
+?>
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/LICENSE.txt b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/LICENSE.txt
new file mode 100644
index 0000000..dadad22
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
+
+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.
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/README.md
new file mode 100644
index 0000000..b913311
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/README.md
@@ -0,0 +1,98 @@
+# Platform.js <sup>v1.0.0</sup>
+
+A platform detection library that works on nearly all JavaScript platforms<sup><a name="fnref1" href="#fn1">1</a></sup>.
+
+## Disclaimer
+
+Platform.js is for informational purposes only and **not** intended as a substitution for [feature detection/inference](http://allyoucanleet.com/post/18087210413/feature-testing-costs#screencast2) checks.
+
+## BestieJS
+
+Platform.js is part of the BestieJS *"Best in Class"* module collection. This means we promote solid browser/environment support, ES5 precedents, unit testing, and plenty of documentation.
+
+## Documentation
+
+The documentation for Platform.js can be viewed here: [/doc/README.md](https://github.com/bestiejs/platform.js/blob/master/doc/README.md#readme)
+
+For a list of upcoming features, check out our [roadmap](https://github.com/bestiejs/platform.js/wiki/Roadmap).
+
+## Support
+
+Platform.js has been tested in at least Adobe AIR 3.1, Chrome 5-21, Firefox 1.5-13, IE 6-9, Opera 9.25-12.01, Safari 3-6, Node.js 0.8.6, Narwhal 0.3.2, RingoJS 0.8, and Rhino 1.7RC5.
+
+## Installation and usage
+
+In a browser or Adobe AIR:
+
+```html
+<script src="platform.js"></script>
+```
+
+Via [npm](http://npmjs.org/):
+
+```bash
+npm install platform
+```
+
+In [Node.js](http://nodejs.org/) and [RingoJS](http://ringojs.org/):
+
+```js
+var platform = require('platform');
+```
+
+In [Rhino](http://www.mozilla.org/rhino/):
+
+```js
+load('platform.js');
+```
+
+In an AMD loader like [RequireJS](http://requirejs.org/):
+
+```js
+require({
+  'paths': {
+    'platform': 'path/to/platform'
+  }
+},
+['platform'], function(platform) {
+  console.log(platform.name);
+});
+```
+
+Usage example:
+
+```js
+// on IE10 x86 platform preview running in IE7 compatibility mode on Windows 7 64 bit edition
+platform.name; // 'IE'
+platform.version; // '10.0'
+platform.layout; // 'Trident'
+platform.os; // 'Windows Server 2008 R2 / 7 x64'
+platform.description; // 'IE 10.0 x86 (platform preview; running in IE 7 mode) on Windows Server 2008 R2 / 7 x64'
+
+// or on an iPad
+platform.name; // 'Safari'
+platform.version; // '5.1'
+platform.product; // 'iPad'
+platform.manufacturer; // 'Apple'
+platform.layout; // 'WebKit'
+platform.os; // 'iOS 5.0'
+platform.description; // 'Safari 5.1 on Apple iPad (iOS 5.0)'
+
+// or parsing a given UA string
+var info = platform.parse('Mozilla/5.0 (Macintosh; Intel Mac OS X 10.7.2; en; rv:2.0) Gecko/20100101 Firefox/4.0 Opera 11.52');
+info.name; // 'Opera'
+info.version; // '11.52'
+info.layout; // 'Presto'
+info.os; // 'Mac OS X 10.7.2'
+info.description; // 'Opera 11.52 (identifying as Firefox 4.0) on Mac OS X 10.7.2'
+```
+
+## Author
+
+* [John-David Dalton](http://allyoucanleet.com/)
+  [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter")
+
+## Contributors
+
+* [Mathias Bynens](http://mathiasbynens.be/)
+  [![twitter/mathias](http://gravatar.com/avatar/24e08a9ea84deb17ae121074d0f17125?s=70)](https://twitter.com/mathias "Follow @mathias on Twitter")
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/platform.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/platform.js
new file mode 100644
index 0000000..a8a5482
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/platform.js/platform.js
@@ -0,0 +1,996 @@
+/*!
+ * Platform.js v1.0.0 <http://mths.be/platform>
+ * Copyright 2010-2012 John-David Dalton <http://allyoucanleet.com/>
+ * Available under MIT license <http://mths.be/mit>
+ */
+;(function(window) {
+  'use strict';
+
+  /** Backup possible window/global object */
+  var oldWin = window;
+
+  /** Detect free variable `exports` */
+  var freeExports = typeof exports == 'object' && exports;
+
+  /** Detect free variable `global` */
+  var freeGlobal = typeof global == 'object' && global &&
+    (global == global.global ? (window = global) : global);
+
+  /** Opera regexp */
+  var reOpera = /Opera/;
+
+  /** Used to resolve a value's internal [[Class]] */
+  var toString = {}.toString;
+
+  /** Detect Java environment */
+  var java = /Java/.test(getClassOf(window.java)) && window.java;
+
+  /** A character to represent alpha */
+  var alpha = java ? 'a' : '\u03b1';
+
+  /** A character to represent beta */
+  var beta = java ? 'b' : '\u03b2';
+
+  /** Browser document object */
+  var doc = window.document || {};
+
+  /** Used to check for own properties of an object */
+  var hasOwnProperty = {}.hasOwnProperty;
+
+  /** Browser navigator object */
+  var nav = window.navigator || {};
+
+  /**
+   * Detect Opera browser
+   * http://www.howtocreate.co.uk/operaStuff/operaObject.html
+   * http://dev.opera.com/articles/view/opera-mini-web-content-authoring-guidelines/#operamini
+   */
+  var opera = window.operamini || window.opera;
+
+  /** Opera [[Class]] */
+  var operaClass = reOpera.test(operaClass = getClassOf(opera)) ? operaClass : (opera = null);
+
+  /** Possible global object */
+  var thisBinding = this;
+
+  /** Browser user agent string */
+  var userAgent = nav.userAgent || '';
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Capitalizes a string value.
+   *
+   * @private
+   * @param {String} string The string to capitalize.
+   * @returns {String} The capitalized string.
+   */
+  function capitalize(string) {
+    string = String(string);
+    return string.charAt(0).toUpperCase() + string.slice(1);
+  }
+
+  /**
+   * An iteration utility for arrays and objects.
+   *
+   * @private
+   * @param {Array|Object} object The object to iterate over.
+   * @param {Function} callback The function called per iteration.
+   */
+  function each(object, callback) {
+    var index = -1,
+        length = object.length;
+
+    if (length == length >>> 0) {
+      while (++index < length) {
+        callback(object[index], index, object);
+      }
+    } else {
+      forOwn(object, callback);
+    }
+  }
+
+  /**
+   * Trim and conditionally capitalize string values.
+   *
+   * @private
+   * @param {String} string The string to format.
+   * @returns {String} The formatted string.
+   */
+  function format(string) {
+    string = trim(string);
+    return /^(?:webOS|i(?:OS|P))/.test(string)
+      ? string
+      : capitalize(string);
+  }
+
+  /**
+   * Iterates over an object's own properties, executing the `callback` for each.
+   *
+   * @private
+   * @param {Object} object The object to iterate over.
+   * @param {Function} callback The function executed per own property.
+   */
+  function forOwn(object, callback) {
+    for (var key in object) {
+      hasKey(object, key) && callback(object[key], key, object);
+    }
+  }
+
+  /**
+   * Gets the internal [[Class]] of a value.
+   *
+   * @private
+   * @param {Mixed} value The value.
+   * @returns {String} The [[Class]].
+   */
+  function getClassOf(value) {
+    return value == null
+      ? capitalize(value)
+      : toString.call(value).slice(8, -1);
+  }
+
+  /**
+   * Checks if an object has the specified key as a direct property.
+   *
+   * @private
+   * @param {Object} object The object to check.
+   * @param {String} key The key to check for.
+   * @returns {Boolean} Returns `true` if key is a direct property, else `false`.
+   */
+  function hasKey() {
+    // lazy define for others (not as accurate)
+    hasKey = function(object, key) {
+      var parent = object != null && (object.constructor || Object).prototype;
+      return !!parent && key in Object(object) && !(key in parent && object[key] === parent[key]);
+    };
+    // for modern browsers
+    if (getClassOf(hasOwnProperty) == 'Function') {
+      hasKey = function(object, key) {
+        return object != null && hasOwnProperty.call(object, key);
+      };
+    }
+    // for Safari 2
+    else if ({}.__proto__ == Object.prototype) {
+      hasKey = function(object, key) {
+        var result = false;
+        if (object != null) {
+          object = Object(object);
+          object.__proto__ = [object.__proto__, object.__proto__ = null, result = key in object][0];
+        }
+        return result;
+      };
+    }
+    return hasKey.apply(this, arguments);
+  }
+
+  /**
+   * Host objects can return type values that are different from their actual
+   * data type. The objects we are concerned with usually return non-primitive
+   * types of object, function, or unknown.
+   *
+   * @private
+   * @param {Mixed} object The owner of the property.
+   * @param {String} property The property to check.
+   * @returns {Boolean} Returns `true` if the property value is a non-primitive, else `false`.
+   */
+  function isHostType(object, property) {
+    var type = object != null ? typeof object[property] : 'number';
+    return !/^(?:boolean|number|string|undefined)$/.test(type) &&
+      (type == 'object' ? !!object[property] : true);
+  }
+
+  /**
+   * Prepares a string for use in a RegExp constructor by making hyphens and
+   * spaces optional.
+   *
+   * @private
+   * @param {String} string The string to qualify.
+   * @returns {String} The qualified string.
+   */
+  function qualify(string) {
+    return String(string).replace(/([ -])(?!$)/g, '$1?');
+  }
+
+  /**
+   * A bare-bones` Array#reduce` like utility function.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   * @param {Mixed} accumulator Initial value of the accumulator.
+   * @returns {Mixed} The accumulator.
+   */
+  function reduce(array, callback) {
+    var accumulator = null;
+    each(array, function(value, index) {
+      accumulator = callback(accumulator, value, index, array);
+    });
+    return accumulator;
+  }
+
+  /**
+   * Removes leading and trailing whitespace from a string.
+   *
+   * @private
+   * @param {String} string The string to trim.
+   * @returns {String} The trimmed string.
+   */
+  function trim(string) {
+    return String(string).replace(/^ +| +$/g, '');
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Creates a new platform object.
+   *
+   * @memberOf platform
+   * @param {String} [ua = navigator.userAgent] The user agent string.
+   * @returns {Object} A platform object.
+   */
+  function parse(ua) {
+
+    ua || (ua = userAgent);
+
+    /** Temporary variable used over the script's lifetime */
+    var data;
+
+    /** The CPU architecture */
+    var arch = ua;
+
+    /** Platform description array */
+    var description = [];
+
+    /** Platform alpha/beta indicator */
+    var prerelease = null;
+
+    /** A flag to indicate that environment features should be used to resolve the platform */
+    var useFeatures = ua == userAgent;
+
+    /** The browser/environment version */
+    var version = useFeatures && opera && typeof opera.version == 'function' && opera.version();
+
+    /* Detectable layout engines (order is important) */
+    var layout = getLayout([
+      { 'label': 'WebKit', 'pattern': 'AppleWebKit' },
+      'iCab',
+      'Presto',
+      'NetFront',
+      'Tasman',
+      'Trident',
+      'KHTML',
+      'Gecko'
+    ]);
+
+    /* Detectable browser names (order is important) */
+    var name = getName([
+      'Adobe AIR',
+      'Arora',
+      'Avant Browser',
+      'Camino',
+      'Epiphany',
+      'Fennec',
+      'Flock',
+      'Galeon',
+      'GreenBrowser',
+      'iCab',
+      'Iceweasel',
+      'Iron',
+      'K-Meleon',
+      'Konqueror',
+      'Lunascape',
+      'Maxthon',
+      'Midori',
+      'Nook Browser',
+      'PhantomJS',
+      'Raven',
+      'Rekonq',
+      'RockMelt',
+      'SeaMonkey',
+      { 'label': 'Silk', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
+      'Sleipnir',
+      'SlimBrowser',
+      'Sunrise',
+      'Swiftfox',
+      'WebPositive',
+      'Opera Mini',
+      'Opera',
+      'Chrome',
+      { 'label': 'Chrome Mobile', 'pattern': '(?:CriOS|CrMo)' },
+      { 'label': 'Firefox', 'pattern': '(?:Firefox|Minefield)' },
+      { 'label': 'IE', 'pattern': 'MSIE' },
+      'Safari'
+    ]);
+
+    /* Detectable products (order is important) */
+    var product = getProduct([
+      'BlackBerry',
+      { 'label': 'Galaxy S', 'pattern': 'GT-I9000' },
+      { 'label': 'Galaxy S2', 'pattern': 'GT-I9100' },
+      'Google TV',
+      'iPad',
+      'iPod',
+      'iPhone',
+      'Kindle',
+      { 'label': 'Kindle Fire', 'pattern': '(?:Cloud9|Silk-Accelerated)' },
+      'Nook',
+      'PlayBook',
+      'PlayStation Vita',
+      'TouchPad',
+      'Transformer',
+      'Xoom'
+    ]);
+
+    /* Detectable manufacturers */
+    var manufacturer = getManufacturer({
+      'Apple': { 'iPad': 1, 'iPhone': 1, 'iPod': 1 },
+      'Amazon': { 'Kindle': 1, 'Kindle Fire': 1 },
+      'Asus': { 'Transformer': 1 },
+      'Barnes & Noble': { 'Nook': 1 },
+      'BlackBerry': { 'PlayBook': 1 },
+      'Google': { 'Google TV': 1 },
+      'HP': { 'TouchPad': 1 },
+      'LG': { },
+      'Motorola': { 'Xoom': 1 },
+      'Nokia': { },
+      'Samsung': { 'Galaxy S': 1, 'Galaxy S2': 1 },
+      'Sony': { 'PlayStation Vita': 1 }
+    });
+
+    /* Detectable OSes (order is important) */
+    var os = getOS([
+      'Android',
+      'CentOS',
+      'Debian',
+      'Fedora',
+      'FreeBSD',
+      'Gentoo',
+      'Haiku',
+      'Kubuntu',
+      'Linux Mint',
+      'Red Hat',
+      'SuSE',
+      'Ubuntu',
+      'Xubuntu',
+      'Cygwin',
+      'Symbian OS',
+      'hpwOS',
+      'webOS ',
+      'webOS',
+      'Tablet OS',
+      'Linux',
+      'Mac OS X',
+      'Macintosh',
+      'Mac',
+      'Windows 98;',
+      'Windows '
+    ]);
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Picks the layout engine from an array of guesses.
+     *
+     * @private
+     * @param {Array} guesses An array of guesses.
+     * @returns {String|Null} The detected layout engine.
+     */
+    function getLayout(guesses) {
+      return reduce(guesses, function(result, guess) {
+        return result || RegExp('\\b' + (
+          guess.pattern || qualify(guess)
+        ) + '\\b', 'i').exec(ua) && (guess.label || guess);
+      });
+    }
+
+    /**
+     * Picks the manufacturer from an array of guesses.
+     *
+     * @private
+     * @param {Array} guesses An array of guesses.
+     * @returns {String|Null} The detected manufacturer.
+     */
+    function getManufacturer(guesses) {
+      return reduce(guesses, function(result, value, key) {
+        // lookup the manufacturer by product or scan the UA for the manufacturer
+        return result || (
+          value[product] ||
+          value[0/*Opera 9.25 fix*/, /^[a-z]+(?: +[a-z]+\b)*/i.exec(product)] ||
+          RegExp('\\b' + (key.pattern || qualify(key)) + '(?:\\b|\\w*\\d)', 'i').exec(ua)
+        ) && (key.label || key);
+      });
+    }
+
+    /**
+     * Picks the browser name from an array of guesses.
+     *
+     * @private
+     * @param {Array} guesses An array of guesses.
+     * @returns {String|Null} The detected browser name.
+     */
+    function getName(guesses) {
+      return reduce(guesses, function(result, guess) {
+        return result || RegExp('\\b' + (
+          guess.pattern || qualify(guess)
+        ) + '\\b', 'i').exec(ua) && (guess.label || guess);
+      });
+    }
+
+    /**
+     * Picks the OS name from an array of guesses.
+     *
+     * @private
+     * @param {Array} guesses An array of guesses.
+     * @returns {String|Null} The detected OS name.
+     */
+    function getOS(guesses) {
+      return reduce(guesses, function(result, guess) {
+        var pattern = guess.pattern || qualify(guess);
+        if (!result && (result =
+            RegExp('\\b' + pattern + '(?:/[\\d.]+|[ \\w.]*)', 'i').exec(ua))) {
+          // platform tokens defined at
+          // http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
+          // http://web.archive.org/web/20081122053950/http://msdn.microsoft.com/en-us/library/ms537503(VS.85).aspx
+          data = {
+            '6.2':  '8',
+            '6.1':  'Server 2008 R2 / 7',
+            '6.0':  'Server 2008 / Vista',
+            '5.2':  'Server 2003 / XP 64-bit',
+            '5.1':  'XP',
+            '5.01': '2000 SP1',
+            '5.0':  '2000',
+            '4.0':  'NT',
+            '4.90': 'ME'
+          };
+          // detect Windows version from platform tokens
+          if (/^Win/i.test(result) &&
+              (data = data[0/*Opera 9.25 fix*/, /[\d.]+$/.exec(result)])) {
+            result = 'Windows ' + data;
+          }
+          // correct character case and cleanup
+          result = format(String(result)
+            .replace(RegExp(pattern, 'i'), guess.label || guess)
+            .replace(/ ce$/i, ' CE')
+            .replace(/hpw/i, 'web')
+            .replace(/Macintosh/, 'Mac OS')
+            .replace(/_PowerPC/i, ' OS')
+            .replace(/(OS X) [^ \d]+/i, '$1')
+            .replace(/\/(\d)/, ' $1')
+            .replace(/_/g, '.')
+            .replace(/(?: BePC|[ .]*fc[ \d.]+)$/i, '')
+            .replace(/x86\.64/gi, 'x86_64')
+            .split(' on ')[0]);
+        }
+        return result;
+      });
+    }
+
+    /**
+     * Picks the product name from an array of guesses.
+     *
+     * @private
+     * @param {Array} guesses An array of guesses.
+     * @returns {String|Null} The detected product name.
+     */
+    function getProduct(guesses) {
+      return reduce(guesses, function(result, guess) {
+        var pattern = guess.pattern || qualify(guess);
+        if (!result && (result =
+              RegExp('\\b' + pattern + ' *\\d+[.\\w_]*', 'i').exec(ua) ||
+              RegExp('\\b' + pattern + '(?:; *(?:[a-z]+[_-])?[a-z]+\\d+|[^ ();-]*)', 'i').exec(ua)
+            )) {
+          // split by forward slash and append product version if needed
+          if ((result = String(guess.label || result).split('/'))[1] && !/[\d.]+/.test(result[0])) {
+            result[0] += ' ' + result[1];
+          }
+          // correct character case and cleanup
+          guess = guess.label || guess;
+          result = format(result[0]
+            .replace(RegExp(pattern, 'i'), guess)
+            .replace(RegExp('; *(?:' + guess + '[_-])?', 'i'), ' ')
+            .replace(RegExp('(' + guess + ')(\\w)', 'i'), '$1 $2'));
+        }
+        return result;
+      });
+    }
+
+    /**
+     * Resolves the version using an array of UA patterns.
+     *
+     * @private
+     * @param {Array} patterns An array of UA patterns.
+     * @returns {String|Null} The detected version.
+     */
+    function getVersion(patterns) {
+      return reduce(patterns, function(result, pattern) {
+        return result || (RegExp(pattern +
+          '(?:-[\\d.]+/|(?: for [\\w-]+)?[ /-])([\\d.]+[^ ();/_-]*)', 'i').exec(ua) || 0)[1] || null;
+      });
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * Returns `platform.description` when the platform object is coerced to a string.
+     *
+     * @name toString
+     * @memberOf platform
+     * @returns {String} Returns `platform.description` if available, else an empty string.
+     */
+    function toStringPlatform() {
+      return this.description || '';
+    }
+
+    /*------------------------------------------------------------------------*/
+
+    // convert layout to an array so we can add extra details
+    layout && (layout = [layout]);
+
+    // detect product names that contain their manufacturer's name
+    if (manufacturer && !product) {
+      product = getProduct([manufacturer]);
+    }
+    // clean up Google TV
+    if ((data = /Google TV/.exec(product))) {
+      product = data[0];
+    }
+    // detect simulators
+    if (/\bSimulator\b/i.test(ua)) {
+      product = (product ? product + ' ' : '') + 'Simulator';
+    }
+    // detect iOS
+    if (/^iP/.test(product)) {
+      name || (name = 'Safari');
+      os = 'iOS' + ((data = / OS ([\d_]+)/i.exec(ua))
+        ? ' ' + data[1].replace(/_/g, '.')
+        : '');
+    }
+    // detect Kubuntu
+    else if (name == 'Konqueror' && !/buntu/i.test(os)) {
+      os = 'Kubuntu';
+    }
+    // detect Android browsers
+    else if (manufacturer && manufacturer != 'Google' &&
+        /Chrome|Vita/.test(name + ';' + product)) {
+      name = 'Android Browser';
+      os = /Android/.test(os) ? os : 'Android';
+    }
+    // detect false positives for Firefox/Safari
+    else if (!name || (data = !/\bMinefield\b/i.test(ua) && /Firefox|Safari/.exec(name))) {
+      // escape the `/` for Firefox 1
+      if (name && !product && /[\/,]|^[^(]+?\)/.test(ua.slice(ua.indexOf(data + '/') + 8))) {
+        // clear name of false positives
+        name = null;
+      }
+      // reassign a generic name
+      if ((data = product || manufacturer || os) &&
+          (product || manufacturer || /Android|Symbian OS|Tablet OS|webOS/.test(os))) {
+        name = /[a-z]+(?: Hat)?/i.exec(/Android/.test(os) ? os : data) + ' Browser';
+      }
+    }
+    // detect non-Opera versions (order is important)
+    if (!version) {
+      version = getVersion([
+        '(?:Cloud9|CriOS|CrMo|Opera ?Mini|Raven|Silk(?!/[\\d.]+$))',
+        'Version',
+        qualify(name),
+        '(?:Firefox|Minefield|NetFront)'
+      ]);
+    }
+    // detect stubborn layout engines
+    if (layout == 'iCab' && parseFloat(version) > 3) {
+      layout = ['WebKit'];
+    } else if (data =
+        /Opera/.test(name) && 'Presto' ||
+        /\b(?:Midori|Nook|Safari)\b/i.test(ua) && 'WebKit' ||
+        !layout && /\bMSIE\b/i.test(ua) && (/^Mac/.test(os) ? 'Tasman' : 'Trident')) {
+      layout = [data];
+    }
+    // leverage environment features
+    if (useFeatures) {
+      // detect server-side environments
+      // Rhino has a global function while others have a global object
+      if (isHostType(window, 'global')) {
+        if (java) {
+          data = java.lang.System;
+          arch = data.getProperty('os.arch');
+          os = os || data.getProperty('os.name') + ' ' + data.getProperty('os.version');
+        }
+        if (typeof exports == 'object' && exports) {
+          // if `thisBinding` is the [ModuleScope]
+          if (thisBinding == oldWin && typeof system == 'object' && (data = [system])[0]) {
+            os || (os = data[0].os || null);
+            try {
+              data[1] = require('ringo/engine').version;
+              version = data[1].join('.');
+              name = 'RingoJS';
+            } catch(e) {
+              if (data[0].global == freeGlobal) {
+                name = 'Narwhal';
+              }
+            }
+          } else if (typeof process == 'object' && (data = process)) {
+            name = 'Node.js';
+            arch = data.arch;
+            os = data.platform;
+            version = /[\d.]+/.exec(data.version)[0];
+          }
+        } else if (getClassOf(window.environment) == 'Environment') {
+          name = 'Rhino';
+        }
+      }
+      // detect Adobe AIR
+      else if (getClassOf(data = window.runtime) == 'ScriptBridgingProxyObject') {
+        name = 'Adobe AIR';
+        os = data.flash.system.Capabilities.os;
+      }
+      // detect PhantomJS
+      else if (getClassOf(data = window.phantom) == 'RuntimeObject') {
+        name = 'PhantomJS';
+        version = (data = data.version || null) && (data.major + '.' + data.minor + '.' + data.patch);
+      }
+      // detect IE compatibility modes
+      else if (typeof doc.documentMode == 'number' && (data = /\bTrident\/(\d+)/i.exec(ua))) {
+        // we're in compatibility mode when the Trident version + 4 doesn't
+        // equal the document mode
+        version = [version, doc.documentMode];
+        if ((data = +data[1] + 4) != version[1]) {
+          description.push('IE ' + version[1] + ' mode');
+          layout[1] = '';
+          version[1] = data;
+        }
+        version = name == 'IE' ? String(version[1].toFixed(1)) : version[0];
+      }
+      os = os && format(os);
+    }
+    // detect prerelease phases
+    if (version && (data =
+        /(?:[ab]|dp|pre|[ab]\d+pre)(?:\d+\+?)?$/i.exec(version) ||
+        /(?:alpha|beta)(?: ?\d)?/i.exec(ua + ';' + (useFeatures && nav.appMinorVersion)) ||
+        /\bMinefield\b/i.test(ua) && 'a')) {
+      prerelease = /b/i.test(data) ? 'beta' : 'alpha';
+      version = version.replace(RegExp(data + '\\+?$'), '') +
+        (prerelease == 'beta' ? beta : alpha) + (/\d+\+?/.exec(data) || '');
+    }
+    // rename code name "Fennec"
+    if (name == 'Fennec') {
+      name = 'Firefox Mobile';
+    }
+    // obscure Maxthon's unreliable version
+    else if (name == 'Maxthon' && version) {
+      version = version.replace(/\.[\d.]+/, '.x');
+    }
+    // detect Silk desktop/accelerated modes
+    else if (name == 'Silk') {
+      if (!/Mobi/i.test(ua)) {
+        os = 'Android';
+        description.unshift('desktop mode');
+      }
+      if (/Accelerated *= *true/i.test(ua)) {
+        description.unshift('accelerated');
+      }
+    }
+    // detect Windows Phone desktop mode
+    else if (name == 'IE' && (data = (/; *(?:XBLWP|ZuneWP)(\d+)/i.exec(ua) || 0)[1])) {
+      name += ' Mobile';
+      os = 'Windows Phone OS ' + data + '.x';
+      description.unshift('desktop mode');
+    }
+    // add mobile postfix
+    else if ((name == 'IE' || name && !product && !/Browser|Mobi/.test(name)) &&
+        (os == 'Windows CE' || /Mobi/i.test(ua))) {
+      name += ' Mobile';
+    }
+    // detect IE platform preview
+    else if (name == 'IE' && useFeatures && typeof external == 'object' && !external) {
+      description.unshift('platform preview');
+    }
+    // detect BlackBerry OS version
+    // http://docs.blackberry.com/en/developers/deliverables/18169/HTTP_headers_sent_by_BB_Browser_1234911_11.jsp
+    else if (/BlackBerry/.test(product) && (data =
+        (RegExp(product.replace(/ +/g, ' *') + '/([.\\d]+)', 'i').exec(ua) || 0)[1] ||
+        version)) {
+      os = 'Device Software ' + data;
+      version = null;
+    }
+    // detect Opera identifying/masking itself as another browser
+    // http://www.opera.com/support/kb/view/843/
+    else if (this != forOwn && (
+          (useFeatures && opera) ||
+          (/Opera/.test(name) && /\b(?:MSIE|Firefox)\b/i.test(ua)) ||
+          (name == 'Firefox' && /OS X (?:\d+\.){2,}/.test(os)) ||
+          (name == 'IE' && (
+            (os && !/^Win/.test(os) && version > 5.5) ||
+            /Windows XP/.test(os) && version > 8 ||
+            version == 8 && !/Trident/.test(ua)
+          ))
+        ) && !reOpera.test(data = parse.call(forOwn, ua.replace(reOpera, '') + ';')) && data.name) {
+
+      // when "indentifying", the UA contains both Opera and the other browser's name
+      data = 'ing as ' + data.name + ((data = data.version) ? ' ' + data : '');
+      if (reOpera.test(name)) {
+        if (/IE/.test(data) && os == 'Mac OS') {
+          os = null;
+        }
+        data = 'identify' + data;
+      }
+      // when "masking", the UA contains only the other browser's name
+      else {
+        data = 'mask' + data;
+        if (operaClass) {
+          name = format(operaClass.replace(/([a-z])([A-Z])/g, '$1 $2'));
+        } else {
+          name = 'Opera';
+        }
+        if (/IE/.test(data)) {
+          os = null;
+        }
+        if (!useFeatures) {
+          version = null;
+        }
+      }
+      layout = ['Presto'];
+      description.push(data);
+    }
+    // detect WebKit Nightly and approximate Chrome/Safari versions
+    if ((data = (/\bAppleWebKit\/([\d.]+\+?)/i.exec(ua) || 0)[1])) {
+      // correct build for numeric comparison
+      // (e.g. "532.5" becomes "532.05")
+      data = [parseFloat(data.replace(/\.(\d)$/, '.0$1')), data];
+      // nightly builds are postfixed with a `+`
+      if (name == 'Safari' && data[1].slice(-1) == '+') {
+        name = 'WebKit Nightly';
+        prerelease = 'alpha';
+        version = data[1].slice(0, -1);
+      }
+      // clear incorrect browser versions
+      else if (version == data[1] ||
+          version == (/\bSafari\/([\d.]+\+?)/i.exec(ua) || 0)[1]) {
+        version = null;
+      }
+      // use the full Chrome version when available
+      data = [data[0], (/\bChrome\/([\d.]+)/i.exec(ua) || 0)[1]];
+
+      // detect JavaScriptCore
+      // http://stackoverflow.com/questions/6768474/how-can-i-detect-which-javascript-engine-v8-or-jsc-is-used-at-runtime-in-androi
+      if (!useFeatures || (/internal|\n/i.test(toString.toString()) && !data[1])) {
+        layout[1] = 'like Safari';
+        data = (data = data[0], data < 400 ? 1 : data < 500 ? 2 : data < 526 ? 3 : data < 533 ? 4 : data < 534 ? '4+' : data < 535 ? 5 : '5');
+      } else {
+        layout[1] = 'like Chrome';
+        data = data[1] || (data = data[0], data < 530 ? 1 : data < 532 ? 2 : data < 532.05 ? 3 : data < 533 ? 4 : data < 534.03 ? 5 : data < 534.07 ? 6 : data < 534.10 ? 7 : data < 534.13 ? 8 : data < 534.16 ? 9 : data < 534.24 ? 10 : data < 534.30 ? 11 : data < 535.01 ? 12 : data < 535.02 ? '13+' : data < 535.07 ? 15 : data < 535.11 ? 16 : data < 535.19 ? 17 : data < 536.05 ? 18 : data < 536.10 ? 19 : data < 537.01 ? 20 : '21');
+      }
+      // add the postfix of ".x" or "+" for approximate versions
+      layout[1] += ' ' + (data += typeof data == 'number' ? '.x' : /[.+]/.test(data) ? '' : '+');
+      // obscure version for some Safari 1-2 releases
+      if (name == 'Safari' && (!version || parseInt(version) > 45)) {
+        version = data;
+      }
+    }
+    // detect Opera desktop modes
+    if (name == 'Opera' &&  (data = /(?:zbov|zvav)$/.exec(os))) {
+      name += ' ';
+      description.unshift('desktop mode');
+      if (data == 'zvav') {
+        name += 'Mini';
+        version = null;
+      } else {
+        name += 'Mobile';
+      }
+    }
+    // detect Chrome desktop mode
+    else if (name == 'Safari' && /Chrome/.exec(layout[1])) {
+      description.unshift('desktop mode');
+      name = 'Chrome Mobile';
+      version = null;
+
+      if (/Mac OS X/.test(os)) {
+        manufacturer = 'Apple';
+        os = 'iOS 4.3+';
+      } else {
+        os = null;
+      }
+    }
+    // strip incorrect OS versions
+    if (version && version.indexOf(data = /[\d.]+$/.exec(os)) == 0 &&
+        ua.indexOf('/' + data + '-') > -1) {
+      os = trim(os.replace(data, ''));
+    }
+    // add layout engine
+    if (layout && !/Avant|Nook/.test(name) && (
+        /Browser|Lunascape|Maxthon/.test(name) ||
+        /^(?:Adobe|Arora|Midori|Phantom|Rekonq|Rock|Sleipnir|Web)/.test(name) && layout[1])) {
+      // don't add layout details to description if they are falsey
+      (data = layout[layout.length - 1]) && description.push(data);
+    }
+    // combine contextual information
+    if (description.length) {
+      description = ['(' + description.join('; ') + ')'];
+    }
+    // append manufacturer
+    if (manufacturer && product && product.indexOf(manufacturer) < 0) {
+      description.push('on ' + manufacturer);
+    }
+    // append product
+    if (product) {
+      description.push((/^on /.test(description[description.length -1]) ? '' : 'on ') + product);
+    }
+    // parse OS into an object
+    if (os) {
+      data = / ([\d.+]+)$/.exec(os);
+      os = {
+        'architecture': 32,
+        'family': data ? os.replace(data[0], '') : os,
+        'version': data ? data[1] : null,
+        'toString': function() {
+          var version = this.version;
+          return this.family + (version ? ' ' + version : '') + (this.architecture == 64 ? ' 64-bit' : '');
+        }
+      };
+    }
+    // add browser/OS architecture
+    if ((data = / (?:AMD|IA|Win|WOW|x86_|x)64\b/i.exec(arch)) && !/\bi686\b/i.test(arch)) {
+      if (os) {
+        os.architecture = 64;
+        os.family = os.family.replace(data, '');
+      }
+      if (name && (/WOW64/i.test(ua) ||
+          (useFeatures && /\w(?:86|32)$/.test(nav.cpuClass || nav.platform)))) {
+        description.unshift('32-bit');
+      }
+    }
+
+    ua || (ua = null);
+
+    /*------------------------------------------------------------------------*/
+
+    /**
+     * The platform object.
+     *
+     * @name platform
+     * @type Object
+     */
+    return {
+
+      /**
+       * The browser/environment version.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'version': name && version && (description.unshift(version), version),
+
+      /**
+       * The name of the browser/environment.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'name': name && (description.unshift(name), name),
+
+      /**
+       * The name of the operating system.
+       *
+       * @memberOf platform
+       * @type Object
+       */
+      'os': os
+        ? (name &&
+            !(os == String(os).split(' ')[0] && (os == name.split(' ')[0] || product)) &&
+              description.push(product ? '(' + os + ')' : 'on ' + os), os)
+        : {
+
+          /**
+           * The CPU architecture the OS is built for.
+           *
+           * @memberOf platform.os
+           * @type String|Null
+           */
+          'architecture': null,
+
+          /**
+           * The family of the OS.
+           *
+           * @memberOf platform.os
+           * @type String|Null
+           */
+          'family': null,
+
+          /**
+           * The version of the OS.
+           *
+           * @memberOf platform.os
+           * @type String|Null
+           */
+          'version': null,
+
+          /**
+           * Returns the OS string.
+           *
+           * @memberOf platform.os
+           * @returns {String} The OS string.
+           */
+          'toString': function() { return 'null'; }
+        },
+
+      /**
+       * The platform description.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'description': description.length ? description.join(' ') : ua,
+
+      /**
+       * The name of the browser layout engine.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'layout': layout && layout[0],
+
+      /**
+       * The name of the product's manufacturer.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'manufacturer': manufacturer,
+
+      /**
+       * The alpha/beta release indicator.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'prerelease': prerelease,
+
+      /**
+       * The name of the product hosting the browser.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'product': product,
+
+      /**
+       * The browser's user agent string.
+       *
+       * @memberOf platform
+       * @type String|Null
+       */
+      'ua': ua,
+
+      // parses a user agent string into a platform object
+      'parse': parse,
+
+      // returns the platform description
+      'toString': toStringPlatform
+    };
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  // expose platform
+  // some AMD build optimizers, like r.js, check for specific condition patterns like the following:
+  if (typeof define == 'function' && typeof define.amd == 'object' && define.amd) {
+    // define as an anonymous module so, through path mapping, it can be aliased
+    define(function() {
+      return parse();
+    });
+  }
+  // check for `exports` after `define` in case a build optimizer adds an `exports` object
+  else if (freeExports) {
+    // in Narwhal, Node.js, or RingoJS
+    forOwn(parse(), function(value, key) {
+      freeExports[key] = value;
+    });
+  }
+  // in a browser or Rhino
+  else {
+    // use square bracket notation so Closure Compiler won't munge `platform`
+    // http://code.google.com/closure/compiler/docs/api-tutorial3.html#export
+    window['platform'] = parse();
+  }
+}(this));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/LICENSE.txt b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/LICENSE.txt
new file mode 100644
index 0000000..dadad22
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/LICENSE.txt
@@ -0,0 +1,20 @@
+Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
+
+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.
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/README.md
new file mode 100644
index 0000000..b84a21c
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/README.md
@@ -0,0 +1,57 @@
+# QUnit CLIB <sup>v1.0.0</sup>
+## command-line interface boilerplate
+
+QUnit CLIB helps extend QUnit's CLI support to many common CLI environments.
+
+## Screenshot
+
+![QUnit CLIB brings QUnit to your favorite shell.](http://i.imgur.com/jpu9l.png)
+
+## Support
+
+QUnit CLIB has been tested in at least Node.js 0.4.8-0.8.6, Narwhal v0.3.2, RingoJS v0.8.0, and Rhino v1.7RC3-RC5.
+
+## Usage
+
+```js
+(function(window) {
+
+  // use a single load function
+  var load = typeof require == 'function' ? require : window.load;
+
+  // load QUnit and CLIB if needed
+  var QUnit =
+    window.QUnit || (
+      window.setTimeout || (window.addEventListener = window.setTimeout = / /),
+      window.QUnit = load('path/to/qunit.js') || window.QUnit,
+      load('path/to/qunit-clib.js'),
+      (window.addEventListener || 0).test && delete window.addEventListener,
+      window.QUnit
+    );
+
+  // explicitly call `QUnit.module()` instead of `module()`
+  // in case we are in a CLI environment
+  QUnit.module('A Test Module');
+
+  test('A Test', function() {
+    // ...
+  });
+
+  // must call `QUnit.start()` if using QUnit < 1.3.0 with Node.js or any
+  // version of QUnit with Narwhal, Rhino, or RingoJS
+  if (!window.document) {
+    QUnit.start();
+  }
+}(typeof global == 'object' && global || this));
+```
+
+## Footnotes
+
+  1. QUnit v1.3.0 does not work with Narwhal or Ringo < v0.8.0
+
+  2. Rhino v1.7RC4 does not support timeout fallbacks `clearTimeout` and `setTimeout`
+
+## Author
+
+* [John-David Dalton](http://allyoucanleet.com/)
+  [![twitter/jdalton](http://gravatar.com/avatar/299a3d891ff1920b69c364d061007043?s=70)](https://twitter.com/jdalton "Follow @jdalton on Twitter")
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/qunit-clib.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/qunit-clib.js
new file mode 100644
index 0000000..e487c03
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit-clib/qunit-clib.js
@@ -0,0 +1,319 @@
+/*!
+ * QUnit CLI Boilerplate v1.0.0
+ * Copyright 2011-2012 John-David Dalton <http://allyoucanleet.com/>
+ * Based on a gist by Jörn Zaefferer <https://gist.github.com/722381>
+ * Available under MIT license <http://mths.be/mit>
+ */
+;(function(global) {
+  'use strict';
+
+  /** Add `console.log()` support for Narwhal, Rhino, and RingoJS */
+  global.console || (global.console = { 'log': global.print });
+
+  /** Reduce global.QUnit.QUnit -> global.QUnit */
+  global.QUnit && (QUnit = QUnit.QUnit || QUnit);
+
+  /*--------------------------------------------------------------------------*/
+
+  /** Used as a horizontal rule in console output */
+  var hr = '----------------------------------------';
+
+  /** Shortcut used to convert array-like objects to arrays */
+  var slice = [].slice;
+
+  /** Used to resolve a value's internal [[Class]] */
+  var toString = {}.toString;
+
+  /** Used by timer methods */
+  var doneCalled,
+      timer,
+      counter = 0,
+      ids = {};
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * An iteration utility for arrays.
+   *
+   * @private
+   * @param {Array} array The array to iterate over.
+   * @param {Function} callback The function called per iteration.
+   */
+  function each(array, callback) {
+    var index = -1,
+        length = array.length;
+
+    while (++index < length) {
+      callback(array[index], index, array);
+    }
+  }
+
+  /**
+   * Checks if the specified `value` is a function.
+   *
+   * @private
+   * @param {Mixed} value The value to check.
+   * @returns {Boolean} Returns `true` if `value` is a function, else `false`.
+   */
+  function isFunction(value) {
+    return toString.call(value) == '[object Function]';
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * Timeout fallbacks based on the work of Andrea Giammarchi and Weston C.
+   * https://github.com/WebReflection/wru/blob/master/src/rhinoTimers.js
+   * http://stackoverflow.com/questions/2261705/how-to-run-a-javascript-function-asynchronously-without-using-settimeout
+   */
+
+  /**
+   * Clears the delay set by `setInterval` or `setTimeout`.
+   *
+   * @memberOf global
+   * @param {Number} id The ID of the timeout to be cleared.
+   */
+  function clearTimer(id) {
+    if (ids[id]) {
+      ids[id].cancel();
+      timer.purge();
+      delete ids[id];
+    }
+  }
+
+  /**
+   * Schedules timer-based callbacks.
+   *
+   * @private
+   * @param {Function} fn The function to call.
+   * @oaram {Number} delay The number of milliseconds to delay the `fn` call.
+   * @param [arg1, arg2, ...] Arguments to invoke `fn` with.
+   * @param {Boolean} repeated A flag to specify whether `fn` is called repeatedly.
+   * @returns {Number} The the ID of the timeout.
+   */
+  function schedule(fn, delay, args, repeated) {
+    // Rhino 1.7RC4 will error assigning `task` below
+    // https://bugzilla.mozilla.org/show_bug.cgi?id=775566
+    var task = ids[++counter] = new JavaAdapter(java.util.TimerTask, {
+      'run': function() {
+        fn.apply(global, args);
+      }
+    });
+    // support non-functions
+    if (!isFunction(fn)) {
+      fn = (function(code) {
+        code = String(code);
+        return function() { eval(code); };
+      }(fn));
+    }
+    // used by setInterval
+    if (repeated) {
+      timer.schedule(task, delay, delay);
+    }
+    // used by setTimeout
+    else {
+      timer.schedule(task, delay);
+    }
+    return counter;
+  }
+
+  /**
+   * Executes a code snippet or function repeatedly, with a delay between each call.
+   *
+   * @memberOf global
+   * @param {Function|String} fn The function to call or string to evaluate.
+   * @oaram {Number} delay The number of milliseconds to delay each `fn` call.
+   * @param [arg1, arg2, ...] Arguments to invoke `fn` with.
+   * @returns {Number} The the ID of the timeout.
+   */
+  function setInterval(fn, delay) {
+    return schedule(fn, delay, slice.call(arguments, 2), true);
+  }
+
+  /**
+   * Executes a code snippet or a function after specified delay.
+   *
+   * @memberOf global
+   * @param {Function|String} fn The function to call or string to evaluate.
+   * @oaram {Number} delay The number of milliseconds to delay the `fn` call.
+   * @param [arg1, arg2, ...] Arguments to invoke `fn` with.
+   * @returns {Number} The the ID of the timeout.
+   */
+  function setTimeout(fn, delay) {
+    return schedule(fn, delay, slice.call(arguments, 2));
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * A logging callback triggered when all testing is completed.
+   *
+   * @memberOf QUnit
+   * @param {Object} details An object with properties `failed`, `passed`,
+   *  `runtime`, and `total`.
+   */
+  function done(details) {
+    // stop `asyncTest()` from erroneously calling `done()` twice in
+    // environments w/o timeouts
+    if (doneCalled) {
+      return;
+    }
+    doneCalled = true;
+    console.log(hr);
+    console.log('    PASS: ' + details.passed + '  FAIL: ' + details.failed + '  TOTAL: ' + details.total);
+    console.log('    Finished in ' + details.runtime + ' milliseconds.');
+    console.log(hr);
+
+    // exit out of Rhino
+    try {
+      quit();
+    } catch(e) { }
+
+    // exit out of Node.js
+    try {
+      process.exit();
+    } catch(e) { }
+  }
+
+  /**
+   * A logging callback triggered after every assertion.
+   *
+   * @memberOf QUnit
+   * @param {Object} details An object with properties `actual`, `expected`,
+   *  `message`, and `result`.
+   */
+  function log(details) {
+    var expected = details.expected,
+        result = details.result,
+        type = typeof expected != 'undefined' ? 'EQ' : 'OK';
+
+    var assertion = [
+      result ? 'PASS' : 'FAIL',
+      type,
+      details.message || 'ok'
+    ];
+
+    if (!result && type == 'EQ') {
+      assertion.push('Expected: ' + expected + ', Actual: ' + details.actual);
+    }
+    QUnit.config.testStats.assertions.push(assertion.join(' | '));
+  }
+
+  /**
+   * A logging callback triggered at the start of every test module.
+   *
+   * @memberOf QUnit
+   * @param {Object} details An object with property `name`.
+   */
+  function moduleStart(details) {
+    console.log(hr);
+    console.log(details.name);
+    console.log(hr);
+  }
+
+  /**
+   * Converts an object into a string representation.
+   *
+   * @memberOf QUnit
+   * @type Function
+   * @param {Object} object The object to stringify.
+   * @returns {String} The result string.
+   */
+  var parseObject = (function() {
+    var func = QUnit.jsDump.parsers.object;
+    return function(object) {
+      // fork to support Rhino's error objects
+      if (typeof object.rhinoException == 'object') {
+        return object.name +
+          ' { message: "' + object.message +
+          '", fileName: "' + object.fileName +
+          '", lineNumber: ' + object.lineNumber + ' }';
+      }
+      return func(object);
+    };
+  }());
+
+  /**
+   * A logging callback triggered after a test is completed.
+   *
+   * @memberOf QUnit
+   * @param {Object} details An object with properties `failed`, `name`,
+   *  `passed`, and `total`.
+   */
+  function testDone(details) {
+    var assertions = QUnit.config.testStats.assertions,
+        testName = details.name;
+
+    if (details.failed > 0) {
+      console.log(' FAIL - '+ testName);
+      each(assertions, function(value) {
+        console.log('    ' + value);
+      });
+    }
+    else {
+      console.log(' PASS - ' + testName);
+    }
+    assertions.length = 0;
+  }
+
+  /*--------------------------------------------------------------------------*/
+
+  /**
+   * An object used to hold information about the current running test.
+   *
+   * @memberOf QUnit.config
+   * @type Object
+   */
+  QUnit.config.testStats = {
+
+    /**
+     * An array of test summaries (pipe separated).
+     *
+     * @memberOf QUnit.config.testStats
+     * @type Array
+     */
+    'assertions': []
+  };
+
+  // add shortcuts to the global
+  // exclude `module` because some environments have it as a built-in object
+  each(['asyncTest', 'deepEqual', 'equal', 'equals', 'expect', 'notDeepEqual',
+        'notEqual', 'notStrictEqual', 'ok', 'raises', 'same', 'start', 'stop',
+        'strictEqual', 'test', 'throws'], function(funcName) {
+    var func = QUnit[funcName];
+    if (func) {
+      global[funcName] = func;
+    }
+  });
+
+  // expose timer methods to global
+  try {
+    timer = new java.util.Timer;
+    if (!isFunction(global.clearInterval)) {
+      global.clearInterval = clearTimer;
+    }
+    if (!isFunction(global.clearTimeout)) {
+      global.clearTimeout = clearTimer;
+    }
+    if (!isFunction(global.setInterval)) {
+      global.setInterval = setInterval;
+    }
+    if (!isFunction(global.setTimeout)) {
+      global.setTimeout = setTimeout;
+    }
+  } catch(e) { }
+
+  // add callbacks
+  QUnit.done(done);
+  QUnit.log(log);
+  QUnit.moduleStart(moduleStart);
+  QUnit.testDone(testDone);
+
+  // add wrapped function
+  QUnit.jsDump.parsers.object = parseObject;
+
+  // must call `QUnit.start()` in the test file if using QUnit < 1.3.0 with
+  // Node.js or any version of QUnit with Narwhal, Rhino, or RingoJS
+  QUnit.init();
+
+}(typeof global == 'object' && global || this));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/README.md
new file mode 100644
index 0000000..3778a27
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/README.md
@@ -0,0 +1,49 @@
+[QUnit](http://docs.jquery.com/QUnit) - A JavaScript Unit Testing framework.
+================================
+
+QUnit is a powerful, easy-to-use, JavaScript test suite. It's used by the jQuery
+project to test its code and plugins but is capable of testing any generic
+JavaScript code (and even capable of testing JavaScript code on the server-side).
+
+QUnit is especially useful for regression testing: Whenever a bug is reported,
+write a test that asserts the existence of that particular bug. Then fix it and
+commit both. Every time you work on the code again, run the tests. If the bug
+comes up again - a regression - you'll spot it immediately and know how to fix
+it, because you know what code you just changed.
+
+Having good unit test coverage makes safe refactoring easy and cheap. You can
+run the tests after each small refactoring step and always know what change
+broke something.
+
+QUnit is similar to other unit testing frameworks like JUnit, but makes use of
+the features JavaScript provides and helps with testing code in the browser, e.g.
+with its stop/start facilities for testing asynchronous code.
+
+If you are interested in helping developing QUnit, you are in the right place.
+For related discussions, visit the
+[QUnit and Testing forum](http://forum.jquery.com/qunit-and-testing).
+
+Planning for a qunitjs.com site and other testing tools related work now happens
+on the [jQuery Testing Team planning wiki](http://jquerytesting.pbworks.com/w/page/41556026/FrontPage).
+
+Development
+-----------
+
+To submit patches, fork the repository, create a branch for the change. Then implement
+the change, run `grunt` to lint and test it, then commit, push and create a pull request.
+
+Include some background for the change in the commit message and `Fixes #nnn`, referring
+to the issue number you're addressing.
+
+To run `grunt`, you need `node` and `npm`, then `npm install grunt -g`.
+
+Releases
+--------
+
+Install git-extras and run `git changelog` to update History.md.
+Update qunit/qunit.js|css and package.json to the release version, commit and
+tag, update them again to the next version, commit and push commits and tags
+(`git push --tags origin master`).
+
+Put the 'v' in front of the tag, e.g. `v1.8.0`. Clean up the changelog, removing merge commits
+or whitespace cleanups.
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit-1.8.0.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit-1.8.0.js
new file mode 100644
index 0000000..c1570c2
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit-1.8.0.js
@@ -0,0 +1,1863 @@
+/**
+ * QUnit v1.8.0 - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+(function( window ) {
+
+var QUnit,
+	config,
+	onErrorFnPrev,
+	testId = 0,
+	fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	defined = {
+	setTimeout: typeof window.setTimeout !== "undefined",
+	sessionStorage: (function() {
+		var x = "qunit-test-string";
+		try {
+			sessionStorage.setItem( x, x );
+			sessionStorage.removeItem( x );
+			return true;
+		} catch( e ) {
+			return false;
+		}
+	}())
+};
+
+function Test( settings ) {
+	extend( this, settings );
+	this.assertions = [];
+	this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+	init: function() {
+		var a, b, li,
+        tests = id( "qunit-tests" );
+
+		if ( tests ) {
+			b = document.createElement( "strong" );
+			b.innerHTML = this.name;
+
+			// `a` initialized at top of scope
+			a = document.createElement( "a" );
+			a.innerHTML = "Rerun";
+			a.href = QUnit.url({ testNumber: this.testNumber });
+
+			li = document.createElement( "li" );
+			li.appendChild( b );
+			li.appendChild( a );
+			li.className = "running";
+			li.id = this.id = "qunit-test-output" + testId++;
+
+			tests.appendChild( li );
+		}
+	},
+	setup: function() {
+		if ( this.module !== config.previousModule ) {
+			if ( config.previousModule ) {
+				runLoggingCallbacks( "moduleDone", QUnit, {
+					name: config.previousModule,
+					failed: config.moduleStats.bad,
+					passed: config.moduleStats.all - config.moduleStats.bad,
+					total: config.moduleStats.all
+				});
+			}
+			config.previousModule = this.module;
+			config.moduleStats = { all: 0, bad: 0 };
+			runLoggingCallbacks( "moduleStart", QUnit, {
+				name: this.module
+			});
+		} else if ( config.autorun ) {
+			runLoggingCallbacks( "moduleStart", QUnit, {
+				name: this.module
+			});
+		}
+
+		config.current = this;
+
+		this.testEnvironment = extend({
+			setup: function() {},
+			teardown: function() {}
+		}, this.moduleTestEnvironment );
+
+		runLoggingCallbacks( "testStart", QUnit, {
+			name: this.testName,
+			module: this.module
+		});
+
+		// allow utility functions to access the current test environment
+		// TODO why??
+		QUnit.current_testEnvironment = this.testEnvironment;
+
+		if ( !config.pollution ) {
+			saveGlobal();
+		}
+		if ( config.notrycatch ) {
+			this.testEnvironment.setup.call( this.testEnvironment );
+			return;
+		}
+		try {
+			this.testEnvironment.setup.call( this.testEnvironment );
+		} catch( e ) {
+			QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+		}
+	},
+	run: function() {
+		config.current = this;
+
+		var running = id( "qunit-testresult" );
+
+		if ( running ) {
+			running.innerHTML = "Running: <br/>" + this.name;
+		}
+
+		if ( this.async ) {
+			QUnit.stop();
+		}
+
+		if ( config.notrycatch ) {
+			this.callback.call( this.testEnvironment, QUnit.assert );
+			return;
+		}
+
+		try {
+			this.callback.call( this.testEnvironment, QUnit.assert );
+		} catch( e ) {
+			QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
+			// else next test will carry the responsibility
+			saveGlobal();
+
+			// Restart the tests if they're blocking
+			if ( config.blocking ) {
+				QUnit.start();
+			}
+		}
+	},
+	teardown: function() {
+		config.current = this;
+		if ( config.notrycatch ) {
+			this.testEnvironment.teardown.call( this.testEnvironment );
+			return;
+		} else {
+			try {
+				this.testEnvironment.teardown.call( this.testEnvironment );
+			} catch( e ) {
+				QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+			}
+		}
+		checkPollution();
+	},
+	finish: function() {
+		config.current = this;
+		if ( config.requireExpects && this.expected == null ) {
+			QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+		} else if ( this.expected != null && this.expected != this.assertions.length ) {
+			QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+		} else if ( this.expected == null && !this.assertions.length ) {
+			QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+		}
+
+		var assertion, a, b, i, li, ol,
+			test = this,
+			good = 0,
+			bad = 0,
+			tests = id( "qunit-tests" );
+
+		config.stats.all += this.assertions.length;
+		config.moduleStats.all += this.assertions.length;
+
+		if ( tests ) {
+			ol = document.createElement( "ol" );
+
+			for ( i = 0; i < this.assertions.length; i++ ) {
+				assertion = this.assertions[i];
+
+				li = document.createElement( "li" );
+				li.className = assertion.result ? "pass" : "fail";
+				li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+				ol.appendChild( li );
+
+				if ( assertion.result ) {
+					good++;
+				} else {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+
+			// store result when possible
+			if ( QUnit.config.reorder && defined.sessionStorage ) {
+				if ( bad ) {
+					sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+				} else {
+					sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+				}
+			}
+
+			if ( bad === 0 ) {
+				ol.style.display = "none";
+			}
+
+			// `b` initialized at top of scope
+			b = document.createElement( "strong" );
+			b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+			addEvent(b, "click", function() {
+				var next = b.nextSibling.nextSibling,
+					display = next.style.display;
+				next.style.display = display === "none" ? "block" : "none";
+			});
+
+			addEvent(b, "dblclick", function( e ) {
+				var target = e && e.target ? e.target : window.event.srcElement;
+				if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+					target = target.parentNode;
+				}
+				if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+					window.location = QUnit.url({ testNumber: test.testNumber });
+				}
+			});
+
+			// `li` initialized at top of scope
+			li = id( this.id );
+			li.className = bad ? "fail" : "pass";
+			li.removeChild( li.firstChild );
+			a = li.firstChild;
+			li.appendChild( b );
+			li.appendChild ( a );
+			li.appendChild( ol );
+
+		} else {
+			for ( i = 0; i < this.assertions.length; i++ ) {
+				if ( !this.assertions[i].result ) {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+		}
+
+		runLoggingCallbacks( "testDone", QUnit, {
+			name: this.testName,
+			module: this.module,
+			failed: bad,
+			passed: this.assertions.length - bad,
+			total: this.assertions.length
+		});
+
+		QUnit.reset();
+
+		config.current = undefined;
+	},
+
+	queue: function() {
+		var bad,
+			test = this;
+
+		synchronize(function() {
+			test.init();
+		});
+		function run() {
+			// each of these can by async
+			synchronize(function() {
+				test.setup();
+			});
+			synchronize(function() {
+				test.run();
+			});
+			synchronize(function() {
+				test.teardown();
+			});
+			synchronize(function() {
+				test.finish();
+			});
+		}
+
+		// `bad` initialized at top of scope
+		// defer when previous test run passed, if storage is available
+		bad = QUnit.config.reorder && defined.sessionStorage &&
+						+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+		if ( bad ) {
+			run();
+		} else {
+			synchronize( run, true );
+		}
+	}
+};
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+	// call on start of module test to prepend name to all tests
+	module: function( name, testEnvironment ) {
+		config.currentModule = name;
+		config.currentModuleTestEnviroment = testEnvironment;
+	},
+
+	asyncTest: function( testName, expected, callback ) {
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		QUnit.test( testName, expected, callback, true );
+	},
+
+	test: function( testName, expected, callback, async ) {
+		var test,
+			name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
+
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		if ( config.currentModule ) {
+			name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
+		}
+
+		test = new Test({
+			name: name,
+			testName: testName,
+			expected: expected,
+			async: async,
+			callback: callback,
+			module: config.currentModule,
+			moduleTestEnvironment: config.currentModuleTestEnviroment,
+			stack: sourceFromStacktrace( 2 )
+		});
+
+		if ( !validTest( test ) ) {
+			return;
+		}
+
+		test.queue();
+	},
+
+	// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+	expect: function( asserts ) {
+		config.current.expected = asserts;
+	},
+
+	start: function( count ) {
+		config.semaphore -= count || 1;
+		// don't start until equal number of stop-calls
+		if ( config.semaphore > 0 ) {
+			return;
+		}
+		// ignore if start is called more often then stop
+		if ( config.semaphore < 0 ) {
+			config.semaphore = 0;
+		}
+		// A slight delay, to avoid any current callbacks
+		if ( defined.setTimeout ) {
+			window.setTimeout(function() {
+				if ( config.semaphore > 0 ) {
+					return;
+				}
+				if ( config.timeout ) {
+					clearTimeout( config.timeout );
+				}
+
+				config.blocking = false;
+				process( true );
+			}, 13);
+		} else {
+			config.blocking = false;
+			process( true );
+		}
+	},
+
+	stop: function( count ) {
+		config.semaphore += count || 1;
+		config.blocking = true;
+
+		if ( config.testTimeout && defined.setTimeout ) {
+			clearTimeout( config.timeout );
+			config.timeout = window.setTimeout(function() {
+				QUnit.ok( false, "Test timed out" );
+				config.semaphore = 1;
+				QUnit.start();
+			}, config.testTimeout );
+		}
+	}
+};
+
+// Asssert helpers
+// All of these must call either QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+QUnit.assert = {
+	/**
+	 * Asserts rough true-ish result.
+	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+	 */
+	ok: function( result, msg ) {
+		if ( !config.current ) {
+			throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+		}
+		result = !!result;
+
+		var source,
+			details = {
+				result: result,
+				message: msg
+			};
+
+		msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
+		msg = "<span class='test-message'>" + msg + "</span>";
+
+		if ( !result ) {
+			source = sourceFromStacktrace( 2 );
+			if ( source ) {
+				details.source = source;
+				msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+			}
+		}
+		runLoggingCallbacks( "log", QUnit, details );
+		config.current.assertions.push({
+			result: result,
+			message: msg
+		});
+	},
+
+	/**
+	 * Assert that the first two arguments are equal, with an optional message.
+	 * Prints out both actual and expected values.
+	 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+	 */
+	equal: function( actual, expected, message ) {
+		QUnit.push( expected == actual, actual, expected, message );
+	},
+
+	notEqual: function( actual, expected, message ) {
+		QUnit.push( expected != actual, actual, expected, message );
+	},
+
+	deepEqual: function( actual, expected, message ) {
+		QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+	},
+
+	notDeepEqual: function( actual, expected, message ) {
+		QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+	},
+
+	strictEqual: function( actual, expected, message ) {
+		QUnit.push( expected === actual, actual, expected, message );
+	},
+
+	notStrictEqual: function( actual, expected, message ) {
+		QUnit.push( expected !== actual, actual, expected, message );
+	},
+
+	raises: function( block, expected, message ) {
+		var actual,
+			ok = false;
+
+		if ( typeof expected === "string" ) {
+			message = expected;
+			expected = null;
+		}
+
+		config.current.ignoreGlobalErrors = true;
+		try {
+			block.call( config.current.testEnvironment );
+		} catch (e) {
+			actual = e;
+		}
+		config.current.ignoreGlobalErrors = false;
+
+		if ( actual ) {
+			// we don't want to validate thrown error
+			if ( !expected ) {
+				ok = true;
+			// expected is a regexp
+			} else if ( QUnit.objectType( expected ) === "regexp" ) {
+				ok = expected.test( actual );
+			// expected is a constructor
+			} else if ( actual instanceof expected ) {
+				ok = true;
+			// expected is a validation function which returns true is validation passed
+			} else if ( expected.call( {}, actual ) === true ) {
+				ok = true;
+			}
+		}
+
+		QUnit.push( ok, actual, null, message );
+	}
+};
+
+// @deprecated: Kept assertion helpers in root for backwards compatibility
+extend( QUnit, QUnit.assert );
+
+/**
+ * @deprecated: Kept for backwards compatibility
+ * next step: remove entirely
+ */
+QUnit.equals = function() {
+	QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+	QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// We want access to the constructor's prototype
+(function() {
+	function F() {}
+	F.prototype = QUnit;
+	QUnit = new F();
+	// Make F QUnit's constructor so that we can add to the prototype later
+	QUnit.constructor = F;
+}());
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+	// The queue of tests to run
+	queue: [],
+
+	// block until document ready
+	blocking: true,
+
+	// when enabled, show only failing tests
+	// gets persisted through sessionStorage and can be changed in UI via checkbox
+	hidepassed: false,
+
+	// by default, run previously failed tests first
+	// very useful in combination with "Hide passed tests" checked
+	reorder: true,
+
+	// by default, modify document.title when suite is done
+	altertitle: true,
+
+	// when enabled, all tests must call expect()
+	requireExpects: false,
+
+	urlConfig: [ "noglobals", "notrycatch" ],
+
+	// logging callback queues
+	begin: [],
+	done: [],
+	log: [],
+	testStart: [],
+	testDone: [],
+	moduleStart: [],
+	moduleDone: []
+};
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+	var i,
+		location = window.location || { search: "", protocol: "file:" },
+		params = location.search.slice( 1 ).split( "&" ),
+		length = params.length,
+		urlParams = {},
+		current;
+
+	if ( params[ 0 ] ) {
+		for ( i = 0; i < length; i++ ) {
+			current = params[ i ].split( "=" );
+			current[ 0 ] = decodeURIComponent( current[ 0 ] );
+			// allow just a key to turn on a flag, e.g., test.html?noglobals
+			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+			urlParams[ current[ 0 ] ] = current[ 1 ];
+		}
+	}
+
+	QUnit.urlParams = urlParams;
+
+	// String search anywhere in moduleName+testName
+	config.filter = urlParams.filter;
+
+	// Exact match of the module name
+	config.module = urlParams.module;
+
+	config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+
+	// Figure out if we're running the tests from a server or not
+	QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+	extend( window, QUnit );
+
+	// Expose QUnit object
+	window.QUnit = QUnit;
+}
+
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
+	config: config,
+
+	// Initialize the configuration options
+	init: function() {
+		extend( config, {
+			stats: { all: 0, bad: 0 },
+			moduleStats: { all: 0, bad: 0 },
+			started: +new Date(),
+			updateRate: 1000,
+			blocking: false,
+			autostart: true,
+			autorun: false,
+			filter: "",
+			queue: [],
+			semaphore: 0
+		});
+
+		var tests, banner, result,
+			qunit = id( "qunit" );
+
+		if ( qunit ) {
+			qunit.innerHTML =
+				"<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
+				"<h2 id='qunit-banner'></h2>" +
+				"<div id='qunit-testrunner-toolbar'></div>" +
+				"<h2 id='qunit-userAgent'></h2>" +
+				"<ol id='qunit-tests'></ol>";
+		}
+
+		tests = id( "qunit-tests" );
+		banner = id( "qunit-banner" );
+		result = id( "qunit-testresult" );
+
+		if ( tests ) {
+			tests.innerHTML = "";
+		}
+
+		if ( banner ) {
+			banner.className = "";
+		}
+
+		if ( result ) {
+			result.parentNode.removeChild( result );
+		}
+
+		if ( tests ) {
+			result = document.createElement( "p" );
+			result.id = "qunit-testresult";
+			result.className = "result";
+			tests.parentNode.insertBefore( result, tests );
+			result.innerHTML = "Running...<br/>&nbsp;";
+		}
+	},
+
+	// Resets the test setup. Useful for tests that modify the DOM.
+	// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
+	reset: function() {
+		var fixture;
+
+		if ( window.jQuery ) {
+			jQuery( "#qunit-fixture" ).html( config.fixture );
+		} else {
+			fixture = id( "qunit-fixture" );
+			if ( fixture ) {
+				fixture.innerHTML = config.fixture;
+			}
+		}
+	},
+
+	// Trigger an event on an element.
+	// @example triggerEvent( document.body, "click" );
+	triggerEvent: function( elem, type, event ) {
+		if ( document.createEvent ) {
+			event = document.createEvent( "MouseEvents" );
+			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+				0, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+			elem.dispatchEvent( event );
+		} else if ( elem.fireEvent ) {
+			elem.fireEvent( "on" + type );
+		}
+	},
+
+	// Safe object type checking
+	is: function( type, obj ) {
+		return QUnit.objectType( obj ) == type;
+	},
+
+	objectType: function( obj ) {
+		if ( typeof obj === "undefined" ) {
+				return "undefined";
+		// consider: typeof null === object
+		}
+		if ( obj === null ) {
+				return "null";
+		}
+
+		var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
+
+		switch ( type ) {
+			case "Number":
+				if ( isNaN(obj) ) {
+					return "nan";
+				}
+				return "number";
+			case "String":
+			case "Boolean":
+			case "Array":
+			case "Date":
+			case "RegExp":
+			case "Function":
+				return type.toLowerCase();
+		}
+		if ( typeof obj === "object" ) {
+			return "object";
+		}
+		return undefined;
+	},
+
+	push: function( result, actual, expected, message ) {
+		if ( !config.current ) {
+			throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+		}
+
+		var output, source,
+			details = {
+				result: result,
+				message: message,
+				actual: actual,
+				expected: expected
+			};
+
+		message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
+		message = "<span class='test-message'>" + message + "</span>";
+		output = message;
+
+		if ( !result ) {
+			expected = escapeInnerText( QUnit.jsDump.parse(expected) );
+			actual = escapeInnerText( QUnit.jsDump.parse(actual) );
+			output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
+
+			if ( actual != expected ) {
+				output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
+				output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
+			}
+
+			source = sourceFromStacktrace();
+
+			if ( source ) {
+				details.source = source;
+				output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+			}
+
+			output += "</table>";
+		}
+
+		runLoggingCallbacks( "log", QUnit, details );
+
+		config.current.assertions.push({
+			result: !!result,
+			message: output
+		});
+	},
+
+	pushFailure: function( message, source ) {
+		if ( !config.current ) {
+			throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+		}
+
+		var output,
+			details = {
+				result: false,
+				message: message
+			};
+
+		message = escapeInnerText(message ) || "error";
+		message = "<span class='test-message'>" + message + "</span>";
+		output = message;
+
+		if ( source ) {
+			details.source = source;
+			output += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+		}
+
+		runLoggingCallbacks( "log", QUnit, details );
+
+		config.current.assertions.push({
+			result: false,
+			message: output
+		});
+	},
+
+	url: function( params ) {
+		params = extend( extend( {}, QUnit.urlParams ), params );
+		var key,
+			querystring = "?";
+
+		for ( key in params ) {
+			if ( !hasOwn.call( params, key ) ) {
+				continue;
+			}
+			querystring += encodeURIComponent( key ) + "=" +
+				encodeURIComponent( params[ key ] ) + "&";
+		}
+		return window.location.pathname + querystring.slice( 0, -1 );
+	},
+
+	extend: extend,
+	id: id,
+	addEvent: addEvent
+	// load, equiv, jsDump, diff: Attached later
+});
+
+/**
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
+ * QUnit object.
+ */
+extend( QUnit.constructor.prototype, {
+
+	// Logging callbacks; all receive a single argument with the listed properties
+	// run test/logs.html for any related changes
+	begin: registerLoggingCallback( "begin" ),
+
+	// done: { failed, passed, total, runtime }
+	done: registerLoggingCallback( "done" ),
+
+	// log: { result, actual, expected, message }
+	log: registerLoggingCallback( "log" ),
+
+	// testStart: { name }
+	testStart: registerLoggingCallback( "testStart" ),
+
+	// testDone: { name, failed, passed, total }
+	testDone: registerLoggingCallback( "testDone" ),
+
+	// moduleStart: { name }
+	moduleStart: registerLoggingCallback( "moduleStart" ),
+
+	// moduleDone: { name, failed, passed, total }
+	moduleDone: registerLoggingCallback( "moduleDone" )
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+	config.autorun = true;
+}
+
+QUnit.load = function() {
+	runLoggingCallbacks( "begin", QUnit, {} );
+
+	// Initialize the config, saving the execution queue
+	var banner, filter, i, label, len, main, ol, toolbar, userAgent, val,
+		urlConfigHtml = "",
+		oldconfig = extend( {}, config );
+
+	QUnit.init();
+	extend(config, oldconfig);
+
+	config.blocking = false;
+
+	len = config.urlConfig.length;
+
+	for ( i = 0; i < len; i++ ) {
+		val = config.urlConfig[i];
+		config[val] = QUnit.urlParams[val];
+		urlConfigHtml += "<label><input name='" + val + "' type='checkbox'" + ( config[val] ? " checked='checked'" : "" ) + ">" + val + "</label>";
+	}
+
+	// `userAgent` initialized at top of scope
+	userAgent = id( "qunit-userAgent" );
+	if ( userAgent ) {
+		userAgent.innerHTML = navigator.userAgent;
+	}
+
+	// `banner` initialized at top of scope
+	banner = id( "qunit-header" );
+	if ( banner ) {
+		banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined }) + "'>" + banner.innerHTML + "</a> " + urlConfigHtml;
+		addEvent( banner, "change", function( event ) {
+			var params = {};
+			params[ event.target.name ] = event.target.checked ? true : undefined;
+			window.location = QUnit.url( params );
+		});
+	}
+
+	// `toolbar` initialized at top of scope
+	toolbar = id( "qunit-testrunner-toolbar" );
+	if ( toolbar ) {
+		// `filter` initialized at top of scope
+		filter = document.createElement( "input" );
+		filter.type = "checkbox";
+		filter.id = "qunit-filter-pass";
+
+		addEvent( filter, "click", function() {
+			var tmp,
+				ol = document.getElementById( "qunit-tests" );
+
+			if ( filter.checked ) {
+				ol.className = ol.className + " hidepass";
+			} else {
+				tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+				ol.className = tmp.replace( / hidepass /, " " );
+			}
+			if ( defined.sessionStorage ) {
+				if (filter.checked) {
+					sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
+				} else {
+					sessionStorage.removeItem( "qunit-filter-passed-tests" );
+				}
+			}
+		});
+
+		if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
+			filter.checked = true;
+			// `ol` initialized at top of scope
+			ol = document.getElementById( "qunit-tests" );
+			ol.className = ol.className + " hidepass";
+		}
+		toolbar.appendChild( filter );
+
+		// `label` initialized at top of scope
+		label = document.createElement( "label" );
+		label.setAttribute( "for", "qunit-filter-pass" );
+		label.innerHTML = "Hide passed tests";
+		toolbar.appendChild( label );
+	}
+
+	// `main` initialized at top of scope
+	main = id( "qunit-fixture" );
+	if ( main ) {
+		config.fixture = main.innerHTML;
+	}
+
+	if ( config.autostart ) {
+		QUnit.start();
+	}
+};
+
+addEvent( window, "load", QUnit.load );
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will surpress the default browser handler,
+// returning false will let it run.
+window.onerror = function ( error, filePath, linerNr ) {
+	var ret = false;
+	if ( onErrorFnPrev ) {
+		ret = onErrorFnPrev( error, filePath, linerNr );
+	}
+
+	// Treat return value as window.onerror itself does,
+	// Only do our handling if not surpressed.
+	if ( ret !== true ) {
+		if ( QUnit.config.current ) {
+			if ( QUnit.config.current.ignoreGlobalErrors ) {
+				return true;
+			}
+			QUnit.pushFailure( error, filePath + ":" + linerNr );
+		} else {
+			QUnit.test( "global failure", function() {
+				QUnit.pushFailure( error, filePath + ":" + linerNr );
+			});
+		}
+		return false;
+	}
+
+	return ret;
+};
+
+function done() {
+	config.autorun = true;
+
+	// Log the last module results
+	if ( config.currentModule ) {
+		runLoggingCallbacks( "moduleDone", QUnit, {
+			name: config.currentModule,
+			failed: config.moduleStats.bad,
+			passed: config.moduleStats.all - config.moduleStats.bad,
+			total: config.moduleStats.all
+		});
+	}
+
+	var i, key,
+		banner = id( "qunit-banner" ),
+		tests = id( "qunit-tests" ),
+		runtime = +new Date() - config.started,
+		passed = config.stats.all - config.stats.bad,
+		html = [
+			"Tests completed in ",
+			runtime,
+			" milliseconds.<br/>",
+			"<span class='passed'>",
+			passed,
+			"</span> tests of <span class='total'>",
+			config.stats.all,
+			"</span> passed, <span class='failed'>",
+			config.stats.bad,
+			"</span> failed."
+		].join( "" );
+
+	if ( banner ) {
+		banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
+	}
+
+	if ( tests ) {
+		id( "qunit-testresult" ).innerHTML = html;
+	}
+
+	if ( config.altertitle && typeof document !== "undefined" && document.title ) {
+		// show ✖ for good, ✔ for bad suite result in title
+		// use escape sequences in case file gets loaded with non-utf-8-charset
+		document.title = [
+			( config.stats.bad ? "\u2716" : "\u2714" ),
+			document.title.replace( /^[\u2714\u2716] /i, "" )
+		].join( " " );
+	}
+
+	// clear own sessionStorage items if all tests passed
+	if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+		// `key` & `i` initialized at top of scope
+		for ( i = 0; i < sessionStorage.length; i++ ) {
+			key = sessionStorage.key( i++ );
+			if ( key.indexOf( "qunit-test-" ) === 0 ) {
+				sessionStorage.removeItem( key );
+			}
+		}
+	}
+
+	runLoggingCallbacks( "done", QUnit, {
+		failed: config.stats.bad,
+		passed: passed,
+		total: config.stats.all,
+		runtime: runtime
+	});
+}
+
+/** @return Boolean: true if this test should be ran */
+function validTest( test ) {
+	var include,
+		filter = config.filter && config.filter.toLowerCase(),
+		module = config.module,
+		fullName = (test.module + ": " + test.testName).toLowerCase();
+
+	if ( config.testNumber ) {
+		return test.testNumber === config.testNumber;
+	}
+
+	if ( module && test.module !== module ) {
+		return false;
+	}
+
+	if ( !filter ) {
+		return true;
+	}
+
+	include = filter.charAt( 0 ) !== "!";
+	if ( !include ) {
+		filter = filter.slice( 1 );
+	}
+
+	// If the filter matches, we need to honour include
+	if ( fullName.indexOf( filter ) !== -1 ) {
+		return include;
+	}
+
+	// Otherwise, do the opposite
+	return !include;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
+// Later Safari and IE10 are supposed to support error.stack as well
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+	offset = offset === undefined ? 3 : offset;
+
+	var stack, include, i, regex;
+
+	if ( e.stacktrace ) {
+		// Opera
+		return e.stacktrace.split( "\n" )[ offset + 3 ];
+	} else if ( e.stack ) {
+		// Firefox, Chrome
+		stack = e.stack.split( "\n" );
+		if (/^error$/i.test( stack[0] ) ) {
+			stack.shift();
+		}
+		if ( fileName ) {
+			include = [];
+			for ( i = offset; i < stack.length; i++ ) {
+				if ( stack[ i ].indexOf( fileName ) != -1 ) {
+					break;
+				}
+				include.push( stack[ i ] );
+			}
+			if ( include.length ) {
+				return include.join( "\n" );
+			}
+		}
+		return stack[ offset ];
+	} else if ( e.sourceURL ) {
+		// Safari, PhantomJS
+		// hopefully one day Safari provides actual stacktraces
+		// exclude useless self-reference for generated Error objects
+		if ( /qunit.js$/.test( e.sourceURL ) ) {
+			return;
+		}
+		// for actual exceptions, this is useful
+		return e.sourceURL + ":" + e.line;
+	}
+}
+function sourceFromStacktrace( offset ) {
+	try {
+		throw new Error();
+	} catch ( e ) {
+		return extractStacktrace( e, offset );
+	}
+}
+
+function escapeInnerText( s ) {
+	if ( !s ) {
+		return "";
+	}
+	s = s + "";
+	return s.replace( /[\&<>]/g, function( s ) {
+		switch( s ) {
+			case "&": return "&amp;";
+			case "<": return "&lt;";
+			case ">": return "&gt;";
+			default: return s;
+		}
+	});
+}
+
+function synchronize( callback, last ) {
+	config.queue.push( callback );
+
+	if ( config.autorun && !config.blocking ) {
+		process( last );
+	}
+}
+
+function process( last ) {
+	function next() {
+		process( last );
+	}
+	var start = new Date().getTime();
+	config.depth = config.depth ? config.depth + 1 : 1;
+
+	while ( config.queue.length && !config.blocking ) {
+		if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+			config.queue.shift()();
+		} else {
+			window.setTimeout( next, 13 );
+			break;
+		}
+	}
+	config.depth--;
+	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+		done();
+	}
+}
+
+function saveGlobal() {
+	config.pollution = [];
+
+	if ( config.noglobals ) {
+		for ( var key in window ) {
+			// in Opera sometimes DOM element ids show up here, ignore them
+			if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
+				continue;
+			}
+			config.pollution.push( key );
+		}
+	}
+}
+
+function checkPollution( name ) {
+	var newGlobals,
+		deletedGlobals,
+		old = config.pollution;
+
+	saveGlobal();
+
+	newGlobals = diff( config.pollution, old );
+	if ( newGlobals.length > 0 ) {
+		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
+	}
+
+	deletedGlobals = diff( old, config.pollution );
+	if ( deletedGlobals.length > 0 ) {
+		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
+	}
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+	var i, j,
+		result = a.slice();
+
+	for ( i = 0; i < result.length; i++ ) {
+		for ( j = 0; j < b.length; j++ ) {
+			if ( result[i] === b[j] ) {
+				result.splice( i, 1 );
+				i--;
+				break;
+			}
+		}
+	}
+	return result;
+}
+
+function extend( a, b ) {
+	for ( var prop in b ) {
+		if ( b[ prop ] === undefined ) {
+			delete a[ prop ];
+
+		// Avoid "Member not found" error in IE8 caused by setting window.constructor
+		} else if ( prop !== "constructor" || a !== window ) {
+			a[ prop ] = b[ prop ];
+		}
+	}
+
+	return a;
+}
+
+function addEvent( elem, type, fn ) {
+	if ( elem.addEventListener ) {
+		elem.addEventListener( type, fn, false );
+	} else if ( elem.attachEvent ) {
+		elem.attachEvent( "on" + type, fn );
+	} else {
+		fn();
+	}
+}
+
+function id( name ) {
+	return !!( typeof document !== "undefined" && document && document.getElementById ) &&
+		document.getElementById( name );
+}
+
+function registerLoggingCallback( key ) {
+	return function( callback ) {
+		config[key].push( callback );
+	};
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks( key, scope, args ) {
+	//debugger;
+	var i, callbacks;
+	if ( QUnit.hasOwnProperty( key ) ) {
+		QUnit[ key ].call(scope, args );
+	} else {
+		callbacks = config[ key ];
+		for ( i = 0; i < callbacks.length; i++ ) {
+			callbacks[ i ].call( scope, args );
+		}
+	}
+}
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = (function() {
+
+	// Call the o related callback with the given arguments.
+	function bindCallbacks( o, callbacks, args ) {
+		var prop = QUnit.objectType( o );
+		if ( prop ) {
+			if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+				return callbacks[ prop ].apply( callbacks, args );
+			} else {
+				return callbacks[ prop ]; // or undefined
+			}
+		}
+	}
+
+	// the real equiv function
+	var innerEquiv,
+		// stack to decide between skip/abort functions
+		callers = [],
+		// stack to avoiding loops from circular referencing
+		parents = [],
+
+		getProto = Object.getPrototypeOf || function ( obj ) {
+			return obj.__proto__;
+		},
+		callbacks = (function () {
+
+			// for string, boolean, number and null
+			function useStrictEquality( b, a ) {
+				if ( b instanceof a.constructor || a instanceof b.constructor ) {
+					// to catch short annotaion VS 'new' annotation of a
+					// declaration
+					// e.g. var i = 1;
+					// var j = new Number(1);
+					return a == b;
+				} else {
+					return a === b;
+				}
+			}
+
+			return {
+				"string": useStrictEquality,
+				"boolean": useStrictEquality,
+				"number": useStrictEquality,
+				"null": useStrictEquality,
+				"undefined": useStrictEquality,
+
+				"nan": function( b ) {
+					return isNaN( b );
+				},
+
+				"date": function( b, a ) {
+					return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+				},
+
+				"regexp": function( b, a ) {
+					return QUnit.objectType( b ) === "regexp" &&
+						// the regex itself
+						a.source === b.source &&
+						// and its modifers
+						a.global === b.global &&
+						// (gmi) ...
+						a.ignoreCase === b.ignoreCase &&
+						a.multiline === b.multiline;
+				},
+
+				// - skip when the property is a method of an instance (OOP)
+				// - abort otherwise,
+				// initial === would have catch identical references anyway
+				"function": function() {
+					var caller = callers[callers.length - 1];
+					return caller !== Object && typeof caller !== "undefined";
+				},
+
+				"array": function( b, a ) {
+					var i, j, len, loop;
+
+					// b could be an object literal here
+					if ( QUnit.objectType( b ) !== "array" ) {
+						return false;
+					}
+
+					len = a.length;
+					if ( len !== b.length ) {
+						// safe and faster
+						return false;
+					}
+
+					// track reference to avoid circular references
+					parents.push( a );
+					for ( i = 0; i < len; i++ ) {
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							if ( parents[j] === a[i] ) {
+								loop = true;// dont rewalk array
+							}
+						}
+						if ( !loop && !innerEquiv(a[i], b[i]) ) {
+							parents.pop();
+							return false;
+						}
+					}
+					parents.pop();
+					return true;
+				},
+
+				"object": function( b, a ) {
+					var i, j, loop,
+						// Default to true
+						eq = true,
+						aProperties = [],
+						bProperties = [];
+
+					// comparing constructors is more strict than using
+					// instanceof
+					if ( a.constructor !== b.constructor ) {
+						// Allow objects with no prototype to be equivalent to
+						// objects with Object as their constructor.
+						if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
+							( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
+								return false;
+						}
+					}
+
+					// stack constructor before traversing properties
+					callers.push( a.constructor );
+					// track reference to avoid circular references
+					parents.push( a );
+
+					for ( i in a ) { // be strict: don't ensures hasOwnProperty
+									// and go deep
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							if ( parents[j] === a[i] ) {
+								// don't go down the same path twice
+								loop = true;
+							}
+						}
+						aProperties.push(i); // collect a's properties
+
+						if (!loop && !innerEquiv( a[i], b[i] ) ) {
+							eq = false;
+							break;
+						}
+					}
+
+					callers.pop(); // unstack, we are done
+					parents.pop();
+
+					for ( i in b ) {
+						bProperties.push( i ); // collect b's properties
+					}
+
+					// Ensures identical properties name
+					return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+				}
+			};
+		}());
+
+	innerEquiv = function() { // can take multiple arguments
+		var args = [].slice.apply( arguments );
+		if ( args.length < 2 ) {
+			return true; // end transition
+		}
+
+		return (function( a, b ) {
+			if ( a === b ) {
+				return true; // catch the most you can
+			} else if ( a === null || b === null || typeof a === "undefined" ||
+					typeof b === "undefined" ||
+					QUnit.objectType(a) !== QUnit.objectType(b) ) {
+				return false; // don't lose time with error prone cases
+			} else {
+				return bindCallbacks(a, callbacks, [ b, a ]);
+			}
+
+			// apply transition with (1..n) arguments
+		}( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
+	};
+
+	return innerEquiv;
+}());
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+	function quote( str ) {
+		return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
+	}
+	function literal( o ) {
+		return o + "";
+	}
+	function join( pre, arr, post ) {
+		var s = jsDump.separator(),
+			base = jsDump.indent(),
+			inner = jsDump.indent(1);
+		if ( arr.join ) {
+			arr = arr.join( "," + s + inner );
+		}
+		if ( !arr ) {
+			return pre + post;
+		}
+		return [ pre, inner + arr, base + post ].join(s);
+	}
+	function array( arr, stack ) {
+		var i = arr.length, ret = new Array(i);
+		this.up();
+		while ( i-- ) {
+			ret[i] = this.parse( arr[i] , undefined , stack);
+		}
+		this.down();
+		return join( "[", ret, "]" );
+	}
+
+	var reName = /^function (\w+)/,
+		jsDump = {
+			parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
+				stack = stack || [ ];
+				var inStack, res,
+					parser = this.parsers[ type || this.typeOf(obj) ];
+
+				type = typeof parser;
+				inStack = inArray( obj, stack );
+
+				if ( inStack != -1 ) {
+					return "recursion(" + (inStack - stack.length) + ")";
+				}
+				//else
+				if ( type == "function" )  {
+					stack.push( obj );
+					res = parser.call( this, obj, stack );
+					stack.pop();
+					return res;
+				}
+				// else
+				return ( type == "string" ) ? parser : this.parsers.error;
+			},
+			typeOf: function( obj ) {
+				var type;
+				if ( obj === null ) {
+					type = "null";
+				} else if ( typeof obj === "undefined" ) {
+					type = "undefined";
+				} else if ( QUnit.is( "regexp", obj) ) {
+					type = "regexp";
+				} else if ( QUnit.is( "date", obj) ) {
+					type = "date";
+				} else if ( QUnit.is( "function", obj) ) {
+					type = "function";
+				} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+					type = "window";
+				} else if ( obj.nodeType === 9 ) {
+					type = "document";
+				} else if ( obj.nodeType ) {
+					type = "node";
+				} else if (
+					// native arrays
+					toString.call( obj ) === "[object Array]" ||
+					// NodeList objects
+					( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+				) {
+					type = "array";
+				} else {
+					type = typeof obj;
+				}
+				return type;
+			},
+			separator: function() {
+				return this.multiline ?	this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
+			},
+			indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+				if ( !this.multiline ) {
+					return "";
+				}
+				var chr = this.indentChar;
+				if ( this.HTML ) {
+					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&nbsp;" );
+				}
+				return new Array( this._depth_ + (extra||0) ).join(chr);
+			},
+			up: function( a ) {
+				this._depth_ += a || 1;
+			},
+			down: function( a ) {
+				this._depth_ -= a || 1;
+			},
+			setParser: function( name, parser ) {
+				this.parsers[name] = parser;
+			},
+			// The next 3 are exposed so you can use them
+			quote: quote,
+			literal: literal,
+			join: join,
+			//
+			_depth_: 1,
+			// This is the list of parsers, to modify them, use jsDump.setParser
+			parsers: {
+				window: "[Window]",
+				document: "[Document]",
+				error: "[ERROR]", //when no parser is found, shouldn"t happen
+				unknown: "[Unknown]",
+				"null": "null",
+				"undefined": "undefined",
+				"function": function( fn ) {
+					var ret = "function",
+						name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE
+
+					if ( name ) {
+						ret += " " + name;
+					}
+					ret += "( ";
+
+					ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
+					return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+				},
+				array: array,
+				nodelist: array,
+				"arguments": array,
+				object: function( map, stack ) {
+					var ret = [ ], keys, key, val, i;
+					QUnit.jsDump.up();
+					if ( Object.keys ) {
+						keys = Object.keys( map );
+					} else {
+						keys = [];
+						for ( key in map ) {
+							keys.push( key );
+						}
+					}
+					keys.sort();
+					for ( i = 0; i < keys.length; i++ ) {
+						key = keys[ i ];
+						val = map[ key ];
+						ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+					}
+					QUnit.jsDump.down();
+					return join( "{", ret, "}" );
+				},
+				node: function( node ) {
+					var a, val,
+						open = QUnit.jsDump.HTML ? "&lt;" : "<",
+						close = QUnit.jsDump.HTML ? "&gt;" : ">",
+						tag = node.nodeName.toLowerCase(),
+						ret = open + tag;
+
+					for ( a in QUnit.jsDump.DOMAttrs ) {
+						val = node[ QUnit.jsDump.DOMAttrs[a] ];
+						if ( val ) {
+							ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
+						}
+					}
+					return ret + close + open + "/" + tag + close;
+				},
+				functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
+					var args,
+						l = fn.length;
+
+					if ( !l ) {
+						return "";
+					}
+
+					args = new Array(l);
+					while ( l-- ) {
+						args[l] = String.fromCharCode(97+l);//97 is 'a'
+					}
+					return " " + args.join( ", " ) + " ";
+				},
+				key: quote, //object calls it internally, the key part of an item in a map
+				functionCode: "[code]", //function calls it internally, it's the content of the function
+				attribute: quote, //node calls it internally, it's an html attribute value
+				string: quote,
+				date: quote,
+				regexp: literal, //regex
+				number: literal,
+				"boolean": literal
+			},
+			DOMAttrs: {
+				//attributes to dump from nodes, name=>realName
+				id: "id",
+				name: "name",
+				"class": "className"
+			},
+			HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
+			indentChar: "  ",//indentation unit
+			multiline: true //if true, items in a collection, are separated by a \n, else just a space.
+		};
+
+	return jsDump;
+}());
+
+// from Sizzle.js
+function getText( elems ) {
+	var i, elem,
+		ret = "";
+
+	for ( i = 0; elems[i]; i++ ) {
+		elem = elems[i];
+
+		// Get the text from text nodes and CDATA nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+			ret += elem.nodeValue;
+
+		// Traverse everything else, except comment nodes
+		} else if ( elem.nodeType !== 8 ) {
+			ret += getText( elem.childNodes );
+		}
+	}
+
+	return ret;
+}
+
+// from jquery.js
+function inArray( elem, array ) {
+	if ( array.indexOf ) {
+		return array.indexOf( elem );
+	}
+
+	for ( var i = 0, length = array.length; i < length; i++ ) {
+		if ( array[ i ] === elem ) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * Javascript Diff Algorithm
+ *  By John Resig (http://ejohn.org/)
+ *  Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ *  http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
+ */
+QUnit.diff = (function() {
+	function diff( o, n ) {
+		var i,
+			ns = {},
+			os = {};
+
+		for ( i = 0; i < n.length; i++ ) {
+			if ( ns[ n[i] ] == null ) {
+				ns[ n[i] ] = {
+					rows: [],
+					o: null
+				};
+			}
+			ns[ n[i] ].rows.push( i );
+		}
+
+		for ( i = 0; i < o.length; i++ ) {
+			if ( os[ o[i] ] == null ) {
+				os[ o[i] ] = {
+					rows: [],
+					n: null
+				};
+			}
+			os[ o[i] ].rows.push( i );
+		}
+
+		for ( i in ns ) {
+			if ( !hasOwn.call( ns, i ) ) {
+				continue;
+			}
+			if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) {
+				n[ ns[i].rows[0] ] = {
+					text: n[ ns[i].rows[0] ],
+					row: os[i].rows[0]
+				};
+				o[ os[i].rows[0] ] = {
+					text: o[ os[i].rows[0] ],
+					row: ns[i].rows[0]
+				};
+			}
+		}
+
+		for ( i = 0; i < n.length - 1; i++ ) {
+			if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
+						n[ i + 1 ] == o[ n[i].row + 1 ] ) {
+
+				n[ i + 1 ] = {
+					text: n[ i + 1 ],
+					row: n[i].row + 1
+				};
+				o[ n[i].row + 1 ] = {
+					text: o[ n[i].row + 1 ],
+					row: i + 1
+				};
+			}
+		}
+
+		for ( i = n.length - 1; i > 0; i-- ) {
+			if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
+						n[ i - 1 ] == o[ n[i].row - 1 ]) {
+
+				n[ i - 1 ] = {
+					text: n[ i - 1 ],
+					row: n[i].row - 1
+				};
+				o[ n[i].row - 1 ] = {
+					text: o[ n[i].row - 1 ],
+					row: i - 1
+				};
+			}
+		}
+
+		return {
+			o: o,
+			n: n
+		};
+	}
+
+	return function( o, n ) {
+		o = o.replace( /\s+$/, "" );
+		n = n.replace( /\s+$/, "" );
+
+		var i, pre,
+			str = "",
+			out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
+			oSpace = o.match(/\s+/g),
+			nSpace = n.match(/\s+/g);
+
+		if ( oSpace == null ) {
+			oSpace = [ " " ];
+		}
+		else {
+			oSpace.push( " " );
+		}
+
+		if ( nSpace == null ) {
+			nSpace = [ " " ];
+		}
+		else {
+			nSpace.push( " " );
+		}
+
+		if ( out.n.length === 0 ) {
+			for ( i = 0; i < out.o.length; i++ ) {
+				str += "<del>" + out.o[i] + oSpace[i] + "</del>";
+			}
+		}
+		else {
+			if ( out.n[0].text == null ) {
+				for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
+					str += "<del>" + out.o[n] + oSpace[n] + "</del>";
+				}
+			}
+
+			for ( i = 0; i < out.n.length; i++ ) {
+				if (out.n[i].text == null) {
+					str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
+				}
+				else {
+					// `pre` initialized at top of scope
+					pre = "";
+
+					for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
+						pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
+					}
+					str += " " + out.n[i].text + nSpace[i] + pre;
+				}
+			}
+		}
+
+		return str;
+	};
+}());
+
+// for CommonJS enviroments, export everything
+if ( typeof exports !== "undefined" ) {
+	extend(exports, QUnit);
+}
+
+// get at whatever the global object is, like window in browsers
+}( (function() {return this;}.call()) ));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit.css b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit.css
new file mode 100644
index 0000000..257b224
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit.css
@@ -0,0 +1,231 @@
+/**
+ * QUnit v1.9.0 - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+/** Font Family and Sizes */
+
+#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
+	font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
+}
+
+#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
+#qunit-tests { font-size: smaller; }
+
+
+/** Resets */
+
+#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult {
+	margin: 0;
+	padding: 0;
+}
+
+
+/** Header */
+
+#qunit-header {
+	padding: 0.5em 0 0.5em 1em;
+
+	color: #8699a4;
+	background-color: #0d3349;
+
+	font-size: 1.5em;
+	line-height: 1em;
+	font-weight: normal;
+
+	border-radius: 5px 5px 0 0;
+	-moz-border-radius: 5px 5px 0 0;
+	-webkit-border-top-right-radius: 5px;
+	-webkit-border-top-left-radius: 5px;
+}
+
+#qunit-header a {
+	text-decoration: none;
+	color: #c2ccd1;
+}
+
+#qunit-header a:hover,
+#qunit-header a:focus {
+	color: #fff;
+}
+
+#qunit-testrunner-toolbar label {
+	display: inline-block;
+	padding: 0 .5em 0 .1em;
+}
+
+#qunit-banner {
+	height: 5px;
+}
+
+#qunit-testrunner-toolbar {
+	padding: 0.5em 0 0.5em 2em;
+	color: #5E740B;
+	background-color: #eee;
+}
+
+#qunit-userAgent {
+	padding: 0.5em 0 0.5em 2.5em;
+	background-color: #2b81af;
+	color: #fff;
+	text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
+}
+
+
+/** Tests: Pass/Fail */
+
+#qunit-tests {
+	list-style-position: inside;
+}
+
+#qunit-tests li {
+	padding: 0.4em 0.5em 0.4em 2.5em;
+	border-bottom: 1px solid #fff;
+	list-style-position: inside;
+}
+
+#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running  {
+	display: none;
+}
+
+#qunit-tests li strong {
+	cursor: pointer;
+}
+
+#qunit-tests li a {
+	padding: 0.5em;
+	color: #c2ccd1;
+	text-decoration: none;
+}
+#qunit-tests li a:hover,
+#qunit-tests li a:focus {
+	color: #000;
+}
+
+#qunit-tests ol {
+	margin-top: 0.5em;
+	padding: 0.5em;
+
+	background-color: #fff;
+
+	border-radius: 5px;
+	-moz-border-radius: 5px;
+	-webkit-border-radius: 5px;
+}
+
+#qunit-tests table {
+	border-collapse: collapse;
+	margin-top: .2em;
+}
+
+#qunit-tests th {
+	text-align: right;
+	vertical-align: top;
+	padding: 0 .5em 0 0;
+}
+
+#qunit-tests td {
+	vertical-align: top;
+}
+
+#qunit-tests pre {
+	margin: 0;
+	white-space: pre-wrap;
+	word-wrap: break-word;
+}
+
+#qunit-tests del {
+	background-color: #e0f2be;
+	color: #374e0c;
+	text-decoration: none;
+}
+
+#qunit-tests ins {
+	background-color: #ffcaca;
+	color: #500;
+	text-decoration: none;
+}
+
+/*** Test Counts */
+
+#qunit-tests b.counts                       { color: black; }
+#qunit-tests b.passed                       { color: #5E740B; }
+#qunit-tests b.failed                       { color: #710909; }
+
+#qunit-tests li li {
+	padding: 5px;
+	background-color: #fff;
+	border-bottom: none;
+	list-style-position: inside;
+}
+
+/*** Passing Styles */
+
+#qunit-tests li li.pass {
+	color: #3c510c;
+	background-color: #fff;
+	border-left: 10px solid #C6E746;
+}
+
+#qunit-tests .pass                          { color: #528CE0; background-color: #D2E0E6; }
+#qunit-tests .pass .test-name               { color: #366097; }
+
+#qunit-tests .pass .test-actual,
+#qunit-tests .pass .test-expected           { color: #999999; }
+
+#qunit-banner.qunit-pass                    { background-color: #C6E746; }
+
+/*** Failing Styles */
+
+#qunit-tests li li.fail {
+	color: #710909;
+	background-color: #fff;
+	border-left: 10px solid #EE5757;
+	white-space: pre;
+}
+
+#qunit-tests > li:last-child {
+	border-radius: 0 0 5px 5px;
+	-moz-border-radius: 0 0 5px 5px;
+	-webkit-border-bottom-right-radius: 5px;
+	-webkit-border-bottom-left-radius: 5px;
+}
+
+#qunit-tests .fail                          { color: #000000; background-color: #EE5757; }
+#qunit-tests .fail .test-name,
+#qunit-tests .fail .module-name             { color: #000000; }
+
+#qunit-tests .fail .test-actual             { color: #EE5757; }
+#qunit-tests .fail .test-expected           { color: green;   }
+
+#qunit-banner.qunit-fail                    { background-color: #EE5757; }
+
+
+/** Result */
+
+#qunit-testresult {
+	padding: 0.5em 0.5em 0.5em 2.5em;
+
+	color: #2b81af;
+	background-color: #D2E0E6;
+
+	border-bottom: 1px solid white;
+}
+#qunit-testresult .module-name {
+	font-weight: bold;
+}
+
+/** Fixture */
+
+#qunit-fixture {
+	position: absolute;
+	top: -10000px;
+	left: -10000px;
+	width: 1000px;
+	height: 1000px;
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit.js
new file mode 100644
index 0000000..9efedcb
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/qunit/qunit/qunit.js
@@ -0,0 +1,1932 @@
+/**
+ * QUnit v1.9.0 - A JavaScript Unit Testing Framework
+ *
+ * http://docs.jquery.com/QUnit
+ *
+ * Copyright (c) 2012 John Resig, Jörn Zaefferer
+ * Dual licensed under the MIT (MIT-LICENSE.txt)
+ * or GPL (GPL-LICENSE.txt) licenses.
+ */
+
+(function( window ) {
+
+var QUnit,
+	config,
+	onErrorFnPrev,
+	testId = 0,
+	fileName = (sourceFromStacktrace( 0 ) || "" ).replace(/(:\d+)+\)?/, "").replace(/.+\//, ""),
+	toString = Object.prototype.toString,
+	hasOwn = Object.prototype.hasOwnProperty,
+	defined = {
+	setTimeout: typeof window.setTimeout !== "undefined",
+	sessionStorage: (function() {
+		var x = "qunit-test-string";
+		try {
+			sessionStorage.setItem( x, x );
+			sessionStorage.removeItem( x );
+			return true;
+		} catch( e ) {
+			return false;
+		}
+	}())
+};
+
+function Test( settings ) {
+	extend( this, settings );
+	this.assertions = [];
+	this.testNumber = ++Test.count;
+}
+
+Test.count = 0;
+
+Test.prototype = {
+	init: function() {
+		var a, b, li,
+        tests = id( "qunit-tests" );
+
+		if ( tests ) {
+			b = document.createElement( "strong" );
+			b.innerHTML = this.name;
+
+			// `a` initialized at top of scope
+			a = document.createElement( "a" );
+			a.innerHTML = "Rerun";
+			a.href = QUnit.url({ testNumber: this.testNumber });
+
+			li = document.createElement( "li" );
+			li.appendChild( b );
+			li.appendChild( a );
+			li.className = "running";
+			li.id = this.id = "qunit-test-output" + testId++;
+
+			tests.appendChild( li );
+		}
+	},
+	setup: function() {
+		if ( this.module !== config.previousModule ) {
+			if ( config.previousModule ) {
+				runLoggingCallbacks( "moduleDone", QUnit, {
+					name: config.previousModule,
+					failed: config.moduleStats.bad,
+					passed: config.moduleStats.all - config.moduleStats.bad,
+					total: config.moduleStats.all
+				});
+			}
+			config.previousModule = this.module;
+			config.moduleStats = { all: 0, bad: 0 };
+			runLoggingCallbacks( "moduleStart", QUnit, {
+				name: this.module
+			});
+		} else if ( config.autorun ) {
+			runLoggingCallbacks( "moduleStart", QUnit, {
+				name: this.module
+			});
+		}
+
+		config.current = this;
+
+		this.testEnvironment = extend({
+			setup: function() {},
+			teardown: function() {}
+		}, this.moduleTestEnvironment );
+
+		runLoggingCallbacks( "testStart", QUnit, {
+			name: this.testName,
+			module: this.module
+		});
+
+		// allow utility functions to access the current test environment
+		// TODO why??
+		QUnit.current_testEnvironment = this.testEnvironment;
+
+		if ( !config.pollution ) {
+			saveGlobal();
+		}
+		if ( config.notrycatch ) {
+			this.testEnvironment.setup.call( this.testEnvironment );
+			return;
+		}
+		try {
+			this.testEnvironment.setup.call( this.testEnvironment );
+		} catch( e ) {
+			QUnit.pushFailure( "Setup failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+		}
+	},
+	run: function() {
+		config.current = this;
+
+		var running = id( "qunit-testresult" );
+
+		if ( running ) {
+			running.innerHTML = "Running: <br/>" + this.name;
+		}
+
+		if ( this.async ) {
+			QUnit.stop();
+		}
+
+		if ( config.notrycatch ) {
+			this.callback.call( this.testEnvironment, QUnit.assert );
+			return;
+		}
+
+		try {
+			this.callback.call( this.testEnvironment, QUnit.assert );
+		} catch( e ) {
+			QUnit.pushFailure( "Died on test #" + (this.assertions.length + 1) + " " + this.stack + ": " + e.message, extractStacktrace( e, 0 ) );
+			// else next test will carry the responsibility
+			saveGlobal();
+
+			// Restart the tests if they're blocking
+			if ( config.blocking ) {
+				QUnit.start();
+			}
+		}
+	},
+	teardown: function() {
+		config.current = this;
+		if ( config.notrycatch ) {
+			this.testEnvironment.teardown.call( this.testEnvironment );
+			return;
+		} else {
+			try {
+				this.testEnvironment.teardown.call( this.testEnvironment );
+			} catch( e ) {
+				QUnit.pushFailure( "Teardown failed on " + this.testName + ": " + e.message, extractStacktrace( e, 1 ) );
+			}
+		}
+		checkPollution();
+	},
+	finish: function() {
+		config.current = this;
+		if ( config.requireExpects && this.expected == null ) {
+			QUnit.pushFailure( "Expected number of assertions to be defined, but expect() was not called.", this.stack );
+		} else if ( this.expected != null && this.expected != this.assertions.length ) {
+			QUnit.pushFailure( "Expected " + this.expected + " assertions, but " + this.assertions.length + " were run", this.stack );
+		} else if ( this.expected == null && !this.assertions.length ) {
+			QUnit.pushFailure( "Expected at least one assertion, but none were run - call expect(0) to accept zero assertions.", this.stack );
+		}
+
+		var assertion, a, b, i, li, ol,
+			test = this,
+			good = 0,
+			bad = 0,
+			tests = id( "qunit-tests" );
+
+		config.stats.all += this.assertions.length;
+		config.moduleStats.all += this.assertions.length;
+
+		if ( tests ) {
+			ol = document.createElement( "ol" );
+
+			for ( i = 0; i < this.assertions.length; i++ ) {
+				assertion = this.assertions[i];
+
+				li = document.createElement( "li" );
+				li.className = assertion.result ? "pass" : "fail";
+				li.innerHTML = assertion.message || ( assertion.result ? "okay" : "failed" );
+				ol.appendChild( li );
+
+				if ( assertion.result ) {
+					good++;
+				} else {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+
+			// store result when possible
+			if ( QUnit.config.reorder && defined.sessionStorage ) {
+				if ( bad ) {
+					sessionStorage.setItem( "qunit-test-" + this.module + "-" + this.testName, bad );
+				} else {
+					sessionStorage.removeItem( "qunit-test-" + this.module + "-" + this.testName );
+				}
+			}
+
+			if ( bad === 0 ) {
+				ol.style.display = "none";
+			}
+
+			// `b` initialized at top of scope
+			b = document.createElement( "strong" );
+			b.innerHTML = this.name + " <b class='counts'>(<b class='failed'>" + bad + "</b>, <b class='passed'>" + good + "</b>, " + this.assertions.length + ")</b>";
+
+			addEvent(b, "click", function() {
+				var next = b.nextSibling.nextSibling,
+					display = next.style.display;
+				next.style.display = display === "none" ? "block" : "none";
+			});
+
+			addEvent(b, "dblclick", function( e ) {
+				var target = e && e.target ? e.target : window.event.srcElement;
+				if ( target.nodeName.toLowerCase() == "span" || target.nodeName.toLowerCase() == "b" ) {
+					target = target.parentNode;
+				}
+				if ( window.location && target.nodeName.toLowerCase() === "strong" ) {
+					window.location = QUnit.url({ testNumber: test.testNumber });
+				}
+			});
+
+			// `li` initialized at top of scope
+			li = id( this.id );
+			li.className = bad ? "fail" : "pass";
+			li.removeChild( li.firstChild );
+			a = li.firstChild;
+			li.appendChild( b );
+			li.appendChild ( a );
+			li.appendChild( ol );
+
+		} else {
+			for ( i = 0; i < this.assertions.length; i++ ) {
+				if ( !this.assertions[i].result ) {
+					bad++;
+					config.stats.bad++;
+					config.moduleStats.bad++;
+				}
+			}
+		}
+
+		runLoggingCallbacks( "testDone", QUnit, {
+			name: this.testName,
+			module: this.module,
+			failed: bad,
+			passed: this.assertions.length - bad,
+			total: this.assertions.length
+		});
+
+		QUnit.reset();
+
+		config.current = undefined;
+	},
+
+	queue: function() {
+		var bad,
+			test = this;
+
+		synchronize(function() {
+			test.init();
+		});
+		function run() {
+			// each of these can by async
+			synchronize(function() {
+				test.setup();
+			});
+			synchronize(function() {
+				test.run();
+			});
+			synchronize(function() {
+				test.teardown();
+			});
+			synchronize(function() {
+				test.finish();
+			});
+		}
+
+		// `bad` initialized at top of scope
+		// defer when previous test run passed, if storage is available
+		bad = QUnit.config.reorder && defined.sessionStorage &&
+						+sessionStorage.getItem( "qunit-test-" + this.module + "-" + this.testName );
+
+		if ( bad ) {
+			run();
+		} else {
+			synchronize( run, true );
+		}
+	}
+};
+
+// Root QUnit object.
+// `QUnit` initialized at top of scope
+QUnit = {
+
+	// call on start of module test to prepend name to all tests
+	module: function( name, testEnvironment ) {
+		config.currentModule = name;
+		config.currentModuleTestEnviroment = testEnvironment;
+	},
+
+	asyncTest: function( testName, expected, callback ) {
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		QUnit.test( testName, expected, callback, true );
+	},
+
+	test: function( testName, expected, callback, async ) {
+		var test,
+			name = "<span class='test-name'>" + escapeInnerText( testName ) + "</span>";
+
+		if ( arguments.length === 2 ) {
+			callback = expected;
+			expected = null;
+		}
+
+		if ( config.currentModule ) {
+			name = "<span class='module-name'>" + config.currentModule + "</span>: " + name;
+		}
+
+		test = new Test({
+			name: name,
+			testName: testName,
+			expected: expected,
+			async: async,
+			callback: callback,
+			module: config.currentModule,
+			moduleTestEnvironment: config.currentModuleTestEnviroment,
+			stack: sourceFromStacktrace( 2 )
+		});
+
+		if ( !validTest( test ) ) {
+			return;
+		}
+
+		test.queue();
+	},
+
+	// Specify the number of expected assertions to gurantee that failed test (no assertions are run at all) don't slip through.
+	expect: function( asserts ) {
+		config.current.expected = asserts;
+	},
+
+	start: function( count ) {
+		config.semaphore -= count || 1;
+		// don't start until equal number of stop-calls
+		if ( config.semaphore > 0 ) {
+			return;
+		}
+		// ignore if start is called more often then stop
+		if ( config.semaphore < 0 ) {
+			config.semaphore = 0;
+		}
+		// A slight delay, to avoid any current callbacks
+		if ( defined.setTimeout ) {
+			window.setTimeout(function() {
+				if ( config.semaphore > 0 ) {
+					return;
+				}
+				if ( config.timeout ) {
+					clearTimeout( config.timeout );
+				}
+
+				config.blocking = false;
+				process( true );
+			}, 13);
+		} else {
+			config.blocking = false;
+			process( true );
+		}
+	},
+
+	stop: function( count ) {
+		config.semaphore += count || 1;
+		config.blocking = true;
+
+		if ( config.testTimeout && defined.setTimeout ) {
+			clearTimeout( config.timeout );
+			config.timeout = window.setTimeout(function() {
+				QUnit.ok( false, "Test timed out" );
+				config.semaphore = 1;
+				QUnit.start();
+			}, config.testTimeout );
+		}
+	}
+};
+
+// Asssert helpers
+// All of these must call either QUnit.push() or manually do:
+// - runLoggingCallbacks( "log", .. );
+// - config.current.assertions.push({ .. });
+QUnit.assert = {
+	/**
+	 * Asserts rough true-ish result.
+	 * @name ok
+	 * @function
+	 * @example ok( "asdfasdf".length > 5, "There must be at least 5 chars" );
+	 */
+	ok: function( result, msg ) {
+		if ( !config.current ) {
+			throw new Error( "ok() assertion outside test context, was " + sourceFromStacktrace(2) );
+		}
+		result = !!result;
+
+		var source,
+			details = {
+				result: result,
+				message: msg
+			};
+
+		msg = escapeInnerText( msg || (result ? "okay" : "failed" ) );
+		msg = "<span class='test-message'>" + msg + "</span>";
+
+		if ( !result ) {
+			source = sourceFromStacktrace( 2 );
+			if ( source ) {
+				details.source = source;
+				msg += "<table><tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr></table>";
+			}
+		}
+		runLoggingCallbacks( "log", QUnit, details );
+		config.current.assertions.push({
+			result: result,
+			message: msg
+		});
+	},
+
+	/**
+	 * Assert that the first two arguments are equal, with an optional message.
+	 * Prints out both actual and expected values.
+	 * @name equal
+	 * @function
+	 * @example equal( format( "Received {0} bytes.", 2), "Received 2 bytes.", "format() replaces {0} with next argument" );
+	 */
+	equal: function( actual, expected, message ) {
+		QUnit.push( expected == actual, actual, expected, message );
+	},
+
+	/**
+	 * @name notEqual
+	 * @function
+	 */
+	notEqual: function( actual, expected, message ) {
+		QUnit.push( expected != actual, actual, expected, message );
+	},
+
+	/**
+	 * @name deepEqual
+	 * @function
+	 */
+	deepEqual: function( actual, expected, message ) {
+		QUnit.push( QUnit.equiv(actual, expected), actual, expected, message );
+	},
+
+	/**
+	 * @name notDeepEqual
+	 * @function
+	 */
+	notDeepEqual: function( actual, expected, message ) {
+		QUnit.push( !QUnit.equiv(actual, expected), actual, expected, message );
+	},
+
+	/**
+	 * @name strictEqual
+	 * @function
+	 */
+	strictEqual: function( actual, expected, message ) {
+		QUnit.push( expected === actual, actual, expected, message );
+	},
+
+	/**
+	 * @name notStrictEqual
+	 * @function
+	 */
+	notStrictEqual: function( actual, expected, message ) {
+		QUnit.push( expected !== actual, actual, expected, message );
+	},
+
+	throws: function( block, expected, message ) {
+		var actual,
+			ok = false;
+
+		// 'expected' is optional
+		if ( typeof expected === "string" ) {
+			message = expected;
+			expected = null;
+		}
+
+		config.current.ignoreGlobalErrors = true;
+		try {
+			block.call( config.current.testEnvironment );
+		} catch (e) {
+			actual = e;
+		}
+		config.current.ignoreGlobalErrors = false;
+
+		if ( actual ) {
+			// we don't want to validate thrown error
+			if ( !expected ) {
+				ok = true;
+			// expected is a regexp
+			} else if ( QUnit.objectType( expected ) === "regexp" ) {
+				ok = expected.test( actual );
+			// expected is a constructor
+			} else if ( actual instanceof expected ) {
+				ok = true;
+			// expected is a validation function which returns true is validation passed
+			} else if ( expected.call( {}, actual ) === true ) {
+				ok = true;
+			}
+
+			QUnit.push( ok, actual, null, message );
+		} else {
+			QUnit.pushFailure( message, null, 'No exception was thrown.' );
+		}
+	}
+};
+
+/**
+ * @deprecate since 1.8.0
+ * Kept assertion helpers in root for backwards compatibility
+ */
+extend( QUnit, QUnit.assert );
+
+/**
+ * @deprecated since 1.9.0
+ * Kept global "raises()" for backwards compatibility
+ */
+QUnit.raises = QUnit.assert.throws;
+
+/**
+ * @deprecated since 1.0.0, replaced with error pushes since 1.3.0
+ * Kept to avoid TypeErrors for undefined methods.
+ */
+QUnit.equals = function() {
+	QUnit.push( false, false, false, "QUnit.equals has been deprecated since 2009 (e88049a0), use QUnit.equal instead" );
+};
+QUnit.same = function() {
+	QUnit.push( false, false, false, "QUnit.same has been deprecated since 2009 (e88049a0), use QUnit.deepEqual instead" );
+};
+
+// We want access to the constructor's prototype
+(function() {
+	function F() {}
+	F.prototype = QUnit;
+	QUnit = new F();
+	// Make F QUnit's constructor so that we can add to the prototype later
+	QUnit.constructor = F;
+}());
+
+/**
+ * Config object: Maintain internal state
+ * Later exposed as QUnit.config
+ * `config` initialized at top of scope
+ */
+config = {
+	// The queue of tests to run
+	queue: [],
+
+	// block until document ready
+	blocking: true,
+
+	// when enabled, show only failing tests
+	// gets persisted through sessionStorage and can be changed in UI via checkbox
+	hidepassed: false,
+
+	// by default, run previously failed tests first
+	// very useful in combination with "Hide passed tests" checked
+	reorder: true,
+
+	// by default, modify document.title when suite is done
+	altertitle: true,
+
+	// when enabled, all tests must call expect()
+	requireExpects: false,
+
+	// add checkboxes that are persisted in the query-string
+	// when enabled, the id is set to `true` as a `QUnit.config` property
+	urlConfig: [
+		{
+			id: "noglobals",
+			label: "Check for Globals",
+			tooltip: "Enabling this will test if any test introduces new properties on the `window` object. Stored as query-strings."
+		},
+		{
+			id: "notrycatch",
+			label: "No try-catch",
+			tooltip: "Enabling this will run tests outside of a try-catch block. Makes debugging exceptions in IE reasonable. Stored as query-strings."
+		}
+	],
+
+	// logging callback queues
+	begin: [],
+	done: [],
+	log: [],
+	testStart: [],
+	testDone: [],
+	moduleStart: [],
+	moduleDone: []
+};
+
+// Initialize more QUnit.config and QUnit.urlParams
+(function() {
+	var i,
+		location = window.location || { search: "", protocol: "file:" },
+		params = location.search.slice( 1 ).split( "&" ),
+		length = params.length,
+		urlParams = {},
+		current;
+
+	if ( params[ 0 ] ) {
+		for ( i = 0; i < length; i++ ) {
+			current = params[ i ].split( "=" );
+			current[ 0 ] = decodeURIComponent( current[ 0 ] );
+			// allow just a key to turn on a flag, e.g., test.html?noglobals
+			current[ 1 ] = current[ 1 ] ? decodeURIComponent( current[ 1 ] ) : true;
+			urlParams[ current[ 0 ] ] = current[ 1 ];
+		}
+	}
+
+	QUnit.urlParams = urlParams;
+
+	// String search anywhere in moduleName+testName
+	config.filter = urlParams.filter;
+
+	// Exact match of the module name
+	config.module = urlParams.module;
+
+	config.testNumber = parseInt( urlParams.testNumber, 10 ) || null;
+
+	// Figure out if we're running the tests from a server or not
+	QUnit.isLocal = location.protocol === "file:";
+}());
+
+// Export global variables, unless an 'exports' object exists,
+// in that case we assume we're in CommonJS (dealt with on the bottom of the script)
+if ( typeof exports === "undefined" ) {
+	extend( window, QUnit );
+
+	// Expose QUnit object
+	window.QUnit = QUnit;
+}
+
+// Extend QUnit object,
+// these after set here because they should not be exposed as global functions
+extend( QUnit, {
+	config: config,
+
+	// Initialize the configuration options
+	init: function() {
+		extend( config, {
+			stats: { all: 0, bad: 0 },
+			moduleStats: { all: 0, bad: 0 },
+			started: +new Date(),
+			updateRate: 1000,
+			blocking: false,
+			autostart: true,
+			autorun: false,
+			filter: "",
+			queue: [],
+			semaphore: 0
+		});
+
+		var tests, banner, result,
+			qunit = id( "qunit" );
+
+		if ( qunit ) {
+			qunit.innerHTML =
+				"<h1 id='qunit-header'>" + escapeInnerText( document.title ) + "</h1>" +
+				"<h2 id='qunit-banner'></h2>" +
+				"<div id='qunit-testrunner-toolbar'></div>" +
+				"<h2 id='qunit-userAgent'></h2>" +
+				"<ol id='qunit-tests'></ol>";
+		}
+
+		tests = id( "qunit-tests" );
+		banner = id( "qunit-banner" );
+		result = id( "qunit-testresult" );
+
+		if ( tests ) {
+			tests.innerHTML = "";
+		}
+
+		if ( banner ) {
+			banner.className = "";
+		}
+
+		if ( result ) {
+			result.parentNode.removeChild( result );
+		}
+
+		if ( tests ) {
+			result = document.createElement( "p" );
+			result.id = "qunit-testresult";
+			result.className = "result";
+			tests.parentNode.insertBefore( result, tests );
+			result.innerHTML = "Running...<br/>&nbsp;";
+		}
+	},
+
+	// Resets the test setup. Useful for tests that modify the DOM.
+	// If jQuery is available, uses jQuery's html(), otherwise just innerHTML.
+	reset: function() {
+		var fixture;
+
+		if ( window.jQuery ) {
+			jQuery( "#qunit-fixture" ).html( config.fixture );
+		} else {
+			fixture = id( "qunit-fixture" );
+			if ( fixture ) {
+				fixture.innerHTML = config.fixture;
+			}
+		}
+	},
+
+	// Trigger an event on an element.
+	// @example triggerEvent( document.body, "click" );
+	triggerEvent: function( elem, type, event ) {
+		if ( document.createEvent ) {
+			event = document.createEvent( "MouseEvents" );
+			event.initMouseEvent(type, true, true, elem.ownerDocument.defaultView,
+				0, 0, 0, 0, 0, false, false, false, false, 0, null);
+
+			elem.dispatchEvent( event );
+		} else if ( elem.fireEvent ) {
+			elem.fireEvent( "on" + type );
+		}
+	},
+
+	// Safe object type checking
+	is: function( type, obj ) {
+		return QUnit.objectType( obj ) == type;
+	},
+
+	objectType: function( obj ) {
+		if ( typeof obj === "undefined" ) {
+				return "undefined";
+		// consider: typeof null === object
+		}
+		if ( obj === null ) {
+				return "null";
+		}
+
+		var type = toString.call( obj ).match(/^\[object\s(.*)\]$/)[1] || "";
+
+		switch ( type ) {
+			case "Number":
+				if ( isNaN(obj) ) {
+					return "nan";
+				}
+				return "number";
+			case "String":
+			case "Boolean":
+			case "Array":
+			case "Date":
+			case "RegExp":
+			case "Function":
+				return type.toLowerCase();
+		}
+		if ( typeof obj === "object" ) {
+			return "object";
+		}
+		return undefined;
+	},
+
+	push: function( result, actual, expected, message ) {
+		if ( !config.current ) {
+			throw new Error( "assertion outside test context, was " + sourceFromStacktrace() );
+		}
+
+		var output, source,
+			details = {
+				result: result,
+				message: message,
+				actual: actual,
+				expected: expected
+			};
+
+		message = escapeInnerText( message ) || ( result ? "okay" : "failed" );
+		message = "<span class='test-message'>" + message + "</span>";
+		output = message;
+
+		if ( !result ) {
+			expected = escapeInnerText( QUnit.jsDump.parse(expected) );
+			actual = escapeInnerText( QUnit.jsDump.parse(actual) );
+			output += "<table><tr class='test-expected'><th>Expected: </th><td><pre>" + expected + "</pre></td></tr>";
+
+			if ( actual != expected ) {
+				output += "<tr class='test-actual'><th>Result: </th><td><pre>" + actual + "</pre></td></tr>";
+				output += "<tr class='test-diff'><th>Diff: </th><td><pre>" + QUnit.diff( expected, actual ) + "</pre></td></tr>";
+			}
+
+			source = sourceFromStacktrace();
+
+			if ( source ) {
+				details.source = source;
+				output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+			}
+
+			output += "</table>";
+		}
+
+		runLoggingCallbacks( "log", QUnit, details );
+
+		config.current.assertions.push({
+			result: !!result,
+			message: output
+		});
+	},
+
+	pushFailure: function( message, source, actual ) {
+		if ( !config.current ) {
+			throw new Error( "pushFailure() assertion outside test context, was " + sourceFromStacktrace(2) );
+		}
+
+		var output,
+			details = {
+				result: false,
+				message: message
+			};
+
+		message = escapeInnerText( message ) || "error";
+		message = "<span class='test-message'>" + message + "</span>";
+		output = message;
+
+		output += "<table>";
+
+		if ( actual ) {
+			output += "<tr class='test-actual'><th>Result: </th><td><pre>" + escapeInnerText( actual ) + "</pre></td></tr>";
+		}
+
+		if ( source ) {
+			details.source = source;
+			output += "<tr class='test-source'><th>Source: </th><td><pre>" + escapeInnerText( source ) + "</pre></td></tr>";
+		}
+
+		output += "</table>";
+
+		runLoggingCallbacks( "log", QUnit, details );
+
+		config.current.assertions.push({
+			result: false,
+			message: output
+		});
+	},
+
+	url: function( params ) {
+		params = extend( extend( {}, QUnit.urlParams ), params );
+		var key,
+			querystring = "?";
+
+		for ( key in params ) {
+			if ( !hasOwn.call( params, key ) ) {
+				continue;
+			}
+			querystring += encodeURIComponent( key ) + "=" +
+				encodeURIComponent( params[ key ] ) + "&";
+		}
+		return window.location.pathname + querystring.slice( 0, -1 );
+	},
+
+	extend: extend,
+	id: id,
+	addEvent: addEvent
+	// load, equiv, jsDump, diff: Attached later
+});
+
+/**
+ * @deprecated: Created for backwards compatibility with test runner that set the hook function
+ * into QUnit.{hook}, instead of invoking it and passing the hook function.
+ * QUnit.constructor is set to the empty F() above so that we can add to it's prototype here.
+ * Doing this allows us to tell if the following methods have been overwritten on the actual
+ * QUnit object.
+ */
+extend( QUnit.constructor.prototype, {
+
+	// Logging callbacks; all receive a single argument with the listed properties
+	// run test/logs.html for any related changes
+	begin: registerLoggingCallback( "begin" ),
+
+	// done: { failed, passed, total, runtime }
+	done: registerLoggingCallback( "done" ),
+
+	// log: { result, actual, expected, message }
+	log: registerLoggingCallback( "log" ),
+
+	// testStart: { name }
+	testStart: registerLoggingCallback( "testStart" ),
+
+	// testDone: { name, failed, passed, total }
+	testDone: registerLoggingCallback( "testDone" ),
+
+	// moduleStart: { name }
+	moduleStart: registerLoggingCallback( "moduleStart" ),
+
+	// moduleDone: { name, failed, passed, total }
+	moduleDone: registerLoggingCallback( "moduleDone" )
+});
+
+if ( typeof document === "undefined" || document.readyState === "complete" ) {
+	config.autorun = true;
+}
+
+QUnit.load = function() {
+	runLoggingCallbacks( "begin", QUnit, {} );
+
+	// Initialize the config, saving the execution queue
+	var banner, filter, i, label, len, main, ol, toolbar, userAgent, val, urlConfigCheckboxes,
+		urlConfigHtml = "",
+		oldconfig = extend( {}, config );
+
+	QUnit.init();
+	extend(config, oldconfig);
+
+	config.blocking = false;
+
+	len = config.urlConfig.length;
+
+	for ( i = 0; i < len; i++ ) {
+		val = config.urlConfig[i];
+		if ( typeof val === "string" ) {
+			val = {
+				id: val,
+				label: val,
+				tooltip: "[no tooltip available]"
+			};
+		}
+		config[ val.id ] = QUnit.urlParams[ val.id ];
+		urlConfigHtml += "<input id='qunit-urlconfig-" + val.id + "' name='" + val.id + "' type='checkbox'" + ( config[ val.id ] ? " checked='checked'" : "" ) + " title='" + val.tooltip + "'><label for='qunit-urlconfig-" + val.id + "' title='" + val.tooltip + "'>" + val.label + "</label>";
+	}
+
+	// `userAgent` initialized at top of scope
+	userAgent = id( "qunit-userAgent" );
+	if ( userAgent ) {
+		userAgent.innerHTML = navigator.userAgent;
+	}
+
+	// `banner` initialized at top of scope
+	banner = id( "qunit-header" );
+	if ( banner ) {
+		banner.innerHTML = "<a href='" + QUnit.url({ filter: undefined, module: undefined, testNumber: undefined }) + "'>" + banner.innerHTML + "</a> ";
+	}
+
+	// `toolbar` initialized at top of scope
+	toolbar = id( "qunit-testrunner-toolbar" );
+	if ( toolbar ) {
+		// `filter` initialized at top of scope
+		filter = document.createElement( "input" );
+		filter.type = "checkbox";
+		filter.id = "qunit-filter-pass";
+
+		addEvent( filter, "click", function() {
+			var tmp,
+				ol = document.getElementById( "qunit-tests" );
+
+			if ( filter.checked ) {
+				ol.className = ol.className + " hidepass";
+			} else {
+				tmp = " " + ol.className.replace( /[\n\t\r]/g, " " ) + " ";
+				ol.className = tmp.replace( / hidepass /, " " );
+			}
+			if ( defined.sessionStorage ) {
+				if (filter.checked) {
+					sessionStorage.setItem( "qunit-filter-passed-tests", "true" );
+				} else {
+					sessionStorage.removeItem( "qunit-filter-passed-tests" );
+				}
+			}
+		});
+
+		if ( config.hidepassed || defined.sessionStorage && sessionStorage.getItem( "qunit-filter-passed-tests" ) ) {
+			filter.checked = true;
+			// `ol` initialized at top of scope
+			ol = document.getElementById( "qunit-tests" );
+			ol.className = ol.className + " hidepass";
+		}
+		toolbar.appendChild( filter );
+
+		// `label` initialized at top of scope
+		label = document.createElement( "label" );
+		label.setAttribute( "for", "qunit-filter-pass" );
+		label.setAttribute( "title", "Only show tests and assertons that fail. Stored in sessionStorage." );
+		label.innerHTML = "Hide passed tests";
+		toolbar.appendChild( label );
+
+		urlConfigCheckboxes = document.createElement( 'span' );
+		urlConfigCheckboxes.innerHTML = urlConfigHtml;
+		addEvent( urlConfigCheckboxes, "change", function( event ) {
+			var params = {};
+			params[ event.target.name ] = event.target.checked ? true : undefined;
+			window.location = QUnit.url( params );
+		});
+		toolbar.appendChild( urlConfigCheckboxes );
+	}
+
+	// `main` initialized at top of scope
+	main = id( "qunit-fixture" );
+	if ( main ) {
+		config.fixture = main.innerHTML;
+	}
+
+	if ( config.autostart ) {
+		QUnit.start();
+	}
+};
+
+addEvent( window, "load", QUnit.load );
+
+// `onErrorFnPrev` initialized at top of scope
+// Preserve other handlers
+onErrorFnPrev = window.onerror;
+
+// Cover uncaught exceptions
+// Returning true will surpress the default browser handler,
+// returning false will let it run.
+window.onerror = function ( error, filePath, linerNr ) {
+	var ret = false;
+	if ( onErrorFnPrev ) {
+		ret = onErrorFnPrev( error, filePath, linerNr );
+	}
+
+	// Treat return value as window.onerror itself does,
+	// Only do our handling if not surpressed.
+	if ( ret !== true ) {
+		if ( QUnit.config.current ) {
+			if ( QUnit.config.current.ignoreGlobalErrors ) {
+				return true;
+			}
+			QUnit.pushFailure( error, filePath + ":" + linerNr );
+		} else {
+			QUnit.test( "global failure", function() {
+				QUnit.pushFailure( error, filePath + ":" + linerNr );
+			});
+		}
+		return false;
+	}
+
+	return ret;
+};
+
+function done() {
+	config.autorun = true;
+
+	// Log the last module results
+	if ( config.currentModule ) {
+		runLoggingCallbacks( "moduleDone", QUnit, {
+			name: config.currentModule,
+			failed: config.moduleStats.bad,
+			passed: config.moduleStats.all - config.moduleStats.bad,
+			total: config.moduleStats.all
+		});
+	}
+
+	var i, key,
+		banner = id( "qunit-banner" ),
+		tests = id( "qunit-tests" ),
+		runtime = +new Date() - config.started,
+		passed = config.stats.all - config.stats.bad,
+		html = [
+			"Tests completed in ",
+			runtime,
+			" milliseconds.<br/>",
+			"<span class='passed'>",
+			passed,
+			"</span> tests of <span class='total'>",
+			config.stats.all,
+			"</span> passed, <span class='failed'>",
+			config.stats.bad,
+			"</span> failed."
+		].join( "" );
+
+	if ( banner ) {
+		banner.className = ( config.stats.bad ? "qunit-fail" : "qunit-pass" );
+	}
+
+	if ( tests ) {
+		id( "qunit-testresult" ).innerHTML = html;
+	}
+
+	if ( config.altertitle && typeof document !== "undefined" && document.title ) {
+		// show ✖ for good, ✔ for bad suite result in title
+		// use escape sequences in case file gets loaded with non-utf-8-charset
+		document.title = [
+			( config.stats.bad ? "\u2716" : "\u2714" ),
+			document.title.replace( /^[\u2714\u2716] /i, "" )
+		].join( " " );
+	}
+
+	// clear own sessionStorage items if all tests passed
+	if ( config.reorder && defined.sessionStorage && config.stats.bad === 0 ) {
+		// `key` & `i` initialized at top of scope
+		for ( i = 0; i < sessionStorage.length; i++ ) {
+			key = sessionStorage.key( i++ );
+			if ( key.indexOf( "qunit-test-" ) === 0 ) {
+				sessionStorage.removeItem( key );
+			}
+		}
+	}
+
+	runLoggingCallbacks( "done", QUnit, {
+		failed: config.stats.bad,
+		passed: passed,
+		total: config.stats.all,
+		runtime: runtime
+	});
+}
+
+/** @return Boolean: true if this test should be ran */
+function validTest( test ) {
+	var include,
+		filter = config.filter && config.filter.toLowerCase(),
+		module = config.module && config.module.toLowerCase(),
+		fullName = (test.module + ": " + test.testName).toLowerCase();
+
+	if ( config.testNumber ) {
+		return test.testNumber === config.testNumber;
+	}
+
+	if ( module && ( !test.module || test.module.toLowerCase() !== module ) ) {
+		return false;
+	}
+
+	if ( !filter ) {
+		return true;
+	}
+
+	include = filter.charAt( 0 ) !== "!";
+	if ( !include ) {
+		filter = filter.slice( 1 );
+	}
+
+	// If the filter matches, we need to honour include
+	if ( fullName.indexOf( filter ) !== -1 ) {
+		return include;
+	}
+
+	// Otherwise, do the opposite
+	return !include;
+}
+
+// so far supports only Firefox, Chrome and Opera (buggy), Safari (for real exceptions)
+// Later Safari and IE10 are supposed to support error.stack as well
+// See also https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/Error/Stack
+function extractStacktrace( e, offset ) {
+	offset = offset === undefined ? 3 : offset;
+
+	var stack, include, i, regex;
+
+	if ( e.stacktrace ) {
+		// Opera
+		return e.stacktrace.split( "\n" )[ offset + 3 ];
+	} else if ( e.stack ) {
+		// Firefox, Chrome
+		stack = e.stack.split( "\n" );
+		if (/^error$/i.test( stack[0] ) ) {
+			stack.shift();
+		}
+		if ( fileName ) {
+			include = [];
+			for ( i = offset; i < stack.length; i++ ) {
+				if ( stack[ i ].indexOf( fileName ) != -1 ) {
+					break;
+				}
+				include.push( stack[ i ] );
+			}
+			if ( include.length ) {
+				return include.join( "\n" );
+			}
+		}
+		return stack[ offset ];
+	} else if ( e.sourceURL ) {
+		// Safari, PhantomJS
+		// hopefully one day Safari provides actual stacktraces
+		// exclude useless self-reference for generated Error objects
+		if ( /qunit.js$/.test( e.sourceURL ) ) {
+			return;
+		}
+		// for actual exceptions, this is useful
+		return e.sourceURL + ":" + e.line;
+	}
+}
+function sourceFromStacktrace( offset ) {
+	try {
+		throw new Error();
+	} catch ( e ) {
+		return extractStacktrace( e, offset );
+	}
+}
+
+function escapeInnerText( s ) {
+	if ( !s ) {
+		return "";
+	}
+	s = s + "";
+	return s.replace( /[\&<>]/g, function( s ) {
+		switch( s ) {
+			case "&": return "&amp;";
+			case "<": return "&lt;";
+			case ">": return "&gt;";
+			default: return s;
+		}
+	});
+}
+
+function synchronize( callback, last ) {
+	config.queue.push( callback );
+
+	if ( config.autorun && !config.blocking ) {
+		process( last );
+	}
+}
+
+function process( last ) {
+	function next() {
+		process( last );
+	}
+	var start = new Date().getTime();
+	config.depth = config.depth ? config.depth + 1 : 1;
+
+	while ( config.queue.length && !config.blocking ) {
+		if ( !defined.setTimeout || config.updateRate <= 0 || ( ( new Date().getTime() - start ) < config.updateRate ) ) {
+			config.queue.shift()();
+		} else {
+			window.setTimeout( next, 13 );
+			break;
+		}
+	}
+	config.depth--;
+	if ( last && !config.blocking && !config.queue.length && config.depth === 0 ) {
+		done();
+	}
+}
+
+function saveGlobal() {
+	config.pollution = [];
+
+	if ( config.noglobals ) {
+		for ( var key in window ) {
+			// in Opera sometimes DOM element ids show up here, ignore them
+			if ( !hasOwn.call( window, key ) || /^qunit-test-output/.test( key ) ) {
+				continue;
+			}
+			config.pollution.push( key );
+		}
+	}
+}
+
+function checkPollution( name ) {
+	var newGlobals,
+		deletedGlobals,
+		old = config.pollution;
+
+	saveGlobal();
+
+	newGlobals = diff( config.pollution, old );
+	if ( newGlobals.length > 0 ) {
+		QUnit.pushFailure( "Introduced global variable(s): " + newGlobals.join(", ") );
+	}
+
+	deletedGlobals = diff( old, config.pollution );
+	if ( deletedGlobals.length > 0 ) {
+		QUnit.pushFailure( "Deleted global variable(s): " + deletedGlobals.join(", ") );
+	}
+}
+
+// returns a new Array with the elements that are in a but not in b
+function diff( a, b ) {
+	var i, j,
+		result = a.slice();
+
+	for ( i = 0; i < result.length; i++ ) {
+		for ( j = 0; j < b.length; j++ ) {
+			if ( result[i] === b[j] ) {
+				result.splice( i, 1 );
+				i--;
+				break;
+			}
+		}
+	}
+	return result;
+}
+
+function extend( a, b ) {
+	for ( var prop in b ) {
+		if ( b[ prop ] === undefined ) {
+			delete a[ prop ];
+
+		// Avoid "Member not found" error in IE8 caused by setting window.constructor
+		} else if ( prop !== "constructor" || a !== window ) {
+			a[ prop ] = b[ prop ];
+		}
+	}
+
+	return a;
+}
+
+function addEvent( elem, type, fn ) {
+	if ( elem.addEventListener ) {
+		elem.addEventListener( type, fn, false );
+	} else if ( elem.attachEvent ) {
+		elem.attachEvent( "on" + type, fn );
+	} else {
+		fn();
+	}
+}
+
+function id( name ) {
+	return !!( typeof document !== "undefined" && document && document.getElementById ) &&
+		document.getElementById( name );
+}
+
+function registerLoggingCallback( key ) {
+	return function( callback ) {
+		config[key].push( callback );
+	};
+}
+
+// Supports deprecated method of completely overwriting logging callbacks
+function runLoggingCallbacks( key, scope, args ) {
+	//debugger;
+	var i, callbacks;
+	if ( QUnit.hasOwnProperty( key ) ) {
+		QUnit[ key ].call(scope, args );
+	} else {
+		callbacks = config[ key ];
+		for ( i = 0; i < callbacks.length; i++ ) {
+			callbacks[ i ].call( scope, args );
+		}
+	}
+}
+
+// Test for equality any JavaScript type.
+// Author: Philippe Rathé <prathe@gmail.com>
+QUnit.equiv = (function() {
+
+	// Call the o related callback with the given arguments.
+	function bindCallbacks( o, callbacks, args ) {
+		var prop = QUnit.objectType( o );
+		if ( prop ) {
+			if ( QUnit.objectType( callbacks[ prop ] ) === "function" ) {
+				return callbacks[ prop ].apply( callbacks, args );
+			} else {
+				return callbacks[ prop ]; // or undefined
+			}
+		}
+	}
+
+	// the real equiv function
+	var innerEquiv,
+		// stack to decide between skip/abort functions
+		callers = [],
+		// stack to avoiding loops from circular referencing
+		parents = [],
+
+		getProto = Object.getPrototypeOf || function ( obj ) {
+			return obj.__proto__;
+		},
+		callbacks = (function () {
+
+			// for string, boolean, number and null
+			function useStrictEquality( b, a ) {
+				if ( b instanceof a.constructor || a instanceof b.constructor ) {
+					// to catch short annotaion VS 'new' annotation of a
+					// declaration
+					// e.g. var i = 1;
+					// var j = new Number(1);
+					return a == b;
+				} else {
+					return a === b;
+				}
+			}
+
+			return {
+				"string": useStrictEquality,
+				"boolean": useStrictEquality,
+				"number": useStrictEquality,
+				"null": useStrictEquality,
+				"undefined": useStrictEquality,
+
+				"nan": function( b ) {
+					return isNaN( b );
+				},
+
+				"date": function( b, a ) {
+					return QUnit.objectType( b ) === "date" && a.valueOf() === b.valueOf();
+				},
+
+				"regexp": function( b, a ) {
+					return QUnit.objectType( b ) === "regexp" &&
+						// the regex itself
+						a.source === b.source &&
+						// and its modifers
+						a.global === b.global &&
+						// (gmi) ...
+						a.ignoreCase === b.ignoreCase &&
+						a.multiline === b.multiline;
+				},
+
+				// - skip when the property is a method of an instance (OOP)
+				// - abort otherwise,
+				// initial === would have catch identical references anyway
+				"function": function() {
+					var caller = callers[callers.length - 1];
+					return caller !== Object && typeof caller !== "undefined";
+				},
+
+				"array": function( b, a ) {
+					var i, j, len, loop;
+
+					// b could be an object literal here
+					if ( QUnit.objectType( b ) !== "array" ) {
+						return false;
+					}
+
+					len = a.length;
+					if ( len !== b.length ) {
+						// safe and faster
+						return false;
+					}
+
+					// track reference to avoid circular references
+					parents.push( a );
+					for ( i = 0; i < len; i++ ) {
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							if ( parents[j] === a[i] ) {
+								loop = true;// dont rewalk array
+							}
+						}
+						if ( !loop && !innerEquiv(a[i], b[i]) ) {
+							parents.pop();
+							return false;
+						}
+					}
+					parents.pop();
+					return true;
+				},
+
+				"object": function( b, a ) {
+					var i, j, loop,
+						// Default to true
+						eq = true,
+						aProperties = [],
+						bProperties = [];
+
+					// comparing constructors is more strict than using
+					// instanceof
+					if ( a.constructor !== b.constructor ) {
+						// Allow objects with no prototype to be equivalent to
+						// objects with Object as their constructor.
+						if ( !(( getProto(a) === null && getProto(b) === Object.prototype ) ||
+							( getProto(b) === null && getProto(a) === Object.prototype ) ) ) {
+								return false;
+						}
+					}
+
+					// stack constructor before traversing properties
+					callers.push( a.constructor );
+					// track reference to avoid circular references
+					parents.push( a );
+
+					for ( i in a ) { // be strict: don't ensures hasOwnProperty
+									// and go deep
+						loop = false;
+						for ( j = 0; j < parents.length; j++ ) {
+							if ( parents[j] === a[i] ) {
+								// don't go down the same path twice
+								loop = true;
+							}
+						}
+						aProperties.push(i); // collect a's properties
+
+						if (!loop && !innerEquiv( a[i], b[i] ) ) {
+							eq = false;
+							break;
+						}
+					}
+
+					callers.pop(); // unstack, we are done
+					parents.pop();
+
+					for ( i in b ) {
+						bProperties.push( i ); // collect b's properties
+					}
+
+					// Ensures identical properties name
+					return eq && innerEquiv( aProperties.sort(), bProperties.sort() );
+				}
+			};
+		}());
+
+	innerEquiv = function() { // can take multiple arguments
+		var args = [].slice.apply( arguments );
+		if ( args.length < 2 ) {
+			return true; // end transition
+		}
+
+		return (function( a, b ) {
+			if ( a === b ) {
+				return true; // catch the most you can
+			} else if ( a === null || b === null || typeof a === "undefined" ||
+					typeof b === "undefined" ||
+					QUnit.objectType(a) !== QUnit.objectType(b) ) {
+				return false; // don't lose time with error prone cases
+			} else {
+				return bindCallbacks(a, callbacks, [ b, a ]);
+			}
+
+			// apply transition with (1..n) arguments
+		}( args[0], args[1] ) && arguments.callee.apply( this, args.splice(1, args.length - 1 )) );
+	};
+
+	return innerEquiv;
+}());
+
+/**
+ * jsDump Copyright (c) 2008 Ariel Flesler - aflesler(at)gmail(dot)com |
+ * http://flesler.blogspot.com Licensed under BSD
+ * (http://www.opensource.org/licenses/bsd-license.php) Date: 5/15/2008
+ *
+ * @projectDescription Advanced and extensible data dumping for Javascript.
+ * @version 1.0.0
+ * @author Ariel Flesler
+ * @link {http://flesler.blogspot.com/2008/05/jsdump-pretty-dump-of-any-javascript.html}
+ */
+QUnit.jsDump = (function() {
+	function quote( str ) {
+		return '"' + str.toString().replace( /"/g, '\\"' ) + '"';
+	}
+	function literal( o ) {
+		return o + "";
+	}
+	function join( pre, arr, post ) {
+		var s = jsDump.separator(),
+			base = jsDump.indent(),
+			inner = jsDump.indent(1);
+		if ( arr.join ) {
+			arr = arr.join( "," + s + inner );
+		}
+		if ( !arr ) {
+			return pre + post;
+		}
+		return [ pre, inner + arr, base + post ].join(s);
+	}
+	function array( arr, stack ) {
+		var i = arr.length, ret = new Array(i);
+		this.up();
+		while ( i-- ) {
+			ret[i] = this.parse( arr[i] , undefined , stack);
+		}
+		this.down();
+		return join( "[", ret, "]" );
+	}
+
+	var reName = /^function (\w+)/,
+		jsDump = {
+			parse: function( obj, type, stack ) { //type is used mostly internally, you can fix a (custom)type in advance
+				stack = stack || [ ];
+				var inStack, res,
+					parser = this.parsers[ type || this.typeOf(obj) ];
+
+				type = typeof parser;
+				inStack = inArray( obj, stack );
+
+				if ( inStack != -1 ) {
+					return "recursion(" + (inStack - stack.length) + ")";
+				}
+				//else
+				if ( type == "function" )  {
+					stack.push( obj );
+					res = parser.call( this, obj, stack );
+					stack.pop();
+					return res;
+				}
+				// else
+				return ( type == "string" ) ? parser : this.parsers.error;
+			},
+			typeOf: function( obj ) {
+				var type;
+				if ( obj === null ) {
+					type = "null";
+				} else if ( typeof obj === "undefined" ) {
+					type = "undefined";
+				} else if ( QUnit.is( "regexp", obj) ) {
+					type = "regexp";
+				} else if ( QUnit.is( "date", obj) ) {
+					type = "date";
+				} else if ( QUnit.is( "function", obj) ) {
+					type = "function";
+				} else if ( typeof obj.setInterval !== undefined && typeof obj.document !== "undefined" && typeof obj.nodeType === "undefined" ) {
+					type = "window";
+				} else if ( obj.nodeType === 9 ) {
+					type = "document";
+				} else if ( obj.nodeType ) {
+					type = "node";
+				} else if (
+					// native arrays
+					toString.call( obj ) === "[object Array]" ||
+					// NodeList objects
+					( typeof obj.length === "number" && typeof obj.item !== "undefined" && ( obj.length ? obj.item(0) === obj[0] : ( obj.item( 0 ) === null && typeof obj[0] === "undefined" ) ) )
+				) {
+					type = "array";
+				} else {
+					type = typeof obj;
+				}
+				return type;
+			},
+			separator: function() {
+				return this.multiline ?	this.HTML ? "<br />" : "\n" : this.HTML ? "&nbsp;" : " ";
+			},
+			indent: function( extra ) {// extra can be a number, shortcut for increasing-calling-decreasing
+				if ( !this.multiline ) {
+					return "";
+				}
+				var chr = this.indentChar;
+				if ( this.HTML ) {
+					chr = chr.replace( /\t/g, "   " ).replace( / /g, "&nbsp;" );
+				}
+				return new Array( this._depth_ + (extra||0) ).join(chr);
+			},
+			up: function( a ) {
+				this._depth_ += a || 1;
+			},
+			down: function( a ) {
+				this._depth_ -= a || 1;
+			},
+			setParser: function( name, parser ) {
+				this.parsers[name] = parser;
+			},
+			// The next 3 are exposed so you can use them
+			quote: quote,
+			literal: literal,
+			join: join,
+			//
+			_depth_: 1,
+			// This is the list of parsers, to modify them, use jsDump.setParser
+			parsers: {
+				window: "[Window]",
+				document: "[Document]",
+				error: "[ERROR]", //when no parser is found, shouldn"t happen
+				unknown: "[Unknown]",
+				"null": "null",
+				"undefined": "undefined",
+				"function": function( fn ) {
+					var ret = "function",
+						name = "name" in fn ? fn.name : (reName.exec(fn) || [])[1];//functions never have name in IE
+
+					if ( name ) {
+						ret += " " + name;
+					}
+					ret += "( ";
+
+					ret = [ ret, QUnit.jsDump.parse( fn, "functionArgs" ), "){" ].join( "" );
+					return join( ret, QUnit.jsDump.parse(fn,"functionCode" ), "}" );
+				},
+				array: array,
+				nodelist: array,
+				"arguments": array,
+				object: function( map, stack ) {
+					var ret = [ ], keys, key, val, i;
+					QUnit.jsDump.up();
+					if ( Object.keys ) {
+						keys = Object.keys( map );
+					} else {
+						keys = [];
+						for ( key in map ) {
+							keys.push( key );
+						}
+					}
+					keys.sort();
+					for ( i = 0; i < keys.length; i++ ) {
+						key = keys[ i ];
+						val = map[ key ];
+						ret.push( QUnit.jsDump.parse( key, "key" ) + ": " + QUnit.jsDump.parse( val, undefined, stack ) );
+					}
+					QUnit.jsDump.down();
+					return join( "{", ret, "}" );
+				},
+				node: function( node ) {
+					var a, val,
+						open = QUnit.jsDump.HTML ? "&lt;" : "<",
+						close = QUnit.jsDump.HTML ? "&gt;" : ">",
+						tag = node.nodeName.toLowerCase(),
+						ret = open + tag;
+
+					for ( a in QUnit.jsDump.DOMAttrs ) {
+						val = node[ QUnit.jsDump.DOMAttrs[a] ];
+						if ( val ) {
+							ret += " " + a + "=" + QUnit.jsDump.parse( val, "attribute" );
+						}
+					}
+					return ret + close + open + "/" + tag + close;
+				},
+				functionArgs: function( fn ) {//function calls it internally, it's the arguments part of the function
+					var args,
+						l = fn.length;
+
+					if ( !l ) {
+						return "";
+					}
+
+					args = new Array(l);
+					while ( l-- ) {
+						args[l] = String.fromCharCode(97+l);//97 is 'a'
+					}
+					return " " + args.join( ", " ) + " ";
+				},
+				key: quote, //object calls it internally, the key part of an item in a map
+				functionCode: "[code]", //function calls it internally, it's the content of the function
+				attribute: quote, //node calls it internally, it's an html attribute value
+				string: quote,
+				date: quote,
+				regexp: literal, //regex
+				number: literal,
+				"boolean": literal
+			},
+			DOMAttrs: {
+				//attributes to dump from nodes, name=>realName
+				id: "id",
+				name: "name",
+				"class": "className"
+			},
+			HTML: false,//if true, entities are escaped ( <, >, \t, space and \n )
+			indentChar: "  ",//indentation unit
+			multiline: true //if true, items in a collection, are separated by a \n, else just a space.
+		};
+
+	return jsDump;
+}());
+
+// from Sizzle.js
+function getText( elems ) {
+	var i, elem,
+		ret = "";
+
+	for ( i = 0; elems[i]; i++ ) {
+		elem = elems[i];
+
+		// Get the text from text nodes and CDATA nodes
+		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
+			ret += elem.nodeValue;
+
+		// Traverse everything else, except comment nodes
+		} else if ( elem.nodeType !== 8 ) {
+			ret += getText( elem.childNodes );
+		}
+	}
+
+	return ret;
+}
+
+// from jquery.js
+function inArray( elem, array ) {
+	if ( array.indexOf ) {
+		return array.indexOf( elem );
+	}
+
+	for ( var i = 0, length = array.length; i < length; i++ ) {
+		if ( array[ i ] === elem ) {
+			return i;
+		}
+	}
+
+	return -1;
+}
+
+/*
+ * Javascript Diff Algorithm
+ *  By John Resig (http://ejohn.org/)
+ *  Modified by Chu Alan "sprite"
+ *
+ * Released under the MIT license.
+ *
+ * More Info:
+ *  http://ejohn.org/projects/javascript-diff-algorithm/
+ *
+ * Usage: QUnit.diff(expected, actual)
+ *
+ * QUnit.diff( "the quick brown fox jumped over", "the quick fox jumps over" ) == "the  quick <del>brown </del> fox <del>jumped </del><ins>jumps </ins> over"
+ */
+QUnit.diff = (function() {
+	function diff( o, n ) {
+		var i,
+			ns = {},
+			os = {};
+
+		for ( i = 0; i < n.length; i++ ) {
+			if ( ns[ n[i] ] == null ) {
+				ns[ n[i] ] = {
+					rows: [],
+					o: null
+				};
+			}
+			ns[ n[i] ].rows.push( i );
+		}
+
+		for ( i = 0; i < o.length; i++ ) {
+			if ( os[ o[i] ] == null ) {
+				os[ o[i] ] = {
+					rows: [],
+					n: null
+				};
+			}
+			os[ o[i] ].rows.push( i );
+		}
+
+		for ( i in ns ) {
+			if ( !hasOwn.call( ns, i ) ) {
+				continue;
+			}
+			if ( ns[i].rows.length == 1 && typeof os[i] != "undefined" && os[i].rows.length == 1 ) {
+				n[ ns[i].rows[0] ] = {
+					text: n[ ns[i].rows[0] ],
+					row: os[i].rows[0]
+				};
+				o[ os[i].rows[0] ] = {
+					text: o[ os[i].rows[0] ],
+					row: ns[i].rows[0]
+				};
+			}
+		}
+
+		for ( i = 0; i < n.length - 1; i++ ) {
+			if ( n[i].text != null && n[ i + 1 ].text == null && n[i].row + 1 < o.length && o[ n[i].row + 1 ].text == null &&
+						n[ i + 1 ] == o[ n[i].row + 1 ] ) {
+
+				n[ i + 1 ] = {
+					text: n[ i + 1 ],
+					row: n[i].row + 1
+				};
+				o[ n[i].row + 1 ] = {
+					text: o[ n[i].row + 1 ],
+					row: i + 1
+				};
+			}
+		}
+
+		for ( i = n.length - 1; i > 0; i-- ) {
+			if ( n[i].text != null && n[ i - 1 ].text == null && n[i].row > 0 && o[ n[i].row - 1 ].text == null &&
+						n[ i - 1 ] == o[ n[i].row - 1 ]) {
+
+				n[ i - 1 ] = {
+					text: n[ i - 1 ],
+					row: n[i].row - 1
+				};
+				o[ n[i].row - 1 ] = {
+					text: o[ n[i].row - 1 ],
+					row: i - 1
+				};
+			}
+		}
+
+		return {
+			o: o,
+			n: n
+		};
+	}
+
+	return function( o, n ) {
+		o = o.replace( /\s+$/, "" );
+		n = n.replace( /\s+$/, "" );
+
+		var i, pre,
+			str = "",
+			out = diff( o === "" ? [] : o.split(/\s+/), n === "" ? [] : n.split(/\s+/) ),
+			oSpace = o.match(/\s+/g),
+			nSpace = n.match(/\s+/g);
+
+		if ( oSpace == null ) {
+			oSpace = [ " " ];
+		}
+		else {
+			oSpace.push( " " );
+		}
+
+		if ( nSpace == null ) {
+			nSpace = [ " " ];
+		}
+		else {
+			nSpace.push( " " );
+		}
+
+		if ( out.n.length === 0 ) {
+			for ( i = 0; i < out.o.length; i++ ) {
+				str += "<del>" + out.o[i] + oSpace[i] + "</del>";
+			}
+		}
+		else {
+			if ( out.n[0].text == null ) {
+				for ( n = 0; n < out.o.length && out.o[n].text == null; n++ ) {
+					str += "<del>" + out.o[n] + oSpace[n] + "</del>";
+				}
+			}
+
+			for ( i = 0; i < out.n.length; i++ ) {
+				if (out.n[i].text == null) {
+					str += "<ins>" + out.n[i] + nSpace[i] + "</ins>";
+				}
+				else {
+					// `pre` initialized at top of scope
+					pre = "";
+
+					for ( n = out.n[i].row + 1; n < out.o.length && out.o[n].text == null; n++ ) {
+						pre += "<del>" + out.o[n] + oSpace[n] + "</del>";
+					}
+					str += " " + out.n[i].text + nSpace[i] + pre;
+				}
+			}
+		}
+
+		return str;
+	};
+}());
+
+// for CommonJS enviroments, export everything
+if ( typeof exports !== "undefined" ) {
+	extend(exports, QUnit);
+}
+
+// get at whatever the global object is, like window in browsers
+}( (function() {return this;}.call()) ));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/LICENSE b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/LICENSE
new file mode 100644
index 0000000..ffaf317
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/LICENSE
@@ -0,0 +1,58 @@
+RequireJS is released under two licenses: new BSD, and MIT. You may pick the
+license that best suits your development needs. The text of both licenses are
+provided below.
+
+
+The "New" BSD License:
+----------------------
+
+Copyright (c) 2010-2011, The Dojo Foundation
+All rights reserved.
+
+Redistribution and use in source and binary forms, with or without
+modification, are permitted provided that the following conditions are met:
+
+  * Redistributions of source code must retain the above copyright notice, this
+    list of conditions and the following disclaimer.
+  * Redistributions in binary form must reproduce the above copyright notice,
+    this list of conditions and the following disclaimer in the documentation
+    and/or other materials provided with the distribution.
+  * Neither the name of the Dojo Foundation nor the names of its contributors
+    may be used to endorse or promote products derived from this software
+    without specific prior written permission.
+
+THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
+ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
+WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
+DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
+FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
+SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
+CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
+OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+
+
+MIT License
+-----------
+
+Copyright (c) 2010-2011, The Dojo Foundation
+
+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.
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/README.md
new file mode 100644
index 0000000..4d3f25e
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/README.md
@@ -0,0 +1,51 @@
+# RequireJS
+
+RequireJS loads plain JavaScript files as well as more defined modules. It is
+optimized for in-browser use, including in
+[a Web Worker](http://requirejs.org/docs/api.html#webworker), but it can be used
+in other JavaScript environments, like Rhino and
+[Node](http://requirejs.org/docs/node.html). It implements the
+[Asynchronous Module](https://github.com/amdjs/amdjs-api/wiki/AMD)
+API.
+
+RequireJS uses plain script tags to load modules/files, so it should allow for
+easy debugging. It can be used
+[simply to load existing JavaScript files](http://requirejs.org/docs/api.html#jsfiles),
+so you can add it to your existing project without having to re-write your
+JavaScript files.
+
+RequireJS includes [an optimization tool](http://requirejs.org/docs/optimization.html)
+you can run as part of your packaging steps for deploying your code. The
+optimization tool can combine and minify your JavaScript files to allow for
+better performance.
+
+If the JavaScript file defines a JavaScript module via
+[define()](http://requirejs.org/docs/api.html#define), then there are other benefits
+RequireJS can offer: [improvements over traditional CommonJS modules](http://requirejs.org/docs/commonjs.html)
+and [loading multiple versions](http://requirejs.org/docs/api.html#multiversion)
+of a module in a page. RequireJS also has a plugin system that supports features like
+[i18n string bundles](http://requirejs.org/docs/api.html#i18n), and
+[text file dependencies](http://requirejs.org/docs/api.html#text).
+
+RequireJS does not have any dependencies on a JavaScript framework.
+It is dual-licensed -- new BSD or MIT.
+
+The standard require.js file is around 5.5KB when minified via Closure Compiler
+and gzipped.
+
+RequireJS works in IE 6+, Firefox 2+, Safari 3.2+, Chrome 3+, and Opera 10+.
+
+[Latest Release](http://requirejs.org/docs/download.html)
+
+## Directories
+
+* **dist**: Scripts and assets to generate the requirejs.org docs, and for
+generating a require.js release.
+* **docs**: The raw HTML files for the requirejs.org docs. Only includes the
+body of each page. Files in **dist** are used to generate a complete HTML page.
+* **tests**: Tests for require.js.
+* **testBaseUrl.js**: A file used in the tests inside **tests**. Purposely
+placed outside the tests directory for testing paths that go outside a baseUrl.
+* **updatesubs.sh**: Updates projects that depend on require.js Assumes the
+projects are siblings to this directory and have specific names. Useful to
+copy require.js to dependent projects easily while in development.
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/require.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/require.js
new file mode 100644
index 0000000..34cfe23
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/benchmark.js/vendor/requirejs/require.js
@@ -0,0 +1,2053 @@
+/** vim: et:ts=4:sw=4:sts=4
+ * @license RequireJS 2.0.5 Copyright (c) 2010-2012, The Dojo Foundation All Rights Reserved.
+ * Available via the MIT or new BSD license.
+ * see: http://github.com/jrburke/requirejs for details
+ */
+//Not using strict: uneven strict support in browsers, #392, and causes
+//problems with requirejs.exec()/transpiler plugins that may not be strict.
+/*jslint regexp: true, nomen: true, sloppy: true */
+/*global window, navigator, document, importScripts, jQuery, setTimeout, opera */
+
+var requirejs, require, define;
+(function (global) {
+    var req, s, head, baseElement, dataMain, src,
+        interactiveScript, currentlyAddingScript, mainScript, subPath,
+        version = '2.0.5',
+        commentRegExp = /(\/\*([\s\S]*?)\*\/|([^:]|^)\/\/(.*)$)/mg,
+        cjsRequireRegExp = /[^.]\s*require\s*\(\s*["']([^'"\s]+)["']\s*\)/g,
+        jsSuffixRegExp = /\.js$/,
+        currDirRegExp = /^\.\//,
+        op = Object.prototype,
+        ostring = op.toString,
+        hasOwn = op.hasOwnProperty,
+        ap = Array.prototype,
+        aps = ap.slice,
+        apsp = ap.splice,
+        isBrowser = !!(typeof window !== 'undefined' && navigator && document),
+        isWebWorker = !isBrowser && typeof importScripts !== 'undefined',
+        //PS3 indicates loaded and complete, but need to wait for complete
+        //specifically. Sequence is 'loading', 'loaded', execution,
+        // then 'complete'. The UA check is unfortunate, but not sure how
+        //to feature test w/o causing perf issues.
+        readyRegExp = isBrowser && navigator.platform === 'PLAYSTATION 3' ?
+                      /^complete$/ : /^(complete|loaded)$/,
+        defContextName = '_',
+        //Oh the tragedy, detecting opera. See the usage of isOpera for reason.
+        isOpera = typeof opera !== 'undefined' && opera.toString() === '[object Opera]',
+        contexts = {},
+        cfg = {},
+        globalDefQueue = [],
+        useInteractive = false;
+
+    function isFunction(it) {
+        return ostring.call(it) === '[object Function]';
+    }
+
+    function isArray(it) {
+        return ostring.call(it) === '[object Array]';
+    }
+
+    /**
+     * Helper function for iterating over an array. If the func returns
+     * a true value, it will break out of the loop.
+     */
+    function each(ary, func) {
+        if (ary) {
+            var i;
+            for (i = 0; i < ary.length; i += 1) {
+                if (ary[i] && func(ary[i], i, ary)) {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Helper function for iterating over an array backwards. If the func
+     * returns a true value, it will break out of the loop.
+     */
+    function eachReverse(ary, func) {
+        if (ary) {
+            var i;
+            for (i = ary.length - 1; i > -1; i -= 1) {
+                if (ary[i] && func(ary[i], i, ary)) {
+                    break;
+                }
+            }
+        }
+    }
+
+    function hasProp(obj, prop) {
+        return hasOwn.call(obj, prop);
+    }
+
+    /**
+     * Cycles over properties in an object and calls a function for each
+     * property value. If the function returns a truthy value, then the
+     * iteration is stopped.
+     */
+    function eachProp(obj, func) {
+        var prop;
+        for (prop in obj) {
+            if (obj.hasOwnProperty(prop)) {
+                if (func(obj[prop], prop)) {
+                    break;
+                }
+            }
+        }
+    }
+
+    /**
+     * Simple function to mix in properties from source into target,
+     * but only if target does not already have a property of the same name.
+     * This is not robust in IE for transferring methods that match
+     * Object.prototype names, but the uses of mixin here seem unlikely to
+     * trigger a problem related to that.
+     */
+    function mixin(target, source, force, deepStringMixin) {
+        if (source) {
+            eachProp(source, function (value, prop) {
+                if (force || !hasProp(target, prop)) {
+                    if (deepStringMixin && typeof value !== 'string') {
+                        if (!target[prop]) {
+                            target[prop] = {};
+                        }
+                        mixin(target[prop], value, force, deepStringMixin);
+                    } else {
+                        target[prop] = value;
+                    }
+                }
+            });
+        }
+        return target;
+    }
+
+    //Similar to Function.prototype.bind, but the 'this' object is specified
+    //first, since it is easier to read/figure out what 'this' will be.
+    function bind(obj, fn) {
+        return function () {
+            return fn.apply(obj, arguments);
+        };
+    }
+
+    function scripts() {
+        return document.getElementsByTagName('script');
+    }
+
+    //Allow getting a global that expressed in
+    //dot notation, like 'a.b.c'.
+    function getGlobal(value) {
+        if (!value) {
+            return value;
+        }
+        var g = global;
+        each(value.split('.'), function (part) {
+            g = g[part];
+        });
+        return g;
+    }
+
+    function makeContextModuleFunc(func, relMap, enableBuildCallback) {
+        return function () {
+            //A version of a require function that passes a moduleName
+            //value for items that may need to
+            //look up paths relative to the moduleName
+            var args = aps.call(arguments, 0), lastArg;
+            if (enableBuildCallback &&
+                    isFunction((lastArg = args[args.length - 1]))) {
+                lastArg.__requireJsBuild = true;
+            }
+            args.push(relMap);
+            return func.apply(null, args);
+        };
+    }
+
+    function addRequireMethods(req, context, relMap) {
+        each([
+            ['toUrl'],
+            ['undef'],
+            ['defined', 'requireDefined'],
+            ['specified', 'requireSpecified']
+        ], function (item) {
+            var prop = item[1] || item[0];
+            req[item[0]] = context ? makeContextModuleFunc(context[prop], relMap) :
+                    //If no context, then use default context. Reference from
+                    //contexts instead of early binding to default context, so
+                    //that during builds, the latest instance of the default
+                    //context with its config gets used.
+                    function () {
+                        var ctx = contexts[defContextName];
+                        return ctx[prop].apply(ctx, arguments);
+                    };
+        });
+    }
+
+    /**
+     * Constructs an error with a pointer to an URL with more information.
+     * @param {String} id the error ID that maps to an ID on a web page.
+     * @param {String} message human readable error.
+     * @param {Error} [err] the original error, if there is one.
+     *
+     * @returns {Error}
+     */
+    function makeError(id, msg, err, requireModules) {
+        var e = new Error(msg + '\nhttp://requirejs.org/docs/errors.html#' + id);
+        e.requireType = id;
+        e.requireModules = requireModules;
+        if (err) {
+            e.originalError = err;
+        }
+        return e;
+    }
+
+    if (typeof define !== 'undefined') {
+        //If a define is already in play via another AMD loader,
+        //do not overwrite.
+        return;
+    }
+
+    if (typeof requirejs !== 'undefined') {
+        if (isFunction(requirejs)) {
+            //Do not overwrite and existing requirejs instance.
+            return;
+        }
+        cfg = requirejs;
+        requirejs = undefined;
+    }
+
+    //Allow for a require config object
+    if (typeof require !== 'undefined' && !isFunction(require)) {
+        //assume it is a config object.
+        cfg = require;
+        require = undefined;
+    }
+
+    function newContext(contextName) {
+        var inCheckLoaded, Module, context, handlers,
+            checkLoadedTimeoutId,
+            config = {
+                waitSeconds: 7,
+                baseUrl: './',
+                paths: {},
+                pkgs: {},
+                shim: {}
+            },
+            registry = {},
+            undefEvents = {},
+            defQueue = [],
+            defined = {},
+            urlFetched = {},
+            requireCounter = 1,
+            unnormalizedCounter = 1,
+            //Used to track the order in which modules
+            //should be executed, by the order they
+            //load. Important for consistent cycle resolution
+            //behavior.
+            waitAry = [];
+
+        /**
+         * Trims the . and .. from an array of path segments.
+         * It will keep a leading path segment if a .. will become
+         * the first path segment, to help with module name lookups,
+         * which act like paths, but can be remapped. But the end result,
+         * all paths that use this function should look normalized.
+         * NOTE: this method MODIFIES the input array.
+         * @param {Array} ary the array of path segments.
+         */
+        function trimDots(ary) {
+            var i, part;
+            for (i = 0; ary[i]; i += 1) {
+                part = ary[i];
+                if (part === '.') {
+                    ary.splice(i, 1);
+                    i -= 1;
+                } else if (part === '..') {
+                    if (i === 1 && (ary[2] === '..' || ary[0] === '..')) {
+                        //End of the line. Keep at least one non-dot
+                        //path segment at the front so it can be mapped
+                        //correctly to disk. Otherwise, there is likely
+                        //no path mapping for a path starting with '..'.
+                        //This can still fail, but catches the most reasonable
+                        //uses of ..
+                        break;
+                    } else if (i > 0) {
+                        ary.splice(i - 1, 2);
+                        i -= 2;
+                    }
+                }
+            }
+        }
+
+        /**
+         * Given a relative module name, like ./something, normalize it to
+         * a real name that can be mapped to a path.
+         * @param {String} name the relative name
+         * @param {String} baseName a real name that the name arg is relative
+         * to.
+         * @param {Boolean} applyMap apply the map config to the value. Should
+         * only be done if this normalization is for a dependency ID.
+         * @returns {String} normalized name
+         */
+        function normalize(name, baseName, applyMap) {
+            var pkgName, pkgConfig, mapValue, nameParts, i, j, nameSegment,
+                foundMap, foundI, foundStarMap, starI,
+                baseParts = baseName && baseName.split('/'),
+                normalizedBaseParts = baseParts,
+                map = config.map,
+                starMap = map && map['*'];
+
+            //Adjust any relative paths.
+            if (name && name.charAt(0) === '.') {
+                //If have a base name, try to normalize against it,
+                //otherwise, assume it is a top-level require that will
+                //be relative to baseUrl in the end.
+                if (baseName) {
+                    if (config.pkgs[baseName]) {
+                        //If the baseName is a package name, then just treat it as one
+                        //name to concat the name with.
+                        normalizedBaseParts = baseParts = [baseName];
+                    } else {
+                        //Convert baseName to array, and lop off the last part,
+                        //so that . matches that 'directory' and not name of the baseName's
+                        //module. For instance, baseName of 'one/two/three', maps to
+                        //'one/two/three.js', but we want the directory, 'one/two' for
+                        //this normalization.
+                        normalizedBaseParts = baseParts.slice(0, baseParts.length - 1);
+                    }
+
+                    name = normalizedBaseParts.concat(name.split('/'));
+                    trimDots(name);
+
+                    //Some use of packages may use a . path to reference the
+                    //'main' module name, so normalize for that.
+                    pkgConfig = config.pkgs[(pkgName = name[0])];
+                    name = name.join('/');
+                    if (pkgConfig && name === pkgName + '/' + pkgConfig.main) {
+                        name = pkgName;
+                    }
+                } else if (name.indexOf('./') === 0) {
+                    // No baseName, so this is ID is resolved relative
+                    // to baseUrl, pull off the leading dot.
+                    name = name.substring(2);
+                }
+            }
+
+            //Apply map config if available.
+            if (applyMap && (baseParts || starMap) && map) {
+                nameParts = name.split('/');
+
+                for (i = nameParts.length; i > 0; i -= 1) {
+                    nameSegment = nameParts.slice(0, i).join('/');
+
+                    if (baseParts) {
+                        //Find the longest baseName segment match in the config.
+                        //So, do joins on the biggest to smallest lengths of baseParts.
+                        for (j = baseParts.length; j > 0; j -= 1) {
+                            mapValue = map[baseParts.slice(0, j).join('/')];
+
+                            //baseName segment has config, find if it has one for
+                            //this name.
+                            if (mapValue) {
+                                mapValue = mapValue[nameSegment];
+                                if (mapValue) {
+                                    //Match, update name to the new value.
+                                    foundMap = mapValue;
+                                    foundI = i;
+                                    break;
+                                }
+                            }
+                        }
+                    }
+
+                    if (foundMap) {
+                        break;
+                    }
+
+                    //Check for a star map match, but just hold on to it,
+                    //if there is a shorter segment match later in a matching
+                    //config, then favor over this star map.
+                    if (!foundStarMap && starMap && starMap[nameSegment]) {
+                        foundStarMap = starMap[nameSegment];
+                        starI = i;
+                    }
+                }
+
+                if (!foundMap && foundStarMap) {
+                    foundMap = foundStarMap;
+                    foundI = starI;
+                }
+
+                if (foundMap) {
+                    nameParts.splice(0, foundI, foundMap);
+                    name = nameParts.join('/');
+                }
+            }
+
+            return name;
+        }
+
+        function removeScript(name) {
+            if (isBrowser) {
+                each(scripts(), function (scriptNode) {
+                    if (scriptNode.getAttribute('data-requiremodule') === name &&
+                            scriptNode.getAttribute('data-requirecontext') === context.contextName) {
+                        scriptNode.parentNode.removeChild(scriptNode);
+                        return true;
+                    }
+                });
+            }
+        }
+
+        function hasPathFallback(id) {
+            var pathConfig = config.paths[id];
+            if (pathConfig && isArray(pathConfig) && pathConfig.length > 1) {
+                removeScript(id);
+                //Pop off the first array value, since it failed, and
+                //retry
+                pathConfig.shift();
+                context.undef(id);
+                context.require([id]);
+                return true;
+            }
+        }
+
+        /**
+         * Creates a module mapping that includes plugin prefix, module
+         * name, and path. If parentModuleMap is provided it will
+         * also normalize the name via require.normalize()
+         *
+         * @param {String} name the module name
+         * @param {String} [parentModuleMap] parent module map
+         * for the module name, used to resolve relative names.
+         * @param {Boolean} isNormalized: is the ID already normalized.
+         * This is true if this call is done for a define() module ID.
+         * @param {Boolean} applyMap: apply the map config to the ID.
+         * Should only be true if this map is for a dependency.
+         *
+         * @returns {Object}
+         */
+        function makeModuleMap(name, parentModuleMap, isNormalized, applyMap) {
+            var url, pluginModule, suffix,
+                index = name ? name.indexOf('!') : -1,
+                prefix = null,
+                parentName = parentModuleMap ? parentModuleMap.name : null,
+                originalName = name,
+                isDefine = true,
+                normalizedName = '';
+
+            //If no name, then it means it is a require call, generate an
+            //internal name.
+            if (!name) {
+                isDefine = false;
+                name = '_@r' + (requireCounter += 1);
+            }
+
+            if (index !== -1) {
+                prefix = name.substring(0, index);
+                name = name.substring(index + 1, name.length);
+            }
+
+            if (prefix) {
+                prefix = normalize(prefix, parentName, applyMap);
+                pluginModule = defined[prefix];
+            }
+
+            //Account for relative paths if there is a base name.
+            if (name) {
+                if (prefix) {
+                    if (pluginModule && pluginModule.normalize) {
+                        //Plugin is loaded, use its normalize method.
+                        normalizedName = pluginModule.normalize(name, function (name) {
+                            return normalize(name, parentName, applyMap);
+                        });
+                    } else {
+                        normalizedName = normalize(name, parentName, applyMap);
+                    }
+                } else {
+                    //A regular module.
+                    normalizedName = normalize(name, parentName, applyMap);
+                    url = context.nameToUrl(normalizedName);
+                }
+            }
+
+            //If the id is a plugin id that cannot be determined if it needs
+            //normalization, stamp it with a unique ID so two matching relative
+            //ids that may conflict can be separate.
+            suffix = prefix && !pluginModule && !isNormalized ?
+                     '_unnormalized' + (unnormalizedCounter += 1) :
+                     '';
+
+            return {
+                prefix: prefix,
+                name: normalizedName,
+                parentMap: parentModuleMap,
+                unnormalized: !!suffix,
+                url: url,
+                originalName: originalName,
+                isDefine: isDefine,
+                id: (prefix ?
+                        prefix + '!' + normalizedName :
+                        normalizedName) + suffix
+            };
+        }
+
+        function getModule(depMap) {
+            var id = depMap.id,
+                mod = registry[id];
+
+            if (!mod) {
+                mod = registry[id] = new context.Module(depMap);
+            }
+
+            return mod;
+        }
+
+        function on(depMap, name, fn) {
+            var id = depMap.id,
+                mod = registry[id];
+
+            if (hasProp(defined, id) &&
+                    (!mod || mod.defineEmitComplete)) {
+                if (name === 'defined') {
+                    fn(defined[id]);
+                }
+            } else {
+                getModule(depMap).on(name, fn);
+            }
+        }
+
+        function onError(err, errback) {
+            var ids = err.requireModules,
+                notified = false;
+
+            if (errback) {
+                errback(err);
+            } else {
+                each(ids, function (id) {
+                    var mod = registry[id];
+                    if (mod) {
+                        //Set error on module, so it skips timeout checks.
+                        mod.error = err;
+                        if (mod.events.error) {
+                            notified = true;
+                            mod.emit('error', err);
+                        }
+                    }
+                });
+
+                if (!notified) {
+                    req.onError(err);
+                }
+            }
+        }
+
+        /**
+         * Internal method to transfer globalQueue items to this context's
+         * defQueue.
+         */
+        function takeGlobalQueue() {
+            //Push all the globalDefQueue items into the context's defQueue
+            if (globalDefQueue.length) {
+                //Array splice in the values since the context code has a
+                //local var ref to defQueue, so cannot just reassign the one
+                //on context.
+                apsp.apply(defQueue,
+                           [defQueue.length - 1, 0].concat(globalDefQueue));
+                globalDefQueue = [];
+            }
+        }
+
+        /**
+         * Helper function that creates a require function object to give to
+         * modules that ask for it as a dependency. It needs to be specific
+         * per module because of the implication of path mappings that may
+         * need to be relative to the module name.
+         */
+        function makeRequire(mod, enableBuildCallback, altRequire) {
+            var relMap = mod && mod.map,
+                modRequire = makeContextModuleFunc(altRequire || context.require,
+                                                   relMap,
+                                                   enableBuildCallback);
+
+            addRequireMethods(modRequire, context, relMap);
+            modRequire.isBrowser = isBrowser;
+
+            return modRequire;
+        }
+
+        handlers = {
+            'require': function (mod) {
+                return makeRequire(mod);
+            },
+            'exports': function (mod) {
+                mod.usingExports = true;
+                if (mod.map.isDefine) {
+                    return (mod.exports = defined[mod.map.id] = {});
+                }
+            },
+            'module': function (mod) {
+                return (mod.module = {
+                    id: mod.map.id,
+                    uri: mod.map.url,
+                    config: function () {
+                        return (config.config && config.config[mod.map.id]) || {};
+                    },
+                    exports: defined[mod.map.id]
+                });
+            }
+        };
+
+        function removeWaiting(id) {
+            //Clean up machinery used for waiting modules.
+            delete registry[id];
+
+            each(waitAry, function (mod, i) {
+                if (mod.map.id === id) {
+                    waitAry.splice(i, 1);
+                    if (!mod.defined) {
+                        context.waitCount -= 1;
+                    }
+                    return true;
+                }
+            });
+        }
+
+        function findCycle(mod, traced) {
+            var id = mod.map.id,
+                depArray = mod.depMaps,
+                foundModule;
+
+            //Do not bother with unitialized modules or not yet enabled
+            //modules.
+            if (!mod.inited) {
+                return;
+            }
+
+            //Found the cycle.
+            if (traced[id]) {
+                return mod;
+            }
+
+            traced[id] = true;
+
+            //Trace through the dependencies.
+            each(depArray, function (depMap) {
+                var depId = depMap.id,
+                    depMod = registry[depId];
+
+                if (!depMod) {
+                    return;
+                }
+
+                if (!depMod.inited || !depMod.enabled) {
+                    //Dependency is not inited, so this cannot
+                    //be used to determine a cycle.
+                    foundModule = null;
+                    delete traced[id];
+                    return true;
+                }
+
+                //mixin traced to a new object for each dependency, so that
+                //sibling dependencies in this object to not generate a
+                //false positive match on a cycle. Ideally an Object.create
+                //type of prototype delegation would be used here, but
+                //optimizing for file size vs. execution speed since hopefully
+                //the trees are small for circular dependency scans relative
+                //to the full app perf.
+                return (foundModule = findCycle(depMod, mixin({}, traced)));
+            });
+
+            return foundModule;
+        }
+
+        function forceExec(mod, traced, uninited) {
+            var id = mod.map.id,
+                depArray = mod.depMaps;
+
+            if (!mod.inited || !mod.map.isDefine) {
+                return;
+            }
+
+            if (traced[id]) {
+                return defined[id];
+            }
+
+            traced[id] = mod;
+
+            each(depArray, function (depMap) {
+                var depId = depMap.id,
+                    depMod = registry[depId],
+                    value;
+
+                if (handlers[depId]) {
+                    return;
+                }
+
+                if (depMod) {
+                    if (!depMod.inited || !depMod.enabled) {
+                        //Dependency is not inited,
+                        //so this module cannot be
+                        //given a forced value yet.
+                        uninited[id] = true;
+                        return;
+                    }
+
+                    //Get the value for the current dependency
+                    value = forceExec(depMod, traced, uninited);
+
+                    //Even with forcing it may not be done,
+                    //in particular if the module is waiting
+                    //on a plugin resource.
+                    if (!uninited[depId]) {
+                        mod.defineDepById(depId, value);
+                    }
+                }
+            });
+
+            mod.check(true);
+
+            return defined[id];
+        }
+
+        function modCheck(mod) {
+            mod.check();
+        }
+
+        function checkLoaded() {
+            var map, modId, err, usingPathFallback,
+                waitInterval = config.waitSeconds * 1000,
+                //It is possible to disable the wait interval by using waitSeconds of 0.
+                expired = waitInterval && (context.startTime + waitInterval) < new Date().getTime(),
+                noLoads = [],
+                stillLoading = false,
+                needCycleCheck = true;
+
+            //Do not bother if this call was a result of a cycle break.
+            if (inCheckLoaded) {
+                return;
+            }
+
+            inCheckLoaded = true;
+
+            //Figure out the state of all the modules.
+            eachProp(registry, function (mod) {
+                map = mod.map;
+                modId = map.id;
+
+                //Skip things that are not enabled or in error state.
+                if (!mod.enabled) {
+                    return;
+                }
+
+                if (!mod.error) {
+                    //If the module should be executed, and it has not
+                    //been inited and time is up, remember it.
+                    if (!mod.inited && expired) {
+                        if (hasPathFallback(modId)) {
+                            usingPathFallback = true;
+                            stillLoading = true;
+                        } else {
+                            noLoads.push(modId);
+                            removeScript(modId);
+                        }
+                    } else if (!mod.inited && mod.fetched && map.isDefine) {
+                        stillLoading = true;
+                        if (!map.prefix) {
+                            //No reason to keep looking for unfinished
+                            //loading. If the only stillLoading is a
+                            //plugin resource though, keep going,
+                            //because it may be that a plugin resource
+                            //is waiting on a non-plugin cycle.
+                            return (needCycleCheck = false);
+                        }
+                    }
+                }
+            });
+
+            if (expired && noLoads.length) {
+                //If wait time expired, throw error of unloaded modules.
+                err = makeError('timeout', 'Load timeout for modules: ' + noLoads, null, noLoads);
+                err.contextName = context.contextName;
+                return onError(err);
+            }
+
+            //Not expired, check for a cycle.
+            if (needCycleCheck) {
+
+                each(waitAry, function (mod) {
+                    if (mod.defined) {
+                        return;
+                    }
+
+                    var cycleMod = findCycle(mod, {}),
+                        traced = {};
+
+                    if (cycleMod) {
+                        forceExec(cycleMod, traced, {});
+
+                        //traced modules may have been
+                        //removed from the registry, but
+                        //their listeners still need to
+                        //be called.
+                        eachProp(traced, modCheck);
+                    }
+                });
+
+                //Now that dependencies have
+                //been satisfied, trigger the
+                //completion check that then
+                //notifies listeners.
+                eachProp(registry, modCheck);
+            }
+
+            //If still waiting on loads, and the waiting load is something
+            //other than a plugin resource, or there are still outstanding
+            //scripts, then just try back later.
+            if ((!expired || usingPathFallback) && stillLoading) {
+                //Something is still waiting to load. Wait for it, but only
+                //if a timeout is not already in effect.
+                if ((isBrowser || isWebWorker) && !checkLoadedTimeoutId) {
+                    checkLoadedTimeoutId = setTimeout(function () {
+                        checkLoadedTimeoutId = 0;
+                        checkLoaded();
+                    }, 50);
+                }
+            }
+
+            inCheckLoaded = false;
+        }
+
+        Module = function (map) {
+            this.events = undefEvents[map.id] || {};
+            this.map = map;
+            this.shim = config.shim[map.id];
+            this.depExports = [];
+            this.depMaps = [];
+            this.depMatched = [];
+            this.pluginMaps = {};
+            this.depCount = 0;
+
+            /* this.exports this.factory
+               this.depMaps = [],
+               this.enabled, this.fetched
+            */
+        };
+
+        Module.prototype = {
+            init: function (depMaps, factory, errback, options) {
+                options = options || {};
+
+                //Do not do more inits if already done. Can happen if there
+                //are multiple define calls for the same module. That is not
+                //a normal, common case, but it is also not unexpected.
+                if (this.inited) {
+                    return;
+                }
+
+                this.factory = factory;
+
+                if (errback) {
+                    //Register for errors on this module.
+                    this.on('error', errback);
+                } else if (this.events.error) {
+                    //If no errback already, but there are error listeners
+                    //on this module, set up an errback to pass to the deps.
+                    errback = bind(this, function (err) {
+                        this.emit('error', err);
+                    });
+                }
+
+                //Do a copy of the dependency array, so that
+                //source inputs are not modified. For example
+                //"shim" deps are passed in here directly, and
+                //doing a direct modification of the depMaps array
+                //would affect that config.
+                this.depMaps = depMaps && depMaps.slice(0);
+                this.depMaps.rjsSkipMap = depMaps.rjsSkipMap;
+
+                this.errback = errback;
+
+                //Indicate this module has be initialized
+                this.inited = true;
+
+                this.ignore = options.ignore;
+
+                //Could have option to init this module in enabled mode,
+                //or could have been previously marked as enabled. However,
+                //the dependencies are not known until init is called. So
+                //if enabled previously, now trigger dependencies as enabled.
+                if (options.enabled || this.enabled) {
+                    //Enable this module and dependencies.
+                    //Will call this.check()
+                    this.enable();
+                } else {
+                    this.check();
+                }
+            },
+
+            defineDepById: function (id, depExports) {
+                var i;
+
+                //Find the index for this dependency.
+                each(this.depMaps, function (map, index) {
+                    if (map.id === id) {
+                        i = index;
+                        return true;
+                    }
+                });
+
+                return this.defineDep(i, depExports);
+            },
+
+            defineDep: function (i, depExports) {
+                //Because of cycles, defined callback for a given
+                //export can be called more than once.
+                if (!this.depMatched[i]) {
+                    this.depMatched[i] = true;
+                    this.depCount -= 1;
+                    this.depExports[i] = depExports;
+                }
+            },
+
+            fetch: function () {
+                if (this.fetched) {
+                    return;
+                }
+                this.fetched = true;
+
+                context.startTime = (new Date()).getTime();
+
+                var map = this.map;
+
+                //If the manager is for a plugin managed resource,
+                //ask the plugin to load it now.
+                if (this.shim) {
+                    makeRequire(this, true)(this.shim.deps || [], bind(this, function () {
+                        return map.prefix ? this.callPlugin() : this.load();
+                    }));
+                } else {
+                    //Regular dependency.
+                    return map.prefix ? this.callPlugin() : this.load();
+                }
+            },
+
+            load: function () {
+                var url = this.map.url;
+
+                //Regular dependency.
+                if (!urlFetched[url]) {
+                    urlFetched[url] = true;
+                    context.load(this.map.id, url);
+                }
+            },
+
+            /**
+             * Checks is the module is ready to define itself, and if so,
+             * define it. If the silent argument is true, then it will just
+             * define, but not notify listeners, and not ask for a context-wide
+             * check of all loaded modules. That is useful for cycle breaking.
+             */
+            check: function (silent) {
+                if (!this.enabled || this.enabling) {
+                    return;
+                }
+
+                var err, cjsModule,
+                    id = this.map.id,
+                    depExports = this.depExports,
+                    exports = this.exports,
+                    factory = this.factory;
+
+                if (!this.inited) {
+                    this.fetch();
+                } else if (this.error) {
+                    this.emit('error', this.error);
+                } else if (!this.defining) {
+                    //The factory could trigger another require call
+                    //that would result in checking this module to
+                    //define itself again. If already in the process
+                    //of doing that, skip this work.
+                    this.defining = true;
+
+                    if (this.depCount < 1 && !this.defined) {
+                        if (isFunction(factory)) {
+                            //If there is an error listener, favor passing
+                            //to that instead of throwing an error.
+                            if (this.events.error) {
+                                try {
+                                    exports = context.execCb(id, factory, depExports, exports);
+                                } catch (e) {
+                                    err = e;
+                                }
+                            } else {
+                                exports = context.execCb(id, factory, depExports, exports);
+                            }
+
+                            if (this.map.isDefine) {
+                                //If setting exports via 'module' is in play,
+                                //favor that over return value and exports. After that,
+                                //favor a non-undefined return value over exports use.
+                                cjsModule = this.module;
+                                if (cjsModule &&
+                                        cjsModule.exports !== undefined &&
+                                        //Make sure it is not already the exports value
+                                        cjsModule.exports !== this.exports) {
+                                    exports = cjsModule.exports;
+                                } else if (exports === undefined && this.usingExports) {
+                                    //exports already set the defined value.
+                                    exports = this.exports;
+                                }
+                            }
+
+                            if (err) {
+                                err.requireMap = this.map;
+                                err.requireModules = [this.map.id];
+                                err.requireType = 'define';
+                                return onError((this.error = err));
+                            }
+
+                        } else {
+                            //Just a literal value
+                            exports = factory;
+                        }
+
+                        this.exports = exports;
+
+                        if (this.map.isDefine && !this.ignore) {
+                            defined[id] = exports;
+
+                            if (req.onResourceLoad) {
+                                req.onResourceLoad(context, this.map, this.depMaps);
+                            }
+                        }
+
+                        //Clean up
+                        delete registry[id];
+
+                        this.defined = true;
+                        context.waitCount -= 1;
+                        if (context.waitCount === 0) {
+                            //Clear the wait array used for cycles.
+                            waitAry = [];
+                        }
+                    }
+
+                    //Finished the define stage. Allow calling check again
+                    //to allow define notifications below in the case of a
+                    //cycle.
+                    this.defining = false;
+
+                    if (!silent) {
+                        if (this.defined && !this.defineEmitted) {
+                            this.defineEmitted = true;
+                            this.emit('defined', this.exports);
+                            this.defineEmitComplete = true;
+                        }
+                    }
+                }
+            },
+
+            callPlugin: function () {
+                var map = this.map,
+                    id = map.id,
+                    pluginMap = makeModuleMap(map.prefix, null, false, true);
+
+                on(pluginMap, 'defined', bind(this, function (plugin) {
+                    var load, normalizedMap, normalizedMod,
+                        name = this.map.name,
+                        parentName = this.map.parentMap ? this.map.parentMap.name : null;
+
+                    //If current map is not normalized, wait for that
+                    //normalized name to load instead of continuing.
+                    if (this.map.unnormalized) {
+                        //Normalize the ID if the plugin allows it.
+                        if (plugin.normalize) {
+                            name = plugin.normalize(name, function (name) {
+                                return normalize(name, parentName, true);
+                            }) || '';
+                        }
+
+                        normalizedMap = makeModuleMap(map.prefix + '!' + name,
+                                                      this.map.parentMap,
+                                                      false,
+                                                      true);
+                        on(normalizedMap,
+                            'defined', bind(this, function (value) {
+                                this.init([], function () { return value; }, null, {
+                                    enabled: true,
+                                    ignore: true
+                                });
+                            }));
+                        normalizedMod = registry[normalizedMap.id];
+                        if (normalizedMod) {
+                            if (this.events.error) {
+                                normalizedMod.on('error', bind(this, function (err) {
+                                    this.emit('error', err);
+                                }));
+                            }
+                            normalizedMod.enable();
+                        }
+
+                        return;
+                    }
+
+                    load = bind(this, function (value) {
+                        this.init([], function () { return value; }, null, {
+                            enabled: true
+                        });
+                    });
+
+                    load.error = bind(this, function (err) {
+                        this.inited = true;
+                        this.error = err;
+                        err.requireModules = [id];
+
+                        //Remove temp unnormalized modules for this module,
+                        //since they will never be resolved otherwise now.
+                        eachProp(registry, function (mod) {
+                            if (mod.map.id.indexOf(id + '_unnormalized') === 0) {
+                                removeWaiting(mod.map.id);
+                            }
+                        });
+
+                        onError(err);
+                    });
+
+                    //Allow plugins to load other code without having to know the
+                    //context or how to 'complete' the load.
+                    load.fromText = function (moduleName, text) {
+                        /*jslint evil: true */
+                        var hasInteractive = useInteractive;
+
+                        //Turn off interactive script matching for IE for any define
+                        //calls in the text, then turn it back on at the end.
+                        if (hasInteractive) {
+                            useInteractive = false;
+                        }
+
+                        //Prime the system by creating a module instance for
+                        //it.
+                        getModule(makeModuleMap(moduleName));
+
+                        req.exec(text);
+
+                        if (hasInteractive) {
+                            useInteractive = true;
+                        }
+
+                        //Support anonymous modules.
+                        context.completeLoad(moduleName);
+                    };
+
+                    //Use parentName here since the plugin's name is not reliable,
+                    //could be some weird string with no path that actually wants to
+                    //reference the parentName's path.
+                    plugin.load(map.name, makeRequire(map.parentMap, true, function (deps, cb, er) {
+                        deps.rjsSkipMap = true;
+                        return context.require(deps, cb, er);
+                    }), load, config);
+                }));
+
+                context.enable(pluginMap, this);
+                this.pluginMaps[pluginMap.id] = pluginMap;
+            },
+
+            enable: function () {
+                this.enabled = true;
+
+                if (!this.waitPushed) {
+                    waitAry.push(this);
+                    context.waitCount += 1;
+                    this.waitPushed = true;
+                }
+
+                //Set flag mentioning that the module is enabling,
+                //so that immediate calls to the defined callbacks
+                //for dependencies do not trigger inadvertent load
+                //with the depCount still being zero.
+                this.enabling = true;
+
+                //Enable each dependency
+                each(this.depMaps, bind(this, function (depMap, i) {
+                    var id, mod, handler;
+
+                    if (typeof depMap === 'string') {
+                        //Dependency needs to be converted to a depMap
+                        //and wired up to this module.
+                        depMap = makeModuleMap(depMap,
+                                               (this.map.isDefine ? this.map : this.map.parentMap),
+                                               false,
+                                               !this.depMaps.rjsSkipMap);
+                        this.depMaps[i] = depMap;
+
+                        handler = handlers[depMap.id];
+
+                        if (handler) {
+                            this.depExports[i] = handler(this);
+                            return;
+                        }
+
+                        this.depCount += 1;
+
+                        on(depMap, 'defined', bind(this, function (depExports) {
+                            this.defineDep(i, depExports);
+                            this.check();
+                        }));
+
+                        if (this.errback) {
+                            on(depMap, 'error', this.errback);
+                        }
+                    }
+
+                    id = depMap.id;
+                    mod = registry[id];
+
+                    //Skip special modules like 'require', 'exports', 'module'
+                    //Also, don't call enable if it is already enabled,
+                    //important in circular dependency cases.
+                    if (!handlers[id] && mod && !mod.enabled) {
+                        context.enable(depMap, this);
+                    }
+                }));
+
+                //Enable each plugin that is used in
+                //a dependency
+                eachProp(this.pluginMaps, bind(this, function (pluginMap) {
+                    var mod = registry[pluginMap.id];
+                    if (mod && !mod.enabled) {
+                        context.enable(pluginMap, this);
+                    }
+                }));
+
+                this.enabling = false;
+
+                this.check();
+            },
+
+            on: function (name, cb) {
+                var cbs = this.events[name];
+                if (!cbs) {
+                    cbs = this.events[name] = [];
+                }
+                cbs.push(cb);
+            },
+
+            emit: function (name, evt) {
+                each(this.events[name], function (cb) {
+                    cb(evt);
+                });
+                if (name === 'error') {
+                    //Now that the error handler was triggered, remove
+                    //the listeners, since this broken Module instance
+                    //can stay around for a while in the registry/waitAry.
+                    delete this.events[name];
+                }
+            }
+        };
+
+        function callGetModule(args) {
+            getModule(makeModuleMap(args[0], null, true)).init(args[1], args[2]);
+        }
+
+        function removeListener(node, func, name, ieName) {
+            //Favor detachEvent because of IE9
+            //issue, see attachEvent/addEventListener comment elsewhere
+            //in this file.
+            if (node.detachEvent && !isOpera) {
+                //Probably IE. If not it will throw an error, which will be
+                //useful to know.
+                if (ieName) {
+                    node.detachEvent(ieName, func);
+                }
+            } else {
+                node.removeEventListener(name, func, false);
+            }
+        }
+
+        /**
+         * Given an event from a script node, get the requirejs info from it,
+         * and then removes the event listeners on the node.
+         * @param {Event} evt
+         * @returns {Object}
+         */
+        function getScriptData(evt) {
+            //Using currentTarget instead of target for Firefox 2.0's sake. Not
+            //all old browsers will be supported, but this one was easy enough
+            //to support and still makes sense.
+            var node = evt.currentTarget || evt.srcElement;
+
+            //Remove the listeners once here.
+            removeListener(node, context.onScriptLoad, 'load', 'onreadystatechange');
+            removeListener(node, context.onScriptError, 'error');
+
+            return {
+                node: node,
+                id: node && node.getAttribute('data-requiremodule')
+            };
+        }
+
+        return (context = {
+            config: config,
+            contextName: contextName,
+            registry: registry,
+            defined: defined,
+            urlFetched: urlFetched,
+            waitCount: 0,
+            defQueue: defQueue,
+            Module: Module,
+            makeModuleMap: makeModuleMap,
+
+            /**
+             * Set a configuration for the context.
+             * @param {Object} cfg config object to integrate.
+             */
+            configure: function (cfg) {
+                //Make sure the baseUrl ends in a slash.
+                if (cfg.baseUrl) {
+                    if (cfg.baseUrl.charAt(cfg.baseUrl.length - 1) !== '/') {
+                        cfg.baseUrl += '/';
+                    }
+                }
+
+                //Save off the paths and packages since they require special processing,
+                //they are additive.
+                var pkgs = config.pkgs,
+                    shim = config.shim,
+                    paths = config.paths,
+                    map = config.map;
+
+                //Mix in the config values, favoring the new values over
+                //existing ones in context.config.
+                mixin(config, cfg, true);
+
+                //Merge paths.
+                config.paths = mixin(paths, cfg.paths, true);
+
+                //Merge map
+                if (cfg.map) {
+                    config.map = mixin(map || {}, cfg.map, true, true);
+                }
+
+                //Merge shim
+                if (cfg.shim) {
+                    eachProp(cfg.shim, function (value, id) {
+                        //Normalize the structure
+                        if (isArray(value)) {
+                            value = {
+                                deps: value
+                            };
+                        }
+                        if (value.exports && !value.exports.__buildReady) {
+                            value.exports = context.makeShimExports(value.exports);
+                        }
+                        shim[id] = value;
+                    });
+                    config.shim = shim;
+                }
+
+                //Adjust packages if necessary.
+                if (cfg.packages) {
+                    each(cfg.packages, function (pkgObj) {
+                        var location;
+
+                        pkgObj = typeof pkgObj === 'string' ? { name: pkgObj } : pkgObj;
+                        location = pkgObj.location;
+
+                        //Create a brand new object on pkgs, since currentPackages can
+                        //be passed in again, and config.pkgs is the internal transformed
+                        //state for all package configs.
+                        pkgs[pkgObj.name] = {
+                            name: pkgObj.name,
+                            location: location || pkgObj.name,
+                            //Remove leading dot in main, so main paths are normalized,
+                            //and remove any trailing .js, since different package
+                            //envs have different conventions: some use a module name,
+                            //some use a file name.
+                            main: (pkgObj.main || 'main')
+                                  .replace(currDirRegExp, '')
+                                  .replace(jsSuffixRegExp, '')
+                        };
+                    });
+
+                    //Done with modifications, assing packages back to context config
+                    config.pkgs = pkgs;
+                }
+
+                //If there are any "waiting to execute" modules in the registry,
+                //update the maps for them, since their info, like URLs to load,
+                //may have changed.
+                eachProp(registry, function (mod, id) {
+                    //If module already has init called, since it is too
+                    //late to modify them, and ignore unnormalized ones
+                    //since they are transient.
+                    if (!mod.inited && !mod.map.unnormalized) {
+                        mod.map = makeModuleMap(id);
+                    }
+                });
+
+                //If a deps array or a config callback is specified, then call
+                //require with those args. This is useful when require is defined as a
+                //config object before require.js is loaded.
+                if (cfg.deps || cfg.callback) {
+                    context.require(cfg.deps || [], cfg.callback);
+                }
+            },
+
+            makeShimExports: function (exports) {
+                var func;
+                if (typeof exports === 'string') {
+                    func = function () {
+                        return getGlobal(exports);
+                    };
+                    //Save the exports for use in nodefine checking.
+                    func.exports = exports;
+                    return func;
+                } else {
+                    return function () {
+                        return exports.apply(global, arguments);
+                    };
+                }
+            },
+
+            requireDefined: function (id, relMap) {
+                return hasProp(defined, makeModuleMap(id, relMap, false, true).id);
+            },
+
+            requireSpecified: function (id, relMap) {
+                id = makeModuleMap(id, relMap, false, true).id;
+                return hasProp(defined, id) || hasProp(registry, id);
+            },
+
+            require: function (deps, callback, errback, relMap) {
+                var moduleName, id, map, requireMod, args;
+                if (typeof deps === 'string') {
+                    if (isFunction(callback)) {
+                        //Invalid call
+                        return onError(makeError('requireargs', 'Invalid require call'), errback);
+                    }
+
+                    //Synchronous access to one module. If require.get is
+                    //available (as in the Node adapter), prefer that.
+                    //In this case deps is the moduleName and callback is
+                    //the relMap
+                    if (req.get) {
+                        return req.get(context, deps, callback);
+                    }
+
+                    //Just return the module wanted. In this scenario, the
+                    //second arg (if passed) is just the relMap.
+                    moduleName = deps;
+                    relMap = callback;
+
+                    //Normalize module name, if it contains . or ..
+                    map = makeModuleMap(moduleName, relMap, false, true);
+                    id = map.id;
+
+                    if (!hasProp(defined, id)) {
+                        return onError(makeError('notloaded', 'Module name "' +
+                                    id +
+                                    '" has not been loaded yet for context: ' +
+                                    contextName));
+                    }
+                    return defined[id];
+                }
+
+                //Callback require. Normalize args. if callback or errback is
+                //not a function, it means it is a relMap. Test errback first.
+                if (errback && !isFunction(errback)) {
+                    relMap = errback;
+                    errback = undefined;
+                }
+                if (callback && !isFunction(callback)) {
+                    relMap = callback;
+                    callback = undefined;
+                }
+
+                //Any defined modules in the global queue, intake them now.
+                takeGlobalQueue();
+
+                //Make sure any remaining defQueue items get properly processed.
+                while (defQueue.length) {
+                    args = defQueue.shift();
+                    if (args[0] === null) {
+                        return onError(makeError('mismatch', 'Mismatched anonymous define() module: ' + args[args.length - 1]));
+                    } else {
+                        //args are id, deps, factory. Should be normalized by the
+                        //define() function.
+                        callGetModule(args);
+                    }
+                }
+
+                //Mark all the dependencies as needing to be loaded.
+                requireMod = getModule(makeModuleMap(null, relMap));
+
+                requireMod.init(deps, callback, errback, {
+                    enabled: true
+                });
+
+                checkLoaded();
+
+                return context.require;
+            },
+
+            undef: function (id) {
+                //Bind any waiting define() calls to this context,
+                //fix for #408
+                takeGlobalQueue();
+
+                var map = makeModuleMap(id, null, true),
+                    mod = registry[id];
+
+                delete defined[id];
+                delete urlFetched[map.url];
+                delete undefEvents[id];
+
+                if (mod) {
+                    //Hold on to listeners in case the
+                    //module will be attempted to be reloaded
+                    //using a different config.
+                    if (mod.events.defined) {
+                        undefEvents[id] = mod.events;
+                    }
+
+                    removeWaiting(id);
+                }
+            },
+
+            /**
+             * Called to enable a module if it is still in the registry
+             * awaiting enablement. parent module is passed in for context,
+             * used by the optimizer.
+             */
+            enable: function (depMap, parent) {
+                var mod = registry[depMap.id];
+                if (mod) {
+                    getModule(depMap).enable();
+                }
+            },
+
+            /**
+             * Internal method used by environment adapters to complete a load event.
+             * A load event could be a script load or just a load pass from a synchronous
+             * load call.
+             * @param {String} moduleName the name of the module to potentially complete.
+             */
+            completeLoad: function (moduleName) {
+                var found, args, mod,
+                    shim = config.shim[moduleName] || {},
+                    shExports = shim.exports && shim.exports.exports;
+
+                takeGlobalQueue();
+
+                while (defQueue.length) {
+                    args = defQueue.shift();
+                    if (args[0] === null) {
+                        args[0] = moduleName;
+                        //If already found an anonymous module and bound it
+                        //to this name, then this is some other anon module
+                        //waiting for its completeLoad to fire.
+                        if (found) {
+                            break;
+                        }
+                        found = true;
+                    } else if (args[0] === moduleName) {
+                        //Found matching define call for this script!
+                        found = true;
+                    }
+
+                    callGetModule(args);
+                }
+
+                //Do this after the cycle of callGetModule in case the result
+                //of those calls/init calls changes the registry.
+                mod = registry[moduleName];
+
+                if (!found && !defined[moduleName] && mod && !mod.inited) {
+                    if (config.enforceDefine && (!shExports || !getGlobal(shExports))) {
+                        if (hasPathFallback(moduleName)) {
+                            return;
+                        } else {
+                            return onError(makeError('nodefine',
+                                             'No define call for ' + moduleName,
+                                             null,
+                                             [moduleName]));
+                        }
+                    } else {
+                        //A script that does not call define(), so just simulate
+                        //the call for it.
+                        callGetModule([moduleName, (shim.deps || []), shim.exports]);
+                    }
+                }
+
+                checkLoaded();
+            },
+
+            /**
+             * Converts a module name + .extension into an URL path.
+             * *Requires* the use of a module name. It does not support using
+             * plain URLs like nameToUrl.
+             */
+            toUrl: function (moduleNamePlusExt, relModuleMap) {
+                var index = moduleNamePlusExt.lastIndexOf('.'),
+                    ext = null;
+
+                if (index !== -1) {
+                    ext = moduleNamePlusExt.substring(index, moduleNamePlusExt.length);
+                    moduleNamePlusExt = moduleNamePlusExt.substring(0, index);
+                }
+
+                return context.nameToUrl(normalize(moduleNamePlusExt, relModuleMap && relModuleMap.id, true),
+                                         ext);
+            },
+
+            /**
+             * Converts a module name to a file path. Supports cases where
+             * moduleName may actually be just an URL.
+             * Note that it **does not** call normalize on the moduleName,
+             * it is assumed to have already been normalized. This is an
+             * internal API, not a public one. Use toUrl for the public API.
+             */
+            nameToUrl: function (moduleName, ext) {
+                var paths, pkgs, pkg, pkgPath, syms, i, parentModule, url,
+                    parentPath;
+
+                //If a colon is in the URL, it indicates a protocol is used and it is just
+                //an URL to a file, or if it starts with a slash, contains a query arg (i.e. ?)
+                //or ends with .js, then assume the user meant to use an url and not a module id.
+                //The slash is important for protocol-less URLs as well as full paths.
+                if (req.jsExtRegExp.test(moduleName)) {
+                    //Just a plain path, not module name lookup, so just return it.
+                    //Add extension if it is included. This is a bit wonky, only non-.js things pass
+                    //an extension, this method probably needs to be reworked.
+                    url = moduleName + (ext || '');
+                } else {
+                    //A module that needs to be converted to a path.
+                    paths = config.paths;
+                    pkgs = config.pkgs;
+
+                    syms = moduleName.split('/');
+                    //For each module name segment, see if there is a path
+                    //registered for it. Start with most specific name
+                    //and work up from it.
+                    for (i = syms.length; i > 0; i -= 1) {
+                        parentModule = syms.slice(0, i).join('/');
+                        pkg = pkgs[parentModule];
+                        parentPath = paths[parentModule];
+                        if (parentPath) {
+                            //If an array, it means there are a few choices,
+                            //Choose the one that is desired
+                            if (isArray(parentPath)) {
+                                parentPath = parentPath[0];
+                            }
+                            syms.splice(0, i, parentPath);
+                            break;
+                        } else if (pkg) {
+                            //If module name is just the package name, then looking
+                            //for the main module.
+                            if (moduleName === pkg.name) {
+                                pkgPath = pkg.location + '/' + pkg.main;
+                            } else {
+                                pkgPath = pkg.location;
+                            }
+                            syms.splice(0, i, pkgPath);
+                            break;
+                        }
+                    }
+
+                    //Join the path parts together, then figure out if baseUrl is needed.
+                    url = syms.join('/');
+                    url += (ext || (/\?/.test(url) ? '' : '.js'));
+                    url = (url.charAt(0) === '/' || url.match(/^[\w\+\.\-]+:/) ? '' : config.baseUrl) + url;
+                }
+
+                return config.urlArgs ? url +
+                                        ((url.indexOf('?') === -1 ? '?' : '&') +
+                                         config.urlArgs) : url;
+            },
+
+            //Delegates to req.load. Broken out as a separate function to
+            //allow overriding in the optimizer.
+            load: function (id, url) {
+                req.load(context, id, url);
+            },
+
+            /**
+             * Executes a module callack function. Broken out as a separate function
+             * solely to allow the build system to sequence the files in the built
+             * layer in the right sequence.
+             *
+             * @private
+             */
+            execCb: function (name, callback, args, exports) {
+                return callback.apply(exports, args);
+            },
+
+            /**
+             * callback for script loads, used to check status of loading.
+             *
+             * @param {Event} evt the event from the browser for the script
+             * that was loaded.
+             */
+            onScriptLoad: function (evt) {
+                //Using currentTarget instead of target for Firefox 2.0's sake. Not
+                //all old browsers will be supported, but this one was easy enough
+                //to support and still makes sense.
+                if (evt.type === 'load' ||
+                        (readyRegExp.test((evt.currentTarget || evt.srcElement).readyState))) {
+                    //Reset interactive script so a script node is not held onto for
+                    //to long.
+                    interactiveScript = null;
+
+                    //Pull out the name of the module and the context.
+                    var data = getScriptData(evt);
+                    context.completeLoad(data.id);
+                }
+            },
+
+            /**
+             * Callback for script errors.
+             */
+            onScriptError: function (evt) {
+                var data = getScriptData(evt);
+                if (!hasPathFallback(data.id)) {
+                    return onError(makeError('scripterror', 'Script error', evt, [data.id]));
+                }
+            }
+        });
+    }
+
+    /**
+     * Main entry point.
+     *
+     * If the only argument to require is a string, then the module that
+     * is represented by that string is fetched for the appropriate context.
+     *
+     * If the first argument is an array, then it will be treated as an array
+     * of dependency string names to fetch. An optional function callback can
+     * be specified to execute when all of those dependencies are available.
+     *
+     * Make a local req variable to help Caja compliance (it assumes things
+     * on a require that are not standardized), and to give a short
+     * name for minification/local scope use.
+     */
+    req = requirejs = function (deps, callback, errback, optional) {
+
+        //Find the right context, use default
+        var context, config,
+            contextName = defContextName;
+
+        // Determine if have config object in the call.
+        if (!isArray(deps) && typeof deps !== 'string') {
+            // deps is a config object
+            config = deps;
+            if (isArray(callback)) {
+                // Adjust args if there are dependencies
+                deps = callback;
+                callback = errback;
+                errback = optional;
+            } else {
+                deps = [];
+            }
+        }
+
+        if (config && config.context) {
+            contextName = config.context;
+        }
+
+        context = contexts[contextName];
+        if (!context) {
+            context = contexts[contextName] = req.s.newContext(contextName);
+        }
+
+        if (config) {
+            context.configure(config);
+        }
+
+        return context.require(deps, callback, errback);
+    };
+
+    /**
+     * Support require.config() to make it easier to cooperate with other
+     * AMD loaders on globally agreed names.
+     */
+    req.config = function (config) {
+        return req(config);
+    };
+
+    /**
+     * Export require as a global, but only if it does not already exist.
+     */
+    if (!require) {
+        require = req;
+    }
+
+    req.version = version;
+
+    //Used to filter out dependencies that are already paths.
+    req.jsExtRegExp = /^\/|:|\?|\.js$/;
+    req.isBrowser = isBrowser;
+    s = req.s = {
+        contexts: contexts,
+        newContext: newContext
+    };
+
+    //Create default context.
+    req({});
+
+    //Exports some context-sensitive methods on global require, using
+    //default context if no context specified.
+    addRequireMethods(req);
+
+    if (isBrowser) {
+        head = s.head = document.getElementsByTagName('head')[0];
+        //If BASE tag is in play, using appendChild is a problem for IE6.
+        //When that browser dies, this can be removed. Details in this jQuery bug:
+        //http://dev.jquery.com/ticket/2709
+        baseElement = document.getElementsByTagName('base')[0];
+        if (baseElement) {
+            head = s.head = baseElement.parentNode;
+        }
+    }
+
+    /**
+     * Any errors that require explicitly generates will be passed to this
+     * function. Intercept/override it if you want custom error handling.
+     * @param {Error} err the error object.
+     */
+    req.onError = function (err) {
+        throw err;
+    };
+
+    /**
+     * Does the request to load a module for the browser case.
+     * Make this a separate function to allow other environments
+     * to override it.
+     *
+     * @param {Object} context the require context to find state.
+     * @param {String} moduleName the name of the module.
+     * @param {Object} url the URL to the module.
+     */
+    req.load = function (context, moduleName, url) {
+        var config = (context && context.config) || {},
+            node;
+        if (isBrowser) {
+            //In the browser so use a script tag
+            node = config.xhtml ?
+                    document.createElementNS('http://www.w3.org/1999/xhtml', 'html:script') :
+                    document.createElement('script');
+            node.type = config.scriptType || 'text/javascript';
+            node.charset = 'utf-8';
+            node.async = true;
+
+            node.setAttribute('data-requirecontext', context.contextName);
+            node.setAttribute('data-requiremodule', moduleName);
+
+            //Set up load listener. Test attachEvent first because IE9 has
+            //a subtle issue in its addEventListener and script onload firings
+            //that do not match the behavior of all other browsers with
+            //addEventListener support, which fire the onload event for a
+            //script right after the script execution. See:
+            //https://connect.microsoft.com/IE/feedback/details/648057/script-onload-event-is-not-fired-immediately-after-script-execution
+            //UNFORTUNATELY Opera implements attachEvent but does not follow the script
+            //script execution mode.
+            if (node.attachEvent &&
+                    //Check if node.attachEvent is artificially added by custom script or
+                    //natively supported by browser
+                    //read https://github.com/jrburke/requirejs/issues/187
+                    //if we can NOT find [native code] then it must NOT natively supported.
+                    //in IE8, node.attachEvent does not have toString()
+                    //Note the test for "[native code" with no closing brace, see:
+                    //https://github.com/jrburke/requirejs/issues/273
+                    !(node.attachEvent.toString && node.attachEvent.toString().indexOf('[native code') < 0) &&
+                    !isOpera) {
+                //Probably IE. IE (at least 6-8) do not fire
+                //script onload right after executing the script, so
+                //we cannot tie the anonymous define call to a name.
+                //However, IE reports the script as being in 'interactive'
+                //readyState at the time of the define call.
+                useInteractive = true;
+
+                node.attachEvent('onreadystatechange', context.onScriptLoad);
+                //It would be great to add an error handler here to catch
+                //404s in IE9+. However, onreadystatechange will fire before
+                //the error handler, so that does not help. If addEvenListener
+                //is used, then IE will fire error before load, but we cannot
+                //use that pathway given the connect.microsoft.com issue
+                //mentioned above about not doing the 'script execute,
+                //then fire the script load event listener before execute
+                //next script' that other browsers do.
+                //Best hope: IE10 fixes the issues,
+                //and then destroys all installs of IE 6-9.
+                //node.attachEvent('onerror', context.onScriptError);
+            } else {
+                node.addEventListener('load', context.onScriptLoad, false);
+                node.addEventListener('error', context.onScriptError, false);
+            }
+            node.src = url;
+
+            //For some cache cases in IE 6-8, the script executes before the end
+            //of the appendChild execution, so to tie an anonymous define
+            //call to the module name (which is stored on the node), hold on
+            //to a reference to this node, but clear after the DOM insertion.
+            currentlyAddingScript = node;
+            if (baseElement) {
+                head.insertBefore(node, baseElement);
+            } else {
+                head.appendChild(node);
+            }
+            currentlyAddingScript = null;
+
+            return node;
+        } else if (isWebWorker) {
+            //In a web worker, use importScripts. This is not a very
+            //efficient use of importScripts, importScripts will block until
+            //its script is downloaded and evaluated. However, if web workers
+            //are in play, the expectation that a build has been done so that
+            //only one script needs to be loaded anyway. This may need to be
+            //reevaluated if other use cases become common.
+            importScripts(url);
+
+            //Account for anonymous modules
+            context.completeLoad(moduleName);
+        }
+    };
+
+    function getInteractiveScript() {
+        if (interactiveScript && interactiveScript.readyState === 'interactive') {
+            return interactiveScript;
+        }
+
+        eachReverse(scripts(), function (script) {
+            if (script.readyState === 'interactive') {
+                return (interactiveScript = script);
+            }
+        });
+        return interactiveScript;
+    }
+
+    //Look for a data-main script attribute, which could also adjust the baseUrl.
+    if (isBrowser) {
+        //Figure out baseUrl. Get it from the script tag with require.js in it.
+        eachReverse(scripts(), function (script) {
+            //Set the 'head' where we can append children by
+            //using the script's parent.
+            if (!head) {
+                head = script.parentNode;
+            }
+
+            //Look for a data-main attribute to set main script for the page
+            //to load. If it is there, the path to data main becomes the
+            //baseUrl, if it is not already set.
+            dataMain = script.getAttribute('data-main');
+            if (dataMain) {
+                //Set final baseUrl if there is not already an explicit one.
+                if (!cfg.baseUrl) {
+                    //Pull off the directory of data-main for use as the
+                    //baseUrl.
+                    src = dataMain.split('/');
+                    mainScript = src.pop();
+                    subPath = src.length ? src.join('/')  + '/' : './';
+
+                    cfg.baseUrl = subPath;
+                    dataMain = mainScript;
+                }
+
+                //Strip off any trailing .js since dataMain is now
+                //like a module name.
+                dataMain = dataMain.replace(jsSuffixRegExp, '');
+
+                //Put the data-main script in the files to load.
+                cfg.deps = cfg.deps ? cfg.deps.concat(dataMain) : [dataMain];
+
+                return true;
+            }
+        });
+    }
+
+    /**
+     * The function that handles definitions of modules. Differs from
+     * require() in that a string for the module should be the first argument,
+     * and the function to execute after dependencies are loaded should
+     * return a value to define the module corresponding to the first argument's
+     * name.
+     */
+    define = function (name, deps, callback) {
+        var node, context;
+
+        //Allow for anonymous functions
+        if (typeof name !== 'string') {
+            //Adjust args appropriately
+            callback = deps;
+            deps = name;
+            name = null;
+        }
+
+        //This module may not have dependencies
+        if (!isArray(deps)) {
+            callback = deps;
+            deps = [];
+        }
+
+        //If no name, and callback is a function, then figure out if it a
+        //CommonJS thing with dependencies.
+        if (!deps.length && isFunction(callback)) {
+            //Remove comments from the callback string,
+            //look for require calls, and pull them into the dependencies,
+            //but only if there are function args.
+            if (callback.length) {
+                callback
+                    .toString()
+                    .replace(commentRegExp, '')
+                    .replace(cjsRequireRegExp, function (match, dep) {
+                        deps.push(dep);
+                    });
+
+                //May be a CommonJS thing even without require calls, but still
+                //could use exports, and module. Avoid doing exports and module
+                //work though if it just needs require.
+                //REQUIRES the function to expect the CommonJS variables in the
+                //order listed below.
+                deps = (callback.length === 1 ? ['require'] : ['require', 'exports', 'module']).concat(deps);
+            }
+        }
+
+        //If in IE 6-8 and hit an anonymous define() call, do the interactive
+        //work.
+        if (useInteractive) {
+            node = currentlyAddingScript || getInteractiveScript();
+            if (node) {
+                if (!name) {
+                    name = node.getAttribute('data-requiremodule');
+                }
+                context = contexts[node.getAttribute('data-requirecontext')];
+            }
+        }
+
+        //Always save off evaluating the def call until the script onload handler.
+        //This allows multiple modules to be in a file without prematurely
+        //tracing dependencies, and allows for anonymous module support,
+        //where the module name is not known until the script onload event
+        //occurs. If no context, use the global queue, and get it processed
+        //in the onscript load callback.
+        (context ? context.defQueue : globalDefQueue).push([name, deps, callback]);
+    };
+
+    define.amd = {
+        jQuery: true
+    };
+
+
+    /**
+     * Executes the text. Normally just uses eval, but can be modified
+     * to use a better, environment-specific call. Only used for transpiling
+     * loader plugins, not for plain JS modules.
+     * @param {String} text the text to execute/evaluate.
+     */
+    req.exec = function (text) {
+        /*jslint evil: true */
+        return eval(text);
+    };
+
+    //Set up with config info.
+    req(cfg);
+}(this));
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/LICENSE.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/LICENSE.md
new file mode 100644
index 0000000..cb518af7
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/LICENSE.md
@@ -0,0 +1,2 @@
+This software is dedicated to the public domain. No warranty is expressed or implied.
+Use this software at your own risk.
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/README.md b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/README.md
new file mode 100644
index 0000000..d6ada56
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/README.md
@@ -0,0 +1,7 @@
+classList.js is a cross-browser JavaScript shim that fully implements `element.classList`. Refer to [the MDN page on `element.classList`][1] for more information.
+
+
+![Tracking image](//in.getclicky.com/212712ns.gif)
+
+
+  [1]: https://developer.mozilla.org/en/DOM/element.classList "MDN / DOM / element.classList"
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/classList.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/classList.js
new file mode 100644
index 0000000..0cf197c
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/classList.js
@@ -0,0 +1,138 @@
+
+/*
+ * classList.js: Cross-browser full element.classList implementation.
+ * 2011-06-15
+ *
+ * By Eli Grey, http://eligrey.com
+ * Public Domain.
+ * NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.
+ */
+
+/*global self, document, DOMException */
+
+/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
+
+if (typeof document !== "undefined" && !("classList" in document.createElement("a"))) {
+
+(function (view) {
+
+"use strict";
+
+var
+	  classListProp = "classList"
+	, protoProp = "prototype"
+	, elemCtrProto = (view.HTMLElement || view.Element)[protoProp]
+	, objCtr = Object
+	, strTrim = String[protoProp].trim || function () {
+		return this.replace(/^\s+|\s+$/g, "");
+	}
+	, arrIndexOf = Array[protoProp].indexOf || function (item) {
+		var
+			  i = 0
+			, len = this.length
+		;
+		for (; i < len; i++) {
+			if (i in this && this[i] === item) {
+				return i;
+			}
+		}
+		return -1;
+	}
+	// Vendors: please allow content code to instantiate DOMExceptions
+	, DOMEx = function (type, message) {
+		this.name = type;
+		this.code = DOMException[type];
+		this.message = message;
+	}
+	, checkTokenAndGetIndex = function (classList, token) {
+		if (token === "") {
+			throw new DOMEx(
+				  "SYNTAX_ERR"
+				, "An invalid or illegal string was specified"
+			);
+		}
+		if (/\s/.test(token)) {
+			throw new DOMEx(
+				  "INVALID_CHARACTER_ERR"
+				, "String contains an invalid character"
+			);
+		}
+		return arrIndexOf.call(classList, token);
+	}
+	, ClassList = function (elem) {
+		var
+			  trimmedClasses = strTrim.call(elem.className)
+			, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
+			, i = 0
+			, len = classes.length
+		;
+		for (; i < len; i++) {
+			this.push(classes[i]);
+		}
+		this._updateClassName = function () {
+			elem.className = this.toString();
+		};
+	}
+	, classListProto = ClassList[protoProp] = []
+	, classListGetter = function () {
+		return new ClassList(this);
+	}
+;
+// Most DOMException implementations don't allow calling DOMException's toString()
+// on non-DOMExceptions. Error's toString() is sufficient here.
+DOMEx[protoProp] = Error[protoProp];
+classListProto.item = function (i) {
+	return this[i] || null;
+};
+classListProto.contains = function (token) {
+	token += "";
+	return checkTokenAndGetIndex(this, token) !== -1;
+};
+classListProto.add = function (token) {
+	token += "";
+	if (checkTokenAndGetIndex(this, token) === -1) {
+		this.push(token);
+		this._updateClassName();
+	}
+};
+classListProto.remove = function (token) {
+	token += "";
+	var index = checkTokenAndGetIndex(this, token);
+	if (index !== -1) {
+		this.splice(index, 1);
+		this._updateClassName();
+	}
+};
+classListProto.toggle = function (token) {
+	token += "";
+	if (checkTokenAndGetIndex(this, token) === -1) {
+		this.add(token);
+	} else {
+		this.remove(token);
+	}
+};
+classListProto.toString = function () {
+	return this.join(" ");
+};
+
+if (objCtr.defineProperty) {
+	var classListPropDesc = {
+		  get: classListGetter
+		, enumerable: true
+		, configurable: true
+	};
+	try {
+		objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+	} catch (ex) { // IE 8 doesn't support enumerable:true
+		if (ex.number === -0x7FF5EC54) {
+			classListPropDesc.enumerable = false;
+			objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
+		}
+	}
+} else if (objCtr[protoProp].__defineGetter__) {
+	elemCtrProto.__defineGetter__(classListProp, classListGetter);
+}
+
+}(self));
+
+}
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/classList.min.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/classList.min.js
new file mode 100644
index 0000000..44f2b4c
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/classList.js/classList.min.js
@@ -0,0 +1,2 @@
+/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js*/
+if(typeof document!=="undefined"&&!("classList" in document.createElement("a"))){(function(j){var a="classList",f="prototype",m=(j.HTMLElement||j.Element)[f],b=Object,k=String[f].trim||function(){return this.replace(/^\s+|\s+$/g,"")},c=Array[f].indexOf||function(q){var p=0,o=this.length;for(;p<o;p++){if(p in this&&this[p]===q){return p}}return -1},n=function(o,p){this.name=o;this.code=DOMException[o];this.message=p},g=function(p,o){if(o===""){throw new n("SYNTAX_ERR","An invalid or illegal string was specified")}if(/\s/.test(o)){throw new n("INVALID_CHARACTER_ERR","String contains an invalid character")}return c.call(p,o)},d=function(s){var r=k.call(s.className),q=r?r.split(/\s+/):[],p=0,o=q.length;for(;p<o;p++){this.push(q[p])}this._updateClassName=function(){s.className=this.toString()}},e=d[f]=[],i=function(){return new d(this)};n[f]=Error[f];e.item=function(o){return this[o]||null};e.contains=function(o){o+="";return g(this,o)!==-1};e.add=function(o){o+="";if(g(this,o)===-1){this.push(o);this._updateClassName()}};e.remove=function(p){p+="";var o=g(this,p);if(o!==-1){this.splice(o,1);this._updateClassName()}};e.toggle=function(o){o+="";if(g(this,o)===-1){this.add(o)}else{this.remove(o)}};e.toString=function(){return this.join(" ")};if(b.defineProperty){var l={get:i,enumerable:true,configurable:true};try{b.defineProperty(m,a,l)}catch(h){if(h.number===-2146823252){l.enumerable=false;b.defineProperty(m,a,l)}}}else{if(b[f].__defineGetter__){m.__defineGetter__(a,i)}}}(self))};
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-core.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-core.js
new file mode 100644
index 0000000..8549d10
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-core.js
@@ -0,0 +1,94 @@
+/*
+ Rangy, a cross-browser JavaScript range and selection library
+ http://code.google.com/p/rangy/
+
+ Copyright 2012, Tim Down
+ Licensed under the MIT license.
+ Version: 1.2.3
+ Build date: 26 February 2012
+*/
+window.rangy=function(){function l(p,u){var w=typeof p[u];return w=="function"||!!(w=="object"&&p[u])||w=="unknown"}function K(p,u){return!!(typeof p[u]=="object"&&p[u])}function H(p,u){return typeof p[u]!="undefined"}function I(p){return function(u,w){for(var B=w.length;B--;)if(!p(u,w[B]))return false;return true}}function z(p){return p&&A(p,x)&&v(p,t)}function C(p){window.alert("Rangy not supported in your browser. Reason: "+p);c.initialized=true;c.supported=false}function N(){if(!c.initialized){var p,
+u=false,w=false;if(l(document,"createRange")){p=document.createRange();if(A(p,n)&&v(p,i))u=true;p.detach()}if((p=K(document,"body")?document.body:document.getElementsByTagName("body")[0])&&l(p,"createTextRange")){p=p.createTextRange();if(z(p))w=true}!u&&!w&&C("Neither Range nor TextRange are implemented");c.initialized=true;c.features={implementsDomRange:u,implementsTextRange:w};u=k.concat(f);w=0;for(p=u.length;w<p;++w)try{u[w](c)}catch(B){K(window,"console")&&l(window.console,"log")&&window.console.log("Init listener threw an exception. Continuing.",
+B)}}}function O(p){this.name=p;this.supported=this.initialized=false}var i=["startContainer","startOffset","endContainer","endOffset","collapsed","commonAncestorContainer","START_TO_START","START_TO_END","END_TO_START","END_TO_END"],n=["setStart","setStartBefore","setStartAfter","setEnd","setEndBefore","setEndAfter","collapse","selectNode","selectNodeContents","compareBoundaryPoints","deleteContents","extractContents","cloneContents","insertNode","surroundContents","cloneRange","toString","detach"],
+t=["boundingHeight","boundingLeft","boundingTop","boundingWidth","htmlText","text"],x=["collapse","compareEndPoints","duplicate","getBookmark","moveToBookmark","moveToElementText","parentElement","pasteHTML","select","setEndPoint","getBoundingClientRect"],A=I(l),q=I(K),v=I(H),c={version:"1.2.3",initialized:false,supported:true,util:{isHostMethod:l,isHostObject:K,isHostProperty:H,areHostMethods:A,areHostObjects:q,areHostProperties:v,isTextRange:z},features:{},modules:{},config:{alertOnWarn:false,preferTextRange:false}};
+c.fail=C;c.warn=function(p){p="Rangy warning: "+p;if(c.config.alertOnWarn)window.alert(p);else typeof window.console!="undefined"&&typeof window.console.log!="undefined"&&window.console.log(p)};if({}.hasOwnProperty)c.util.extend=function(p,u){for(var w in u)if(u.hasOwnProperty(w))p[w]=u[w]};else C("hasOwnProperty not supported");var f=[],k=[];c.init=N;c.addInitListener=function(p){c.initialized?p(c):f.push(p)};var r=[];c.addCreateMissingNativeApiListener=function(p){r.push(p)};c.createMissingNativeApi=
+function(p){p=p||window;N();for(var u=0,w=r.length;u<w;++u)r[u](p)};O.prototype.fail=function(p){this.initialized=true;this.supported=false;throw Error("Module '"+this.name+"' failed to load: "+p);};O.prototype.warn=function(p){c.warn("Module "+this.name+": "+p)};O.prototype.createError=function(p){return Error("Error in Rangy "+this.name+" module: "+p)};c.createModule=function(p,u){var w=new O(p);c.modules[p]=w;k.push(function(B){u(B,w);w.initialized=true;w.supported=true})};c.requireModules=function(p){for(var u=
+0,w=p.length,B,V;u<w;++u){V=p[u];B=c.modules[V];if(!B||!(B instanceof O))throw Error("Module '"+V+"' not found");if(!B.supported)throw Error("Module '"+V+"' not supported");}};var L=false;q=function(){if(!L){L=true;c.initialized||N()}};if(typeof window=="undefined")C("No window found");else if(typeof document=="undefined")C("No document found");else{l(document,"addEventListener")&&document.addEventListener("DOMContentLoaded",q,false);if(l(window,"addEventListener"))window.addEventListener("load",
+q,false);else l(window,"attachEvent")?window.attachEvent("onload",q):C("Window does not have required addEventListener or attachEvent method");return c}}();
+rangy.createModule("DomUtil",function(l,K){function H(c){for(var f=0;c=c.previousSibling;)f++;return f}function I(c,f){var k=[],r;for(r=c;r;r=r.parentNode)k.push(r);for(r=f;r;r=r.parentNode)if(v(k,r))return r;return null}function z(c,f,k){for(k=k?c:c.parentNode;k;){c=k.parentNode;if(c===f)return k;k=c}return null}function C(c){c=c.nodeType;return c==3||c==4||c==8}function N(c,f){var k=f.nextSibling,r=f.parentNode;k?r.insertBefore(c,k):r.appendChild(c);return c}function O(c){if(c.nodeType==9)return c;
+else if(typeof c.ownerDocument!="undefined")return c.ownerDocument;else if(typeof c.document!="undefined")return c.document;else if(c.parentNode)return O(c.parentNode);else throw Error("getDocument: no document found for node");}function i(c){if(!c)return"[No node]";return C(c)?'"'+c.data+'"':c.nodeType==1?"<"+c.nodeName+(c.id?' id="'+c.id+'"':"")+">["+c.childNodes.length+"]":c.nodeName}function n(c){this._next=this.root=c}function t(c,f){this.node=c;this.offset=f}function x(c){this.code=this[c];
+this.codeName=c;this.message="DOMException: "+this.codeName}var A=l.util;A.areHostMethods(document,["createDocumentFragment","createElement","createTextNode"])||K.fail("document missing a Node creation method");A.isHostMethod(document,"getElementsByTagName")||K.fail("document missing getElementsByTagName method");var q=document.createElement("div");A.areHostMethods(q,["insertBefore","appendChild","cloneNode"])||K.fail("Incomplete Element implementation");A.isHostProperty(q,"innerHTML")||K.fail("Element is missing innerHTML property");
+q=document.createTextNode("test");A.areHostMethods(q,["splitText","deleteData","insertData","appendData","cloneNode"])||K.fail("Incomplete Text Node implementation");var v=function(c,f){for(var k=c.length;k--;)if(c[k]===f)return true;return false};n.prototype={_current:null,hasNext:function(){return!!this._next},next:function(){var c=this._current=this._next,f;if(this._current)if(f=c.firstChild)this._next=f;else{for(f=null;c!==this.root&&!(f=c.nextSibling);)c=c.parentNode;this._next=f}return this._current},
+detach:function(){this._current=this._next=this.root=null}};t.prototype={equals:function(c){return this.node===c.node&this.offset==c.offset},inspect:function(){return"[DomPosition("+i(this.node)+":"+this.offset+")]"}};x.prototype={INDEX_SIZE_ERR:1,HIERARCHY_REQUEST_ERR:3,WRONG_DOCUMENT_ERR:4,NO_MODIFICATION_ALLOWED_ERR:7,NOT_FOUND_ERR:8,NOT_SUPPORTED_ERR:9,INVALID_STATE_ERR:11};x.prototype.toString=function(){return this.message};l.dom={arrayContains:v,isHtmlNamespace:function(c){var f;return typeof c.namespaceURI==
+"undefined"||(f=c.namespaceURI)===null||f=="http://www.w3.org/1999/xhtml"},parentElement:function(c){c=c.parentNode;return c.nodeType==1?c:null},getNodeIndex:H,getNodeLength:function(c){var f;return C(c)?c.length:(f=c.childNodes)?f.length:0},getCommonAncestor:I,isAncestorOf:function(c,f,k){for(f=k?f:f.parentNode;f;)if(f===c)return true;else f=f.parentNode;return false},getClosestAncestorIn:z,isCharacterDataNode:C,insertAfter:N,splitDataNode:function(c,f){var k=c.cloneNode(false);k.deleteData(0,f);
+c.deleteData(f,c.length-f);N(k,c);return k},getDocument:O,getWindow:function(c){c=O(c);if(typeof c.defaultView!="undefined")return c.defaultView;else if(typeof c.parentWindow!="undefined")return c.parentWindow;else throw Error("Cannot get a window object for node");},getIframeWindow:function(c){if(typeof c.contentWindow!="undefined")return c.contentWindow;else if(typeof c.contentDocument!="undefined")return c.contentDocument.defaultView;else throw Error("getIframeWindow: No Window object found for iframe element");
+},getIframeDocument:function(c){if(typeof c.contentDocument!="undefined")return c.contentDocument;else if(typeof c.contentWindow!="undefined")return c.contentWindow.document;else throw Error("getIframeWindow: No Document object found for iframe element");},getBody:function(c){return A.isHostObject(c,"body")?c.body:c.getElementsByTagName("body")[0]},getRootContainer:function(c){for(var f;f=c.parentNode;)c=f;return c},comparePoints:function(c,f,k,r){var L;if(c==k)return f===r?0:f<r?-1:1;else if(L=z(k,
+c,true))return f<=H(L)?-1:1;else if(L=z(c,k,true))return H(L)<r?-1:1;else{f=I(c,k);c=c===f?f:z(c,f,true);k=k===f?f:z(k,f,true);if(c===k)throw Error("comparePoints got to case 4 and childA and childB are the same!");else{for(f=f.firstChild;f;){if(f===c)return-1;else if(f===k)return 1;f=f.nextSibling}throw Error("Should not be here!");}}},inspectNode:i,fragmentFromNodeChildren:function(c){for(var f=O(c).createDocumentFragment(),k;k=c.firstChild;)f.appendChild(k);return f},createIterator:function(c){return new n(c)},
+DomPosition:t};l.DOMException=x});
+rangy.createModule("DomRange",function(l){function K(a,e){return a.nodeType!=3&&(g.isAncestorOf(a,e.startContainer,true)||g.isAncestorOf(a,e.endContainer,true))}function H(a){return g.getDocument(a.startContainer)}function I(a,e,j){if(e=a._listeners[e])for(var o=0,E=e.length;o<E;++o)e[o].call(a,{target:a,args:j})}function z(a){return new Z(a.parentNode,g.getNodeIndex(a))}function C(a){return new Z(a.parentNode,g.getNodeIndex(a)+1)}function N(a,e,j){var o=a.nodeType==11?a.firstChild:a;if(g.isCharacterDataNode(e))j==
+e.length?g.insertAfter(a,e):e.parentNode.insertBefore(a,j==0?e:g.splitDataNode(e,j));else j>=e.childNodes.length?e.appendChild(a):e.insertBefore(a,e.childNodes[j]);return o}function O(a){for(var e,j,o=H(a.range).createDocumentFragment();j=a.next();){e=a.isPartiallySelectedSubtree();j=j.cloneNode(!e);if(e){e=a.getSubtreeIterator();j.appendChild(O(e));e.detach(true)}if(j.nodeType==10)throw new S("HIERARCHY_REQUEST_ERR");o.appendChild(j)}return o}function i(a,e,j){var o,E;for(j=j||{stop:false};o=a.next();)if(a.isPartiallySelectedSubtree())if(e(o)===
+false){j.stop=true;return}else{o=a.getSubtreeIterator();i(o,e,j);o.detach(true);if(j.stop)return}else for(o=g.createIterator(o);E=o.next();)if(e(E)===false){j.stop=true;return}}function n(a){for(var e;a.next();)if(a.isPartiallySelectedSubtree()){e=a.getSubtreeIterator();n(e);e.detach(true)}else a.remove()}function t(a){for(var e,j=H(a.range).createDocumentFragment(),o;e=a.next();){if(a.isPartiallySelectedSubtree()){e=e.cloneNode(false);o=a.getSubtreeIterator();e.appendChild(t(o));o.detach(true)}else a.remove();
+if(e.nodeType==10)throw new S("HIERARCHY_REQUEST_ERR");j.appendChild(e)}return j}function x(a,e,j){var o=!!(e&&e.length),E,T=!!j;if(o)E=RegExp("^("+e.join("|")+")$");var m=[];i(new q(a,false),function(s){if((!o||E.test(s.nodeType))&&(!T||j(s)))m.push(s)});return m}function A(a){return"["+(typeof a.getName=="undefined"?"Range":a.getName())+"("+g.inspectNode(a.startContainer)+":"+a.startOffset+", "+g.inspectNode(a.endContainer)+":"+a.endOffset+")]"}function q(a,e){this.range=a;this.clonePartiallySelectedTextNodes=
+e;if(!a.collapsed){this.sc=a.startContainer;this.so=a.startOffset;this.ec=a.endContainer;this.eo=a.endOffset;var j=a.commonAncestorContainer;if(this.sc===this.ec&&g.isCharacterDataNode(this.sc)){this.isSingleCharacterDataNode=true;this._first=this._last=this._next=this.sc}else{this._first=this._next=this.sc===j&&!g.isCharacterDataNode(this.sc)?this.sc.childNodes[this.so]:g.getClosestAncestorIn(this.sc,j,true);this._last=this.ec===j&&!g.isCharacterDataNode(this.ec)?this.ec.childNodes[this.eo-1]:g.getClosestAncestorIn(this.ec,
+j,true)}}}function v(a){this.code=this[a];this.codeName=a;this.message="RangeException: "+this.codeName}function c(a,e,j){this.nodes=x(a,e,j);this._next=this.nodes[0];this._position=0}function f(a){return function(e,j){for(var o,E=j?e:e.parentNode;E;){o=E.nodeType;if(g.arrayContains(a,o))return E;E=E.parentNode}return null}}function k(a,e){if(G(a,e))throw new v("INVALID_NODE_TYPE_ERR");}function r(a){if(!a.startContainer)throw new S("INVALID_STATE_ERR");}function L(a,e){if(!g.arrayContains(e,a.nodeType))throw new v("INVALID_NODE_TYPE_ERR");
+}function p(a,e){if(e<0||e>(g.isCharacterDataNode(a)?a.length:a.childNodes.length))throw new S("INDEX_SIZE_ERR");}function u(a,e){if(h(a,true)!==h(e,true))throw new S("WRONG_DOCUMENT_ERR");}function w(a){if(D(a,true))throw new S("NO_MODIFICATION_ALLOWED_ERR");}function B(a,e){if(!a)throw new S(e);}function V(a){return!!a.startContainer&&!!a.endContainer&&!(!g.arrayContains(ba,a.startContainer.nodeType)&&!h(a.startContainer,true))&&!(!g.arrayContains(ba,a.endContainer.nodeType)&&!h(a.endContainer,
+true))&&a.startOffset<=(g.isCharacterDataNode(a.startContainer)?a.startContainer.length:a.startContainer.childNodes.length)&&a.endOffset<=(g.isCharacterDataNode(a.endContainer)?a.endContainer.length:a.endContainer.childNodes.length)}function J(a){r(a);if(!V(a))throw Error("Range error: Range is no longer valid after DOM mutation ("+a.inspect()+")");}function ca(){}function Y(a){a.START_TO_START=ia;a.START_TO_END=la;a.END_TO_END=ra;a.END_TO_START=ma;a.NODE_BEFORE=na;a.NODE_AFTER=oa;a.NODE_BEFORE_AND_AFTER=
+pa;a.NODE_INSIDE=ja}function W(a){Y(a);Y(a.prototype)}function da(a,e){return function(){J(this);var j=this.startContainer,o=this.startOffset,E=this.commonAncestorContainer,T=new q(this,true);if(j!==E){j=g.getClosestAncestorIn(j,E,true);o=C(j);j=o.node;o=o.offset}i(T,w);T.reset();E=a(T);T.detach();e(this,j,o,j,o);return E}}function fa(a,e,j){function o(m,s){return function(y){r(this);L(y,$);L(d(y),ba);y=(m?z:C)(y);(s?E:T)(this,y.node,y.offset)}}function E(m,s,y){var F=m.endContainer,Q=m.endOffset;
+if(s!==m.startContainer||y!==m.startOffset){if(d(s)!=d(F)||g.comparePoints(s,y,F,Q)==1){F=s;Q=y}e(m,s,y,F,Q)}}function T(m,s,y){var F=m.startContainer,Q=m.startOffset;if(s!==m.endContainer||y!==m.endOffset){if(d(s)!=d(F)||g.comparePoints(s,y,F,Q)==-1){F=s;Q=y}e(m,F,Q,s,y)}}a.prototype=new ca;l.util.extend(a.prototype,{setStart:function(m,s){r(this);k(m,true);p(m,s);E(this,m,s)},setEnd:function(m,s){r(this);k(m,true);p(m,s);T(this,m,s)},setStartBefore:o(true,true),setStartAfter:o(false,true),setEndBefore:o(true,
+false),setEndAfter:o(false,false),collapse:function(m){J(this);m?e(this,this.startContainer,this.startOffset,this.startContainer,this.startOffset):e(this,this.endContainer,this.endOffset,this.endContainer,this.endOffset)},selectNodeContents:function(m){r(this);k(m,true);e(this,m,0,m,g.getNodeLength(m))},selectNode:function(m){r(this);k(m,false);L(m,$);var s=z(m);m=C(m);e(this,s.node,s.offset,m.node,m.offset)},extractContents:da(t,e),deleteContents:da(n,e),canSurroundContents:function(){J(this);w(this.startContainer);
+w(this.endContainer);var m=new q(this,true),s=m._first&&K(m._first,this)||m._last&&K(m._last,this);m.detach();return!s},detach:function(){j(this)},splitBoundaries:function(){J(this);var m=this.startContainer,s=this.startOffset,y=this.endContainer,F=this.endOffset,Q=m===y;g.isCharacterDataNode(y)&&F>0&&F<y.length&&g.splitDataNode(y,F);if(g.isCharacterDataNode(m)&&s>0&&s<m.length){m=g.splitDataNode(m,s);if(Q){F-=s;y=m}else y==m.parentNode&&F>=g.getNodeIndex(m)&&F++;s=0}e(this,m,s,y,F)},normalizeBoundaries:function(){J(this);
+var m=this.startContainer,s=this.startOffset,y=this.endContainer,F=this.endOffset,Q=function(U){var R=U.nextSibling;if(R&&R.nodeType==U.nodeType){y=U;F=U.length;U.appendData(R.data);R.parentNode.removeChild(R)}},qa=function(U){var R=U.previousSibling;if(R&&R.nodeType==U.nodeType){m=U;var sa=U.length;s=R.length;U.insertData(0,R.data);R.parentNode.removeChild(R);if(m==y){F+=s;y=m}else if(y==U.parentNode){R=g.getNodeIndex(U);if(F==R){y=U;F=sa}else F>R&&F--}}},ga=true;if(g.isCharacterDataNode(y))y.length==
+F&&Q(y);else{if(F>0)(ga=y.childNodes[F-1])&&g.isCharacterDataNode(ga)&&Q(ga);ga=!this.collapsed}if(ga)if(g.isCharacterDataNode(m))s==0&&qa(m);else{if(s<m.childNodes.length)(Q=m.childNodes[s])&&g.isCharacterDataNode(Q)&&qa(Q)}else{m=y;s=F}e(this,m,s,y,F)},collapseToPoint:function(m,s){r(this);k(m,true);p(m,s);if(m!==this.startContainer||s!==this.startOffset||m!==this.endContainer||s!==this.endOffset)e(this,m,s,m,s)}});W(a)}function ea(a){a.collapsed=a.startContainer===a.endContainer&&a.startOffset===
+a.endOffset;a.commonAncestorContainer=a.collapsed?a.startContainer:g.getCommonAncestor(a.startContainer,a.endContainer)}function ha(a,e,j,o,E){var T=a.startContainer!==e||a.startOffset!==j,m=a.endContainer!==o||a.endOffset!==E;a.startContainer=e;a.startOffset=j;a.endContainer=o;a.endOffset=E;ea(a);I(a,"boundarychange",{startMoved:T,endMoved:m})}function M(a){this.startContainer=a;this.startOffset=0;this.endContainer=a;this.endOffset=0;this._listeners={boundarychange:[],detach:[]};ea(this)}l.requireModules(["DomUtil"]);
+var g=l.dom,Z=g.DomPosition,S=l.DOMException;q.prototype={_current:null,_next:null,_first:null,_last:null,isSingleCharacterDataNode:false,reset:function(){this._current=null;this._next=this._first},hasNext:function(){return!!this._next},next:function(){var a=this._current=this._next;if(a){this._next=a!==this._last?a.nextSibling:null;if(g.isCharacterDataNode(a)&&this.clonePartiallySelectedTextNodes){if(a===this.ec)(a=a.cloneNode(true)).deleteData(this.eo,a.length-this.eo);if(this._current===this.sc)(a=
+a.cloneNode(true)).deleteData(0,this.so)}}return a},remove:function(){var a=this._current,e,j;if(g.isCharacterDataNode(a)&&(a===this.sc||a===this.ec)){e=a===this.sc?this.so:0;j=a===this.ec?this.eo:a.length;e!=j&&a.deleteData(e,j-e)}else a.parentNode&&a.parentNode.removeChild(a)},isPartiallySelectedSubtree:function(){return K(this._current,this.range)},getSubtreeIterator:function(){var a;if(this.isSingleCharacterDataNode){a=this.range.cloneRange();a.collapse()}else{a=new M(H(this.range));var e=this._current,
+j=e,o=0,E=e,T=g.getNodeLength(e);if(g.isAncestorOf(e,this.sc,true)){j=this.sc;o=this.so}if(g.isAncestorOf(e,this.ec,true)){E=this.ec;T=this.eo}ha(a,j,o,E,T)}return new q(a,this.clonePartiallySelectedTextNodes)},detach:function(a){a&&this.range.detach();this.range=this._current=this._next=this._first=this._last=this.sc=this.so=this.ec=this.eo=null}};v.prototype={BAD_BOUNDARYPOINTS_ERR:1,INVALID_NODE_TYPE_ERR:2};v.prototype.toString=function(){return this.message};c.prototype={_current:null,hasNext:function(){return!!this._next},
+next:function(){this._current=this._next;this._next=this.nodes[++this._position];return this._current},detach:function(){this._current=this._next=this.nodes=null}};var $=[1,3,4,5,7,8,10],ba=[2,9,11],aa=[1,3,4,5,7,8,10,11],b=[1,3,4,5,7,8],d=g.getRootContainer,h=f([9,11]),D=f([5,6,10,12]),G=f([6,10,12]),P=document.createElement("style"),X=false;try{P.innerHTML="<b>x</b>";X=P.firstChild.nodeType==3}catch(ta){}l.features.htmlParsingConforms=X;var ka=["startContainer","startOffset","endContainer","endOffset",
+"collapsed","commonAncestorContainer"],ia=0,la=1,ra=2,ma=3,na=0,oa=1,pa=2,ja=3;ca.prototype={attachListener:function(a,e){this._listeners[a].push(e)},compareBoundaryPoints:function(a,e){J(this);u(this.startContainer,e.startContainer);var j=a==ma||a==ia?"start":"end",o=a==la||a==ia?"start":"end";return g.comparePoints(this[j+"Container"],this[j+"Offset"],e[o+"Container"],e[o+"Offset"])},insertNode:function(a){J(this);L(a,aa);w(this.startContainer);if(g.isAncestorOf(a,this.startContainer,true))throw new S("HIERARCHY_REQUEST_ERR");
+this.setStartBefore(N(a,this.startContainer,this.startOffset))},cloneContents:function(){J(this);var a,e;if(this.collapsed)return H(this).createDocumentFragment();else{if(this.startContainer===this.endContainer&&g.isCharacterDataNode(this.startContainer)){a=this.startContainer.cloneNode(true);a.data=a.data.slice(this.startOffset,this.endOffset);e=H(this).createDocumentFragment();e.appendChild(a);return e}else{e=new q(this,true);a=O(e);e.detach()}return a}},canSurroundContents:function(){J(this);w(this.startContainer);
+w(this.endContainer);var a=new q(this,true),e=a._first&&K(a._first,this)||a._last&&K(a._last,this);a.detach();return!e},surroundContents:function(a){L(a,b);if(!this.canSurroundContents())throw new v("BAD_BOUNDARYPOINTS_ERR");var e=this.extractContents();if(a.hasChildNodes())for(;a.lastChild;)a.removeChild(a.lastChild);N(a,this.startContainer,this.startOffset);a.appendChild(e);this.selectNode(a)},cloneRange:function(){J(this);for(var a=new M(H(this)),e=ka.length,j;e--;){j=ka[e];a[j]=this[j]}return a},
+toString:function(){J(this);var a=this.startContainer;if(a===this.endContainer&&g.isCharacterDataNode(a))return a.nodeType==3||a.nodeType==4?a.data.slice(this.startOffset,this.endOffset):"";else{var e=[];a=new q(this,true);i(a,function(j){if(j.nodeType==3||j.nodeType==4)e.push(j.data)});a.detach();return e.join("")}},compareNode:function(a){J(this);var e=a.parentNode,j=g.getNodeIndex(a);if(!e)throw new S("NOT_FOUND_ERR");a=this.comparePoint(e,j);e=this.comparePoint(e,j+1);return a<0?e>0?pa:na:e>0?
+oa:ja},comparePoint:function(a,e){J(this);B(a,"HIERARCHY_REQUEST_ERR");u(a,this.startContainer);if(g.comparePoints(a,e,this.startContainer,this.startOffset)<0)return-1;else if(g.comparePoints(a,e,this.endContainer,this.endOffset)>0)return 1;return 0},createContextualFragment:X?function(a){var e=this.startContainer,j=g.getDocument(e);if(!e)throw new S("INVALID_STATE_ERR");var o=null;if(e.nodeType==1)o=e;else if(g.isCharacterDataNode(e))o=g.parentElement(e);o=o===null||o.nodeName=="HTML"&&g.isHtmlNamespace(g.getDocument(o).documentElement)&&
+g.isHtmlNamespace(o)?j.createElement("body"):o.cloneNode(false);o.innerHTML=a;return g.fragmentFromNodeChildren(o)}:function(a){r(this);var e=H(this).createElement("body");e.innerHTML=a;return g.fragmentFromNodeChildren(e)},toHtml:function(){J(this);var a=H(this).createElement("div");a.appendChild(this.cloneContents());return a.innerHTML},intersectsNode:function(a,e){J(this);B(a,"NOT_FOUND_ERR");if(g.getDocument(a)!==H(this))return false;var j=a.parentNode,o=g.getNodeIndex(a);B(j,"NOT_FOUND_ERR");
+var E=g.comparePoints(j,o,this.endContainer,this.endOffset);j=g.comparePoints(j,o+1,this.startContainer,this.startOffset);return e?E<=0&&j>=0:E<0&&j>0},isPointInRange:function(a,e){J(this);B(a,"HIERARCHY_REQUEST_ERR");u(a,this.startContainer);return g.comparePoints(a,e,this.startContainer,this.startOffset)>=0&&g.comparePoints(a,e,this.endContainer,this.endOffset)<=0},intersectsRange:function(a,e){J(this);if(H(a)!=H(this))throw new S("WRONG_DOCUMENT_ERR");var j=g.comparePoints(this.startContainer,
+this.startOffset,a.endContainer,a.endOffset),o=g.comparePoints(this.endContainer,this.endOffset,a.startContainer,a.startOffset);return e?j<=0&&o>=0:j<0&&o>0},intersection:function(a){if(this.intersectsRange(a)){var e=g.comparePoints(this.startContainer,this.startOffset,a.startContainer,a.startOffset),j=g.comparePoints(this.endContainer,this.endOffset,a.endContainer,a.endOffset),o=this.cloneRange();e==-1&&o.setStart(a.startContainer,a.startOffset);j==1&&o.setEnd(a.endContainer,a.endOffset);return o}return null},
+union:function(a){if(this.intersectsRange(a,true)){var e=this.cloneRange();g.comparePoints(a.startContainer,a.startOffset,this.startContainer,this.startOffset)==-1&&e.setStart(a.startContainer,a.startOffset);g.comparePoints(a.endContainer,a.endOffset,this.endContainer,this.endOffset)==1&&e.setEnd(a.endContainer,a.endOffset);return e}else throw new v("Ranges do not intersect");},containsNode:function(a,e){return e?this.intersectsNode(a,false):this.compareNode(a)==ja},containsNodeContents:function(a){return this.comparePoint(a,
+0)>=0&&this.comparePoint(a,g.getNodeLength(a))<=0},containsRange:function(a){return this.intersection(a).equals(a)},containsNodeText:function(a){var e=this.cloneRange();e.selectNode(a);var j=e.getNodes([3]);if(j.length>0){e.setStart(j[0],0);a=j.pop();e.setEnd(a,a.length);a=this.containsRange(e);e.detach();return a}else return this.containsNodeContents(a)},createNodeIterator:function(a,e){J(this);return new c(this,a,e)},getNodes:function(a,e){J(this);return x(this,a,e)},getDocument:function(){return H(this)},
+collapseBefore:function(a){r(this);this.setEndBefore(a);this.collapse(false)},collapseAfter:function(a){r(this);this.setStartAfter(a);this.collapse(true)},getName:function(){return"DomRange"},equals:function(a){return M.rangesEqual(this,a)},isValid:function(){return V(this)},inspect:function(){return A(this)}};fa(M,ha,function(a){r(a);a.startContainer=a.startOffset=a.endContainer=a.endOffset=null;a.collapsed=a.commonAncestorContainer=null;I(a,"detach",null);a._listeners=null});l.rangePrototype=ca.prototype;
+M.rangeProperties=ka;M.RangeIterator=q;M.copyComparisonConstants=W;M.createPrototypeRange=fa;M.inspect=A;M.getRangeDocument=H;M.rangesEqual=function(a,e){return a.startContainer===e.startContainer&&a.startOffset===e.startOffset&&a.endContainer===e.endContainer&&a.endOffset===e.endOffset};l.DomRange=M;l.RangeException=v});
+rangy.createModule("WrappedRange",function(l){function K(i,n,t,x){var A=i.duplicate();A.collapse(t);var q=A.parentElement();z.isAncestorOf(n,q,true)||(q=n);if(!q.canHaveHTML)return new C(q.parentNode,z.getNodeIndex(q));n=z.getDocument(q).createElement("span");var v,c=t?"StartToStart":"StartToEnd";do{q.insertBefore(n,n.previousSibling);A.moveToElementText(n)}while((v=A.compareEndPoints(c,i))>0&&n.previousSibling);c=n.nextSibling;if(v==-1&&c&&z.isCharacterDataNode(c)){A.setEndPoint(t?"EndToStart":"EndToEnd",
+i);if(/[\r\n]/.test(c.data)){q=A.duplicate();t=q.text.replace(/\r\n/g,"\r").length;for(t=q.moveStart("character",t);q.compareEndPoints("StartToEnd",q)==-1;){t++;q.moveStart("character",1)}}else t=A.text.length;q=new C(c,t)}else{c=(x||!t)&&n.previousSibling;q=(t=(x||t)&&n.nextSibling)&&z.isCharacterDataNode(t)?new C(t,0):c&&z.isCharacterDataNode(c)?new C(c,c.length):new C(q,z.getNodeIndex(n))}n.parentNode.removeChild(n);return q}function H(i,n){var t,x,A=i.offset,q=z.getDocument(i.node),v=q.body.createTextRange(),
+c=z.isCharacterDataNode(i.node);if(c){t=i.node;x=t.parentNode}else{t=i.node.childNodes;t=A<t.length?t[A]:null;x=i.node}q=q.createElement("span");q.innerHTML="&#feff;";t?x.insertBefore(q,t):x.appendChild(q);v.moveToElementText(q);v.collapse(!n);x.removeChild(q);if(c)v[n?"moveStart":"moveEnd"]("character",A);return v}l.requireModules(["DomUtil","DomRange"]);var I,z=l.dom,C=z.DomPosition,N=l.DomRange;if(l.features.implementsDomRange&&(!l.features.implementsTextRange||!l.config.preferTextRange)){(function(){function i(f){for(var k=
+t.length,r;k--;){r=t[k];f[r]=f.nativeRange[r]}}var n,t=N.rangeProperties,x,A;I=function(f){if(!f)throw Error("Range must be specified");this.nativeRange=f;i(this)};N.createPrototypeRange(I,function(f,k,r,L,p){var u=f.endContainer!==L||f.endOffset!=p;if(f.startContainer!==k||f.startOffset!=r||u){f.setEnd(L,p);f.setStart(k,r)}},function(f){f.nativeRange.detach();f.detached=true;for(var k=t.length,r;k--;){r=t[k];f[r]=null}});n=I.prototype;n.selectNode=function(f){this.nativeRange.selectNode(f);i(this)};
+n.deleteContents=function(){this.nativeRange.deleteContents();i(this)};n.extractContents=function(){var f=this.nativeRange.extractContents();i(this);return f};n.cloneContents=function(){return this.nativeRange.cloneContents()};n.surroundContents=function(f){this.nativeRange.surroundContents(f);i(this)};n.collapse=function(f){this.nativeRange.collapse(f);i(this)};n.cloneRange=function(){return new I(this.nativeRange.cloneRange())};n.refresh=function(){i(this)};n.toString=function(){return this.nativeRange.toString()};
+var q=document.createTextNode("test");z.getBody(document).appendChild(q);var v=document.createRange();v.setStart(q,0);v.setEnd(q,0);try{v.setStart(q,1);x=true;n.setStart=function(f,k){this.nativeRange.setStart(f,k);i(this)};n.setEnd=function(f,k){this.nativeRange.setEnd(f,k);i(this)};A=function(f){return function(k){this.nativeRange[f](k);i(this)}}}catch(c){x=false;n.setStart=function(f,k){try{this.nativeRange.setStart(f,k)}catch(r){this.nativeRange.setEnd(f,k);this.nativeRange.setStart(f,k)}i(this)};
+n.setEnd=function(f,k){try{this.nativeRange.setEnd(f,k)}catch(r){this.nativeRange.setStart(f,k);this.nativeRange.setEnd(f,k)}i(this)};A=function(f,k){return function(r){try{this.nativeRange[f](r)}catch(L){this.nativeRange[k](r);this.nativeRange[f](r)}i(this)}}}n.setStartBefore=A("setStartBefore","setEndBefore");n.setStartAfter=A("setStartAfter","setEndAfter");n.setEndBefore=A("setEndBefore","setStartBefore");n.setEndAfter=A("setEndAfter","setStartAfter");v.selectNodeContents(q);n.selectNodeContents=
+v.startContainer==q&&v.endContainer==q&&v.startOffset==0&&v.endOffset==q.length?function(f){this.nativeRange.selectNodeContents(f);i(this)}:function(f){this.setStart(f,0);this.setEnd(f,N.getEndOffset(f))};v.selectNodeContents(q);v.setEnd(q,3);x=document.createRange();x.selectNodeContents(q);x.setEnd(q,4);x.setStart(q,2);n.compareBoundaryPoints=v.compareBoundaryPoints(v.START_TO_END,x)==-1&v.compareBoundaryPoints(v.END_TO_START,x)==1?function(f,k){k=k.nativeRange||k;if(f==k.START_TO_END)f=k.END_TO_START;
+else if(f==k.END_TO_START)f=k.START_TO_END;return this.nativeRange.compareBoundaryPoints(f,k)}:function(f,k){return this.nativeRange.compareBoundaryPoints(f,k.nativeRange||k)};if(l.util.isHostMethod(v,"createContextualFragment"))n.createContextualFragment=function(f){return this.nativeRange.createContextualFragment(f)};z.getBody(document).removeChild(q);v.detach();x.detach()})();l.createNativeRange=function(i){i=i||document;return i.createRange()}}else if(l.features.implementsTextRange){I=function(i){this.textRange=
+i;this.refresh()};I.prototype=new N(document);I.prototype.refresh=function(){var i,n,t=this.textRange;i=t.parentElement();var x=t.duplicate();x.collapse(true);n=x.parentElement();x=t.duplicate();x.collapse(false);t=x.parentElement();n=n==t?n:z.getCommonAncestor(n,t);n=n==i?n:z.getCommonAncestor(i,n);if(this.textRange.compareEndPoints("StartToEnd",this.textRange)==0)n=i=K(this.textRange,n,true,true);else{i=K(this.textRange,n,true,false);n=K(this.textRange,n,false,false)}this.setStart(i.node,i.offset);
+this.setEnd(n.node,n.offset)};N.copyComparisonConstants(I);var O=function(){return this}();if(typeof O.Range=="undefined")O.Range=I;l.createNativeRange=function(i){i=i||document;return i.body.createTextRange()}}if(l.features.implementsTextRange)I.rangeToTextRange=function(i){if(i.collapsed)return H(new C(i.startContainer,i.startOffset),true);else{var n=H(new C(i.startContainer,i.startOffset),true),t=H(new C(i.endContainer,i.endOffset),false);i=z.getDocument(i.startContainer).body.createTextRange();
+i.setEndPoint("StartToStart",n);i.setEndPoint("EndToEnd",t);return i}};I.prototype.getName=function(){return"WrappedRange"};l.WrappedRange=I;l.createRange=function(i){i=i||document;return new I(l.createNativeRange(i))};l.createRangyRange=function(i){i=i||document;return new N(i)};l.createIframeRange=function(i){return l.createRange(z.getIframeDocument(i))};l.createIframeRangyRange=function(i){return l.createRangyRange(z.getIframeDocument(i))};l.addCreateMissingNativeApiListener(function(i){i=i.document;
+if(typeof i.createRange=="undefined")i.createRange=function(){return l.createRange(this)};i=i=null})});
+rangy.createModule("WrappedSelection",function(l,K){function H(b){return(b||window).getSelection()}function I(b){return(b||window).document.selection}function z(b,d,h){var D=h?"end":"start";h=h?"start":"end";b.anchorNode=d[D+"Container"];b.anchorOffset=d[D+"Offset"];b.focusNode=d[h+"Container"];b.focusOffset=d[h+"Offset"]}function C(b){b.anchorNode=b.focusNode=null;b.anchorOffset=b.focusOffset=0;b.rangeCount=0;b.isCollapsed=true;b._ranges.length=0}function N(b){var d;if(b instanceof k){d=b._selectionNativeRange;
+if(!d){d=l.createNativeRange(c.getDocument(b.startContainer));d.setEnd(b.endContainer,b.endOffset);d.setStart(b.startContainer,b.startOffset);b._selectionNativeRange=d;b.attachListener("detach",function(){this._selectionNativeRange=null})}}else if(b instanceof r)d=b.nativeRange;else if(l.features.implementsDomRange&&b instanceof c.getWindow(b.startContainer).Range)d=b;return d}function O(b){var d=b.getNodes(),h;a:if(!d.length||d[0].nodeType!=1)h=false;else{h=1;for(var D=d.length;h<D;++h)if(!c.isAncestorOf(d[0],
+d[h])){h=false;break a}h=true}if(!h)throw Error("getSingleElementFromRange: range "+b.inspect()+" did not consist of a single element");return d[0]}function i(b,d){var h=new r(d);b._ranges=[h];z(b,h,false);b.rangeCount=1;b.isCollapsed=h.collapsed}function n(b){b._ranges.length=0;if(b.docSelection.type=="None")C(b);else{var d=b.docSelection.createRange();if(d&&typeof d.text!="undefined")i(b,d);else{b.rangeCount=d.length;for(var h,D=c.getDocument(d.item(0)),G=0;G<b.rangeCount;++G){h=l.createRange(D);
+h.selectNode(d.item(G));b._ranges.push(h)}b.isCollapsed=b.rangeCount==1&&b._ranges[0].collapsed;z(b,b._ranges[b.rangeCount-1],false)}}}function t(b,d){var h=b.docSelection.createRange(),D=O(d),G=c.getDocument(h.item(0));G=c.getBody(G).createControlRange();for(var P=0,X=h.length;P<X;++P)G.add(h.item(P));try{G.add(D)}catch(ta){throw Error("addRange(): Element within the specified Range could not be added to control selection (does it have layout?)");}G.select();n(b)}function x(b,d,h){this.nativeSelection=
+b;this.docSelection=d;this._ranges=[];this.win=h;this.refresh()}function A(b,d){var h=c.getDocument(d[0].startContainer);h=c.getBody(h).createControlRange();for(var D=0,G;D<rangeCount;++D){G=O(d[D]);try{h.add(G)}catch(P){throw Error("setRanges(): Element within the one of the specified Ranges could not be added to control selection (does it have layout?)");}}h.select();n(b)}function q(b,d){if(b.anchorNode&&c.getDocument(b.anchorNode)!==c.getDocument(d))throw new L("WRONG_DOCUMENT_ERR");}function v(b){var d=
+[],h=new p(b.anchorNode,b.anchorOffset),D=new p(b.focusNode,b.focusOffset),G=typeof b.getName=="function"?b.getName():"Selection";if(typeof b.rangeCount!="undefined")for(var P=0,X=b.rangeCount;P<X;++P)d[P]=k.inspect(b.getRangeAt(P));return"["+G+"(Ranges: "+d.join(", ")+")(anchor: "+h.inspect()+", focus: "+D.inspect()+"]"}l.requireModules(["DomUtil","DomRange","WrappedRange"]);l.config.checkSelectionRanges=true;var c=l.dom,f=l.util,k=l.DomRange,r=l.WrappedRange,L=l.DOMException,p=c.DomPosition,u,w,
+B=l.util.isHostMethod(window,"getSelection"),V=l.util.isHostObject(document,"selection"),J=V&&(!B||l.config.preferTextRange);if(J){u=I;l.isSelectionValid=function(b){b=(b||window).document;var d=b.selection;return d.type!="None"||c.getDocument(d.createRange().parentElement())==b}}else if(B){u=H;l.isSelectionValid=function(){return true}}else K.fail("Neither document.selection or window.getSelection() detected.");l.getNativeSelection=u;B=u();var ca=l.createNativeRange(document),Y=c.getBody(document),
+W=f.areHostObjects(B,f.areHostProperties(B,["anchorOffset","focusOffset"]));l.features.selectionHasAnchorAndFocus=W;var da=f.isHostMethod(B,"extend");l.features.selectionHasExtend=da;var fa=typeof B.rangeCount=="number";l.features.selectionHasRangeCount=fa;var ea=false,ha=true;f.areHostMethods(B,["addRange","getRangeAt","removeAllRanges"])&&typeof B.rangeCount=="number"&&l.features.implementsDomRange&&function(){var b=document.createElement("iframe");b.frameBorder=0;b.style.position="absolute";b.style.left=
+"-10000px";Y.appendChild(b);var d=c.getIframeDocument(b);d.open();d.write("<html><head></head><body>12</body></html>");d.close();var h=c.getIframeWindow(b).getSelection(),D=d.documentElement.lastChild.firstChild;d=d.createRange();d.setStart(D,1);d.collapse(true);h.addRange(d);ha=h.rangeCount==1;h.removeAllRanges();var G=d.cloneRange();d.setStart(D,0);G.setEnd(D,2);h.addRange(d);h.addRange(G);ea=h.rangeCount==2;d.detach();G.detach();Y.removeChild(b)}();l.features.selectionSupportsMultipleRanges=ea;
+l.features.collapsedNonEditableSelectionsSupported=ha;var M=false,g;if(Y&&f.isHostMethod(Y,"createControlRange")){g=Y.createControlRange();if(f.areHostProperties(g,["item","add"]))M=true}l.features.implementsControlRange=M;w=W?function(b){return b.anchorNode===b.focusNode&&b.anchorOffset===b.focusOffset}:function(b){return b.rangeCount?b.getRangeAt(b.rangeCount-1).collapsed:false};var Z;if(f.isHostMethod(B,"getRangeAt"))Z=function(b,d){try{return b.getRangeAt(d)}catch(h){return null}};else if(W)Z=
+function(b){var d=c.getDocument(b.anchorNode);d=l.createRange(d);d.setStart(b.anchorNode,b.anchorOffset);d.setEnd(b.focusNode,b.focusOffset);if(d.collapsed!==this.isCollapsed){d.setStart(b.focusNode,b.focusOffset);d.setEnd(b.anchorNode,b.anchorOffset)}return d};l.getSelection=function(b){b=b||window;var d=b._rangySelection,h=u(b),D=V?I(b):null;if(d){d.nativeSelection=h;d.docSelection=D;d.refresh(b)}else{d=new x(h,D,b);b._rangySelection=d}return d};l.getIframeSelection=function(b){return l.getSelection(c.getIframeWindow(b))};
+g=x.prototype;if(!J&&W&&f.areHostMethods(B,["removeAllRanges","addRange"])){g.removeAllRanges=function(){this.nativeSelection.removeAllRanges();C(this)};var S=function(b,d){var h=k.getRangeDocument(d);h=l.createRange(h);h.collapseToPoint(d.endContainer,d.endOffset);b.nativeSelection.addRange(N(h));b.nativeSelection.extend(d.startContainer,d.startOffset);b.refresh()};g.addRange=fa?function(b,d){if(M&&V&&this.docSelection.type=="Control")t(this,b);else if(d&&da)S(this,b);else{var h;if(ea)h=this.rangeCount;
+else{this.removeAllRanges();h=0}this.nativeSelection.addRange(N(b));this.rangeCount=this.nativeSelection.rangeCount;if(this.rangeCount==h+1){if(l.config.checkSelectionRanges)if((h=Z(this.nativeSelection,this.rangeCount-1))&&!k.rangesEqual(h,b))b=new r(h);this._ranges[this.rangeCount-1]=b;z(this,b,aa(this.nativeSelection));this.isCollapsed=w(this)}else this.refresh()}}:function(b,d){if(d&&da)S(this,b);else{this.nativeSelection.addRange(N(b));this.refresh()}};g.setRanges=function(b){if(M&&b.length>
+1)A(this,b);else{this.removeAllRanges();for(var d=0,h=b.length;d<h;++d)this.addRange(b[d])}}}else if(f.isHostMethod(B,"empty")&&f.isHostMethod(ca,"select")&&M&&J){g.removeAllRanges=function(){try{this.docSelection.empty();if(this.docSelection.type!="None"){var b;if(this.anchorNode)b=c.getDocument(this.anchorNode);else if(this.docSelection.type=="Control"){var d=this.docSelection.createRange();if(d.length)b=c.getDocument(d.item(0)).body.createTextRange()}if(b){b.body.createTextRange().select();this.docSelection.empty()}}}catch(h){}C(this)};
+g.addRange=function(b){if(this.docSelection.type=="Control")t(this,b);else{r.rangeToTextRange(b).select();this._ranges[0]=b;this.rangeCount=1;this.isCollapsed=this._ranges[0].collapsed;z(this,b,false)}};g.setRanges=function(b){this.removeAllRanges();var d=b.length;if(d>1)A(this,b);else d&&this.addRange(b[0])}}else{K.fail("No means of selecting a Range or TextRange was found");return false}g.getRangeAt=function(b){if(b<0||b>=this.rangeCount)throw new L("INDEX_SIZE_ERR");else return this._ranges[b]};
+var $;if(J)$=function(b){var d;if(l.isSelectionValid(b.win))d=b.docSelection.createRange();else{d=c.getBody(b.win.document).createTextRange();d.collapse(true)}if(b.docSelection.type=="Control")n(b);else d&&typeof d.text!="undefined"?i(b,d):C(b)};else if(f.isHostMethod(B,"getRangeAt")&&typeof B.rangeCount=="number")$=function(b){if(M&&V&&b.docSelection.type=="Control")n(b);else{b._ranges.length=b.rangeCount=b.nativeSelection.rangeCount;if(b.rangeCount){for(var d=0,h=b.rangeCount;d<h;++d)b._ranges[d]=
+new l.WrappedRange(b.nativeSelection.getRangeAt(d));z(b,b._ranges[b.rangeCount-1],aa(b.nativeSelection));b.isCollapsed=w(b)}else C(b)}};else if(W&&typeof B.isCollapsed=="boolean"&&typeof ca.collapsed=="boolean"&&l.features.implementsDomRange)$=function(b){var d;d=b.nativeSelection;if(d.anchorNode){d=Z(d,0);b._ranges=[d];b.rangeCount=1;d=b.nativeSelection;b.anchorNode=d.anchorNode;b.anchorOffset=d.anchorOffset;b.focusNode=d.focusNode;b.focusOffset=d.focusOffset;b.isCollapsed=w(b)}else C(b)};else{K.fail("No means of obtaining a Range or TextRange from the user's selection was found");
+return false}g.refresh=function(b){var d=b?this._ranges.slice(0):null;$(this);if(b){b=d.length;if(b!=this._ranges.length)return false;for(;b--;)if(!k.rangesEqual(d[b],this._ranges[b]))return false;return true}};var ba=function(b,d){var h=b.getAllRanges(),D=false;b.removeAllRanges();for(var G=0,P=h.length;G<P;++G)if(D||d!==h[G])b.addRange(h[G]);else D=true;b.rangeCount||C(b)};g.removeRange=M?function(b){if(this.docSelection.type=="Control"){var d=this.docSelection.createRange();b=O(b);var h=c.getDocument(d.item(0));
+h=c.getBody(h).createControlRange();for(var D,G=false,P=0,X=d.length;P<X;++P){D=d.item(P);if(D!==b||G)h.add(d.item(P));else G=true}h.select();n(this)}else ba(this,b)}:function(b){ba(this,b)};var aa;if(!J&&W&&l.features.implementsDomRange){aa=function(b){var d=false;if(b.anchorNode)d=c.comparePoints(b.anchorNode,b.anchorOffset,b.focusNode,b.focusOffset)==1;return d};g.isBackwards=function(){return aa(this)}}else aa=g.isBackwards=function(){return false};g.toString=function(){for(var b=[],d=0,h=this.rangeCount;d<
+h;++d)b[d]=""+this._ranges[d];return b.join("")};g.collapse=function(b,d){q(this,b);var h=l.createRange(c.getDocument(b));h.collapseToPoint(b,d);this.removeAllRanges();this.addRange(h);this.isCollapsed=true};g.collapseToStart=function(){if(this.rangeCount){var b=this._ranges[0];this.collapse(b.startContainer,b.startOffset)}else throw new L("INVALID_STATE_ERR");};g.collapseToEnd=function(){if(this.rangeCount){var b=this._ranges[this.rangeCount-1];this.collapse(b.endContainer,b.endOffset)}else throw new L("INVALID_STATE_ERR");
+};g.selectAllChildren=function(b){q(this,b);var d=l.createRange(c.getDocument(b));d.selectNodeContents(b);this.removeAllRanges();this.addRange(d)};g.deleteFromDocument=function(){if(M&&V&&this.docSelection.type=="Control"){for(var b=this.docSelection.createRange(),d;b.length;){d=b.item(0);b.remove(d);d.parentNode.removeChild(d)}this.refresh()}else if(this.rangeCount){b=this.getAllRanges();this.removeAllRanges();d=0;for(var h=b.length;d<h;++d)b[d].deleteContents();this.addRange(b[h-1])}};g.getAllRanges=
+function(){return this._ranges.slice(0)};g.setSingleRange=function(b){this.setRanges([b])};g.containsNode=function(b,d){for(var h=0,D=this._ranges.length;h<D;++h)if(this._ranges[h].containsNode(b,d))return true;return false};g.toHtml=function(){var b="";if(this.rangeCount){b=k.getRangeDocument(this._ranges[0]).createElement("div");for(var d=0,h=this._ranges.length;d<h;++d)b.appendChild(this._ranges[d].cloneContents());b=b.innerHTML}return b};g.getName=function(){return"WrappedSelection"};g.inspect=
+function(){return v(this)};g.detach=function(){this.win=this.anchorNode=this.focusNode=this.win._rangySelection=null};x.inspect=v;l.Selection=x;l.selectionPrototype=g;l.addCreateMissingNativeApiListener(function(b){if(typeof b.getSelection=="undefined")b.getSelection=function(){return l.getSelection(this)};b=null})});
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-cssclassapplier.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-cssclassapplier.js
new file mode 100644
index 0000000..60064a3
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-cssclassapplier.js
@@ -0,0 +1,32 @@
+/*
+ CSS Class Applier module for Rangy.
+ Adds, removes and toggles CSS classes on Ranges and Selections
+
+ Part of Rangy, a cross-browser JavaScript range and selection library
+ http://code.google.com/p/rangy/
+
+ Depends on Rangy core.
+
+ Copyright 2012, Tim Down
+ Licensed under the MIT license.
+ Version: 1.2.3
+ Build date: 26 February 2012
+*/
+rangy.createModule("CssClassApplier",function(i,v){function r(a,b){return a.className&&RegExp("(?:^|\\s)"+b+"(?:\\s|$)").test(a.className)}function s(a,b){if(a.className)r(a,b)||(a.className+=" "+b);else a.className=b}function o(a){return a.split(/\s+/).sort().join(" ")}function w(a,b){return o(a.className)==o(b.className)}function x(a){for(var b=a.parentNode;a.hasChildNodes();)b.insertBefore(a.firstChild,a);b.removeChild(a)}function y(a,b){var c=a.cloneRange();c.selectNodeContents(b);var d=c.intersection(a);
+d=d?d.toString():"";c.detach();return d!=""}function z(a){return a.getNodes([3],function(b){return y(a,b)})}function A(a,b){if(a.attributes.length!=b.attributes.length)return false;for(var c=0,d=a.attributes.length,e,f;c<d;++c){e=a.attributes[c];f=e.name;if(f!="class"){f=b.attributes.getNamedItem(f);if(e.specified!=f.specified)return false;if(e.specified&&e.nodeValue!==f.nodeValue)return false}}return true}function B(a,b){for(var c=0,d=a.attributes.length,e;c<d;++c){e=a.attributes[c].name;if(!(b&&
+h.arrayContains(b,e))&&a.attributes[c].specified&&e!="class")return true}return false}function C(a){var b;return a&&a.nodeType==1&&((b=a.parentNode)&&b.nodeType==9&&b.designMode=="on"||k(a)&&!k(a.parentNode))}function D(a){return(k(a)||a.nodeType!=1&&k(a.parentNode))&&!C(a)}function E(a){return a&&a.nodeType==1&&!M.test(p(a,"display"))}function N(a){if(a.data.length==0)return true;if(O.test(a.data))return false;switch(p(a.parentNode,"whiteSpace")){case "pre":case "pre-wrap":case "-moz-pre-wrap":return false;
+case "pre-line":if(/[\r\n]/.test(a.data))return false}return E(a.previousSibling)||E(a.nextSibling)}function m(a,b,c,d){var e,f=c==0;if(h.isAncestorOf(b,a))return a;if(h.isCharacterDataNode(b))if(c==0){c=h.getNodeIndex(b);b=b.parentNode}else if(c==b.length){c=h.getNodeIndex(b)+1;b=b.parentNode}else throw v.createError("splitNodeAt should not be called with offset in the middle of a data node ("+c+" in "+b.data);var g;g=b;var j=c;g=h.isCharacterDataNode(g)?j==0?!!g.previousSibling:j==g.length?!!g.nextSibling:
+true:j>0&&j<g.childNodes.length;if(g){if(!e){e=b.cloneNode(false);for(e.id&&e.removeAttribute("id");f=b.childNodes[c];)e.appendChild(f);h.insertAfter(e,b)}return b==a?e:m(a,e.parentNode,h.getNodeIndex(e),d)}else if(a!=b){e=b.parentNode;b=h.getNodeIndex(b);f||b++;return m(a,e,b,d)}return a}function F(a){var b=a?"nextSibling":"previousSibling";return function(c,d){var e=c.parentNode,f=c[b];if(f){if(f&&f.nodeType==3)return f}else if(d)if((f=e[b])&&f.nodeType==1&&e.tagName==f.tagName&&w(e,f)&&A(e,f))return f[a?
+"firstChild":"lastChild"];return null}}function t(a){this.firstTextNode=(this.isElementMerge=a.nodeType==1)?a.lastChild:a;this.textNodes=[this.firstTextNode]}function q(a,b,c){this.cssClass=a;var d,e,f=null;if(typeof b=="object"&&b!==null){c=b.tagNames;f=b.elementProperties;for(d=0;e=P[d++];)if(b.hasOwnProperty(e))this[e]=b[e];d=b.normalize}else d=b;this.normalize=typeof d=="undefined"?true:d;this.attrExceptions=[];d=document.createElement(this.elementTagName);this.elementProperties={};for(var g in f)if(f.hasOwnProperty(g)){if(G.hasOwnProperty(g))g=
+G[g];d[g]=f[g];this.elementProperties[g]=d[g];this.attrExceptions.push(g)}this.elementSortedClassName=this.elementProperties.hasOwnProperty("className")?o(this.elementProperties.className+" "+a):a;this.applyToAnyTagName=false;a=typeof c;if(a=="string")if(c=="*")this.applyToAnyTagName=true;else this.tagNames=c.toLowerCase().replace(/^\s\s*/,"").replace(/\s\s*$/,"").split(/\s*,\s*/);else if(a=="object"&&typeof c.length=="number"){this.tagNames=[];d=0;for(a=c.length;d<a;++d)if(c[d]=="*")this.applyToAnyTagName=
+true;else this.tagNames.push(c[d].toLowerCase())}else this.tagNames=[this.elementTagName]}i.requireModules(["WrappedSelection","WrappedRange"]);var h=i.dom,H=function(){function a(b,c,d){return c&&d?" ":""}return function(b,c){if(b.className)b.className=b.className.replace(RegExp("(?:^|\\s)"+c+"(?:\\s|$)"),a)}}(),p;if(typeof window.getComputedStyle!="undefined")p=function(a,b){return h.getWindow(a).getComputedStyle(a,null)[b]};else if(typeof document.documentElement.currentStyle!="undefined")p=function(a,
+b){return a.currentStyle[b]};else v.fail("No means of obtaining computed style properties found");var k;(function(){k=typeof document.createElement("div").isContentEditable=="boolean"?function(a){return a&&a.nodeType==1&&a.isContentEditable}:function(a){if(!a||a.nodeType!=1||a.contentEditable=="false")return false;return a.contentEditable=="true"||k(a.parentNode)}})();var M=/^inline(-block|-table)?$/i,O=/[^\r\n\t\f \u200B]/,Q=F(false),R=F(true);t.prototype={doMerge:function(){for(var a=[],b,c,d=0,
+e=this.textNodes.length;d<e;++d){b=this.textNodes[d];c=b.parentNode;a[d]=b.data;if(d){c.removeChild(b);c.hasChildNodes()||c.parentNode.removeChild(c)}}return this.firstTextNode.data=a=a.join("")},getLength:function(){for(var a=this.textNodes.length,b=0;a--;)b+=this.textNodes[a].length;return b},toString:function(){for(var a=[],b=0,c=this.textNodes.length;b<c;++b)a[b]="'"+this.textNodes[b].data+"'";return"[Merge("+a.join(",")+")]"}};var P=["elementTagName","ignoreWhiteSpace","applyToEditableOnly"],
+G={"class":"className"};q.prototype={elementTagName:"span",elementProperties:{},ignoreWhiteSpace:true,applyToEditableOnly:false,hasClass:function(a){return a.nodeType==1&&h.arrayContains(this.tagNames,a.tagName.toLowerCase())&&r(a,this.cssClass)},getSelfOrAncestorWithClass:function(a){for(;a;){if(this.hasClass(a,this.cssClass))return a;a=a.parentNode}return null},isModifiable:function(a){return!this.applyToEditableOnly||D(a)},isIgnorableWhiteSpaceNode:function(a){return this.ignoreWhiteSpace&&a&&
+a.nodeType==3&&N(a)},postApply:function(a,b,c){for(var d=a[0],e=a[a.length-1],f=[],g,j=d,I=e,J=0,K=e.length,n,L,l=0,u=a.length;l<u;++l){n=a[l];if(L=Q(n,!c)){if(!g){g=new t(L);f.push(g)}g.textNodes.push(n);if(n===d){j=g.firstTextNode;J=j.length}if(n===e){I=g.firstTextNode;K=g.getLength()}}else g=null}if(a=R(e,!c)){if(!g){g=new t(e);f.push(g)}g.textNodes.push(a)}if(f.length){l=0;for(u=f.length;l<u;++l)f[l].doMerge();b.setStart(j,J);b.setEnd(I,K)}},createContainer:function(a){a=a.createElement(this.elementTagName);
+i.util.extend(a,this.elementProperties);s(a,this.cssClass);return a},applyToTextNode:function(a){var b=a.parentNode;if(b.childNodes.length==1&&h.arrayContains(this.tagNames,b.tagName.toLowerCase()))s(b,this.cssClass);else{b=this.createContainer(h.getDocument(a));a.parentNode.insertBefore(b,a);b.appendChild(a)}},isRemovable:function(a){var b;if(b=a.tagName.toLowerCase()==this.elementTagName){if(b=o(a.className)==this.elementSortedClassName){var c;a:{b=this.elementProperties;for(c in b)if(b.hasOwnProperty(c)&&
+a[c]!==b[c]){c=false;break a}c=true}b=c&&!B(a,this.attrExceptions)&&this.isModifiable(a)}b=b}return b},undoToTextNode:function(a,b,c){if(!b.containsNode(c)){a=b.cloneRange();a.selectNode(c);if(a.isPointInRange(b.endContainer,b.endOffset)){m(c,b.endContainer,b.endOffset,[b]);b.setEndAfter(c)}if(a.isPointInRange(b.startContainer,b.startOffset))c=m(c,b.startContainer,b.startOffset,[b])}this.isRemovable(c)?x(c):H(c,this.cssClass)},applyToRange:function(a){a.splitBoundaries();var b=z(a);if(b.length){for(var c,
+d=0,e=b.length;d<e;++d){c=b[d];!this.isIgnorableWhiteSpaceNode(c)&&!this.getSelfOrAncestorWithClass(c)&&this.isModifiable(c)&&this.applyToTextNode(c)}a.setStart(b[0],0);c=b[b.length-1];a.setEnd(c,c.length);this.normalize&&this.postApply(b,a,false)}},applyToSelection:function(a){a=a||window;a=i.getSelection(a);var b,c=a.getAllRanges();a.removeAllRanges();for(var d=c.length;d--;){b=c[d];this.applyToRange(b);a.addRange(b)}},undoToRange:function(a){a.splitBoundaries();var b=z(a),c,d,e=b[b.length-1];if(b.length){for(var f=
+0,g=b.length;f<g;++f){c=b[f];(d=this.getSelfOrAncestorWithClass(c))&&this.isModifiable(c)&&this.undoToTextNode(c,a,d);a.setStart(b[0],0);a.setEnd(e,e.length)}this.normalize&&this.postApply(b,a,true)}},undoToSelection:function(a){a=a||window;a=i.getSelection(a);var b=a.getAllRanges(),c;a.removeAllRanges();for(var d=0,e=b.length;d<e;++d){c=b[d];this.undoToRange(c);a.addRange(c)}},getTextSelectedByRange:function(a,b){var c=b.cloneRange();c.selectNodeContents(a);var d=c.intersection(b);d=d?d.toString():
+"";c.detach();return d},isAppliedToRange:function(a){if(a.collapsed)return!!this.getSelfOrAncestorWithClass(a.commonAncestorContainer);else{for(var b=a.getNodes([3]),c=0,d;d=b[c++];)if(!this.isIgnorableWhiteSpaceNode(d)&&y(a,d)&&this.isModifiable(d)&&!this.getSelfOrAncestorWithClass(d))return false;return true}},isAppliedToSelection:function(a){a=a||window;a=i.getSelection(a).getAllRanges();for(var b=a.length;b--;)if(!this.isAppliedToRange(a[b]))return false;return true},toggleRange:function(a){this.isAppliedToRange(a)?
+this.undoToRange(a):this.applyToRange(a)},toggleSelection:function(a){this.isAppliedToSelection(a)?this.undoToSelection(a):this.applyToSelection(a)},detach:function(){}};q.util={hasClass:r,addClass:s,removeClass:H,hasSameClasses:w,replaceWithOwnChildren:x,elementsHaveSameNonClassAttributes:A,elementHasNonClassAttributes:B,splitNodeAt:m,isEditableElement:k,isEditingHost:C,isEditable:D};i.CssClassApplier=q;i.createCssClassApplier=function(a,b,c){return new q(a,b,c)}});
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-selectionsaverestore.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-selectionsaverestore.js
new file mode 100644
index 0000000..73e3070
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-selectionsaverestore.js
@@ -0,0 +1,19 @@
+/*
+ Selection save and restore module for Rangy.
+ Saves and restores user selections using marker invisible elements in the DOM.
+
+ Part of Rangy, a cross-browser JavaScript range and selection library
+ http://code.google.com/p/rangy/
+
+ Depends on Rangy core.
+
+ Copyright 2012, Tim Down
+ Licensed under the MIT license.
+ Version: 1.2.3
+ Build date: 26 February 2012
+*/
+rangy.createModule("SaveRestore",function(h,m){function n(a,g){var e="selectionBoundary_"+ +new Date+"_"+(""+Math.random()).slice(2),c,f=p.getDocument(a.startContainer),d=a.cloneRange();d.collapse(g);c=f.createElement("span");c.id=e;c.style.lineHeight="0";c.style.display="none";c.className="rangySelectionBoundary";c.appendChild(f.createTextNode(q));d.insertNode(c);d.detach();return c}function o(a,g,e,c){if(a=(a||document).getElementById(e)){g[c?"setStartBefore":"setEndBefore"](a);a.parentNode.removeChild(a)}else m.warn("Marker element has been removed. Cannot restore selection.")}
+function r(a,g){return g.compareBoundaryPoints(a.START_TO_START,a)}function k(a,g){var e=(a||document).getElementById(g);e&&e.parentNode.removeChild(e)}h.requireModules(["DomUtil","DomRange","WrappedRange"]);var p=h.dom,q="\ufeff";h.saveSelection=function(a){a=a||window;var g=a.document;if(h.isSelectionValid(a)){var e=h.getSelection(a),c=e.getAllRanges(),f=[],d,j;c.sort(r);for(var b=0,i=c.length;b<i;++b){d=c[b];if(d.collapsed){j=n(d,false);f.push({markerId:j.id,collapsed:true})}else{j=n(d,false);
+d=n(d,true);f[b]={startMarkerId:d.id,endMarkerId:j.id,collapsed:false,backwards:c.length==1&&e.isBackwards()}}}for(b=i-1;b>=0;--b){d=c[b];if(d.collapsed)d.collapseBefore((g||document).getElementById(f[b].markerId));else{d.setEndBefore((g||document).getElementById(f[b].endMarkerId));d.setStartAfter((g||document).getElementById(f[b].startMarkerId))}}e.setRanges(c);return{win:a,doc:g,rangeInfos:f,restored:false}}else m.warn("Cannot save selection. This usually happens when the selection is collapsed and the selection document has lost focus.")};
+h.restoreSelection=function(a,g){if(!a.restored){for(var e=a.rangeInfos,c=h.getSelection(a.win),f=[],d=e.length,j=d-1,b,i;j>=0;--j){b=e[j];i=h.createRange(a.doc);if(b.collapsed)if(b=(a.doc||document).getElementById(b.markerId)){b.style.display="inline";var l=b.previousSibling;if(l&&l.nodeType==3){b.parentNode.removeChild(b);i.collapseToPoint(l,l.length)}else{i.collapseBefore(b);b.parentNode.removeChild(b)}}else m.warn("Marker element has been removed. Cannot restore selection.");else{o(a.doc,i,b.startMarkerId,
+true);o(a.doc,i,b.endMarkerId,false)}d==1&&i.normalizeBoundaries();f[j]=i}if(d==1&&g&&h.features.selectionHasExtend&&e[0].backwards){c.removeAllRanges();c.addRange(f[0],true)}else c.setRanges(f);a.restored=true}};h.removeMarkerElement=k;h.removeMarkers=function(a){for(var g=a.rangeInfos,e=0,c=g.length,f;e<c;++e){f=g[e];if(f.collapsed)k(a.doc,f.markerId);else{k(a.doc,f.startMarkerId);k(a.doc,f.endMarkerId)}}}});
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-serializer.js b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-serializer.js
new file mode 100644
index 0000000..72f19f6
--- /dev/null
+++ b/suite/pts/hostTests/browser/browserlauncher/assets/robohornet/third_party/rangy/rangy-serializer.js
@@ -0,0 +1,23 @@
+/*
+ Serializer module for Rangy.
+ Serializes Ranges and Selections. An example use would be to store a user's selection on a particular page in a
+ cookie or local storage and restore it on the user's next visit to the same page.
+
+ Part of Rangy, a cross-browser JavaScript range and selection library
+ http://code.google.com/p/rangy/
+
+ Depends on Rangy core.
+
+ Copyright 2012, Tim Down
+ Licensed under the MIT license.
+ Version: 1.2.3
+ Build date: 26 February 2012
+*/
+rangy.createModule("Serializer",function(g,n){function o(c,a){a=a||[];var b=c.nodeType,e=c.childNodes,d=e.length,f=[b,c.nodeName,d].join(":"),h="",k="";switch(b){case 3:h=c.nodeValue.replace(/</g,"&lt;").replace(/>/g,"&gt;");break;case 8:h="<!--"+c.nodeValue.replace(/</g,"&lt;").replace(/>/g,"&gt;")+"--\>";break;default:h="<"+f+">";k="</>";break}h&&a.push(h);for(b=0;b<d;++b)o(e[b],a);k&&a.push(k);return a}function j(c){c=o(c).join("");return u(c).toString(16)}function l(c,a,b){var e=[],d=c;for(b=
+b||i.getDocument(c).documentElement;d&&d!=b;){e.push(i.getNodeIndex(d,true));d=d.parentNode}return e.join("/")+":"+a}function m(c,a,b){if(a)b||i.getDocument(a);else{b=b||document;a=b.documentElement}c=c.split(":");a=a;b=c[0]?c[0].split("/"):[];for(var e=b.length,d;e--;){d=parseInt(b[e],10);if(d<a.childNodes.length)a=a.childNodes[parseInt(b[e],10)];else throw n.createError("deserializePosition failed: node "+i.inspectNode(a)+" has no child with index "+d+", "+e);}return new i.DomPosition(a,parseInt(c[1],
+10))}function p(c,a,b){b=b||g.DomRange.getRangeDocument(c).documentElement;if(!i.isAncestorOf(b,c.commonAncestorContainer,true))throw Error("serializeRange: range is not wholly contained within specified root node");c=l(c.startContainer,c.startOffset,b)+","+l(c.endContainer,c.endOffset,b);a||(c+="{"+j(b)+"}");return c}function q(c,a,b){if(a)b=b||i.getDocument(a);else{b=b||document;a=b.documentElement}c=/^([^,]+),([^,\{]+)({([^}]+)})?$/.exec(c);var e=c[4],d=j(a);if(e&&e!==j(a))throw Error("deserializeRange: checksums of serialized range root node ("+
+e+") and target root node ("+d+") do not match");e=m(c[1],a,b);a=m(c[2],a,b);b=g.createRange(b);b.setStart(e.node,e.offset);b.setEnd(a.node,a.offset);return b}function r(c,a,b){if(a)b||i.getDocument(a);else{b=b||document;a=b.documentElement}c=/^([^,]+),([^,]+)({([^}]+)})?$/.exec(c)[3];return!c||c===j(a)}function s(c,a,b){c=c||g.getSelection();c=c.getAllRanges();for(var e=[],d=0,f=c.length;d<f;++d)e[d]=p(c[d],a,b);return e.join("|")}function t(c,a,b){if(a)b=b||i.getWindow(a);else{b=b||window;a=b.document.documentElement}c=
+c.split("|");for(var e=g.getSelection(b),d=[],f=0,h=c.length;f<h;++f)d[f]=q(c[f],a,b.document);e.setRanges(d);return e}g.requireModules(["WrappedSelection","WrappedRange"]);if(typeof encodeURIComponent=="undefined"||typeof decodeURIComponent=="undefined")n.fail("Global object is missing encodeURIComponent and/or decodeURIComponent method");var u=function(){var c=null;return function(a){for(var b=[],e=0,d=a.length,f;e<d;++e){f=a.charCodeAt(e);if(f<128)b.push(f);else f<2048?b.push(f>>6|192,f&63|128):
+b.push(f>>12|224,f>>6&63|128,f&63|128)}a=-1;if(!c){e=[];d=0;for(var h;d<256;++d){h=d;for(f=8;f--;)if((h&1)==1)h=h>>>1^3988292384;else h>>>=1;e[d]=h>>>0}c=e}e=c;d=0;for(f=b.length;d<f;++d){h=(a^b[d])&255;a=a>>>8^e[h]}return(a^-1)>>>0}}(),i=g.dom;g.serializePosition=l;g.deserializePosition=m;g.serializeRange=p;g.deserializeRange=q;g.canDeserializeRange=r;g.serializeSelection=s;g.deserializeSelection=t;g.canDeserializeSelection=function(c,a,b){var e;if(a)e=b?b.document:i.getDocument(a);else{b=b||window;
+a=b.document.documentElement}c=c.split("|");b=0;for(var d=c.length;b<d;++b)if(!r(c[b],a,e))return false;return true};g.restoreSelectionFromCookie=function(c){c=c||window;var a;a:{a=c.document.cookie.split(/[;,]/);for(var b=0,e=a.length,d;b<e;++b){d=a[b].split("=");if(d[0].replace(/^\s+/,"")=="rangySerializedSelection")if(d=d[1]){a=decodeURIComponent(d.replace(/\s+$/,""));break a}}a=null}a&&t(a,c.doc)};g.saveSelectionCookie=function(c,a){c=c||window;a=typeof a=="object"?a:{};var b=a.expires?";expires="+
+a.expires.toUTCString():"",e=a.path?";path="+a.path:"",d=a.domain?";domain="+a.domain:"",f=a.secure?";secure":"",h=s(g.getSelection(c));c.document.cookie=encodeURIComponent("rangySerializedSelection")+"="+encodeURIComponent(h)+b+e+d+f};g.getElementChecksum=j});
\ No newline at end of file
diff --git a/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java b/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java
index a9e48f3..4667903 100644
--- a/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java
+++ b/suite/pts/hostTests/browser/browserlauncher/src/com/android/pts/browser/LaunchBrowserTest.java
@@ -35,8 +35,10 @@
 public class LaunchBrowserTest extends PtsAndroidTestCase {
 
     private static final String OCTANE_START_FILE = "octane/index.html";
+    private static final String ROBOHORNET_START_FILE = "robohornet/robohornet.html";
     private static final String HOST_COMPLETION_BROADCAST = "com.android.pts.browser.completion";
-    private static long BROWSER_COMPLETION_TIMEOUT = 10 * 60;
+    // host-side will time-out anyway. So make it long enough.
+    private static long BROWSER_COMPLETION_TIMEOUT = 60 * 60;
     private CtsTestServer mWebServer;
     private HostBroadcastReceiver mReceiver;
 
@@ -65,6 +67,15 @@
         mReceiver.waitForBroadcast(BROWSER_COMPLETION_TIMEOUT);
     }
 
+    public void testRoboHornet() throws InterruptedException {
+        String url = mWebServer.getAssetUrl(ROBOHORNET_START_FILE);
+        Uri uri = Uri.parse(url);
+        Intent intent = new Intent(Intent.ACTION_VIEW, uri);
+        intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+        getContext().startActivity(intent);
+        mReceiver.waitForBroadcast(BROWSER_COMPLETION_TIMEOUT);
+    }
+
     class HostBroadcastReceiver extends BroadcastReceiver {
         private final Semaphore mSemaphore = new Semaphore(0);
 
diff --git a/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java b/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java
index 54f2eb0..5865eba 100644
--- a/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java
+++ b/suite/pts/hostTests/browser/src/com/android/pts/browser/BrowserTest.java
@@ -22,7 +22,8 @@
 import com.android.ddmlib.Log;
 import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
 import com.android.pts.ptsutil.LogcatLineReceiver;
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
 import com.android.tradefed.build.IBuildInfo;
@@ -50,9 +51,10 @@
     private static final String CTS_RUNNER = "android.test.InstrumentationCtsTestRunner";
     private static final String PACKAGE = "com.android.pts.browser";
     private static final String APK = "PtsDeviceBrowserLauncher.apk";
-    private static final long LOGCAT_TIMEOUT_IN_SEC = 10 * 60L;
     private static final String LOGCAT_FILTER = " browser:D chromium:D *:S";
     private static final long REBOOT_WAIT_TIME_IN_MS = 2 * 60 * 1000L;
+    private static final int OCTANE_TIMEOUT_IN_MS = 10 * 60 * 1000;
+    private static final int ROBOHORNET_TIMEOUT_IN_MS = 40 * 60 * 1000;
 
     private CtsBuildHelper mBuild;
     private ITestDevice mDevice;
@@ -60,6 +62,7 @@
     private ReportLog mReport;
 
     private volatile boolean mIgnoreLine = true;
+    private volatile boolean mResultReceived = false;
     private double mResult;
 
     @Override
@@ -88,29 +91,64 @@
     public void testOctane() throws Exception {
         String resultPattern = "([A-Z][\\d\\w]+): ([\\d]+)";
         String summaryPattern = "(Octane Score.*): ([\\d]+)";
-        int numberRepeat = 5;
+        final int numberRepeat = 5;
         double[] results = new double[numberRepeat];
         for (int i = 0; i < numberRepeat; i++) {
             Log.i(TAG, i + "-th round");
             // browser will not refresh if the page is already loaded.
             mDevice.reboot();
             Thread.sleep(REBOOT_WAIT_TIME_IN_MS);
-            results[i] = runBenchmarking("testOctane", resultPattern, summaryPattern);
+            results[i] = runBenchmarking("testOctane", resultPattern, summaryPattern,
+                    OCTANE_TIMEOUT_IN_MS,
+                    ResultType.HIGHER_BETTER, ResultUnit.SCORE,
+                    ResultType.HIGHER_BETTER, ResultUnit.SCORE);
         }
-        mReport.printArray("scores", results, true);
+        mReport.printArray("scores", results, ResultType.HIGHER_BETTER,
+                ResultUnit.SCORE);
         Stat.StatResult stat = Stat.getStat(results);
         // Octane score is higher-better
-        mReport.printSummary("Score", stat.mAverage, PerfResultType.HIGHER_BETTER, stat.mStddev);
+        mReport.printSummary("Octane score", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.SCORE);
+    }
+
+    @TimeoutReq(minutes = 40)
+    public void testRoboHornet() throws Exception {
+        String resultPattern = "([A-Z][\\d\\w]+):([\\d]+[\\.]?[\\d]*)ms";
+        String summaryPattern = "(RoboHornet Score):([\\d]+[\\.]?[\\d]*)";
+        // currently robohornet takes too long to repeat
+        final int numberRepeat = 1;
+        double[] results = new double[numberRepeat];
+        for (int i = 0; i < numberRepeat; i++) {
+            Log.i(TAG, i + "-th round");
+            // browser will not refresh if the page is already loaded.
+            mDevice.reboot();
+            Thread.sleep(REBOOT_WAIT_TIME_IN_MS);
+            results[i] = runBenchmarking("testRoboHornet", resultPattern, summaryPattern,
+                    ROBOHORNET_TIMEOUT_IN_MS,
+                    ResultType.LOWER_BETTER, ResultUnit.MS,
+                    ResultType.HIGHER_BETTER, ResultUnit.SCORE);
+        }
+        mReport.printArray("time for each test", results, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
+        Stat.StatResult stat = Stat.getStat(results);
+        mReport.printSummary("Robohornet Score", stat.mAverage, ResultType.HIGHER_BETTER,
+                ResultUnit.SCORE);
     }
 
     private double runBenchmarking(String testMethodName, String resultPattern,
-            String summaryPattern) throws DeviceNotAvailableException, InterruptedException {
+            String summaryPattern, int timeOutInMs,
+            ResultType resultType, ResultUnit resultUnit,
+            ResultType summaryType, ResultUnit summaryUnit)
+                    throws DeviceNotAvailableException, InterruptedException {
         RemoteAndroidTestRunner testRunner = new RemoteAndroidTestRunner(PACKAGE, CTS_RUNNER,
                 mDevice.getIDevice());
+        testRunner.setMaxtimeToOutputResponse(timeOutInMs);
         testRunner.setMethodName("com.android.pts.browser.LaunchBrowserTest", testMethodName);
         CollectingTestListener listener = new CollectingTestListener();
         mIgnoreLine = true;
-        startLogMonitoring(resultPattern, summaryPattern);
+        mResultReceived = false;
+        startLogMonitoring(resultPattern, summaryPattern, timeOutInMs, resultType, resultUnit,
+                summaryType, summaryUnit);
         // hack to ignore already captured logcat as the monitor will get it again.
         // Checking time and skipping may be a better logic, but simply throwing away also works.
         Thread.sleep(5000);
@@ -125,14 +163,17 @@
             fail("maybe timeout");
         }
         stopLogMonitoring();
+        assertTrue("No result found", mResultReceived);
         return mResult;
     }
 
-    void startLogMonitoring(String resultPattern, String summaryPattern)
+    void startLogMonitoring(String resultPattern, String summaryPattern, int timeOutInMs,
+            final ResultType resultType, final ResultUnit resultUnit,
+            final ResultType summaryType, final ResultUnit summaryUnit)
             throws InterruptedException, DeviceNotAvailableException {
         final Pattern result = Pattern.compile(resultPattern);
         final Pattern summary = Pattern.compile(summaryPattern);
-        mReceiver = new LogcatLineReceiver(mDevice, LOGCAT_FILTER, LOGCAT_TIMEOUT_IN_SEC) {
+        mReceiver = new LogcatLineReceiver(mDevice, LOGCAT_FILTER, timeOutInMs / 1000) {
             @Override
             public void processALine(String line) throws DeviceNotAvailableException {
                 Log.i(TAG, "processALine " + line + " ignore " + mIgnoreLine);
@@ -142,13 +183,16 @@
                 Matcher matchResult = result.matcher(line);
                 if (matchResult.find()) {
                     mReport.printValue(matchResult.group(1),
-                            Double.parseDouble(matchResult.group(2)));
+                            Double.parseDouble(matchResult.group(2)),
+                            resultType, resultUnit);
                 }
                 Matcher matchSummary = summary.matcher(line);
                 if (matchSummary.find()) {
                     mResult = Double.parseDouble(matchSummary.group(2));
-                    mReport.printValue(matchSummary.group(1), mResult);
+                    mReport.printValue(matchSummary.group(1), mResult,
+                            summaryType, summaryUnit);
                     mDevice.executeShellCommand(BROADCAST_CMD);
+                    mResultReceived = true;
                 }
             }
         };
diff --git a/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java b/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
index da9d8e2..5147eaf 100644
--- a/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
+++ b/suite/pts/hostTests/uihost/control/src/com/android/pts/taskswitching/control/TaskswitchingDeviceTest.java
@@ -27,7 +27,8 @@
 
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PerfResultType;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.PtsAndroidTestCase;
 import com.android.pts.util.Stat;
 
@@ -82,10 +83,11 @@
                 }
             }
         });
-        getReportLog().printArray("ms", results, false);
+        getReportLog().printArray("taskswitching time", results, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         Stat.StatResult stat = Stat.getStat(results);
-        getReportLog().printSummary("Time ms", stat.mAverage, PerfResultType.LOWER_BETTER,
-                stat.mStddev);
+        getReportLog().printSummary("taskswitching time", stat.mAverage,
+                ResultType.LOWER_BETTER, ResultUnit.MS);
     }
 
     private void startActivity(String packageName, String activityName) {
diff --git a/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java b/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
index dedf19c..056e0f4 100644
--- a/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
+++ b/suite/pts/hostTests/uihost/src/com/android/pts/uihost/InstallTimeTest.java
@@ -16,38 +16,26 @@
 
 package com.android.pts.uihost;
 
-import android.cts.util.TimeoutReq;
-
 import com.android.cts.tradefed.build.CtsBuildHelper;
-import com.android.ddmlib.Log;
-import com.android.ddmlib.testrunner.RemoteAndroidTestRunner;
-import com.android.ddmlib.testrunner.TestIdentifier;
 import com.android.pts.util.MeasureRun;
 import com.android.pts.util.MeasureTime;
-import com.android.pts.util.PerfResultType;
-import com.android.pts.util.PtsException;
+import com.android.pts.util.ResultType;
+import com.android.pts.util.ResultUnit;
 import com.android.pts.util.ReportLog;
 import com.android.pts.util.Stat;
 import com.android.pts.util.Stat.StatResult;
 import com.android.tradefed.build.IBuildInfo;
-import com.android.tradefed.device.DeviceNotAvailableException;
 import com.android.tradefed.device.ITestDevice;
-import com.android.tradefed.device.TestDeviceOptions;
-import com.android.tradefed.result.CollectingTestListener;
-import com.android.tradefed.result.TestResult;
-import com.android.tradefed.result.TestRunResult;
 import com.android.tradefed.testtype.DeviceTestCase;
 import com.android.tradefed.testtype.IBuildReceiver;
 
 import java.io.File;
-import java.util.Map;
+
 
 /**
  * Test to measure installation time of a APK.
  */
 public class InstallTimeTest extends DeviceTestCase implements IBuildReceiver {
-    private static final String TAG = "InstallTimeTest";
-    private final static String CTS_RUNNER = "android.test.InstrumentationCtsTestRunner";
     private CtsBuildHelper mBuild;
     private ITestDevice mDevice;
     private ReportLog mReport = null;
@@ -90,10 +78,11 @@
                 device.installPackage(app, false);
             }
         });
-        mReport.printArray("time in ms", result, false);
+        mReport.printArray("install time", result, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
         StatResult stat = Stat.getStat(result);
-        mReport.printSummary("time in ms", stat.mAverage, PerfResultType.LOWER_BETTER,
-                stat.mStddev);
+        mReport.printSummary("install time", stat.mAverage, ResultType.LOWER_BETTER,
+                ResultUnit.MS);
     }
 
 }
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/PerfResultType.java b/suite/pts/lib/commonutil/src/com/android/pts/util/PerfResultType.java
deleted file mode 100644
index 0d0f13d..0000000
--- a/suite/pts/lib/commonutil/src/com/android/pts/util/PerfResultType.java
+++ /dev/null
@@ -1,28 +0,0 @@
-/*
- * Copyright (C) 2012 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package com.android.pts.util;
-
-/**
- * Enum for distinguish performance results.
- * HIGHER_BETTER: better performance with higher value
- * LOWER_BETTER: better performance with lower value
- *
- */
-public enum PerfResultType {
-    LOWER_BETTER,
-    HIGHER_BETTER;
-}
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
index 721a196..eff8508 100644
--- a/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ReportLog.java
@@ -25,11 +25,9 @@
  * For now, throws know exception with message.
  *
  * Format:
- * LOG_SEPARATOR : separates each log
- * Message = log [LOG_SEPARATOR log]*
- * log for single value = classMethodName:line_number|header|d|value
- * log for array = classMethodName:line_number|header|da|values|
- *                     average average_value min|max value stddev value
+ * Message = summary log SUMMARY_SEPARATOR [LOG_SEPARATOR log]*
+ * summary = message|unit|type|value
+ * log for array = classMethodName:line_number|message|unit|type|space separated values
  */
 public class ReportLog {
     private static final String LOG_SEPARATOR = "+++";
@@ -41,65 +39,49 @@
     protected static int mDepth = 3;
 
     /**
-     * print given value to the report
-     * @param header string to explain the contents. It can be unit for the value.
-     * @param val
+     * print array of values to output log
      */
-    public void printValue(String header, double val) {
-        String message = getClassMethodNames(mDepth, true) + LOG_ELEM_SEPARATOR + header +
-                LOG_ELEM_SEPARATOR + "d" + LOG_ELEM_SEPARATOR + val;
-        mMessages.add(message);
-        printLog(message);
+    public void printArray(String message, double[] values, ResultType type,
+            ResultUnit unit) {
+        doPrintArray(message, values, type, unit);
     }
 
     /**
-     * array version of printValue
-     * @param header
-     * @param val
-     * @param addMin add minimum to the result. If false, add maximum to the result
+     * Print a value to output log
      */
-    public void printArray(String header, double[] val, boolean addMin) {
+    public void printValue(String message, double value, ResultType type,
+            ResultUnit unit) {
+        double[] vals = { value };
+        doPrintArray(message, vals, type, unit);
+    }
+
+    private void doPrintArray(String message, double[] values, ResultType type,
+    ResultUnit unit) {
         StringBuilder builder = new StringBuilder();
-        builder.append(getClassMethodNames(mDepth, true) + LOG_ELEM_SEPARATOR + header +
-                LOG_ELEM_SEPARATOR + "da" + LOG_ELEM_SEPARATOR);
-        for (double v : val) {
+        // note mDepth + 1 as this function will be called by printVaue or printArray
+        // and we need caller of printValue / printArray
+        builder.append(getClassMethodNames(mDepth + 1, true) + LOG_ELEM_SEPARATOR + message +
+                LOG_ELEM_SEPARATOR + type.getXmlString() + LOG_ELEM_SEPARATOR +
+                unit.getXmlString() + LOG_ELEM_SEPARATOR);
+        for (double v : values) {
             builder.append(v);
             builder.append(" ");
         }
-        Stat.StatResult stat = Stat.getStat(val);
-        builder.append(LOG_ELEM_SEPARATOR + "average " + stat.mAverage +
-                (addMin ? (" min " + stat.mMin) : (" max " + stat.mMax)) + " stddev " + stat.mStddev);
         mMessages.add(builder.toString());
         printLog(builder.toString());
     }
 
     /**
      * For standard report summary with average and stddev
-     * @param header
-     * @param average
+     * @param messsage
+     * @param value
      * @param type type of average value. stddev does not need type.
-     * @param stddev
+     * @param unit unit of the data
      */
-    public void printSummary(String header, double average, PerfResultType type, double stddev) {
-        mSummary = header + LOG_ELEM_SEPARATOR + "average " + average + " " + type.ordinal() +
-                LOG_ELEM_SEPARATOR + "stddev " + stddev + " " +
-                PerfResultType.LOWER_BETTER.ordinal();
-    }
-
-    /**
-     * For a report with two representative values with custom key strings
-     * @param header
-     * @param key1 String key for val1
-     * @param val1
-     * @param val1type
-     * @param key2 String key for val2
-     * @param val2
-     * @param val2type
-     */
-    public void printSummaryFull(String header, String key1, double val1, PerfResultType val1type,
-            String key2, double val2, PerfResultType val2type) {
-        mSummary = header + LOG_ELEM_SEPARATOR + key1 + " " + val1 + " " + val1type.ordinal() +
-                LOG_ELEM_SEPARATOR + key2 + " " + val2 + " " + val2type.ordinal();
+    public void printSummary(String message, double value, ResultType type,
+            ResultUnit unit) {
+        mSummary = message + LOG_ELEM_SEPARATOR + type.getXmlString() + LOG_ELEM_SEPARATOR +
+                unit.getXmlString() + LOG_ELEM_SEPARATOR + value;
     }
 
     public void throwReportToHost() throws PtsException {
@@ -139,9 +121,6 @@
 
     /**
      * array version of calcRatePerSecArray
-     * @param change
-     * @param timeInMSec
-     * @return
      */
     public static double[] calcRatePerSecArray(double change, double[] timeInMSec) {
         double[] result = new double[timeInMSec.length];
@@ -159,9 +138,6 @@
     /**
      * copy array from src to dst with given offset in dst.
      * dst should be big enough to hold src
-     * @param src
-     * @param dst
-     * @param dstOffset
      */
     public static void copyArray(double[] src, double[] dst, int dstOffset) {
         for (int i = 0; i < src.length; i++) {
@@ -171,8 +147,6 @@
 
     /**
      * get classname.methodname from call stack of the current thread
-     *
-     * @return
      */
     public static String getClassMethodNames() {
         return getClassMethodNames(mDepth, false);
@@ -180,14 +154,13 @@
 
     private static String getClassMethodNames(int depth, boolean addLineNumber) {
         StackTraceElement[] elements = Thread.currentThread().getStackTrace();
-        String names = elements[depth].getClassName() + "." + elements[depth].getMethodName() +
+        String names = elements[depth].getClassName() + "#" + elements[depth].getMethodName() +
                 (addLineNumber ? ":" + elements[depth].getLineNumber() : "");
         return names;
     }
 
     /**
      * to be overridden by child to print message to be passed
-     * @param msg
      */
     protected void printLog(String msg) {
 
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java
new file mode 100644
index 0000000..1cbb522
--- /dev/null
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultType.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.util;
+
+/**
+ * Enum for distinguishing performance results.
+ */
+public enum ResultType {
+    /** lower score shows better performance */
+    LOWER_BETTER("lowerBetter"),
+    /** higher score shows better performance */
+    HIGHER_BETTER("higherBetter"),
+    /** This value is not directly correlated with performance. */
+    NEUTRAL("neutral");
+
+    final private String mXmlString;
+    ResultType(String xmlString) {
+        mXmlString = xmlString;
+    }
+
+    /**
+     * Return string used in CTS XML report
+     */
+    public String getXmlString() {
+        return mXmlString;
+    }
+}
diff --git a/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java
new file mode 100644
index 0000000..b234cb5
--- /dev/null
+++ b/suite/pts/lib/commonutil/src/com/android/pts/util/ResultUnit.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.pts.util;
+
+/**
+ * Enum for representing the unit of performance results.
+ *
+ */
+public enum ResultUnit {
+    /** for value with no unit */
+    NONE,
+    /** milli-seconds */
+    MS,
+    /** frames per second */
+    FPS,
+    /** operations per second */
+    OPS,
+    /** kilo-bytes-per-second, not bits-per-second */
+    KBPS,
+    /** mega-bytes-per-second */
+    MBPS,
+    /** amount of data, bytes */
+    BYTE,
+    /** tell how many times it did happen. */
+    COUNT,
+    /** unit for benchmarking with generic score. */
+    SCORE;
+
+    /**
+     * Return string used in CTS XML report
+     */
+    public String getXmlString() {
+        return name().toLowerCase();
+    }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/EncoderTest.java b/tests/tests/media/src/android/media/cts/EncoderTest.java
new file mode 100644
index 0000000..e9d0b5f
--- /dev/null
+++ b/tests/tests/media/src/android/media/cts/EncoderTest.java
@@ -0,0 +1,292 @@
+/*
+ * Copyright (C) 2012 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.media.cts;
+
+import com.android.cts.media.R;
+
+import android.content.Context;
+import android.media.MediaCodec;
+import android.media.MediaCodecInfo;
+import android.media.MediaCodecList;
+import android.media.MediaFormat;
+import android.test.AndroidTestCase;
+import android.util.Log;
+
+import java.nio.ByteBuffer;
+import java.util.LinkedList;
+import java.util.List;
+
+public class EncoderTest extends AndroidTestCase {
+    private static final String TAG = "EncoderTest";
+    private static final boolean VERBOSE = false;
+
+    private static final int kNumInputBytes = 256 * 1024;
+    private static final long kTimeoutUs = 10000;
+
+    @Override
+    public void setContext(Context context) {
+        super.setContext(context);
+    }
+
+    public void testAMRNBEncoders() {
+        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
+
+        final int kBitRates[] =
+            { 4750, 5150, 5900, 6700, 7400, 7950, 10200, 12200 };
+
+        for (int j = 0; j < kBitRates.length; ++j) {
+            MediaFormat format  = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, "audio/3gpp");
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 8000);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
+            formats.push(format);
+        }
+
+        testEncoderWithFormats("audio/3gpp", formats);
+    }
+
+    public void testAMRWBEncoders() {
+        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
+
+        final int kBitRates[] =
+            { 6600, 8850, 12650, 14250, 15850, 18250, 19850, 23050, 23850 };
+
+        for (int j = 0; j < kBitRates.length; ++j) {
+            MediaFormat format  = new MediaFormat();
+            format.setString(MediaFormat.KEY_MIME, "audio/amr-wb");
+            format.setInteger(MediaFormat.KEY_SAMPLE_RATE, 16000);
+            format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, 1);
+            format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
+            formats.push(format);
+        }
+
+        testEncoderWithFormats("audio/amr-wb", formats);
+    }
+
+    public void testAACEncoders() {
+        LinkedList<MediaFormat> formats = new LinkedList<MediaFormat>();
+
+        final int kAACProfiles[] = {
+            2 /* OMX_AUDIO_AACObjectLC */,
+            5 /* OMX_AUDIO_AACObjectHE */,
+            39 /* OMX_AUDIO_AACObjectELD */
+        };
+
+        final int kSampleRates[] = { 8000, 11025, 22050, 44100, 48000 };
+        final int kBitRates[] = { 64000, 128000 };
+
+        for (int k = 0; k < kAACProfiles.length; ++k) {
+            for (int i = 0; i < kSampleRates.length; ++i) {
+                if (kAACProfiles[k] == 5 && kSampleRates[i] < 22050) {
+                    // Is this right? HE does not support sample rates < 22050Hz?
+                    continue;
+                }
+                for (int j = 0; j < kBitRates.length; ++j) {
+                    for (int ch = 1; ch <= 2; ++ch) {
+                        MediaFormat format  = new MediaFormat();
+                        format.setString(MediaFormat.KEY_MIME, "audio/mp4a-latm");
+
+                        format.setInteger(
+                                MediaFormat.KEY_AAC_PROFILE, kAACProfiles[k]);
+
+                        format.setInteger(
+                                MediaFormat.KEY_SAMPLE_RATE, kSampleRates[i]);
+
+                        format.setInteger(MediaFormat.KEY_CHANNEL_COUNT, ch);
+                        format.setInteger(MediaFormat.KEY_BIT_RATE, kBitRates[j]);
+                        formats.push(format);
+                    }
+                }
+            }
+        }
+
+        testEncoderWithFormats("audio/mp4a-latm", formats);
+    }
+
+    private void testEncoderWithFormats(
+            String mime, List<MediaFormat> formats) {
+        List<String> componentNames = getEncoderNamesForType(mime);
+
+        for (String componentName : componentNames) {
+            Log.d(TAG, "testing component '" + componentName + "'");
+            for (MediaFormat format : formats) {
+                Log.d(TAG, "  testing format '" + format + "'");
+                assertEquals(mime, format.getString(MediaFormat.KEY_MIME));
+                testEncoder(componentName, format);
+            }
+        }
+    }
+
+    private List<String> getEncoderNamesForType(String mime) {
+        LinkedList<String> names = new LinkedList<String>();
+
+        int n = MediaCodecList.getCodecCount();
+        for (int i = 0; i < n; ++i) {
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+
+            if (!info.isEncoder()) {
+                continue;
+            }
+
+            if (!info.getName().startsWith("OMX.")) {
+                // Unfortunately for legacy reasons, "AACEncoder", a
+                // non OMX component had to be in this list for the video
+                // editor code to work... but it cannot actually be instantiated
+                // using MediaCodec.
+                Log.d(TAG, "skipping '" + info.getName() + "'.");
+                continue;
+            }
+
+            String[] supportedTypes = info.getSupportedTypes();
+
+            for (int j = 0; j < supportedTypes.length; ++j) {
+                if (supportedTypes[j].equalsIgnoreCase(mime)) {
+                    names.push(info.getName());
+                    break;
+                }
+            }
+        }
+
+        return names;
+    }
+
+    private int queueInputBuffer(
+            MediaCodec codec, ByteBuffer[] inputBuffers, int index) {
+        ByteBuffer buffer = inputBuffers[index];
+        buffer.clear();
+
+        int size = buffer.limit();
+
+        byte[] zeroes = new byte[size];
+        buffer.put(zeroes);
+
+        codec.queueInputBuffer(index, 0 /* offset */, size, 0 /* timeUs */, 0);
+
+        return size;
+    }
+
+    private void dequeueOutputBuffer(
+            MediaCodec codec, ByteBuffer[] outputBuffers,
+            int index, MediaCodec.BufferInfo info) {
+        codec.releaseOutputBuffer(index, false /* render */);
+    }
+
+    private void testEncoder(String componentName, MediaFormat format) {
+        MediaCodec codec = MediaCodec.createByCodecName(componentName);
+
+        try {
+            codec.configure(
+                    format,
+                    null /* surface */,
+                    null /* crypto */,
+                    MediaCodec.CONFIGURE_FLAG_ENCODE);
+        } catch (IllegalStateException e) {
+            Log.e(TAG, "codec '" + componentName + "' failed configuration.");
+
+            assertTrue("codec '" + componentName + "' failed configuration.", false);
+        }
+
+        codec.start();
+        ByteBuffer[] codecInputBuffers = codec.getInputBuffers();
+        ByteBuffer[] codecOutputBuffers = codec.getOutputBuffers();
+
+        int numBytesSubmitted = 0;
+        boolean doneSubmittingInput = false;
+        int numBytesDequeued = 0;
+
+        while (true) {
+            int index;
+
+            if (!doneSubmittingInput) {
+                index = codec.dequeueInputBuffer(kTimeoutUs /* timeoutUs */);
+
+                if (index != MediaCodec.INFO_TRY_AGAIN_LATER) {
+                    if (numBytesSubmitted >= kNumInputBytes) {
+                        codec.queueInputBuffer(
+                                index,
+                                0 /* offset */,
+                                0 /* size */,
+                                0 /* timeUs */,
+                                MediaCodec.BUFFER_FLAG_END_OF_STREAM);
+
+                        if (VERBOSE) {
+                            Log.d(TAG, "queued input EOS.");
+                        }
+
+                        doneSubmittingInput = true;
+                    } else {
+                        int size = queueInputBuffer(
+                                codec, codecInputBuffers, index);
+
+                        numBytesSubmitted += size;
+
+                        if (VERBOSE) {
+                            Log.d(TAG, "queued " + size + " bytes of input data.");
+                        }
+                    }
+                }
+            }
+
+            MediaCodec.BufferInfo info = new MediaCodec.BufferInfo();
+            index = codec.dequeueOutputBuffer(info, kTimeoutUs /* timeoutUs */);
+
+            if (index == MediaCodec.INFO_TRY_AGAIN_LATER) {
+            } else if (index == MediaCodec.INFO_OUTPUT_FORMAT_CHANGED) {
+            } else if (index == MediaCodec.INFO_OUTPUT_BUFFERS_CHANGED) {
+                codecOutputBuffers = codec.getOutputBuffers();
+            } else {
+                dequeueOutputBuffer(codec, codecOutputBuffers, index, info);
+
+                numBytesDequeued += info.size;
+
+                if ((info.flags & MediaCodec.BUFFER_FLAG_END_OF_STREAM) != 0) {
+                    if (VERBOSE) {
+                        Log.d(TAG, "dequeued output EOS.");
+                    }
+                    break;
+                }
+
+                if (VERBOSE) {
+                    Log.d(TAG, "dequeued " + info.size + " bytes of output data.");
+                }
+            }
+        }
+
+        if (VERBOSE) {
+            Log.d(TAG, "queued a total of " + numBytesSubmitted + "bytes, "
+                    + "dequeued " + numBytesDequeued + " bytes.");
+        }
+
+        int sampleRate = format.getInteger(MediaFormat.KEY_SAMPLE_RATE);
+        int channelCount = format.getInteger(MediaFormat.KEY_CHANNEL_COUNT);
+        int inBitrate = sampleRate * channelCount * 16;  // bit/sec
+        int outBitrate = format.getInteger(MediaFormat.KEY_BIT_RATE);
+
+        float desiredRatio = (float)outBitrate / (float)inBitrate;
+        float actualRatio = (float)numBytesDequeued / (float)numBytesSubmitted;
+
+        if (actualRatio < 0.9 * desiredRatio || actualRatio > 1.1 * desiredRatio) {
+            Log.w(TAG, "desiredRatio = " + desiredRatio
+                    + ", actualRatio = " + actualRatio);
+        }
+
+        codec.release();
+        codec = null;
+    }
+}
+
diff --git a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
index e6abe31..0973c1a 100755
--- a/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaCodecListTest.java
@@ -17,10 +17,11 @@
 package android.media.cts;
 
 
-import android.media.MediaCodecList;
+import android.media.MediaCodec;
 import android.media.MediaCodecInfo;
 import android.media.MediaCodecInfo.CodecProfileLevel;
 import android.media.MediaCodecInfo.CodecCapabilities;
+import android.media.MediaCodecList;
 import android.test.AndroidTestCase;
 import android.util.Log;
 
@@ -53,6 +54,63 @@
         assertTrue("/etc/media_codecs.xml does not exist", file.exists());
     }
 
+    // Each component advertised by MediaCodecList should at least be
+    // instantiate-able.
+    public void testComponentInstantiation() {
+        Log.d(TAG, "testComponentInstantiation");
+
+        int codecCount = MediaCodecList.getCodecCount();
+        for (int i = 0; i < codecCount; ++i) {
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+
+            Log.d(TAG, (i + 1) + ": " + info.getName());
+            Log.d(TAG, "  isEncoder = " + info.isEncoder());
+
+            if (!info.getName().startsWith("OMX.")) {
+                // Unfortunately for legacy reasons, "AACEncoder", a
+                // non OMX component had to be in this list for the video
+                // editor code to work... but it cannot actually be instantiated
+                // using MediaCodec.
+                Log.d(TAG, "  skipping...");
+                continue;
+            }
+
+            MediaCodec codec = MediaCodec.createByCodecName(info.getName());
+
+            codec.release();
+            codec = null;
+        }
+    }
+
+    // For each type advertised by any of the components we should be able
+    // to get capabilities.
+    public void testGetCapabilities() {
+        Log.d(TAG, "testGetCapabilities");
+
+        int codecCount = MediaCodecList.getCodecCount();
+        for (int i = 0; i < codecCount; ++i) {
+            MediaCodecInfo info = MediaCodecList.getCodecInfoAt(i);
+
+            Log.d(TAG, (i + 1) + ": " + info.getName());
+            Log.d(TAG, "  isEncoder = " + info.isEncoder());
+
+            if (!info.getName().startsWith("OMX.")) {
+                // Unfortunately for legacy reasons, "AACEncoder", a
+                // non OMX component had to be in this list for the video
+                // editor code to work... but it cannot actually be instantiated
+                // using MediaCodec.
+                Log.d(TAG, "  skipping...");
+                continue;
+            }
+
+            String[] types = info.getSupportedTypes();
+            for (int j = 0; j < types.length; ++j) {
+                Log.d(TAG, "calling getCapabilitiesForType " + types[j]);
+                CodecCapabilities cap = info.getCapabilitiesForType(types[j]);
+            }
+        }
+    }
+
     public void testRequiredMediaCodecList() {
         List<CodecType> requiredList = getRequiredCodecTypes();
         List<CodecType> supportedList = getSupportedCodecTypes();
diff --git a/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java b/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
index f714a07..c13f041 100644
--- a/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
+++ b/tests/tests/media/src/android/media/cts/MediaScannerNotificationTest.java
@@ -22,6 +22,12 @@
 import android.os.Environment;
 import android.test.AndroidTestCase;
 
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
+
 public class MediaScannerNotificationTest extends AndroidTestCase {
 
     public void testMediaScannerNotification() throws Exception {
@@ -38,10 +44,81 @@
         mContext.registerReceiver(startedReceiver, startedIntentFilter);
         mContext.registerReceiver(finishedReceiver, finshedIntentFilter);
 
+        String [] temps = new String[] { "avi", "gif", "jpg", "dat", "mp3", "mp4", "txt" };
+        String tmpPath = createTempFiles(temps);
+
         mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
                 + Environment.getExternalStorageDirectory())));
 
         startedReceiver.waitForBroadcast();
         finishedReceiver.waitForBroadcast();
+
+        checkTempFiles(tmpPath, temps);
+
+        // add .nomedia file and scan again
+        File noMedia = new File(tmpPath, ".nomedia");
+        try {
+            noMedia.createNewFile();
+        } catch (IOException e) {
+            fail("couldn't create .nomedia file");
+        }
+        startedReceiver.reset();
+        finishedReceiver.reset();
+        mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+                + Environment.getExternalStorageDirectory())));
+        startedReceiver.waitForBroadcast();
+        finishedReceiver.waitForBroadcast();
+
+        checkTempFiles(tmpPath, temps);
+        assertTrue(noMedia.delete());
+        deleteTempFiles(tmpPath, temps);
+
+        // scan one more time just to clean everything up nicely
+        startedReceiver.reset();
+        finishedReceiver.reset();
+        mContext.sendBroadcast(new Intent(Intent.ACTION_MEDIA_MOUNTED, Uri.parse("file://"
+                + Environment.getExternalStorageDirectory())));
+        startedReceiver.waitForBroadcast();
+        finishedReceiver.waitForBroadcast();
+
+    }
+
+    String createTempFiles(String [] extensions) {
+        String externalPath = Environment.getExternalStorageDirectory().getAbsolutePath();
+        File tmpDir = new File(externalPath, "" + System.nanoTime());
+        String tmpPath = tmpDir.getAbsolutePath();
+        assertFalse(tmpPath + " already exists", tmpDir.exists());
+        assertTrue("failed to create " + tmpDir, tmpDir.mkdirs());
+
+        for (int i = 0; i < extensions.length; i++) {
+            File foo = new File(tmpPath, "foobar." + extensions[i]);
+            try {
+                // create a non-empty file
+                foo.createNewFile();
+                FileOutputStream out = new FileOutputStream(foo);
+                out.write(0x12);
+                out.flush();
+                out.close();
+                assertTrue(foo.length() != 0);
+            } catch (IOException e) {
+                fail("Error creating " + foo.getAbsolutePath() + ": " + e);
+            }
+        }
+        return tmpPath;
+    }
+
+    void checkTempFiles(String tmpPath, String [] extensions) {
+        for (int i = 0; i < extensions.length; i++) {
+            File foo = new File(tmpPath, "foobar." + extensions[i]);
+            assertTrue(foo.getAbsolutePath() + " no longer exists or was truncated",
+                    foo.length() != 0);
+        }
+    }
+
+    void deleteTempFiles(String tmpPath, String [] extensions) {
+        for (int i = 0; i < extensions.length; i++) {
+            assertTrue(new File(tmpPath, "foobar." + extensions[i]).delete());
+        }
+        assertTrue(new File(tmpPath).delete());
     }
 }
diff --git a/tests/tests/media/src/android/media/cts/ScannerNotificationReceiver.java b/tests/tests/media/src/android/media/cts/ScannerNotificationReceiver.java
index 4357bee..9d91671 100644
--- a/tests/tests/media/src/android/media/cts/ScannerNotificationReceiver.java
+++ b/tests/tests/media/src/android/media/cts/ScannerNotificationReceiver.java
@@ -30,7 +30,7 @@
     private static final int TIMEOUT_MS = 4 * 60 * 1000;
 
     private final String mAction;
-    private final CountDownLatch mLatch = new CountDownLatch(1);
+    private CountDownLatch mLatch = new CountDownLatch(1);
 
     ScannerNotificationReceiver(String action) {
         mAction = action;
@@ -51,6 +51,10 @@
         }
     }
 
+    void reset() {
+        mLatch = new CountDownLatch(1);
+    }
+
     private int countFiles(File dir) {
         int count = 0;
         File[] files = dir.listFiles();
@@ -65,4 +69,4 @@
         }
         return count;
     }
-}
\ No newline at end of file
+}
diff --git a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
index 42991d8..4010d41 100644
--- a/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
+++ b/tests/tests/webkit/src/android/webkit/cts/WebSettingsTest.java
@@ -435,13 +435,6 @@
         assertEquals(WebSettings.LayoutAlgorithm.SINGLE_COLUMN, mSettings.getLayoutAlgorithm());
     }
 
-    public void testAccessLightTouchEnabled() {
-        assertFalse(mSettings.getLightTouchEnabled());
-
-        mSettings.setLightTouchEnabled(true);
-        assertTrue(mSettings.getLightTouchEnabled());
-    }
-
     public void testAccessMinimumFontSize() {
         assertEquals(8, mSettings.getMinimumFontSize());
 
diff --git a/tests/tests/widget/src/android/widget/cts/HeaderViewListAdapterTest.java b/tests/tests/widget/src/android/widget/cts/HeaderViewListAdapterTest.java
index e583dce..5426ae9 100644
--- a/tests/tests/widget/src/android/widget/cts/HeaderViewListAdapterTest.java
+++ b/tests/tests/widget/src/android/widget/cts/HeaderViewListAdapterTest.java
@@ -81,6 +81,21 @@
         HeaderViewFullAdapter fullAdapter = new HeaderViewFullAdapter();
         headerViewListAdapter = new HeaderViewListAdapter(null, null, fullAdapter);
         assertFalse(headerViewListAdapter.isEmpty());
+
+        ListView lv = new ListView(getContext());
+        ArrayList<ListView.FixedViewInfo> header = new ArrayList<ListView.FixedViewInfo>(4);
+        header.add(lv.new FixedViewInfo());
+        headerViewListAdapter = new HeaderViewListAdapter(header, null, null);
+        assertEquals(1, headerViewListAdapter.getHeadersCount());
+        assertFalse(headerViewListAdapter.isEmpty());
+
+        lv = new ListView(getContext());
+        ArrayList<ListView.FixedViewInfo> footer = new ArrayList<ListView.FixedViewInfo>(4);
+        footer.add(lv.new FixedViewInfo());
+        headerViewListAdapter = new HeaderViewListAdapter(null, footer, null);
+        assertEquals(1, headerViewListAdapter.getFootersCount());
+        assertFalse(headerViewListAdapter.isEmpty());
+
     }
 
     public void testRemoveHeader() {
diff --git a/tests/uiautomator/Android.mk b/tests/uiautomator/Android.mk
index b5bfae3..68b1dc2 100644
--- a/tests/uiautomator/Android.mk
+++ b/tests/uiautomator/Android.mk
@@ -22,7 +22,7 @@
 LOCAL_SRC_FILES := $(call all-java-files-under, src)
 
 LOCAL_MODULE := CtsUiAutomatorTests
-LOCAL_JAVA_LIBRARIES := uiautomator_sdk_v16
+LOCAL_JAVA_LIBRARIES := uiautomator_sdk_v17
 LOCAL_PROGUARD_ENABLED := disabled
 LOCAL_CTS_TEST_APK := CtsUiAutomatorApp
 LOCAL_CTS_TEST_APP_PACKAGE := com.android.cts.uiautomator
diff --git a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
index 1595e93..5de9805 100644
--- a/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
+++ b/tests/uiautomator/src/com/android/cts/uiautomatortest/CtsUiAutomatorTest.java
@@ -16,6 +16,7 @@
 package com.android.cts.uiautomatortest;
 
 import android.graphics.Rect;
+import android.os.RemoteException;
 import android.os.SystemClock;
 import android.util.Log;
 
@@ -29,6 +30,7 @@
 import com.android.uiautomator.testrunner.UiAutomatorTestCase;
 
 import java.io.BufferedReader;
+import java.io.File;
 import java.io.IOException;
 
 /**
@@ -37,7 +39,7 @@
 public class CtsUiAutomatorTest extends UiAutomatorTestCase {
     private static final String LOG_TAG = CtsUiAutomatorTest.class.getSimpleName();
     private static final String[] LIST_SCROLL_TESTS = new String[] {
-            "Test 17", "Test 11", "Test 30", "Test 29"
+            "Test 17", "Test 11", "Test 20"
     };
     private static final String LAUNCH_APP = "am start -a android.intent.action.MAIN"
             + " -n com.android.cts.uiautomator/.MainActivity -W";
@@ -46,6 +48,8 @@
     // Maximum wait for key object to become visible
     private static final int WAIT_EXIST_TIMEOUT = 5 * 1000;
 
+    private static final String SCREEN_SHOT_FILE_PATH_NAME = "/data/local/tmp/ctsScreenShot";
+
     @Override
     protected void setUp() throws Exception {
         super.setUp();
@@ -85,6 +89,13 @@
      * scroll is required to reach each item at each of the far ends.
      */
     public void testListScrollAndSelect() throws UiObjectNotFoundException {
+        UiScrollable listView = new UiScrollable(
+                new UiSelector().className(android.widget.ListView.class.getName()));
+
+        // on single fragment display
+        if (!listView.exists())
+            UiDevice.getInstance().pressBack();
+
         for (String test : LIST_SCROLL_TESTS) {
             openTest(test);
             verifyTestDetailsExists(test);
@@ -230,6 +241,7 @@
         assertFalse("Wait for exist must return false after press back", result.exists());
 
         // Home button test
+        openTest("Test 5");
         String pkgName = device.getCurrentPackageName();
         assertTrue("CTS test app must be running", pkgName.equals(PKG_NAME));
         device.pressHome();
@@ -363,6 +375,7 @@
      *
      * @throws UiObjectNotFoundException
      */
+    /*// broken in MR1
     public void testWebViewTextTraversal() throws UiObjectNotFoundException {
         openTest("Test 6");
         UiObject webView = new UiObject(new UiSelector().className(android.webkit.WebView.class
@@ -390,7 +403,7 @@
         device.pressDPadDown();
         text = device.getLastTraversedText();
         assertTrue("h4 text", text.contains("h4"));
-    }
+    }*/
 
     /**
      * Test when an object does not exist, an exception is thrown
@@ -608,6 +621,166 @@
     }
 
     /**
+     * Test Orientation APIs by causing rotations and verifying current state
+     *
+     * @throws RemoteException
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testRotation() throws RemoteException, UiObjectNotFoundException {
+        openTest("Test 5");
+        UiDevice device = UiDevice.getInstance();
+
+        device.setOrientationLeft();
+        device.waitForIdle(); // isNaturalOrientation is not waiting for idle
+        SystemClock.sleep(1000);
+        assertFalse("Device orientation should not be natural", device.isNaturalOrientation());
+
+        device.setOrientationNatural();
+        device.waitForIdle(); // isNaturalOrientation is not waiting for idle
+        SystemClock.sleep(1000);
+        assertTrue("Device orientation should be natural", device.isNaturalOrientation());
+
+        device.setOrientationRight();
+        device.waitForIdle(); // isNaturalOrientation is not waiting for idle
+        SystemClock.sleep(1000);
+        assertFalse("Device orientation should not be natural", device.isNaturalOrientation());
+
+        device.setOrientationNatural();
+    }
+
+    /**
+     * Reads the current device's product name. Since it is not possible to predetermine the
+     * would be value, the check verifies that the value is not null and not empty.
+     *
+     * @since API Level 17
+     */
+    public void testGetProductName() {
+        String name = UiDevice.getInstance().getProductName();
+        assertFalse("Product name check returned empty string", name.isEmpty());
+    }
+
+    /**
+     * Select each of the buttons by using only regex text
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testSelectByTextMatch() throws UiObjectNotFoundException {
+        openTest("Test 2");
+        getObjectByTextMatch(".*n\\s1$").click();
+        verifyDialogActionResults("Button 1");
+        getObjectByTextMatch(".*n\\s2$").click();
+        verifyDialogActionResults("Button 2");
+        getObjectByTextMatch(".*n\\s3$").click();
+        verifyDialogActionResults("Button 3");
+    }
+
+    /**
+     * Select each of the buttons by using only regex content-description
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testSelectByDescriptionMatch() throws UiObjectNotFoundException {
+        openTest("Test 2");
+        getObjectByDescriptionMatch(".*n\\s1$").click();
+        verifyDialogActionResults("Button 1");
+        getObjectByDescriptionMatch(".*n\\s2$").click();
+        verifyDialogActionResults("Button 2");
+        getObjectByDescriptionMatch(".*n\\s3$").click();
+        verifyDialogActionResults("Button 3");
+    }
+
+    /**
+     * Select each of the buttons by using only regex class name
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testSelectByClassMatch() throws UiObjectNotFoundException {
+        openTest("Test 5");
+        UiObject tgl = getObjectByClassMatch(".*ToggleButton$", 0);
+        String tglValue = tgl.getText();
+        tgl.click();
+
+        assertFalse("Matching class by Regex failed", tglValue.equals(tgl.getText()));
+    }
+
+    /**
+     * Select each of the buttons by using only class type
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testSelectByClassType() throws UiObjectNotFoundException {
+        openTest("Test 5");
+        UiObject tgl = getObjectByClass(android.widget.ToggleButton.class, 0);
+        String tglValue = tgl.getText();
+        tgl.click();
+
+        assertFalse("Matching class by class type failed", tglValue.equals(tgl.getText()));
+    }
+
+    /**
+     * Test the coordinates of 3 buttons side by side verifying vertical and
+     * horizontal coordinates.
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testGetVisibleBounds() throws UiObjectNotFoundException {
+        openTest("Test 2");
+        Rect rect1 = getObjectByText("Button 1").getVisibleBounds();
+        Rect rect2 = getObjectByText("Button 2").getVisibleBounds();
+        Rect rect3 = getObjectByText("Button 3").getVisibleBounds();
+
+        assertTrue("X coordinate check failed",
+                rect1.left < rect2.left && rect2.right < rect3.right);
+        assertTrue("Y coordinate check failed",
+                rect1.top == rect2.top && rect2.bottom == rect3.bottom);
+    }
+
+   /**
+     * Tests the LongClick functionality in the API
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testSelectorLongClickable() throws UiObjectNotFoundException {
+        openTest("Test 2");
+        getObjectByText("Button 1").longClick();
+        verifyDialogActionResults("Longclick Button 1");
+    }
+
+    /**
+     * Test the UiSelector's long-clickable property
+     *
+     * @throws UiObjectNotFoundException
+     * @since API Level 17
+     */
+    public void testSelectorLongClickableProperty() throws UiObjectNotFoundException {
+        UiObject button3 = new UiObject(new UiSelector().className(
+                android.widget.Button.class).longClickable(true).instance(2));
+        button3.longClick();
+        verifyDialogActionResults("Longclick Button 3");
+    }
+
+    /**
+     * Takes a screen shot of the current display and checks if the file is
+     * created and is not zero size.
+     *
+     * @since API Level 17
+     */
+    public void testTakeScreenShots() {
+        File storePath = new File(SCREEN_SHOT_FILE_PATH_NAME);
+        getUiDevice().takeScreenshot(storePath);
+
+        assertTrue("Screenshot file not detected in store", storePath.exists());
+        assertTrue("Zero size for screenshot file", storePath.length() > 0);
+    }
+
+    /**
      * Private helper to open test views. Also covers UiScrollable tests
      *
      * @param name
@@ -636,10 +809,26 @@
         return new UiObject(new UiSelector().text(txt));
     }
 
+    private UiObject getObjectByTextMatch(String regex) {
+        return new UiObject(new UiSelector().textMatches(regex));
+    }
+
+    private UiObject getObjectByDescriptionMatch(String regex) {
+        return new UiObject(new UiSelector().descriptionMatches(regex));
+    }
+
     private UiObject getObjectByDescription(String txt) {
         return new UiObject(new UiSelector().description(txt));
     }
 
+    private UiObject getObjectByClassMatch(String regex, int instance) {
+        return new UiObject(new UiSelector().classNameMatches(regex).instance(instance));
+    }
+
+    private <T> UiObject getObjectByClass(Class<T> type, int instance) {
+        return new UiObject(new UiSelector().className(type).instance(instance));
+    }
+
     private UiObject getObjectByIndex(String className, int index) {
         return new UiObject(new UiSelector().className(className).index(index));
     }
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
index e3689f7..db2450a 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/res/values/strings.xml
@@ -31,8 +31,11 @@
     <string name="test2_description_2">This tests a Button text changing dynamically after a click. The test should attempt to find the button after it has changed using its new text.</string>
     <string name="test3_description">The tests should read the clock, then write the clock back into the EditText then press submit. A dialog will display the time it took from reading the clock until submit is press. This can be used to get an idea of performance differences.</string>
     <string name="button1">Button 1</string>
+    <string name="button1long">Longclick Button 1</string>
     <string name="button2">Button 2</string>
+    <string name="button2long">Longclick Button 2</string>
     <string name="button3">Button 3</string>
+    <string name="button3long">Longclick Button 3</string>
     <string name="buttonBefore">Before</string>
     <string name="buttonAfter">After</string>
     <string name="dialog_title_result">Action results</string>
diff --git a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
index cec98b5..4eade4b 100644
--- a/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
+++ b/tests/uiautomator/test-apps/CtsUiAutomatorApp/src/com/android/cts/uiautomator/Test2DetailFragment.java
@@ -78,6 +78,19 @@
             }
         });
 
+        mButton1.setOnLongClickListener(new Button.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+                builder.setTitle(R.string.dialog_title_result);
+                builder.setPositiveButton(R.string.OK, null);
+                builder.setMessage(R.string.button1long);
+                AlertDialog diag = builder.create();
+                diag.show();
+                return true;
+            }
+        });
+
         mButton2.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -90,6 +103,19 @@
             }
         });
 
+        mButton2.setOnLongClickListener(new Button.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+                builder.setTitle(R.string.dialog_title_result);
+                builder.setPositiveButton(R.string.OK, null);
+                builder.setMessage(R.string.button2long);
+                AlertDialog diag = builder.create();
+                diag.show();
+                return true;
+            }
+        });
+
         mButton3.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
@@ -102,6 +128,19 @@
             }
         });
 
+        mButton3.setOnLongClickListener(new Button.OnLongClickListener() {
+            @Override
+            public boolean onLongClick(View v) {
+                AlertDialog.Builder builder = new AlertDialog.Builder(getActivity());
+                builder.setTitle(R.string.dialog_title_result);
+                builder.setPositiveButton(R.string.OK, null);
+                builder.setMessage(R.string.button3long);
+                AlertDialog diag = builder.create();
+                diag.show();
+                return true;
+            }
+        });
+
         mDynaButton.setOnClickListener(new Button.OnClickListener() {
             @Override
             public void onClick(View v) {
diff --git a/tools/tradefed-host/res/report/cts_result.xsd b/tools/tradefed-host/res/report/cts_result.xsd
index 74eec37..826fbc5 100644
--- a/tools/tradefed-host/res/report/cts_result.xsd
+++ b/tools/tradefed-host/res/report/cts_result.xsd
@@ -16,8 +16,8 @@
  -->
 
 <xs:schema xmlns:xs="http://www.w3.org/2001/XMLSchema"
-           targetNamespace="http://compatibility.android.com/cts_result/1.14"
-           xmlns="http://compatibility.android.com/cts_result/1.14"
+           targetNamespace="http://compatibility.android.com/cts_result/1.15"
+           xmlns="http://compatibility.android.com/cts_result/1.15"
            elementFormDefault="qualified">
 
 <xs:element name="TestResult">
@@ -196,6 +196,28 @@
   <xs:attribute name="priority" type="xs:string"/>
 </xs:complexType>
 
+<xs:simpleType name="unitType">
+  <xs:restriction base="xs:string">
+    <xs:enumeration value="none"/>
+    <xs:enumeration value="ms"/>
+    <xs:enumeration value="fps"/>
+    <xs:enumeration value="ops"/>
+    <xs:enumeration value="kbps"/>
+    <xs:enumeration value="mbps"/>
+    <xs:enumeration value="byte"/>
+    <xs:enumeration value="count"/>
+    <xs:enumeration value="score"/>
+  </xs:restriction>
+</xs:simpleType>
+
+<xs:simpleType name="scoreTypeType">
+  <xs:restriction base="xs:string">
+    <xs:enumeration value="higherBetter"/>
+    <xs:enumeration value="lowerBetter"/>
+    <xs:enumeration value="neutral"/>
+  </xs:restriction>
+</xs:simpleType>
+
 <xs:complexType name="testType">
   <xs:sequence>
     <xs:element name="FailedScene" minOccurs="0" maxOccurs="1">
@@ -206,6 +228,34 @@
         <xs:attribute name="message" type="xs:string"/>
       </xs:complexType>
     </xs:element>
+    <xs:element name="Summary" minOccurs="0" maxOccurs="1">
+      <xs:complexType>
+        <xs:simpleContent>
+          <xs:extension base="xs:decimal">
+            <xs:attribute name="message" type="xs:string" use="required" />
+            <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+            <xs:attribute name="unit" type="unitType" use="required" />
+          </xs:extension>
+        </xs:simpleContent>
+      </xs:complexType>
+    </xs:element>
+    <xs:element name="Details" minOccurs="0" maxOccurs="1">
+      <xs:complexType>
+        <xs:sequence>
+          <xs:element name="ValueArray" minOccurs="0" maxOccurs="unbounded">
+            <xs:complexType>
+              <xs:sequence>
+                <xs:element name="Value" type="xs:decimal" minOccurs="0" maxOccurs="unbounded" />
+              </xs:sequence>
+              <xs:attribute name="source" type="xs:string" use="required" />
+              <xs:attribute name="message" type="xs:string" use="required" />
+              <xs:attribute name="scoreType" type="scoreTypeType" use="required" />
+              <xs:attribute name="unit" type="unitType" use="required" />
+            </xs:complexType>
+          </xs:element>
+        </xs:sequence>
+      </xs:complexType>
+    </xs:element>
   </xs:sequence>
   <xs:attribute name="name" type="xs:string" use="required"/>
   <xs:attribute name="result" type="resultType" use="required"/>
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
index dd4f31f..ab88908 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/Test.java
@@ -15,6 +15,7 @@
  */
 package com.android.cts.tradefed.result;
 
+import com.android.ddmlib.Log;
 import com.android.tradefed.result.TestResult;
 
 import org.kxml2.io.KXmlSerializer;
@@ -27,7 +28,6 @@
  * Data structure that represents a "Test" result XML element.
  */
 class Test extends AbstractXmlPullParser {
-
     static final String TAG = "Test";
     private static final String NAME_ATTR = "name";
     private static final String MESSAGE_ATTR = "message";
@@ -36,7 +36,16 @@
     private static final String RESULT_ATTR = "result";
     private static final String SCENE_TAG = "FailedScene";
     private static final String STACK_TAG = "StackTrace";
+    private static final String SUMMARY_TAG = "Summary";
     private static final String DETAILS_TAG = "Details";
+    private static final String VALUEARRAY_TAG = "ValueArray";
+    private static final String VALUE_TAG = "Value";
+    private static final String SCORETYPE_ATTR = "scoreType";
+    private static final String UNIT_ATTR = "unit";
+    private static final String SOURCE_ATTR = "source";
+    // separators for the message from PTS
+    private static final String LOG_SEPARATOR = "\\+\\+\\+";
+    private static final String LOG_ELEM_SEPARATOR = "\\|";
 
     private String mName;
     private CtsTestStatus mResult;
@@ -44,7 +53,8 @@
     private String mEndTime;
     private String mMessage;
     private String mStackTrace;
-    // details passed from pts
+    // summary and details passed from pts
+    private String mSummary;
     private String mDetails;
 
     /**
@@ -109,6 +119,14 @@
         mMessage = getFailureMessageFromStackTrace(mStackTrace);
     }
 
+    public String getSummary() {
+        return mSummary;
+    }
+
+    public void setSummary(String summary) {
+        mSummary = summary;
+    }
+
     public String getDetails() {
         return mDetails;
     }
@@ -147,17 +165,110 @@
                 serializer.text(mStackTrace);
                 serializer.endTag(CtsXmlResultReporter.ns, STACK_TAG);
             }
-            if (mDetails != null) {
-                serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
-                serializer.text(mDetails);
-                serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
-            }
             serializer.endTag(CtsXmlResultReporter.ns, SCENE_TAG);
         }
+        if (mSummary != null) {
+            // <Summary message = "screen copies per sec" scoretype="higherBetter" unit="fps">
+            // 23938.82978723404</Summary>
+            PerfResultSummary summary = parseSummary(mSummary);
+            if (summary != null) {
+                serializer.startTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
+                serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR, summary.mMessage);
+                serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, summary.mType);
+                serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, summary.mUnit);
+                serializer.text(summary.mValue);
+                serializer.endTag(CtsXmlResultReporter.ns, SUMMARY_TAG);
+                // add details only if summary is present
+                // <Details>
+                //   <ValueArray source=”com.android.pts.dram.BandwidthTest#doRunMemcpy:98”
+                //                    message=”measure1” unit="ms" scoretype="higherBetter">
+                //     <Value>0.0</Value>
+                //     <Value>0.1</Value>
+                //   </ValueArray>
+                // </Details>
+                if (mDetails != null) {
+                    PerfResultDetail[] ds = parseDetails(mDetails);
+                    serializer.startTag(CtsXmlResultReporter.ns, DETAILS_TAG);
+                        for (PerfResultDetail d : ds) {
+                            if (d == null) {
+                                continue;
+                            }
+                            serializer.startTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
+                            serializer.attribute(CtsXmlResultReporter.ns, SOURCE_ATTR, d.mSource);
+                            serializer.attribute(CtsXmlResultReporter.ns, MESSAGE_ATTR,
+                                    d.mMessage);
+                            serializer.attribute(CtsXmlResultReporter.ns, SCORETYPE_ATTR, d.mType);
+                            serializer.attribute(CtsXmlResultReporter.ns, UNIT_ATTR, d.mUnit);
+                            for (String v : d.mValues) {
+                                if (v == null) {
+                                    continue;
+                                }
+                                serializer.startTag(CtsXmlResultReporter.ns, VALUE_TAG);
+                                serializer.text(v);
+                                serializer.endTag(CtsXmlResultReporter.ns, VALUE_TAG);
+                            }
+                            serializer.endTag(CtsXmlResultReporter.ns, VALUEARRAY_TAG);
+                        }
+                    serializer.endTag(CtsXmlResultReporter.ns, DETAILS_TAG);
+                }
+            }
+        }
         serializer.endTag(CtsXmlResultReporter.ns, TAG);
     }
 
     /**
+     *  class containing performance result.
+     */
+    public static class PerfResultCommon {
+        public String mMessage;
+        public String mType;
+        public String mUnit;
+    }
+
+    private class PerfResultSummary extends PerfResultCommon {
+        public String mValue;
+    }
+
+    private class PerfResultDetail extends PerfResultCommon {
+        public String mSource;
+        public String[] mValues;
+    }
+
+    private PerfResultSummary parseSummary(String summary) {
+        String[] elems = summary.split(LOG_ELEM_SEPARATOR);
+        PerfResultSummary r = new PerfResultSummary();
+        if (elems.length < 4) {
+            Log.w(TAG, "wrong message " + summary);
+            return null;
+        }
+        r.mMessage = elems[0];
+        r.mType = elems[1];
+        r.mUnit = elems[2];
+        r.mValue = elems[3];
+        return r;
+    }
+
+    private PerfResultDetail[] parseDetails(String details) {
+        String[] arrays = details.split(LOG_SEPARATOR);
+        PerfResultDetail[] rs = new PerfResultDetail[arrays.length];
+        for (int i = 0; i < arrays.length; i++) {
+            String[] elems = arrays[i].split(LOG_ELEM_SEPARATOR);
+            if (elems.length < 5) {
+                Log.w(TAG, "wrong message " + arrays[i]);
+                continue;
+            }
+            PerfResultDetail r = new PerfResultDetail();
+            r.mSource = elems[0];
+            r.mMessage = elems[1];
+            r.mType = elems[2];
+            r.mUnit = elems[3];
+            r.mValues = elems[4].split(" ");
+            rs[i] = r;
+        }
+        return rs;
+    }
+
+    /**
      * Strip out any invalid XML characters that might cause the report to be unviewable.
      * http://www.w3.org/TR/REC-xml/#dt-character
      */
diff --git a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
index ce1c2f3..015a7ae 100644
--- a/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
+++ b/tools/tradefed-host/src/com/android/cts/tradefed/result/TestPackageResult.java
@@ -283,7 +283,7 @@
     public void reportPerformanceResult(TestIdentifier test, CtsTestStatus status, String summary, String details) {
         Test result = findTest(test);
         result.setResultStatus(status);
-        result.setMessage(summary);
+        result.setSummary(summary);
         result.setDetails(details);
     }