Adding benchmarks to measure the performance of updating existing tables.
Also refactored WidgetCreationTest into a more generic MicrobenchmarkSurvey,
which runs a set of NanoTests.
You can see the app running here: http://microb.jlabanca-testing.appspot.com/

Review at http://gwt-code-reviews.appspot.com/1419801


git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@10528 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/Microbenchmarks.gwt.xml b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/Microbenchmarks.gwt.xml
index a9c43cc..7b85430 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/Microbenchmarks.gwt.xml
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/Microbenchmarks.gwt.xml
@@ -1,16 +1,7 @@
 <?xml version="1.0" encoding="UTF-8"?>
 <module rename-to='microbenchmarks'>
-  <!-- Inherit the core Web Toolkit stuff.                        -->
   <inherits name='com.google.gwt.user.User'/>
-
-  <!-- Inherit the default GWT style sheet.  You can change       -->
-  <!-- the theme of your GWT application by uncommenting          -->
-  <!-- any one of the following lines.                            -->
   <inherits name='com.google.gwt.user.theme.standard.Standard'/>
-  <!-- <inherits name='com.google.gwt.user.theme.chrome.Chrome'/> -->
-  <!-- <inherits name='com.google.gwt.user.theme.dark.Dark'/>     -->
-
-  <!-- Other module inherits                                      -->
 
   <!-- Specify the app entry point class.                         -->
   <entry-point class='com.google.gwt.reference.microbenchmark.client.Microbenchmarks'/>
@@ -18,4 +9,14 @@
   <!-- Specify the paths for translatable code                    -->
   <source path='client'/>
 
+  <!-- Deferred binding for Util. -->
+  <replace-with class="com.google.gwt.reference.microbenchmark.client.UtilImplTrident">
+    <when-type-is class="com.google.gwt.reference.microbenchmark.client.UtilImpl" />
+    <any>
+      <when-property-is name="user.agent" value="ie6" />
+      <when-property-is name="user.agent" value="ie8" />
+      <when-property-is name="user.agent" value="ie9" />
+    </any>
+  </replace-with>
+
 </module>
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/EmptyBinder.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/EmptyBinder.java
index e3382c4..0f39b9b 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/EmptyBinder.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/EmptyBinder.java
@@ -25,10 +25,12 @@
  */
 public class EmptyBinder extends Composite {
 
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("HTMLPanel UiBinder");
     }
