aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeif Åstrand <leif@vaadin.com>2015-03-27 13:57:51 +0200
committerMarkus Koivisto <markus@vaadin.com>2015-04-15 11:31:39 +0300
commit9c9fc07f62ddaf5b9d27b0e09e91673a196e5a7f (patch)
tree9c75b3d6bbf3a0684deabb62db38df38b16fa4f3
parent73c9c8ba75d8b8329a185534d952a6e468fe4b22 (diff)
downloadvaadin-framework-9c9fc07f62ddaf5b9d27b0e09e91673a196e5a7f.tar.gz
vaadin-framework-9c9fc07f62ddaf5b9d27b0e09e91673a196e5a7f.zip
Reduce reflows when sizing columns (#17315)
This patch increases the reported fps from 10 to 17 in Chrome and from 5 to 10 in Firefox. No automatic test since performance testing on our shared testing infrastructure would be quite error-prone. Change-Id: I0bb6af250743058a8f32bb2df89da97660e94b52
-rw-r--r--client/src/com/vaadin/client/widgets/Escalator.java110
-rw-r--r--uitest/src/com/vaadin/tests/components/grid/GridResizeTerror.java45
-rw-r--r--uitest/src/com/vaadin/tests/util/ResizeTerrorizer.java51
-rw-r--r--uitest/src/com/vaadin/tests/widgetset/client/ResizeTerrorizerControlConnector.java157
-rw-r--r--uitest/src/com/vaadin/tests/widgetset/rebind/TestWidgetRegistryGenerator.java3
5 files changed, 328 insertions, 38 deletions
diff --git a/client/src/com/vaadin/client/widgets/Escalator.java b/client/src/com/vaadin/client/widgets/Escalator.java
index 3d4459a0cc..83c176d6fd 100644
--- a/client/src/com/vaadin/client/widgets/Escalator.java
+++ b/client/src/com/vaadin/client/widgets/Escalator.java
@@ -16,6 +16,7 @@
package com.vaadin.client.widgets;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedList;
@@ -2010,9 +2011,8 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
return new Cell(domRowIndex, domColumnIndex, cellElement);
}
- double getMaxCellWidth(int colIndex) throws IllegalArgumentException {
- double maxCellWidth = -1;
-
+ void createAutoSizeElements(int colIndex,
+ Collection<TableCellElement> elements) {
assert isAttached() : "Can't measure max width of cell, since Escalator is not attached to the DOM.";
NodeList<TableRowElement> rows = root.getRows();
@@ -2041,24 +2041,9 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
cellClone.getStyle().clearWidth();
rowElement.insertBefore(cellClone, cellOriginal);
- double requiredWidth = WidgetUtil
- .getRequiredWidthBoundingClientRectDouble(cellClone);
-
- if (BrowserInfo.get().isIE()) {
- /*
- * IE browsers have some issues with subpixels. Occasionally
- * content is overflown even if not necessary. Increase the
- * counted required size by 0.01 just to be on the safe
- * side.
- */
- requiredWidth += 0.01;
- }
- maxCellWidth = Math.max(requiredWidth, maxCellWidth);
- cellClone.removeFromParent();
+ elements.add(cellClone);
}
-
- return maxCellWidth;
}
private boolean cellIsPartOfSpan(TableCellElement cell) {
@@ -3789,7 +3774,8 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
if (px < 0) {
if (isAttached()) {
- calculateWidth();
+ autosizeColumns(Collections.singletonList(columns
+ .indexOf(this)));
} else {
/*
* the column's width is calculated at Escalator.onLoad
@@ -3843,10 +3829,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
}
return false;
}
-
- private void calculateWidth() {
- calculatedWidth = getMaxCellWidth(columns.indexOf(this));
- }
}
private final List<Column> columns = new ArrayList<Column>();
@@ -4151,6 +4133,7 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
return;
}
+ List<Integer> autosizeColumns = new ArrayList<Integer>();
for (Entry<Integer, Double> entry : indexWidthMap.entrySet()) {
int index = entry.getKey().intValue();
double width = entry.getValue().doubleValue();
@@ -4160,10 +4143,15 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
}
checkValidColumnIndex(index);
- columns.get(index).setWidth(width);
-
+ if (width >= 0) {
+ columns.get(index).setWidth(width);
+ } else {
+ autosizeColumns.add(index);
+ }
}
+ autosizeColumns(autosizeColumns);
+
widthsArray = null;
header.reapplyColumnWidths();
body.reapplyColumnWidths();
@@ -4174,6 +4162,64 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
recalculateElementSizes();
}
+ private void autosizeColumns(List<Integer> columns) {
+ if (columns.isEmpty()) {
+ return;
+ }
+
+ // Must process columns in index order
+ Collections.sort(columns);
+
+ Map<Integer, List<TableCellElement>> autoSizeElements = new HashMap<Integer, List<TableCellElement>>();
+ try {
+ // Set up the entire DOM at once
+ for (int i = columns.size() - 1; i >= 0; i--) {
+ // Iterate backwards to not mess with the indexing
+ Integer colIndex = columns.get(i);
+
+ ArrayList<TableCellElement> elements = new ArrayList<TableCellElement>();
+ autoSizeElements.put(colIndex, elements);
+
+ header.createAutoSizeElements(colIndex, elements);
+ body.createAutoSizeElements(colIndex, elements);
+ footer.createAutoSizeElements(colIndex, elements);
+ }
+
+ // Extract all measurements & update values
+ for (Integer colIndex : columns) {
+ double maxWidth = Double.NEGATIVE_INFINITY;
+ List<TableCellElement> elements = autoSizeElements
+ .get(colIndex);
+ for (TableCellElement element : elements) {
+
+ double cellWidth = WidgetUtil
+ .getRequiredWidthBoundingClientRectDouble(element);
+
+ maxWidth = Math.max(maxWidth, cellWidth);
+ }
+ assert maxWidth >= 0 : "Got a negative max width for a column, which should be impossible.";
+
+ if (BrowserInfo.get().isIE()) {
+ /*
+ * IE browsers have some issues with subpixels.
+ * Occasionally content is overflown even if not
+ * necessary. Increase the counted required size by 0.01
+ * just to be on the safe side.
+ */
+ maxWidth += 0.01;
+ }
+
+ this.columns.get(colIndex).calculatedWidth = maxWidth;
+ }
+ } finally {
+ for (List<TableCellElement> list : autoSizeElements.values()) {
+ for (TableCellElement element : list) {
+ element.removeFromParent();
+ }
+ }
+ }
+ }
+
private void checkValidColumnIndex(int index)
throws IllegalArgumentException {
if (!Range.withLength(0, getColumnCount()).contains(index)) {
@@ -4193,18 +4239,6 @@ public class Escalator extends Widget implements RequiresResize, DeferredWorker
return columns.get(index).getCalculatedWidth();
}
- private double getMaxCellWidth(int colIndex)
- throws IllegalArgumentException {
- double headerWidth = header.getMaxCellWidth(colIndex);
- double bodyWidth = body.getMaxCellWidth(colIndex);
- double footerWidth = footer.getMaxCellWidth(colIndex);
-
- double maxWidth = Math.max(headerWidth,
- Math.max(bodyWidth, footerWidth));
- assert maxWidth >= 0 : "Got a negative max width for a column, which should be impossible.";
- return maxWidth;
- }
-
/**
* Calculates the width of the columns in a given range.
*
diff --git a/uitest/src/com/vaadin/tests/components/grid/GridResizeTerror.java b/uitest/src/com/vaadin/tests/components/grid/GridResizeTerror.java
new file mode 100644
index 0000000000..365461caa9
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/components/grid/GridResizeTerror.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * 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.vaadin.tests.components.grid;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.util.ResizeTerrorizer;
+import com.vaadin.tests.widgetset.TestingWidgetSet;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.UI;
+
+@Widgetset(TestingWidgetSet.NAME)
+public class GridResizeTerror extends UI {
+ @Override
+ protected void init(VaadinRequest request) {
+ Grid grid = new Grid();
+
+ int cols = 10;
+ Object[] data = new Object[cols];
+
+ for (int i = 0; i < cols; i++) {
+ grid.addColumn("Col " + i);
+ data[i] = "Data " + i;
+ }
+
+ for (int i = 0; i < 500; i++) {
+ grid.addRow(data);
+ }
+
+ setContent(new ResizeTerrorizer(grid));
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/util/ResizeTerrorizer.java b/uitest/src/com/vaadin/tests/util/ResizeTerrorizer.java
new file mode 100644
index 0000000000..f124305d8a
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/util/ResizeTerrorizer.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * 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.vaadin.tests.util;
+
+import com.vaadin.tests.widgetset.client.ResizeTerrorizerControlConnector.ResizeTerorrizerState;
+import com.vaadin.ui.AbstractComponent;
+import com.vaadin.ui.Component;
+import com.vaadin.ui.VerticalLayout;
+
+public class ResizeTerrorizer extends VerticalLayout {
+ private final ResizeTerrorizerControl control;
+
+ public class ResizeTerrorizerControl extends AbstractComponent implements
+ Component {
+
+ public ResizeTerrorizerControl(Component target) {
+ getState().target = target;
+ }
+
+ @Override
+ protected ResizeTerorrizerState getState() {
+ return (ResizeTerorrizerState) super.getState();
+ }
+ }
+
+ public ResizeTerrorizer(Component target) {
+ target.setWidth("700px");
+ setSizeFull();
+ addComponent(target);
+ setExpandRatio(target, 1);
+ control = new ResizeTerrorizerControl(target);
+ addComponent(control);
+ }
+
+ public void setDefaultWidthOffset(int px) {
+ control.getState().defaultWidthOffset = px;
+ }
+}
diff --git a/uitest/src/com/vaadin/tests/widgetset/client/ResizeTerrorizerControlConnector.java b/uitest/src/com/vaadin/tests/widgetset/client/ResizeTerrorizerControlConnector.java
new file mode 100644
index 0000000000..9fe037706b
--- /dev/null
+++ b/uitest/src/com/vaadin/tests/widgetset/client/ResizeTerrorizerControlConnector.java
@@ -0,0 +1,157 @@
+/*
+ * Copyright 2000-2014 Vaadin Ltd.
+ *
+ * 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.vaadin.tests.widgetset.client;
+
+import com.google.gwt.animation.client.AnimationScheduler;
+import com.google.gwt.animation.client.AnimationScheduler.AnimationCallback;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.event.dom.client.ClickHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.FlowPanel;
+import com.google.gwt.user.client.ui.IntegerBox;
+import com.google.gwt.user.client.ui.Label;
+import com.google.gwt.user.client.ui.RequiresResize;
+import com.vaadin.client.ui.AbstractComponentConnector;
+import com.vaadin.client.ui.PostLayoutListener;
+import com.vaadin.shared.AbstractComponentState;
+import com.vaadin.shared.Connector;
+import com.vaadin.shared.ui.Connect;
+import com.vaadin.tests.util.ResizeTerrorizer;
+
+@Connect(ResizeTerrorizer.ResizeTerrorizerControl.class)
+public class ResizeTerrorizerControlConnector extends
+ AbstractComponentConnector implements PostLayoutListener {
+
+ public static class ResizeTerorrizerState extends AbstractComponentState {
+ public Connector target;
+ public int defaultWidthOffset = 200;
+ }
+
+ public class ResizeTerrorizerControlPanel extends FlowPanel {
+ private Label results = new Label("Results");
+ private IntegerBox startWidth = new IntegerBox();
+ private IntegerBox endWidth = new IntegerBox();
+ private final Button terrorizeButton = new Button("Terrorize",
+ new ClickHandler() {
+ @Override
+ public void onClick(ClickEvent event) {
+ terrorize(startWidth.getValue(), endWidth.getValue(),
+ 1000);
+ }
+ });
+
+ public ResizeTerrorizerControlPanel() {
+ add(new Label("Start width"));
+ add(startWidth);
+
+ add(new Label("End width"));
+ add(endWidth);
+
+ add(terrorizeButton);
+ add(results);
+
+ startWidth.getElement().setId("terror-start-width");
+ endWidth.getElement().setId("terror-end-width");
+ terrorizeButton.getElement().setId("terror-button");
+ results.getElement().setId("terror-results");
+ }
+
+ private void showResults(String results) {
+ Integer temp = startWidth.getValue();
+ startWidth.setValue(endWidth.getValue());
+ endWidth.setValue(temp);
+
+ this.results.setText(results);
+ }
+ }
+
+ private void terrorize(final double startWidth, final double endWidth,
+ final double duration) {
+ final AbstractComponentConnector target = getTarget();
+
+ final AnimationScheduler scheduler = AnimationScheduler.get();
+ AnimationCallback callback = new AnimationCallback() {
+ double startTime = -1;
+ int frameCount = 0;
+
+ @Override
+ public void execute(double timestamp) {
+ frameCount++;
+
+ boolean done = false;
+ if (startTime == -1) {
+ startTime = timestamp;
+ }
+
+ double time = timestamp - startTime;
+ if (time > duration) {
+ time = duration;
+ done = true;
+ }
+
+ double progress = time / duration;
+ double widthToSet = startWidth + (endWidth - startWidth)
+ * progress;
+
+ // TODO Optionally inform LayoutManager as well
+ target.getWidget().setWidth(widthToSet + "px");
+ if (target.getWidget() instanceof RequiresResize) {
+ ((RequiresResize) target.getWidget()).onResize();
+ }
+
+ if (!done) {
+ scheduler.requestAnimationFrame(this);
+ } else {
+ double fps = Math.round(frameCount / (duration / 1000));
+ String results = frameCount + " frames, " + fps + " fps";
+
+ getWidget().showResults(results);
+ }
+ }
+ };
+ scheduler.requestAnimationFrame(callback);
+ }
+
+ private AbstractComponentConnector getTarget() {
+ return (AbstractComponentConnector) getState().target;
+ }
+
+ @Override
+ public ResizeTerorrizerState getState() {
+ return (ResizeTerorrizerState) super.getState();
+ }
+
+ @Override
+ public ResizeTerrorizerControlPanel getWidget() {
+ return (ResizeTerrorizerControlPanel) super.getWidget();
+ }
+
+ @Override
+ protected ResizeTerrorizerControlPanel createWidget() {
+ return new ResizeTerrorizerControlPanel();
+ }
+
+ @Override
+ public void postLayout() {
+ if (getWidget().startWidth.getValue() == null) {
+ int width = getTarget().getWidget().getElement().getOffsetWidth();
+ getWidget().startWidth.setValue(width);
+ getWidget().endWidth
+ .setValue(width + getState().defaultWidthOffset);
+ }
+ }
+
+}
diff --git a/uitest/src/com/vaadin/tests/widgetset/rebind/TestWidgetRegistryGenerator.java b/uitest/src/com/vaadin/tests/widgetset/rebind/TestWidgetRegistryGenerator.java
index 1bdbba2c36..c7b29e271b 100644
--- a/uitest/src/com/vaadin/tests/widgetset/rebind/TestWidgetRegistryGenerator.java
+++ b/uitest/src/com/vaadin/tests/widgetset/rebind/TestWidgetRegistryGenerator.java
@@ -136,6 +136,9 @@ public class TestWidgetRegistryGenerator extends Generator {
} else if (!widgetType.getPackage().getName()
.startsWith(TestWidgetConnector.class.getPackage().getName())) {
return false;
+ } else if (widgetType.getEnclosingType() != null
+ && !widgetType.isStatic()) {
+ return false;
}
return true;