Causes regression with IE8: http://r2d2.devnet.vaadin.com:8111/viewLog.html?buildTypeId=Vaadin72_Vaadin72DevelopmentBuildTb2Tests&buildId=86020 Change-Id: I6d848777b28a1d3f27a25fec778cba8d68a45690tags/7.3.0.beta1
@@ -36,7 +36,6 @@ import com.vaadin.client.ui.VNotification; | |||
import com.vaadin.client.ui.layout.ElementResizeEvent; | |||
import com.vaadin.client.ui.layout.ElementResizeListener; | |||
import com.vaadin.client.ui.layout.LayoutDependencyTree; | |||
import com.vaadin.client.ui.orderedlayout.AbstractOrderedLayoutConnector; | |||
public class LayoutManager { | |||
private static final String LOOP_ABORT_MESSAGE = "Aborting layout after 100 passes. This would probably be an infinite loop."; | |||
@@ -721,16 +720,6 @@ public class LayoutManager { | |||
Profiler.enter("LayoutManager.measureConnector"); | |||
Element element = connector.getWidget().getElement(); | |||
MeasuredSize measuredSize = getMeasuredSize(connector); | |||
if (isBrowserOptimizedMeasuringNeeded() | |||
&& isWrappedInsideExpandBlock(connector)) { | |||
// this fixes zoom/sub-pixel issues with ie9+ and Chrome | |||
// (#13359) | |||
// Update measuring logic to round up and/or down depending on | |||
// browser. | |||
measuredSize.setMeasurer(new MeasuredSize.RoundingMeasurer()); | |||
} else { | |||
measuredSize.setMeasurer(null);// resets to default measurer | |||
} | |||
MeasureResult measureResult = measuredAndUpdate(element, measuredSize); | |||
if (measureResult.isChanged()) { | |||
@@ -740,23 +729,6 @@ public class LayoutManager { | |||
Profiler.leave("LayoutManager.measureConnector"); | |||
} | |||
/** | |||
* Return true if browser may need some optimized width and height measuring | |||
* for MeasuredSize. | |||
* <p> | |||
* Usually optimization is needed to avoid unnecessary scroll bars appearing | |||
* in layouts caused by sub-pixel rounding. And to avoid LayoutManager | |||
* doLayout() going into a loop trying to fit content with fractional size | |||
* (percentages) inside a parent element and stopping only after safety | |||
* iteration limit exceeds, also caused by sub-pixel rounding. | |||
* <p> | |||
* For internal use only. May be removed or replaced in the future. | |||
*/ | |||
private static boolean isBrowserOptimizedMeasuringNeeded() { | |||
return BrowserInfo.get().isChrome() | |||
|| (BrowserInfo.get().isIE() && !BrowserInfo.get().isIE8()); | |||
} | |||
private void onConnectorChange(ComponentConnector connector, | |||
boolean widthChanged, boolean heightChanged) { | |||
Profiler.enter("LayoutManager.onConnectorChange"); | |||
@@ -838,26 +810,6 @@ public class LayoutManager { | |||
return connector instanceof ManagedLayout; | |||
} | |||
/** | |||
* Is the given connector wrapped inside a 'expanding' content. Detected by | |||
* checking if the connector's parent is AbstractOrderedLayoutConnector that | |||
* expands. 'Expand' means that some layout slots may expand its content | |||
* width or height to some percentage fraction. | |||
* | |||
* @param connector | |||
* The connector to check | |||
* @return True if connector is wrapped inside a | |||
* AbstractOrderedLayoutConnector that expands | |||
*/ | |||
private static boolean isWrappedInsideExpandBlock( | |||
ComponentConnector connector) { | |||
ServerConnector parent = connector.getParent(); | |||
if (parent instanceof AbstractOrderedLayoutConnector) { | |||
return ((AbstractOrderedLayoutConnector) parent).needsExpand(); | |||
} | |||
return false; | |||
} | |||
public void forceLayout() { | |||
ConnectorMap connectorMap = connection.getConnectorMap(); | |||
JsArrayObject<ComponentConnector> componentConnectors = connectorMap |
@@ -43,49 +43,6 @@ public class MeasuredSize { | |||
} | |||
} | |||
public interface Measurer { | |||
int measureHeight(Element element); | |||
int measureWidth(Element element); | |||
} | |||
/** | |||
* Default measurer rounds always up. | |||
*/ | |||
public static class DefaultMeasurer implements Measurer { | |||
@Override | |||
public int measureHeight(Element element) { | |||
int requiredHeight = Util.getRequiredHeight(element); | |||
return requiredHeight; | |||
} | |||
@Override | |||
public int measureWidth(Element element) { | |||
int requiredWidth = Util.getRequiredWidth(element); | |||
return requiredWidth; | |||
} | |||
} | |||
/** | |||
* RoundingMeasurer measurer rounds up and down, optimized for different | |||
* browsers. | |||
*/ | |||
public static class RoundingMeasurer implements Measurer { | |||
@Override | |||
public int measureHeight(Element element) { | |||
double requiredHeight = Util.getPreciseRequiredHeight(element); | |||
return Util.roundPreciseSize(requiredHeight); | |||
} | |||
@Override | |||
public int measureWidth(Element element) { | |||
double requiredWidth = Util.getPreciseRequiredWidth(element); | |||
return Util.roundPreciseSize(requiredWidth); | |||
} | |||
} | |||
private int width = -1; | |||
private int height = -1; | |||
@@ -95,15 +52,6 @@ public class MeasuredSize { | |||
private FastStringSet dependents = FastStringSet.create(); | |||
private Measurer measurer = new DefaultMeasurer(); | |||
public void setMeasurer(Measurer measurer) { | |||
if (measurer == null) { | |||
measurer = new DefaultMeasurer(); | |||
} | |||
this.measurer = measurer; | |||
} | |||
public int getOuterHeight() { | |||
return height; | |||
} | |||
@@ -288,7 +236,7 @@ public class MeasuredSize { | |||
Profiler.leave("Measure borders"); | |||
Profiler.enter("Measure height"); | |||
int requiredHeight = measurer.measureHeight(element); | |||
int requiredHeight = Util.getRequiredHeight(element); | |||
int marginHeight = sumHeights(margins); | |||
int oldHeight = height; | |||
int oldWidth = width; | |||
@@ -299,7 +247,7 @@ public class MeasuredSize { | |||
Profiler.leave("Measure height"); | |||
Profiler.enter("Measure width"); | |||
int requiredWidth = measurer.measureWidth(element); | |||
int requiredWidth = Util.getRequiredWidth(element); | |||
int marginWidth = sumWidths(margins); | |||
if (setOuterWidth(requiredWidth + marginWidth)) { | |||
debugSizeChange(element, "Width (outer)", oldWidth, width); |
@@ -627,48 +627,16 @@ public class Util { | |||
return reqHeight; | |||
} | |||
/** | |||
* Gets the border-box width for the given element, i.e. element width + | |||
* border + padding. | |||
* | |||
* @param element | |||
* The element to check | |||
* @return The border-box width for the element | |||
*/ | |||
public static double getPreciseRequiredWidth( | |||
com.google.gwt.dom.client.Element element) { | |||
double reqWidth; | |||
if (browserUsesPreciseSizeByComputedStyle()) { | |||
reqWidth = getPreciseWidthByComputedStyle(element); | |||
} else { | |||
reqWidth = getPreciseBoundingClientRectWidth(element); | |||
} | |||
return reqWidth; | |||
} | |||
/** | |||
* Gets the border-box height for the given element, i.e. element height + | |||
* border + padding. | |||
* | |||
* @param element | |||
* The element to check | |||
* @return The border-box height for the element | |||
*/ | |||
public static double getPreciseRequiredHeight( | |||
com.google.gwt.dom.client.Element element) { | |||
double reqHeight; | |||
if (browserUsesPreciseSizeByComputedStyle()) { | |||
reqHeight = getPreciseHeightByComputedStyle(element); | |||
public static native int getRequiredWidthBoundingClientRect( | |||
com.google.gwt.dom.client.Element element) | |||
/*-{ | |||
if (element.getBoundingClientRect) { | |||
var rect = element.getBoundingClientRect(); | |||
return Math.ceil(rect.right - rect.left); | |||
} else { | |||
reqHeight = getPreciseBoundingClientRectHeight(element); | |||
return element.offsetWidth; | |||
} | |||
return reqHeight; | |||
} | |||
public static int getRequiredWidthBoundingClientRect( | |||
com.google.gwt.dom.client.Element element) { | |||
return (int) Math.ceil(getPreciseBoundingClientRectWidth(element)); | |||
} | |||
}-*/; | |||
public static native int getRequiredHeightComputedStyle( | |||
com.google.gwt.dom.client.Element element) | |||
@@ -710,10 +678,18 @@ public class Util { | |||
return Math.ceil(width+border+padding); | |||
}-*/; | |||
public static int getRequiredHeightBoundingClientRect( | |||
com.google.gwt.dom.client.Element element) { | |||
return (int) Math.ceil(getPreciseBoundingClientRectHeight(element)); | |||
} | |||
public static native int getRequiredHeightBoundingClientRect( | |||
com.google.gwt.dom.client.Element element) | |||
/*-{ | |||
var height; | |||
if (element.getBoundingClientRect != null) { | |||
var rect = element.getBoundingClientRect(); | |||
height = Math.ceil(rect.bottom - rect.top); | |||
} else { | |||
height = element.offsetHeight; | |||
} | |||
return height; | |||
}-*/; | |||
public static int getRequiredWidth(Widget widget) { | |||
return getRequiredWidth(widget.getElement()); | |||
@@ -723,128 +699,6 @@ public class Util { | |||
return getRequiredHeight(widget.getElement()); | |||
} | |||
/** | |||
* Rounds pixel size up or down depending on the browser. | |||
* <p> | |||
* <li> | |||
* IE9, IE10 - rounds always down to closest integer | |||
* <li> | |||
* Others - rounds half up to closest integer (1.49 -> 1.0, 1.5 -> 2.0) | |||
* | |||
* @param preciseSize | |||
* Decimal number to round. | |||
* @return Rounded integer | |||
*/ | |||
public static int roundPreciseSize(double preciseSize) { | |||
boolean roundAlwaysDown = BrowserInfo.get().isIE9() | |||
|| BrowserInfo.get().isIE10(); | |||
if (roundAlwaysDown) { | |||
return (int) preciseSize; | |||
} else { | |||
// round to closest integer | |||
return (int) Math.round(preciseSize); | |||
} | |||
} | |||
/** | |||
* Returns getBoundingClientRect.width, if supported. Otherwise return | |||
* offsetWidth. Includes the padding, scrollbar, and the border. Excludes | |||
* the margin. | |||
* | |||
* @param elem | |||
* Target element to measure | |||
*/ | |||
private static native double getPreciseBoundingClientRectWidth(Element elem) | |||
/*-{ | |||
try { | |||
if (elem.getBoundingClientRect) { | |||
return elem.getBoundingClientRect().width; | |||
} else { | |||
return (elem.offsetWidth) || 0; | |||
} | |||
} catch (e) { | |||
// JS exception is thrown if the elem is not attached to the document. | |||
return 0; | |||
} | |||
}-*/; | |||
/** | |||
* Returns getBoundingClientRect.height, if supported. Otherwise return | |||
* offsetHeight. Includes the padding, scrollbar, and the border. Excludes | |||
* the margin. | |||
* | |||
* @param elem | |||
* Target element to measure | |||
*/ | |||
private static native double getPreciseBoundingClientRectHeight(Element elem) | |||
/*-{ | |||
try { | |||
if (elem.getBoundingClientRect) { | |||
return elem.getBoundingClientRect().height; | |||
} else { | |||
return (elem.offsetHeight) || 0; | |||
} | |||
} catch (e) { | |||
// JS exception is thrown if the elem is not attached to the document. | |||
return 0; | |||
} | |||
}-*/; | |||
/** | |||
* Parse computed styles to get precise width for the given element. | |||
* Excludes the margin. Fallback to offsetWidth if computed styles is not | |||
* defined, or if width can't be read or it's not fractional. | |||
* | |||
* @param elem | |||
* Target element to measure | |||
*/ | |||
private static native double getPreciseWidthByComputedStyle( | |||
com.google.gwt.dom.client.Element elem) | |||
/*-{ | |||
var cs = elem.ownerDocument.defaultView.getComputedStyle(elem); | |||
var size = cs && cs.getPropertyValue('width') || ''; | |||
if (size && size.indexOf('.') > -1) { | |||
size = parseFloat(size) | |||
+ parseInt(cs.getPropertyValue('padding-left')) | |||
+ parseInt(cs.getPropertyValue('padding-right')) | |||
+ parseInt(cs.getPropertyValue('border-left-width')) | |||
+ parseInt(cs.getPropertyValue('border-right-width')); | |||
} else { | |||
size = elem.offsetWidth; | |||
} | |||
return size; | |||
}-*/; | |||
/** | |||
* Parse computed styles to get precise height for the given element. | |||
* Excludes the margin. Fallback to offsetHeight if computed styles is not | |||
* defined, or if height can't be read or it's not fractional. | |||
* | |||
* @param elem | |||
* Target element to measure | |||
*/ | |||
private static native double getPreciseHeightByComputedStyle( | |||
com.google.gwt.dom.client.Element elem) | |||
/*-{ | |||
var cs = elem.ownerDocument.defaultView.getComputedStyle(elem); | |||
var size = cs && cs.getPropertyValue('height') || ''; | |||
if (size && size.indexOf('.') > -1) { | |||
size = parseFloat(size) | |||
+ parseInt(cs.getPropertyValue('padding-top')) | |||
+ parseInt(cs.getPropertyValue('padding-bottom')) | |||
+ parseInt(cs.getPropertyValue('border-top-width')) | |||
+ parseInt(cs.getPropertyValue('border-bottom-width')); | |||
} else { | |||
size = elem.offsetHeight; | |||
} | |||
return size; | |||
}-*/; | |||
/** For internal use only. May be removed or replaced in the future. */ | |||
private static boolean browserUsesPreciseSizeByComputedStyle() { | |||
return BrowserInfo.get().isIE9(); | |||
} | |||
/** | |||
* Detects what is currently the overflow style attribute in given element. | |||
* |
@@ -551,10 +551,8 @@ public abstract class AbstractOrderedLayoutConnector extends | |||
/** | |||
* Does the layout need to expand? | |||
* <p> | |||
* For internal use only. May be removed or replaced in the future. | |||
*/ | |||
public boolean needsExpand() { | |||
private boolean needsExpand() { | |||
return needsExpand; | |||
} | |||
@@ -1,109 +0,0 @@ | |||
/* | |||
* 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.orderedlayout; | |||
import com.vaadin.server.VaadinRequest; | |||
import com.vaadin.tests.components.AbstractTestUI; | |||
import com.vaadin.ui.Alignment; | |||
import com.vaadin.ui.Component; | |||
import com.vaadin.ui.HorizontalLayout; | |||
import com.vaadin.ui.Label; | |||
import com.vaadin.ui.UI; | |||
@SuppressWarnings("serial") | |||
public class OrderedLayoutExpand extends AbstractTestUI { | |||
@Override | |||
protected void setup(VaadinRequest request) { | |||
ThreeColumnLayout layout = new ThreeColumnLayout( | |||
createLabelWithUndefinedSize("first component"), | |||
createLabelWithUndefinedSize("second component"), | |||
createLabelWithUndefinedSize("third component")); | |||
addComponent(layout); | |||
setWidth("600px"); | |||
if (UI.getCurrent().getPage().getWebBrowser().isChrome()) { | |||
getPage().getStyles().add("body { zoom: 1.10; }"); | |||
} | |||
} | |||
@Override | |||
protected String getTestDescription() { | |||
StringBuilder sb = new StringBuilder( | |||
"You should not see 'Aborting layout after 100 passes.' error with debug mode and "); | |||
if (UI.getCurrent().getPage().getWebBrowser().isChrome()) { | |||
sb.append(" Zoom is "); | |||
sb.append("1.10"); | |||
} else { | |||
sb.append(" zoom level 95%."); | |||
} | |||
return sb.toString(); | |||
} | |||
@Override | |||
protected Integer getTicketNumber() { | |||
return 13359; | |||
} | |||
private Label createLabelWithUndefinedSize(String caption) { | |||
Label label = new Label(caption); | |||
label.setSizeUndefined(); | |||
return label; | |||
} | |||
private static class ThreeColumnLayout extends HorizontalLayout { | |||
public ThreeColumnLayout(Component leftComponent, | |||
Component centerComponent, Component rightComponent) { | |||
setWidth("100%"); | |||
setMargin(true); | |||
Component left = createLeftHolder(leftComponent); | |||
this.addComponent(left); | |||
setComponentAlignment(left, Alignment.MIDDLE_LEFT); | |||
setExpandRatio(left, 1f); | |||
Component center = createCenterHolder(centerComponent); | |||
this.addComponent(center); | |||
setComponentAlignment(center, Alignment.MIDDLE_CENTER); | |||
setExpandRatio(center, 0f); | |||
Component right = createRightHolder(rightComponent); | |||
this.addComponent(right); | |||
setComponentAlignment(right, Alignment.MIDDLE_RIGHT); | |||
setExpandRatio(right, 1f); | |||
} | |||
private Component createLeftHolder(Component c) { | |||
HorizontalLayout hl = new HorizontalLayout(); | |||
hl.addComponent(c); | |||
hl.setWidth(100, Unit.PERCENTAGE); | |||
return hl; | |||
} | |||
private Component createCenterHolder(Component c) { | |||
HorizontalLayout hl = new HorizontalLayout(); | |||
hl.addComponent(c); | |||
hl.setComponentAlignment(c, Alignment.MIDDLE_CENTER); | |||
return hl; | |||
} | |||
private Component createRightHolder(Component c) { | |||
HorizontalLayout hl = new HorizontalLayout(); | |||
hl.addComponent(c); | |||
hl.setWidth(100, Unit.PERCENTAGE); | |||
return hl; | |||
} | |||
} | |||
} |
@@ -1,132 +0,0 @@ | |||
/* | |||
* 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.orderedlayout; | |||
import java.util.List; | |||
import org.junit.Assert; | |||
import org.junit.Test; | |||
import org.openqa.selenium.By; | |||
import org.openqa.selenium.Keys; | |||
import org.openqa.selenium.NoSuchElementException; | |||
import org.openqa.selenium.WebElement; | |||
import org.openqa.selenium.remote.DesiredCapabilities; | |||
import com.vaadin.tests.tb3.MultiBrowserTest; | |||
public class OrderedLayoutExpandTest extends MultiBrowserTest { | |||
/* | |||
* (non-Javadoc) | |||
* | |||
* @see com.vaadin.tests.tb3.MultiBrowserTest#getBrowsersToTest() | |||
*/ | |||
@Override | |||
public List<DesiredCapabilities> getBrowsersToTest() { | |||
List<DesiredCapabilities> browsersToTest = super.getBrowsersToTest(); | |||
// Not sure why, but IE11 might give the following error message: | |||
// "org.openqa.selenium.WebDriverException: Unexpected error launching | |||
// Internet Explorer. Browser zoom level was set to 75%. It should be | |||
// set to 100%". | |||
// setting "ignoreZoomSetting" capability to true seems to help, but if | |||
// it keeps bugging, just skip the IE11 completely bu uncommenting the | |||
// line below. | |||
// browsersToTest.remove(Browser.IE11.getDesiredCapabilities()); | |||
Browser.IE11.getDesiredCapabilities().setCapability( | |||
"ignoreZoomSetting", true); | |||
// Can't test with IE8 with a zoom level other than 100%. IE8 could be | |||
// removed to speed up the test run. | |||
// browsersToTest.remove(Browser.IE8.getDesiredCapabilities()); | |||
return browsersToTest; | |||
} | |||
@Test | |||
public void testNoAbortingLayoutAfter100PassesError() throws Exception { | |||
setDebug(true); | |||
openTestURL(); | |||
if (!getDesiredCapabilities().equals( | |||
Browser.CHROME.getDesiredCapabilities())) { | |||
// Chrome uses css to to set zoom level to 110% that is known to | |||
// cause issues with the test app. | |||
// Other browsers tries to set browser's zoom level directly. | |||
WebElement html = driver.findElement(By.tagName("html")); | |||
// reset to 100% just in case | |||
html.sendKeys(Keys.chord(Keys.CONTROL, "0")); | |||
// zoom browser to 75% (ie) or 90% (FF). It depends on browser | |||
// how much "Ctrl + '-'" zooms out. | |||
html.sendKeys(Keys.chord(Keys.CONTROL, Keys.SUBTRACT)); | |||
} | |||
// open debug window's Examine component hierarchy tab | |||
openDebugExamineComponentHierarchyTab(); | |||
// click "Check layouts for potential problems" button | |||
clickDebugCheckLayoutsForPotentialProblems(); | |||
// find div containing a successful layout analyze result | |||
WebElement pass = findLayoutAnalyzePassElement(); | |||
// find div containing a error message with | |||
// "Aborting layout after 100 passess" message. | |||
WebElement error = findLayoutAnalyzeAbortedElement(); | |||
Assert.assertNull(error); | |||
Assert.assertNotNull(pass); | |||
if (!getDesiredCapabilities().equals( | |||
Browser.CHROME.getDesiredCapabilities())) { | |||
WebElement html = driver.findElement(By.tagName("html")); | |||
// reset zoom level back to 100% | |||
html.sendKeys(Keys.chord(Keys.CONTROL, "0")); | |||
} | |||
} | |||
private void openDebugExamineComponentHierarchyTab() { | |||
WebElement button = findElement(By | |||
.xpath("//button[@title='Examine component hierarchy']")); | |||
// can't use 'click()' with zoom levels other than 100% | |||
button.sendKeys(Keys.chord(Keys.SPACE)); | |||
} | |||
private void clickDebugCheckLayoutsForPotentialProblems() { | |||
WebElement button = findElement(By | |||
.xpath("//button[@title='Check layouts for potential problems']")); | |||
button.sendKeys(Keys.chord(Keys.SPACE)); | |||
} | |||
private WebElement findLayoutAnalyzePassElement() { | |||
try { | |||
return findElement(By | |||
.xpath("//div[text()='Layouts analyzed, no top level problems']")); | |||
} catch (NoSuchElementException e) { | |||
return null; | |||
} | |||
} | |||
private WebElement findLayoutAnalyzeAbortedElement() { | |||
try { | |||
return findElement(By | |||
.xpath("//div[text()='Aborting layout after 100 passess']")); | |||
} catch (NoSuchElementException e) { | |||
return null; | |||
} | |||
} | |||
} |