]> source.dussan.org Git - vaadin-framework.git/commitdiff
Use computed style for Escalator size calculations (#8861)
authorArtur <artur@vaadin.com>
Mon, 27 Mar 2017 08:14:49 +0000 (11:14 +0300)
committerPekka Hyvönen <pekka@vaadin.com>
Mon, 27 Mar 2017 08:14:49 +0000 (11:14 +0300)
* Use computed style for Escalator size calculations

The old method of using getBoundingClientRect does not work as expected
if a transform has been applied to the element or one of its parents.
For instance PopupView animates itself using a scale(0) -> scale(1)
animation. When scale(0) is active, getBoundingClientRect will return 0
for all sizes while computed style ignores the transform and returns the
expected value.

Fixes #8793

client/src/main/java/com/vaadin/client/ComputedStyle.java
client/src/main/java/com/vaadin/client/widgets/Escalator.java
uitest/src/main/java/com/vaadin/tests/components/grid/GridPopupView.java [new file with mode: 0644]
uitest/src/test/java/com/vaadin/tests/components/grid/GridPopupViewTest.java [new file with mode: 0644]

index 977da6bafc93c5c95a4fd2b47a5032b6ccc8e832..e9108f2ee6b685873e50362796ba19bd330f4ba3 100644 (file)
@@ -20,6 +20,7 @@ import com.google.gwt.dom.client.Element;
 
 public class ComputedStyle {
 
+    private static final String CONTENT_BOX = "content-box";
     protected final JavaScriptObject computedStyle;
     private final Element elem;
 
@@ -32,7 +33,6 @@ public class ComputedStyle {
      *
      * @param elem
      *            the element
-     * @return the computed style
      */
     public ComputedStyle(Element elem) {
         computedStyle = getComputedStyle(elem);
@@ -44,17 +44,18 @@ public class ComputedStyle {
       if(elem.nodeType != 1) {
           return {};
       }
-    
+
       if($wnd.document.defaultView && $wnd.document.defaultView.getComputedStyle) {
           return $wnd.document.defaultView.getComputedStyle(elem, null);
       }
-    
+
       if(elem.currentStyle) {
           return elem.currentStyle;
       }
     }-*/;
 
     /**
+     * Gets the value of the given property.
      *
      * @param name
      *            name of the CSS property in camelCase
@@ -65,7 +66,7 @@ public class ComputedStyle {
     /*-{
         var cs = this.@com.vaadin.client.ComputedStyle::computedStyle;
         var elem = this.@com.vaadin.client.ComputedStyle::elem;
-    
+
         // Border values need to be checked separately. The width might have a
         // meaningful value even if the border style is "none". In that case the
         // value should be 0.
@@ -78,45 +79,45 @@ public class ComputedStyle {
             if(borderStyle == "none")
                 return "0px";
         }
-    
+
         if(cs.getPropertyValue) {
-    
+
             // Convert name to dashed format
             name = name.replace(/([A-Z])/g, "-$1").toLowerCase();
             var ret = cs.getPropertyValue(name);
-    
+
         } else {
-    
+
             var ret = cs[name];
             var style = elem.style;
-    
+
             // From the awesome hack by Dean Edwards
             // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291
-    
+
             // If we're not dealing with a regular pixel number
             // but a number that has a weird ending, we need to convert it to pixels
                 if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
                     // Remember the original values
                     var left = style.left, rsLeft = elem.runtimeStyle.left;
-    
+
                     // Put in the new values to get a computed value out
                     elem.runtimeStyle.left = cs.left;
                     style.left = ret || 0;
                     ret = style.pixelLeft + "px";
-    
+
                     // Revert the changed values
                     style.left = left;
                     elem.runtimeStyle.left = rsLeft;
                 }
-    
+
         }
-    
+
         // Normalize margin values. This is not totally valid, but in most cases
         // it is what the user wants to know.
         if(name.indexOf("margin") > -1 && ret == "auto") {
             return "0px";
         }
-    
+
         // Some browsers return undefined width and height values as "auto", so
         // we need to retrieve those ourselves.
         if (name == "width" && ret == "auto") {
@@ -124,13 +125,13 @@ public class ComputedStyle {
         } else if (name == "height" && ret == "auto") {
             ret = elem.clientHeight + "px";
         }
-    
+
         return ret;
-    
+
     }-*/;
 
     /**
-     * Retrieves the given computed property as an integer
+     * Retrieves the given computed property as an integer.
      *
      * Returns 0 if the property cannot be converted to an integer
      *
@@ -147,7 +148,7 @@ public class ComputedStyle {
     }
 
     /**
-     * Retrieves the given computed property as a double
+     * Retrieves the given computed property as a double.
      *
      * Returns NaN if the property cannot be converted to a double
      *
@@ -165,8 +166,10 @@ public class ComputedStyle {
     }
 
     /**
-     * Get current margin values from the DOM. The array order is the default
-     * CSS order: top, right, bottom, left.
+     * Get current margin values from the DOM.
+     *
+     * @return an array containing four values for the four edges, in the
+     *         default CSS order: top, right, bottom, left.
      */
     public final int[] getMargin() {
         int[] margin = { 0, 0, 0, 0 };
@@ -178,8 +181,10 @@ public class ComputedStyle {
     }
 
     /**
-     * Get current padding values from the DOM. The array order is the default
-     * CSS order: top, right, bottom, left.
+     * Get current padding values from the DOM.
+     *
+     * @return an array containing four values for the four edges, in the
+     *         default CSS order: top, right, bottom, left.
      */
     public final int[] getPadding() {
         int[] padding = { 0, 0, 0, 0 };
@@ -191,8 +196,10 @@ public class ComputedStyle {
     }
 
     /**
-     * Get current border values from the DOM. The array order is the default
-     * CSS order: top, right, bottom, left.
+     * Get current border values from the DOM.
+     *
+     * @return an array containing four values for the four edges, in the
+     *         default CSS order: top, right, bottom, left.
      */
     public final int[] getBorder() {
         int[] border = { 0, 0, 0, 0 };
@@ -226,7 +233,7 @@ public class ComputedStyle {
     /**
      * Takes a String value e.g. "12px" and parses that to Integer 12.
      *
-     * @param String
+     * @param value
      *            a value starting with a number
      * @return Integer the value from the string before any non-numeric
      *         characters. If the value cannot be parsed to a number, returns
@@ -282,7 +289,7 @@ public class ComputedStyle {
     }-*/;
 
     /**
-     * Returns the sum of the top and bottom border width
+     * Returns the sum of the top and bottom border width.
      *
      * @since 7.5.3
      * @return the sum of the top and bottom border
@@ -295,7 +302,7 @@ public class ComputedStyle {
     }
 
     /**
-     * Returns the sum of the left and right border width
+     * Returns the sum of the left and right border width.
      *
      * @since 7.5.3
      * @return the sum of the left and right border
@@ -308,7 +315,7 @@ public class ComputedStyle {
     }
 
     /**
-     * Returns the sum of the top and bottom padding
+     * Returns the sum of the top and bottom padding.
      *
      * @since 7.5.3
      * @return the sum of the top and bottom padding
@@ -321,7 +328,7 @@ public class ComputedStyle {
     }
 
     /**
-     * Returns the sum of the top and bottom padding
+     * Returns the sum of the top and bottom padding.
      *
      * @since 7.5.3
      * @return the sum of the left and right padding
@@ -334,7 +341,7 @@ public class ComputedStyle {
     }
 
     /**
-     * Returns the sum of the top and bottom margin
+     * Returns the sum of the top and bottom margin.
      *
      * @since 7.5.6
      * @return the sum of the top and bottom margin
@@ -347,7 +354,7 @@ public class ComputedStyle {
     }
 
     /**
-     * Returns the sum of the top and bottom margin
+     * Returns the sum of the left and right margin.
      *
      * @since 7.5.6
      * @return the sum of the left and right margin
@@ -359,4 +366,46 @@ public class ComputedStyle {
         return marginWidth;
     }
 
+    /**
+     * Returns the current height, padding and border from the DOM.
+     *
+     * @return the computed height including padding and borders
+     */
+    public double getHeightIncludingBorderPadding() {
+        double h = getHeight();
+        if (BrowserInfo.get().isIE() || isContentBox()) {
+            // IE11 always returns only the height without padding/border
+            h += getBorderHeight() + getPaddingHeight();
+        }
+
+        return h;
+    }
+
+    /**
+     * Returns the current width, padding and border from the DOM.
+     *
+     * @return the computed width including padding and borders
+     */
+    public double getWidthIncludingBorderPadding() {
+        double w = getWidth();
+        if (BrowserInfo.get().isIE() || isContentBox()) {
+            // IE11 always returns only the width without padding/border
+            w += getBorderWidth() + getPaddingWidth();
+        }
+        return w;
+    }
+
+    private boolean isContentBox() {
+        return getBoxSizing().equals(CONTENT_BOX);
+    }
+
+    /**
+     * Returns the value of the boxSizing property.
+     *
+     * @return the value of the boxSizing property
+     */
+    private String getBoxSizing() {
+        return getProperty("boxSizing");
+    }
+
 }
index 8f79483eb81217dfa44028476421d2d1cc9ea3a7..11bfa113cbd90611d5350855f5e03e6a529fd7d7 100644 (file)
@@ -60,6 +60,7 @@ import com.google.gwt.user.client.ui.RootPanel;
 import com.google.gwt.user.client.ui.UIObject;
 import com.google.gwt.user.client.ui.Widget;
 import com.vaadin.client.BrowserInfo;
+import com.vaadin.client.ComputedStyle;
 import com.vaadin.client.DeferredWorker;
 import com.vaadin.client.Profiler;
 import com.vaadin.client.WidgetUtil;
@@ -701,13 +702,13 @@ public class Escalator extends Widget
         /*-{
             var vScroll = esc.@com.vaadin.client.widgets.Escalator::verticalScrollbar;
             var vScrollElem = vScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()();
-        
+
             var hScroll = esc.@com.vaadin.client.widgets.Escalator::horizontalScrollbar;
             var hScrollElem = hScroll.@com.vaadin.client.widget.escalator.ScrollbarBundle::getElement()();
-        
+
             return $entry(function(e) {
                 var target = e.target;
-        
+
                 // in case the scroll event was native (i.e. scrollbars were dragged, or
                 // the scrollTop/Left was manually modified), the bundles have old cache
                 // values. We need to make sure that the caches are kept up to date.
@@ -728,29 +729,29 @@ public class Escalator extends Widget
             return $entry(function(e) {
                 var deltaX = e.deltaX ? e.deltaX : -0.5*e.wheelDeltaX;
                 var deltaY = e.deltaY ? e.deltaY : -0.5*e.wheelDeltaY;
-        
+
                 // Delta mode 0 is in pixels; we don't need to do anything...
-        
+
                 // A delta mode of 1 means we're scrolling by lines instead of pixels
                 // We need to scale the number of lines by the default line height
                 if(e.deltaMode === 1) {
                     var brc = esc.@com.vaadin.client.widgets.Escalator::body;
                     deltaY *= brc.@com.vaadin.client.widgets.Escalator.AbstractRowContainer::getDefaultRowHeight()();
                 }
-        
+
                 // Other delta modes aren't supported
                 if((e.deltaMode !== undefined) && (e.deltaMode >= 2 || e.deltaMode < 0)) {
                     var msg = "Unsupported wheel delta mode \"" + e.deltaMode + "\"";
-        
+
                     // Print warning message
                     esc.@com.vaadin.client.widgets.Escalator::logWarning(*)(msg);
                 }
-        
+
                 // IE8 has only delta y
                 if (isNaN(deltaY)) {
                     deltaY = -0.5*e.wheelDelta;
                 }
-        
+
                 @com.vaadin.client.widgets.Escalator.JsniUtil::moveScrollFromEvent(*)(esc, deltaX, deltaY, e);
             });
         }-*/;
@@ -1054,9 +1055,8 @@ public class Escalator extends Widget
                     + columnConfiguration.getColumnWidthActual(columnIndex);
 
             final double viewportStartPx = getScrollLeft();
-            double viewportEndPx = viewportStartPx + WidgetUtil
-                    .getRequiredWidthBoundingClientRectDouble(getElement())
-                    - frozenPixels;
+            double viewportEndPx = viewportStartPx
+                    + getBoundingWidth(getElement()) - frozenPixels;
             if (verticalScrollbar.showsScrollHandle()) {
                 viewportEndPx -= WidgetUtil.getNativeScrollbarSize();
             }
@@ -1744,8 +1744,7 @@ public class Escalator extends Widget
                 final boolean isVisible = !cell.getStyle().getDisplay()
                         .equals(Display.NONE.getCssName());
                 if (isVisible) {
-                    maxWidth = Math.max(maxWidth, WidgetUtil
-                            .getRequiredWidthBoundingClientRectDouble(cell));
+                    maxWidth = Math.max(maxWidth, getBoundingWidth(cell));
                 }
                 row = TableRowElement.as(row.getNextSiblingElement());
             }
@@ -1982,8 +1981,7 @@ public class Escalator extends Widget
 
             detectionTr.appendChild(cellElem);
             root.appendChild(detectionTr);
-            double boundingHeight = WidgetUtil
-                    .getRequiredHeightBoundingClientRectDouble(cellElem);
+            double boundingHeight = getBoundingHeight(cellElem);
             defaultRowHeight = Math.max(1.0d, boundingHeight);
             root.removeChild(detectionTr);
 
@@ -2059,8 +2057,7 @@ public class Escalator extends Widget
             cellClone.getStyle().clearWidth();
 
             cell.getParentElement().insertBefore(cellClone, cell);
-            double requiredWidth = WidgetUtil
-                    .getRequiredWidthBoundingClientRectDouble(cellClone);
+            double requiredWidth = getBoundingWidth(cellClone);
             if (BrowserInfo.get().isIE()) {
                 /*
                  * IE browsers have some issues with subpixels. Occasionally
@@ -5279,9 +5276,7 @@ public class Escalator extends Widget
             if (spacerDecoContainer.getParentElement() == null) {
                 getElement().appendChild(spacerDecoContainer);
                 // calculate the spacer deco width, it won't change
-                spacerDecoWidth = WidgetUtil
-                        .getRequiredWidthBoundingClientRectDouble(
-                                spacer.getDecoElement());
+                spacerDecoWidth = getBoundingWidth(spacer.getDecoElement());
             }
 
             initSpacerContent(spacer);
@@ -5682,6 +5677,19 @@ public class Escalator extends Widget
         publishJSHelpers(root);
     }
 
+    private double getBoundingWidth(Element element) {
+        // Gets the current width, including border and padding, for the element
+        // while ignoring any transforms applied to the element (e.g. scale)
+        return new ComputedStyle(element).getWidthIncludingBorderPadding();
+    }
+
+    private double getBoundingHeight(Element element) {
+        // Gets the current height, including border and padding, for the
+        // element while ignoring any transforms applied to the element (e.g.
+        // scale)
+        return new ComputedStyle(element).getHeightIncludingBorderPadding();
+    }
+
     private int getBodyRowCount() {
         return getBody().getRowCount();
     }
@@ -6292,10 +6300,8 @@ public class Escalator extends Widget
         }
 
         Profiler.enter("Escalator.recalculateElementSizes");
-        widthOfEscalator = Math.max(0, WidgetUtil
-                .getRequiredWidthBoundingClientRectDouble(getElement()));
-        heightOfEscalator = Math.max(0, WidgetUtil
-                .getRequiredHeightBoundingClientRectDouble(getElement()));
+        widthOfEscalator = Math.max(0, getBoundingWidth(getElement()));
+        heightOfEscalator = Math.max(0, getBoundingHeight(getElement()));
 
         header.recalculateSectionHeight();
         body.recalculateSectionHeight();
@@ -6662,8 +6668,7 @@ public class Escalator extends Widget
      * @return escalator's inner width
      */
     public double getInnerWidth() {
-        return WidgetUtil
-                .getRequiredWidthBoundingClientRectDouble(tableWrapper);
+        return getBoundingWidth(tableWrapper);
     }
 
     /**
diff --git a/uitest/src/main/java/com/vaadin/tests/components/grid/GridPopupView.java b/uitest/src/main/java/com/vaadin/tests/components/grid/GridPopupView.java
new file mode 100644 (file)
index 0000000..3d27f60
--- /dev/null
@@ -0,0 +1,26 @@
+package com.vaadin.tests.components.grid;
+
+import com.vaadin.annotations.Widgetset;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.tests.components.AbstractTestUI;
+import com.vaadin.ui.Grid;
+import com.vaadin.ui.PopupView;
+
+@Widgetset("com.vaadin.DefaultWidgetSet")
+public class GridPopupView extends AbstractTestUI {
+
+    @Override
+    protected void setup(VaadinRequest vaadinRequest) {
+        Grid<String> grid = new Grid<>();
+        grid.setItems("Foo", "Bar", "Baz");
+
+        PopupView popupView = new PopupView(
+                "Show grid (click me multiple times)", grid);
+        popupView.setHideOnMouseOut(false);
+
+        grid.addColumn(str -> str).setCaption("Something");
+
+        addComponent(popupView);
+    }
+
+}
diff --git a/uitest/src/test/java/com/vaadin/tests/components/grid/GridPopupViewTest.java b/uitest/src/test/java/com/vaadin/tests/components/grid/GridPopupViewTest.java
new file mode 100644 (file)
index 0000000..0616933
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2000-2016 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 java.util.List;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.openqa.selenium.By;
+import org.openqa.selenium.Dimension;
+import org.openqa.selenium.remote.DesiredCapabilities;
+
+import com.vaadin.testbench.elements.GridElement;
+import com.vaadin.testbench.elements.PopupViewElement;
+import com.vaadin.testbench.parallel.Browser;
+import com.vaadin.tests.tb3.MultiBrowserTest;
+
+public class GridPopupViewTest extends MultiBrowserTest {
+
+    @Override
+    public List<DesiredCapabilities> getBrowsersToTest() {
+        List<DesiredCapabilities> l = getBrowserCapabilities(Browser.IE11,
+                Browser.FIREFOX, Browser.CHROME);
+        l.add(PHANTOMJS2());
+        return l;
+    }
+
+    @Test
+    public void gridSizeCorrect() {
+        openTestURL();
+        PopupViewElement pv = $(PopupViewElement.class).first();
+
+        for (int i = 0; i < 3; i++) {
+            pv.click();
+            GridElement grid = $(GridElement.class).first();
+            Dimension rect = grid.getCell(0, 0).getSize();
+            Assert.assertEquals(500, rect.width);
+            Assert.assertEquals(38, rect.height);
+            findElement(By.className("v-ui")).click();
+            Assert.assertTrue($(GridElement.class).all().isEmpty());
+        }
+
+    }
+
+}