+
+    @Override
     public Widget make() {
       return new EmptyBinder();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/MicrobenchmarkSurvey.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/MicrobenchmarkSurvey.java
new file mode 100644
index 0000000..f37ee1a
--- /dev/null
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/MicrobenchmarkSurvey.java
@@ -0,0 +1,414 @@
+/*
+ * Copyright 2011 Google Inc.
+ * 
+ * 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.google.gwt.reference.microbenchmark.client;
+
+import com.google.gwt.core.client.Duration;
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.event.dom.client.BlurEvent;
+import com.google.gwt.event.dom.client.BlurHandler;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.user.client.Cookies;
+import com.google.gwt.user.client.Window;
+import com.google.gwt.user.client.Window.ClosingEvent;
+import com.google.gwt.user.client.Window.ClosingHandler;
+import com.google.gwt.user.client.ui.CheckBox;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.Grid;
+import com.google.gwt.user.client.ui.InlineLabel;
+import com.google.gwt.user.client.ui.PopupPanel;
+import com.google.gwt.user.client.ui.RootPanel;
+import com.google.gwt.user.client.ui.ScrollPanel;
+import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Widget;
+
+import java.util.Collections;
+import java.util.Date;
+import java.util.List;
+
+/**
+ * An implementation of {@link Microbenchmark} that surveys multiple timed
+ * tests.
+ */
+public class MicrobenchmarkSurvey implements Microbenchmark {
+
+  /**
+   * A single runnable test that makes up the survey.
+   */
+  static abstract class NanoTest {
+
+    private final String name;
+
+    /**
+     * Construct a new {@link NanoTest}.
+     * 
+     * @param name the display name
+     */
+    public NanoTest(String name) {
+      this.name = name;
+    }
+
+    public String getName() {
+      return name;
+    }
+
+    /**
+     * Get the widget to display in a popup when the user clicks on the test
+     * name.
+     * 
+     * @return the popup widget, or null not to show one
+     */
+    public Widget getPopup() {
+      return null;
+    }
+
+    /**
+     * Run the test.
+     */
+    public abstract void runTest();
+
+    /**
+     * Setup the test before starting the timer. Override this method to prepare
+     * the test before it starts running.
+     */
+    public void setup() {
+      // No-op by default.
+    }
+
+    /**
+     * Tear down the test after stopping the timer. Override this method to
+     * cleanup the test after it completes.
+     */
+    public void teardown() {
+      // No-op by default.
+    }
+  }
+
+  /**
+   * A nano test that makes a widget and attaches it to the {@link RootPanel}.
+   */
+  static abstract class WidgetMaker extends NanoTest {
+
+    private final RootPanel root = RootPanel.get();
+    private Widget popupWidget;
+    private Widget w;
+
+    public WidgetMaker(String name) {
+      super(name);
+    }
+
+    @Override
+    public Widget getPopup() {
+      if (popupWidget == null) {
+        popupWidget = make();
+      }
+      return popupWidget;
+    }
+
+    @Override
+    public void runTest() {
+      w = make();
+      root.add(w);
+
+      /*
+       * Force a layout by finding the body's offsetTop and height. We avoid
+       * doing setTimeout(0), which would allow paint to happen, to keep the
+       * test synchronous and because different browsers round that zero to
+       * different minimums. Layout should be the bulk of the time.
+       */
+      Document.get().getBody().getOffsetTop();
+      Document.get().getBody().getOffsetHeight();
+      w.getOffsetHeight();
+    }
+
+    @Override
+    public void teardown() {
+      // Clean up to keep the dom. Attached widgets will affect later tests.
+      root.remove(w);
+    }
+
+    /**
+     * Make the widget to test.
+     * 
+     * @return the widget
+     */
+    protected abstract Widget make();
+  }
+
+  /**
+   * A nano test that updates an existing widget that is already attached to the
+   * {@link RootPanel}.
+   * 
+   * @param <W> the widget type
+   */
+  static abstract class WidgetUpdater<W extends Widget> extends MicrobenchmarkSurvey.NanoTest {
+
+    private final RootPanel root = RootPanel.get();
+    private W w;
+
+    public WidgetUpdater(String name) {
+      super(name);
+    }
+
+    @Override
+    public Widget getPopup() {
+      return ensureWidget();
+    }
+
+    @Override
+    public void setup() {
+      root.add(ensureWidget());
+    }
+
+    @Override
+    public void runTest() {
+      updateWidget(w);
+    }
+
+    @Override
+    public void teardown() {
+      root.remove(w);
+    }
+
+    /**
+     * Make the widget to test.
+     * 
+     * @return the widget
+     */
+    protected abstract W make();
+
+    /**
+     * Update the widget.
+     * 
+     * @param w the widget to update
+     */
+    protected abstract void updateWidget(W w);
+
+    private W ensureWidget() {
+      if (w == null) {
+        w = make();
+      }
+      return w;
+    }
+  }
+
+  interface Binder extends UiBinder<Widget, MicrobenchmarkSurvey> {
+  }
+
+  private static final Binder BINDER = GWT.create(Binder.class);
+
+  private static final String COOKIE = "gwt_microb_survey";
+
+  private static final int DEFAULT_INSTANCES = 100;
+
+  public static native void log(String msg) /*-{
+    var logger = $wnd.console;
+    if (logger) {
+      logger.log(msg);
+      if (logger.markTimeline) {
+        logger.markTimeline(msg);
+      }
+    }
+  }-*/;
+
+  @UiField(provided = true)
+  Grid grid;
+  @UiField
+  CheckBox includeLargeWidget;
+  @UiField
+  TextBox number;
+  @UiField
+  Widget root;
+  final String name;
+  private final List<NanoTest> nanos;
+
+  /**
+   * Construct a new {@link MicrobenchmarkSurvey} micro benchmark.
+   * 
+   * @param name the name of the benchmark
+   * @param nanos the {@link NanoTest}s that make up the survey
+   */
+  public MicrobenchmarkSurvey(String name, List<NanoTest> nanos) {
+    this.name = name;
+    this.nanos = Collections.unmodifiableList(nanos);
+
+    int instances = DEFAULT_INSTANCES;
+    try {
+      instances = Integer.parseInt(Cookies.getCookie(COOKIE));
+    } catch (NumberFormatException ignored) {
+    }
+
+    // Initialize the grid.
+    grid = new Grid(nanos.size() + 2, 3);
+    grid.setText(0, 0, "median");
+    grid.setText(0, 1, "mean");
+
+    int row = 1;
+    for (final NanoTest nano : nanos) {
+      grid.setText(row, 0, "0");
+      grid.setText(row, 1, "0");
+      InlineLabel a = new InlineLabel();
+      a.setText(nano.getName());
+      a.addClickHandler(new ClickHandler() {
+        public void onClick(ClickEvent event) {
+          Widget toDisplay = nano.getPopup();
+          if (toDisplay != null) {
+            PopupPanel popup = new PopupPanel(true, true);
+            ScrollPanel container = new ScrollPanel(toDisplay);
+            container.setPixelSize(500, 500);
+            popup.setWidget(container);
+            popup.center();
+          }
+        }
+      });
+      // TODO: popup.
+      grid.setWidget(row, 2, a);
+      row++;
+    }
+
+    // Create the widget.
+    root = BINDER.createAndBindUi(this);
+    number.setVisibleLength(7);
+    number.setValue("" + instances);
+    number.addBlurHandler(new BlurHandler() {
+      public void onBlur(BlurEvent event) {
+        saveInstances();
+      }
+    });
+
+    Window.addWindowClosingHandler(new ClosingHandler() {
+      public void onWindowClosing(ClosingEvent event) {
+        saveInstances();
+      }
+    });
+  }
+
+  public String getName() {
+    return name;
+  }
+
+  public Widget getWidget() {
+    return root;
+  }
+
+  public void run() {
+    RootPanel root = RootPanel.get();
+
+    // Add a large widget to the root to reflect a typical application.
+    FlowPanel largeWidget = null;
+    if (includeLargeWidget.getValue()) {
+      largeWidget = new FlowPanel();
+      TestWidgetBinder.Maker widgetMaker = new TestWidgetBinder.Maker();
+      for (int i = 0; i < 100; i++) {
+        largeWidget.add(widgetMaker.make());
+      }
+      root.add(largeWidget);
+    }
+
+    int nanosCount = nanos.size();
+    double[] times = new double[nanosCount];
+
+    int column = grid.getColumnCount();
+    grid.resizeColumns(column + 1);
+    grid.setText(0, column, "Run " + (column - 3));
+
+    final int instances = getInstances();
+    boolean forward = false;
+    for (int i = 0; i < instances; ++i) {
+      forward = !forward;
+      for (int m = 0; m < nanosCount; m++) {
+        /*
+         * Alternate the order that we invoke the makers to cancel out the
+         * performance impact of adding elements to the DOM, which would cause
+         * later tests to run more slowly than earlier tests.
+         */
+        NanoTest nano = nanos.get(forward ? m : (nanosCount - 1 - m));
+        nano.setup();
+
+        // Execute the test.
+        log(i + ": " + nano.name);
+        double start = Duration.currentTimeMillis();
+        nano.runTest();
+
+        // Record the end time.
+        double thisTime = Duration.currentTimeMillis() - start;
+        times[m] += thisTime;
+
+        // Cleanup after the test.
+        nano.teardown();
+      }
+    }
+
+    // Record the times.
+    double allTimes = 0;
+    for (int m = 0; m < nanosCount; ++m) {
+      record(m + 1, times[m]);
+      allTimes += times[m];
+    }
+    grid.setText(grid.getRowCount() - 1, grid.getColumnCount() - 1, Util.format(allTimes));
+
+    // Cleanup the dom.
+    if (largeWidget != null) {
+      root.remove(largeWidget);
+    }
+  }
+
+  private int getInstances() {
+    try {
+      int instances = Integer.parseInt(number.getValue());
+      return instances;
+    } catch (NumberFormatException ignored) {
+      return 0;
+    }
+  }
+
+  private void record(int row, double thisTime) {
+    final int columns = grid.getColumnCount();
+    grid.setText(row, columns - 1, Util.format(thisTime));
+
+    double max = 0, min = 0, mean = 0;
+
+    for (int column = 3; column < columns; column++) {
+      double value = Double.parseDouble(grid.getText(row, column));
+      mean += value;
+      max = Math.max(max, value);
+      if (min == 0) {
+        min = max;
+      } else {
+        min = Math.min(min, value);
+      }
+    }
+
+    double range = max - min;
+    double halfRange = range / 2;
+    double median = min + halfRange;
+    grid.setText(row, 0, Util.format(Util.roundToTens(median)));
+
+    mean = mean / (columns - 3);
+    grid.setText(row, 1, Util.format(Util.roundToTens(mean)));
+  }
+
+  @SuppressWarnings("deprecation")
+  private void saveInstances() {
+    String value = number.getValue();
+    Date expires = new Date();
+    expires.setYear(expires.getYear() + 3);
+    Cookies.setCookie(COOKIE, value, expires);
+  }
+}
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/WidgetCreation.ui.xml b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/MicrobenchmarkSurvey.ui.xml
similarity index 100%
rename from reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/WidgetCreation.ui.xml
rename to reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/MicrobenchmarkSurvey.ui.xml
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Microbenchmarks.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Microbenchmarks.java
index 367d38d..5669374 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Microbenchmarks.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Microbenchmarks.java
@@ -18,15 +18,16 @@
 import com.google.gwt.core.client.Duration;
 import com.google.gwt.core.client.EntryPoint;
 import com.google.gwt.core.client.GWT;
+import com.google.gwt.core.client.Scheduler;
+import com.google.gwt.core.client.Scheduler.ScheduledCommand;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.event.dom.client.ChangeEvent;
 import com.google.gwt.event.dom.client.ClickEvent;
-import com.google.gwt.reference.microbenchmark.client.WidgetCreation.Maker;
+import com.google.gwt.reference.microbenchmark.client.MicrobenchmarkSurvey.NanoTest;
+import com.google.gwt.reference.microbenchmark.client.MicrobenchmarkSurvey.WidgetMaker;
 import com.google.gwt.uibinder.client.UiBinder;
 import com.google.gwt.uibinder.client.UiField;
 import com.google.gwt.uibinder.client.UiHandler;
-import com.google.gwt.user.client.Command;
-import com.google.gwt.user.client.DeferredCommand;
 import com.google.gwt.user.client.ui.Button;
 import com.google.gwt.user.client.ui.DeckPanel;
 import com.google.gwt.user.client.ui.FlowPanel;
@@ -60,20 +61,20 @@
 
   public Microbenchmarks() {
     // Add entries for new widget benchmarks here.
-    List<Maker> widgetMakers = new ArrayList<Maker>();
-    widgetMakers.add(new Maker("SimplePanel") {
+    List<NanoTest> widgetMakers = new ArrayList<NanoTest>();
+    widgetMakers.add(new WidgetMaker("SimplePanel") {
       @Override
       public Widget make() {
         return new SimplePanel();
       }
     });
-    widgetMakers.add(new Maker("FlowPanel") {
+    widgetMakers.add(new WidgetMaker("FlowPanel") {
       @Override
       public Widget make() {
         return new FlowPanel();
       }
     });
-    widgetMakers.add(new Maker("HTMLPanel") {
+    widgetMakers.add(new WidgetMaker("HTMLPanel") {
       @Override
       public Widget make() {
         return new HTMLPanel("");
@@ -96,16 +97,30 @@
     widgetMakers.add(new TestManualHTMLPanel.Maker());
     widgetMakers.add(new TestWidgetBinder.Maker());
 
-    // Add entries for new table benchmarks here.
-    List<Maker> tableMakers = new ArrayList<Maker>();
+    // Add entries for table creation benchmarks here.
+    List<NanoTest> tableMakers = new ArrayList<NanoTest>();
     tableMakers.add(new TestCreateTableInnerHtml.Maker());
     tableMakers.add(new TestCreateTablePrecreatedInnerHtml.Maker());
     tableMakers.add(new TestCreateTableDom.Maker());
     tableMakers.add(new TestCreateTableDomWithEvents.Maker());
 
-    benchmarks = new Microbenchmark[2];
-    benchmarks[0] = new WidgetCreation("Widget Creation Survey", widgetMakers);
-    benchmarks[1] = new WidgetCreation("Table Creation Survey", tableMakers);
+    // Add entries for table update benchmarks here.
+    List<NanoTest> tableUpdaters = new ArrayList<NanoTest>();
+    tableUpdaters.add(new TestCreateTableInnerHtml.Updater());
+    tableUpdaters.add(new TestCreateTablePrecreatedInnerHtml.Updater());
+    tableUpdaters.add(new TestCreateTableDom.Updater());
+    tableUpdaters.add(new TestCreateTableDomWithEvents.Updater());
+
+    // Combine all table tests.
+    List<NanoTest> allTableTests = new ArrayList<MicrobenchmarkSurvey.NanoTest>();
+    allTableTests.addAll(tableMakers);
+    allTableTests.addAll(tableUpdaters);
+
+    benchmarks = new Microbenchmark[4];
+    benchmarks[0] = new MicrobenchmarkSurvey("Widget Creation Survey", widgetMakers);
+    benchmarks[1] = new MicrobenchmarkSurvey("Table Creation and Update Survey", allTableTests);
+    benchmarks[2] = new MicrobenchmarkSurvey("Table Creation Survey", tableMakers);
+    benchmarks[3] = new MicrobenchmarkSurvey("Table Update Survey", tableUpdaters);
   }
 
   @UiHandler("listBox")
@@ -119,7 +134,7 @@
     final int index = listBox.getSelectedIndex();
     UIObject.setVisible(running, true);
     button.setEnabled(false);
-    DeferredCommand.addCommand(new Command() {
+    Scheduler.get().scheduleDeferred(new ScheduledCommand() {
       public void execute() {
         double start = Duration.currentTimeMillis();
         benchmarks[index].run();
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDom.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDom.java
index 3c9f4e7..31332e6 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDom.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDom.java
@@ -25,13 +25,12 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link TestCreateTableDom.Maker#name} for
- * details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestCreateTableDom extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
-      super(Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+      super("Create " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
           + " table via DOM api calls, no widgets");
     }
 
@@ -41,26 +40,43 @@
     }
   }
 
+  public static class Updater extends MicrobenchmarkSurvey.WidgetUpdater<TestCreateTableDom> {
+    Updater() {
+      this("Replace rows in " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+          + " table via DOM api calls, no widgets");
+    }
+
+    Updater(String name) {
+      super(name);
+    }
+
+    @Override
+    protected TestCreateTableDom make() {
+      return new TestCreateTableDom();
+    }
+
+    @Override
+    protected void updateWidget(TestCreateTableDom w) {
+      // Remove the old rows.
+      int rowCount = w.tableBody.getRows().getLength();
+      for (int i = 0; i < rowCount; i++) {
+        w.tableBody.deleteRow(0);
+      }
+
+      // Add new rows.
+      w.fillTableBodyRows();
+    }
+  }
+
+  private final TableElement table;
+  private final TableSectionElement tableBody;
+
   TestCreateTableDom() {
     // This table should match the structure defined in Util#createTableHtml().
-    TableElement table = Document.get().createTableElement();
-    TableSectionElement tbody = Document.get().createTBodyElement();
-    table.appendChild(tbody);
-    for (int row = 0; row < Util.TABLE_ROW_COUNT; row++) {
-      TableRowElement tr = Document.get().createTRElement();
-      tbody.appendChild(tr);
-      if (row % 2 == 0) {
-        tr.addClassName("evenRow");
-      } else {
-        tr.addClassName("oddRow");
-      }
-      for (int column = 0; column < Util.TABLE_COLUMN_COUNT; column++) {
-        TableCellElement td = Document.get().createTDElement();
-        td.setAlign("center");
-        td.setVAlign("middle");
-        td.appendChild(createCellContents(row, column));
-      }
-    }
+    table = Document.get().createTableElement();
+    tableBody = Document.get().createTBodyElement();
+    table.appendChild(tableBody);
+    fillTableBodyRows();
     setElement(table);
   }
 
@@ -76,4 +92,26 @@
     div.setInnerHTML("Cell " + row + ":" + column);
     return div;
   }
+
+  /**
+   * Fill the table body element with rows.
+   */
+  private void fillTableBodyRows() {
+    for (int row = 0; row < Util.TABLE_ROW_COUNT; row++) {
+      TableRowElement tr = Document.get().createTRElement();
+      tableBody.appendChild(tr);
+      if (row % 2 == 0) {
+        tr.addClassName("evenRow");
+      } else {
+        tr.addClassName("oddRow");
+      }
+      for (int column = 0; column < Util.TABLE_COLUMN_COUNT; column++) {
+        TableCellElement td = Document.get().createTDElement();
+        td.setAlign("center");
+        td.setVAlign("middle");
+        td.appendChild(createCellContents(row, column));
+        tr.appendChild(td);
+      }
+    }
+  }
 }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDomWithEvents.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDomWithEvents.java
index 0a529b0..11be2be 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDomWithEvents.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableDomWithEvents.java
@@ -20,14 +20,13 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see
- * {@link TestCreateTableDomWithEvents.Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestCreateTableDomWithEvents extends TestCreateTableDom {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
 
     Maker() {
-      super(Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+      super("Create " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
           + " table via DOM api calls, no widgets, sink events on each cell");
     }
 
@@ -37,6 +36,18 @@
     }
   }
 
+  public static class Updater extends TestCreateTableDom.Updater {
+    Updater() {
+      super("Replace rows in " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+          + " table via DOM api calls, no widgets, sink events on each cell");
+    }
+
+    @Override
+    protected TestCreateTableDom make() {
+      return new TestCreateTableDomWithEvents();
+    }
+  }
+
   @Override
   Element createCellContents(int row, int column) {
     Element div = super.createCellContents(row, column);
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableInnerHtml.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableInnerHtml.java
index bc2ba98..10c936e 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableInnerHtml.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTableInnerHtml.java
@@ -15,16 +15,17 @@
  */
 package com.google.gwt.reference.microbenchmark.client;
 
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableSectionElement;
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see
- * {@link TestCreateTableInnerHtml.Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestCreateTableInnerHtml extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
-      super(Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+      super("Create " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
           + " table via innerHTML built with StringBuilder, no widgets");
     }
 
@@ -34,7 +35,33 @@
     }
   }
 
+  public static class Updater extends MicrobenchmarkSurvey.WidgetUpdater<TestCreateTableInnerHtml> {
+    Updater() {
+      super("Update " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+          + " tbody via innerHTML built with StringBuilder, no widgets");
+    }
+
+    @Override
+    protected TestCreateTableInnerHtml make() {
+      return new TestCreateTableInnerHtml();
+    }
+
+    @Override
+    protected void updateWidget(TestCreateTableInnerHtml w) {
+      w.replaceAllRows();
+    }
+  }
+
+  private final TableElement table;
+  private final TableSectionElement  tableBody;
+
   private TestCreateTableInnerHtml() {
-    setElement(Util.fromHtml(Util.createTableHtml()));
+    table = Util.fromHtml(Util.createTableHtml()).cast();
+    setElement(table);
+    tableBody = table.getTBodies().getItem(0).cast();
+  }
+
+  private void replaceAllRows() {
+    Util.replaceTableBodyRows(tableBody, Util.createTableRowsHtml());
   }
 }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTablePrecreatedInnerHtml.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTablePrecreatedInnerHtml.java
index 13d0b93..c99a8cf 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTablePrecreatedInnerHtml.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCreateTablePrecreatedInnerHtml.java
@@ -15,20 +15,21 @@
  */
 package com.google.gwt.reference.microbenchmark.client;
 
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableSectionElement;
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see
- * {@link TestCreateTablePrecreatedInnerHtml.Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestCreateTablePrecreatedInnerHtml extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
-    
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
+
     private final String tableHtml = Util.createTableHtml();
 
     Maker() {
-      super(Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
-          + " table via precreated innerHTML String, no widgets");
+      super("Create " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+          + " tbody via precreated innerHTML String, no widgets");
     }
 
     @Override
@@ -37,7 +38,37 @@
     }
   }
 
+  public static class Updater extends
+      MicrobenchmarkSurvey.WidgetUpdater<TestCreateTablePrecreatedInnerHtml> {
+
+    private final String tableRowsHtml = Util.createTableRowsHtml();
+
+    Updater() {
+      super("Update " + Util.TABLE_ROW_COUNT + "x" + Util.TABLE_COLUMN_COUNT
+          + " table via precreated innerHTML String, no widgets");
+    }
+
+    @Override
+    protected TestCreateTablePrecreatedInnerHtml make() {
+      return new TestCreateTablePrecreatedInnerHtml(Util.createTableHtml());
+    }
+
+    @Override
+    protected void updateWidget(TestCreateTablePrecreatedInnerHtml w) {
+      w.replaceAllRows(tableRowsHtml);
+    }
+  }
+
+  private final TableElement table;
+  private final TableSectionElement tableBody;
+
   private TestCreateTablePrecreatedInnerHtml(String tableHtml) {
-    setElement(Util.fromHtml(tableHtml));
+    table = Util.fromHtml(tableHtml).cast();
+    setElement(table);
+    tableBody = table.getTBodies().getItem(0).cast();
+  }
+
+  private void replaceAllRows(String tableRowsHtml) {
+    Util.replaceTableBodyRows(tableBody, tableRowsHtml);
   }
 }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCursorDomCrawl.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCursorDomCrawl.java
index 0fbbe18..0717a69 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCursorDomCrawl.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestCursorDomCrawl.java
@@ -6,13 +6,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestCursorDomCrawl extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via innerHTML, no widgets, get children by idealized crawl");
     }
+
+    @Override
     public Widget make() {
       return new TestCursorDomCrawl();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomBinder.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomBinder.java
index 9488543..32a9d7d 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomBinder.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomBinder.java
@@ -24,14 +24,16 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestDomBinder extends Widget {
 
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via UiBinder, no widgets");
     }
+
+    @Override
     public Widget make() {
       return new TestDomBinder();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlById.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlById.java
index 14ad279..207e673 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlById.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlById.java
@@ -22,13 +22,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestDomInnerHtmlById extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via innerHTML, no widgets, getElementById");
     }
+
+    @Override
     public Widget make() {
       return new TestDomInnerHtmlById();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlQuerySelectorAll.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlQuerySelectorAll.java
index 868fc50..f7bfb79 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlQuerySelectorAll.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomInnerHtmlQuerySelectorAll.java
@@ -22,13 +22,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestDomInnerHtmlQuerySelectorAll extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via innerHTML, no widgets, querySelectorAll");
     }
+
+    @Override
     public Widget make() {
       return new TestDomInnerHtmlQuerySelectorAll();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomViaApi.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomViaApi.java
index e16ba30..6699f82 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomViaApi.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestDomViaApi.java
@@ -21,14 +21,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestDomViaApi extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via DOM api calls, no widgets");
     }
 
+    @Override
     public Widget make() {
       return new TestDomViaApi();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyCursorDomCrawl.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyCursorDomCrawl.java
index 20a09bd..4611d76 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyCursorDomCrawl.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyCursorDomCrawl.java
@@ -6,13 +6,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestEmptyCursorDomCrawl extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Empty UI via innerHTML, no widgets, get children by idealized crawl");
     }
+
+    @Override
     public Widget make() {
       return new TestEmptyCursorDomCrawl();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDom.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDom.java
index b0a47be9..e642b7f 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDom.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDom.java
@@ -22,13 +22,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestEmptyDom extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Empty UI via innerHTML, no widgets, get children by id");
     }
+
+    @Override
     public Widget make() {
       return new TestEmptyDom();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDomViaApi.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDomViaApi.java
index d3051fa..38672e7 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDomViaApi.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyDomViaApi.java
@@ -21,14 +21,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestEmptyDomViaApi extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Empty UI via DOM api calls, no widgets");
     }
 
+    @Override
     public Widget make() {
       return new TestEmptyDomViaApi();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyRealisticDomCrawl.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyRealisticDomCrawl.java
index e6d3e15..909bd68 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyRealisticDomCrawl.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestEmptyRealisticDomCrawl.java
@@ -6,13 +6,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestEmptyRealisticDomCrawl extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Empty UI via innerHTML, no widgets, get children by nav from root");
     }
+
+    @Override
     public Widget make() {
       return new TestEmptyRealisticDomCrawl();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestFlows.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestFlows.java
index c46b4f6..5d7acbc 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestFlows.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestFlows.java
@@ -6,13 +6,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestFlows extends Composite {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via FlowPanels (DIVs) and InlineLabels (SPANs)");
     }
+
+    @Override
     public Widget make() {
       return new TestFlows();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestManualHTMLPanel.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestManualHTMLPanel.java
index 3be721f..d3d813d 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestManualHTMLPanel.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestManualHTMLPanel.java
@@ -7,13 +7,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestManualHTMLPanel extends Composite {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via typical manual HTMLPanel usage");
     }
+
+    @Override
     public Widget make() {
       return new TestManualHTMLPanel();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestRealisticDomCrawl.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestRealisticDomCrawl.java
index 7100b63..e8facdc 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestRealisticDomCrawl.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestRealisticDomCrawl.java
@@ -6,13 +6,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestRealisticDomCrawl extends Widget {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI via innerHTML, no widgets, get children by nav from root");
     }
+
+    @Override
     public Widget make() {
       return new TestRealisticDomCrawl();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestWidgetBinder.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestWidgetBinder.java
index 1b29c99..6dc3a44 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestWidgetBinder.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/TestWidgetBinder.java
@@ -24,13 +24,15 @@
 import com.google.gwt.user.client.ui.Widget;
 
 /**
- * Run by {@link WidgetCreation}, see {@link Maker#name} for details.
+ * Run by {@link MicrobenchmarkSurvey}, see name for details.
  */
 public class TestWidgetBinder extends Composite {
-  public static class Maker extends WidgetCreation.Maker {
+  public static class Maker extends MicrobenchmarkSurvey.WidgetMaker {
     Maker() {
       super("Text heavy UI with HTMLPanel via UiBinder");
     }
+
+    @Override
     public Widget make() {
       return new TestWidgetBinder();
     }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Util.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Util.java
index 6e87528..469db17 100644
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Util.java
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/Util.java
@@ -1,10 +1,12 @@
 package com.google.gwt.reference.microbenchmark.client;
 
+import com.google.gwt.core.client.GWT;
 import com.google.gwt.core.client.JsArray;
 import com.google.gwt.dom.client.DivElement;
 import com.google.gwt.dom.client.Document;
 import com.google.gwt.dom.client.Element;
 import com.google.gwt.dom.client.Node;
+import com.google.gwt.dom.client.TableSectionElement;
 import com.google.gwt.i18n.client.NumberFormat;
 
 class Util {
@@ -44,13 +46,25 @@
   +   "Div anon end</div>"
   + "Div root end";
 
+  private static int uniqueId = 0;
+  private static UtilImpl impl = GWT.create(UtilImpl.class);
+
   static void addText(Element elm, String text) {
     elm.appendChild(Document.get().createTextNode(text));
   }
 
   static String createTableHtml() {
     StringBuilder sb = new StringBuilder();
-    sb.append("<table>");
+    sb.append("<table><tbody>");
+    sb.append(createTableRowsHtml());
+    sb.append("</tbody></table>");
+    return sb.toString();
+  }
+
+  static String createTableRowsHtml() {
+    // Assign a unique ID to ensure that we actually change the content.
+    uniqueId++;
+    StringBuilder sb = new StringBuilder();
     for (int row = 0; row < Util.TABLE_ROW_COUNT; row++) {
       if (row % 2 == 0) {
         sb.append("<tr class=\"evenRow\">");
@@ -59,12 +73,11 @@
       }
       for (int column = 0; column < Util.TABLE_COLUMN_COUNT; column++) {
         sb.append("<td align=\"center\" valign=\"middle\"><div>");
-        sb.append("Cell " + row + ":" + column);
+        sb.append(uniqueId + " - Cell " + row + ":" + column);
         sb.append("</div></td>");
       }
       sb.append("</tr>");
     }
-    sb.append("</table>");
     return sb.toString();
   }
 
@@ -90,6 +103,16 @@
     return root.querySelectorAll(selector);
   }-*/;
 
+  /**
+   * Replace all of the rows in the specified tbody.
+   * 
+   * @param tbody the tbody element
+   * @param rowHtml the HTML that represents the rows
+   */
+  static void replaceTableBodyRows(TableSectionElement tbody, String rowHtml) {
+    impl.replaceTableBodyRows(tbody, rowHtml);
+  }
+
   static long roundToTens(double median) {
     return Math.round(median/10)*10;
   }
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/UtilImpl.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/UtilImpl.java
new file mode 100644
index 0000000..5e50ef5
--- /dev/null
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/UtilImpl.java
@@ -0,0 +1,13 @@
+package com.google.gwt.reference.microbenchmark.client;
+
+import com.google.gwt.dom.client.TableSectionElement;
+
+/**
+ * Implementation of {@link Util}.
+ */
+class UtilImpl {
+
+  void replaceTableBodyRows(TableSectionElement tbody, String rowHtml) {
+    tbody.setInnerHTML(rowHtml);
+  }
+}
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/UtilImplTrident.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/UtilImplTrident.java
new file mode 100644
index 0000000..efe9429
--- /dev/null
+++ b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/UtilImplTrident.java
@@ -0,0 +1,44 @@
+package com.google.gwt.reference.microbenchmark.client;
+
+import com.google.gwt.dom.client.Document;
+import com.google.gwt.dom.client.Element;
+import com.google.gwt.dom.client.TableElement;
+import com.google.gwt.dom.client.TableSectionElement;
+
+/**
+ * IE implementation of Util.
+ */
+class UtilImplTrident extends UtilImpl {
+
+  private final com.google.gwt.user.client.Element tmpElem = Document.get().createDivElement()
+      .cast();
+
+  /**
+   * IE doesn't support innerHTML on tbody, nor does it support removing or
+   * replacing a tbody. The only solution is to remove and replace the rows
+   * themselves.
+   */
+  @Override
+  void replaceTableBodyRows(TableSectionElement tbody, String rowHtml) {
+    // Remove all children.
+    Element child = tbody.getFirstChildElement();
+    while (child != null) {
+      Element next = child.getNextSiblingElement();
+      tbody.removeChild(child);
+      child = next;
+    }
+
+    // Convert the row html to child elements.
+    tmpElem.setInnerHTML("<table><tbody>" + rowHtml + "</tbody></table>");
+    TableElement tableElem = tmpElem.getFirstChildElement().cast();
+    TableSectionElement newRows = tableElem.getTBodies().getItem(0);
+
+    // Add new child elements.
+    child = newRows.getFirstChildElement();
+    while (child != null) {
+      Element next = child.getNextSiblingElement();
+      tbody.appendChild(child);
+      child = next;
+    }
+  }
+}
diff --git a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/WidgetCreation.java b/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/WidgetCreation.java
deleted file mode 100644
index fb7f183..0000000
--- a/reference/Microbenchmarks/src/com/google/gwt/reference/microbenchmark/client/WidgetCreation.java
+++ /dev/null
@@ -1,248 +0,0 @@
-/*
- * Copyright 2009 Google Inc.
- * 
- * 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.google.gwt.reference.microbenchmark.client;
-
-import com.google.gwt.core.client.Duration;
-import com.google.gwt.core.client.GWT;
-import com.google.gwt.dom.client.Document;
-import com.google.gwt.event.dom.client.BlurEvent;
-import com.google.gwt.event.dom.client.BlurHandler;
-import com.google.gwt.uibinder.client.UiBinder;
-import com.google.gwt.uibinder.client.UiField;
-import com.google.gwt.user.client.Cookies;
-import com.google.gwt.user.client.Window;
-import com.google.gwt.user.client.Window.ClosingEvent;
-import com.google.gwt.user.client.Window.ClosingHandler;
-import com.google.gwt.user.client.ui.CheckBox;
-import com.google.gwt.user.client.ui.FlowPanel;
-import com.google.gwt.user.client.ui.Grid;
-import com.google.gwt.user.client.ui.InlineLabel;
-import com.google.gwt.user.client.ui.RootPanel;
-import com.google.gwt.user.client.ui.TextBox;
-import com.google.gwt.user.client.ui.Widget;
-
-import java.util.Collections;
-import java.util.Date;
-import java.util.List;
-
-/**
- * Compares various widget creation strategies.
- */
-public class WidgetCreation implements Microbenchmark {
-  static abstract class Maker {
-    final String name;
-
-    Maker(String name) {
-      this.name = name;
-    }
-
-    abstract Widget make();
-  }
-
-  interface Binder extends UiBinder<Widget, WidgetCreation> {}
-
-  private static final Binder BINDER = GWT.create(Binder.class);
-
-  private static final String COOKIE = "gwt_microb_widgetCreation";
-
-  private static final int DEFAULT_INSTANCES = 100;
-
-  public static native void log(String msg) /*-{
-    var logger = $wnd.console;
-    if (logger) {
-      logger.log(msg);
-      if(logger.markTimeline) {
-        logger.markTimeline(msg); 
-      }
-    }
-  }-*/;
-
-  @UiField(provided = true) Grid grid;
-  @UiField CheckBox includeLargeWidget;
-  @UiField TextBox number;
-  @UiField Widget root;
-  final String name;
-  final List<Maker> makers;
-
-  /**
-   * Construct a new {@link WidgetCreation} micro benchmark.
-   * 
-   * @param name the name of the benchmark
-   * @param makers the makers for the widget strategies
-   */
-  public WidgetCreation(String name, List<Maker> makers) {
-    this.name = name;
-    this.makers = Collections.unmodifiableList(makers);
-
-    int instances = DEFAULT_INSTANCES;
-    try {
-      instances = Integer.parseInt(Cookies.getCookie(COOKIE));
-    } catch (NumberFormatException ignored) {
-    }
-
-    // Initialize the grid.
-    grid = new Grid(makers.size() + 2, 3);
-    grid.setText(0, 0, "median");
-    grid.setText(0, 1, "mean");
-
-    int row = 1;
-    for (Maker m : makers) {
-      grid.setText(row, 0, "0");
-      grid.setText(row, 1, "0");
-      InlineLabel a = new InlineLabel();
-      a.setText(m.name);
-      a.setTitle(Util.outerHtml(m.make().getElement()));
-      grid.setWidget(row, 2, a);
-      row++;
-    }
-    
-    // Create the widget.
-    root = BINDER.createAndBindUi(this);
-    number.setVisibleLength(7);
-    number.setValue("" + instances);
-    number.addBlurHandler(new BlurHandler() {
-      public void onBlur(BlurEvent event) {
-        saveInstances();
-      }
-    });
-
-    Window.addWindowClosingHandler(new ClosingHandler() {
-      public void onWindowClosing(ClosingEvent event) {
-        saveInstances();
-      }
-    });
-  }
-
-  public String getName() {
-    return name;
-  }
-
-  public Widget getWidget() {
-    return root;
-  }
-
-  public void run() {
-    RootPanel root = RootPanel.get();
-
-    // Add a large widget to the root to reflect a typical application.
-    FlowPanel largeWidget = null;
-    if (includeLargeWidget.getValue()) {
-      largeWidget = new FlowPanel();
-      TestWidgetBinder.Maker widgetMaker = new TestWidgetBinder.Maker();
-      for (int i = 0; i < 100; i++) {
-        largeWidget.add(widgetMaker.make());
-      }
-      root.add(largeWidget);
-    }
-
-    int makersCount = makers.size();
-    double[] times = new double[makersCount];
-
-    int column = grid.getColumnCount();
-    grid.resizeColumns(column + 1);
-    grid.setText(0, column, "Run " + (column - 3));
-
-    final int instances = getInstances();
-    boolean forward = false;
-    for (int i = 0; i < instances; ++i) {
-      forward = !forward;
-      for (int m = 0; m < makersCount; m++) {
-        /*
-         * Alternate the order that we invoke the makers to cancel out the
-         * performance impact of adding elements to the DOM, which would cause
-         * later tests to run more slowly than earlier tests.
-         */
-        Maker maker = makers.get(forward ? m : (makersCount - 1 - m));
-        log(i + ": " + maker.name);
-        double start = Duration.currentTimeMillis();
-        Widget w = maker.make();
-        root.add(w);
-
-        /*
-         * Force a layout by finding the body's offsetTop and height. We avoid
-         * doing setTimeout(0), which would allow paint to happen, to keep the
-         * test synchronous and because different browsers round that zero to
-         * different minimums. Layout should be the bulk of the time.
-         */
-        Document.get().getBody().getOffsetTop();
-        Document.get().getBody().getOffsetHeight();
-        w.getOffsetHeight();
-
-        double thisTime = Duration.currentTimeMillis() - start;
-        times[m] += thisTime;
-
-        // Clean up to keep the dom. Attached widgets will affect later tests.
-        root.remove(w);
-      }
-    }
-
-    // Record the times.
-    double allTimes = 0;
-    for (int m = 0; m < makersCount; ++m) {
-      record(m + 1, times[m]);
-      allTimes += times[m];
-    }
-    grid.setText(grid.getRowCount() - 1, grid.getColumnCount() - 1, Util.format(allTimes));
-
-    // Cleanup the dom.
-    if (largeWidget != null) {
-      root.remove(largeWidget);
-    }
-  }
-
-  private int getInstances() {
-    try {
-      int instances = Integer.parseInt(number.getValue());
-      return instances;
-    } catch (NumberFormatException ignored) {
-      return 0;
-    }
-  }
-
-  private void record(int row, double thisTime) {
-    final int columns = grid.getColumnCount();
-    grid.setText(row, columns - 1, Util.format(thisTime));
-
-    double max = 0, min = 0, mean = 0;
-
-    for (int column = 3; column < columns; column++) {
-      double value = Double.parseDouble(grid.getText(row, column));
-      mean += value;
-      max = Math.max(max, value);
-      if (min == 0) {
-        min = max;
-      } else {
-        min = Math.min(min, value);
-      }
-    }
-
-    double range = max - min;
-    double halfRange = range / 2;
-    double median = min + halfRange;
-    grid.setText(row, 0, Util.format(Util.roundToTens(median)));
-
-    mean = mean / (columns - 3);
-    grid.setText(row, 1, Util.format(Util.roundToTens(mean)));
-  }
-
-  @SuppressWarnings("deprecation")
-  private void saveInstances() {
-    String value = number.getValue();
-    Date expires = new Date();
-    expires.setYear(expires.getYear() + 3);
-    Cookies.setCookie(COOKIE, value, expires);
-  }
-}