diff --git a/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlImage.java b/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlImage.java index 8276f1f9794..9b7961f0a8a 100644 --- a/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlImage.java +++ b/src/main/java/com/gargoylesoftware/htmlunit/html/HtmlImage.java @@ -20,6 +20,8 @@ import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLIMAGE_HTMLUNKNOWNELEMENT; import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.HTMLIMAGE_INVISIBLE_NO_SRC; import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_IMAGE_COMPLETE_RETURNS_TRUE_FOR_NO_REQUEST; +import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0; +import static com.gargoylesoftware.htmlunit.BrowserVersionFeatures.JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30; import java.io.File; import java.io.IOException; @@ -462,6 +464,22 @@ public int getHeight() throws IOException { } return height_; } + + public int getHeightOrDefault() { + if (getPage().getWebClient().getOptions().isDownloadImages()) { + try { + return getHeight(); + } catch (IOException e) {} + } + final BrowserVersion browserVersion = getPage().getWebClient().getBrowserVersion(); + if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30)) { + return 30; + } + if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0)) { + return 16; + } + return 24; + } /** *

Returns the image's actual width (not the image's {@link #getWidthAttribute() width attribute}).

@@ -477,6 +495,22 @@ public int getWidth() throws IOException { } return width_; } + + public int getWidthOrDefault() { + if (getPage().getWebClient().getOptions().isDownloadImages()) { + try { + return getWidth(); + } catch (IOException e) {} + } + final BrowserVersion browserVersion = getPage().getWebClient().getBrowserVersion(); + if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_28x30_28x30)) { + return 28; + } + if (browserVersion.hasFeature(JS_IMAGE_WIDTH_HEIGHT_RETURNS_16x16_0x0)) { + return 16; + } + return 24; + } /** *

Returns the ImageReader which can be used to read the image contained by this image element.

diff --git a/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclaration.java b/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclaration.java index 2f00d09f707..c6892e34ec7 100644 --- a/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclaration.java +++ b/src/main/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclaration.java @@ -116,6 +116,7 @@ import com.gargoylesoftware.htmlunit.html.HtmlElement; import com.gargoylesoftware.htmlunit.html.HtmlFileInput; import com.gargoylesoftware.htmlunit.html.HtmlHiddenInput; +import com.gargoylesoftware.htmlunit.html.HtmlImage; import com.gargoylesoftware.htmlunit.html.HtmlInlineFrame; import com.gargoylesoftware.htmlunit.html.HtmlInput; import com.gargoylesoftware.htmlunit.html.HtmlPasswordInput; @@ -134,6 +135,8 @@ import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLCanvasElement; import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLElement; import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLIFrameElement; +import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLImageElement; +import com.gargoylesoftware.htmlunit.javascript.host.html.HTMLScriptElement; import net.sourceforge.htmlunit.corejs.javascript.Context; import net.sourceforge.htmlunit.corejs.javascript.Scriptable; @@ -1017,7 +1020,7 @@ else if (BLOCK.equals(display)) { final HTMLElement parentJS = parent.getScriptableObject(); width = pixelValue(parentJS, new CssValue(0, windowWidth) { @Override public String get(final ComputedCSSStyleDeclaration style) { - return style.getWidth(); + return style.getCalculatedWidth(false, false) + "px"; } }) - (getBorderHorizontal() + getPaddingHorizontal()); } @@ -1045,6 +1048,9 @@ else if (node instanceof HtmlRadioButtonInput || node instanceof HtmlCheckBoxInp else if (node instanceof HtmlTextArea) { width = 100; // wild guess } + else if (node instanceof HtmlImage) { + width = ((HtmlImage)node).getWidthOrDefault(); + } else { // Inline elements take up however much space is required by their children. width = getContentWidth(); @@ -1081,10 +1087,29 @@ public int getContentWidth() { } } for (final DomNode child : children) { - if (child.getScriptableObject() instanceof HTMLElement) { + int w = 0; + if (!child.isDisplayed() || child.getScriptableObject() instanceof HTMLScriptElement) { + continue; + } + if (child.getScriptableObject() instanceof Element) { // check if explicit width is specified + final Element e = child.getScriptableObject(); + final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null); + final String widthAttr = style.getStyleAttribute(WIDTH, true).trim(); + w = style.getPixelWidth(); + if (!widthAttr.isEmpty() && (w > 0 || widthAttr.trim().startsWith("0"))) { // use this width only if defined and it's a valid value + width += w; + continue; + } + } + + if (child.getScriptableObject() instanceof HTMLImageElement) { + w = ((HtmlImage)child).getWidthOrDefault(); + width += w; + } + else if (child.getScriptableObject() instanceof HTMLElement) { final HTMLElement e = child.getScriptableObject(); final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null); - final int w = style.getCalculatedWidth(true, true); + w = style.getContentWidth(); width += w; } else if (child.getScriptableObject() instanceof Text) { @@ -1093,10 +1118,15 @@ else if (child.getScriptableObject() instanceof Text) { final HTMLElement e = child.getParentNode().getScriptableObject(); final ComputedCSSStyleDeclaration style = e.getWindow().getComputedStyle(e, null); final int height = getBrowserVersion().getFontHeight(style.getFontSize()); - width += child.getTextContent().length() * (int) (height / 1.8f); + final String textContent = child.getTextContent().replaceAll("^(\s|\t|\r|\n)*|(\s|\t|\r|\n)*$", ""); // indentation and line breaks between HTML elements might be represented as Text elements with whitespace. We'll trim these when calculating width + if (textContent.length() == 0) + continue; + w = textContent.length() * (int) (height / 1.8f); + width += w; } else { - width += child.getTextContent().length() * getBrowserVersion().getPixesPerChar(); + w = child.getTextContent().length() * getBrowserVersion().getPixesPerChar(); + width += w; } } } @@ -1517,7 +1547,7 @@ else if (FIXED.equals(p) && !AUTO.equals(r)) { final HTMLElement parent = (HTMLElement) getElement().getParentElement(); final ComputedCSSStyleDeclaration style = getWindow().getComputedStyle(getElement(), null); final ComputedCSSStyleDeclaration parentStyle = parent.getWindow().getComputedStyle(parent, null); - left = pixelValue(parentStyle.getWidth()) - pixelValue(style.getWidth()) - pixelValue(r); + left = parentStyle.getCalculatedWidth(true, true) - pixelValue(style.getWidth()) - pixelValue(r); } else if (FIXED.equals(p) && AUTO.equals(l)) { // Fixed to the location at which the browser puts it via normal element flowing. diff --git a/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclarationTest.java b/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclarationTest.java index 2c9685f338c..c0535878854 100644 --- a/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclarationTest.java +++ b/src/test/java/com/gargoylesoftware/htmlunit/javascript/host/css/ComputedCSSStyleDeclarationTest.java @@ -20,15 +20,23 @@ import static com.gargoylesoftware.htmlunit.BrowserRunner.TestedBrowser.FF78; import static com.gargoylesoftware.htmlunit.BrowserRunner.TestedBrowser.IE; +import java.io.InputStream; +import java.net.URL; +import java.util.Collections; +import java.util.List; + +import org.apache.commons.io.IOUtils; import org.junit.Test; import org.junit.runner.RunWith; import org.openqa.selenium.By; import org.openqa.selenium.WebDriver; +import org.openqa.selenium.htmlunit.HtmlUnitDriver; import com.gargoylesoftware.htmlunit.BrowserRunner; import com.gargoylesoftware.htmlunit.BrowserRunner.Alerts; import com.gargoylesoftware.htmlunit.BrowserRunner.HtmlUnitNYI; import com.gargoylesoftware.htmlunit.BrowserRunner.NotYetImplemented; +import com.gargoylesoftware.htmlunit.util.NameValuePair; import com.gargoylesoftware.htmlunit.WebDriverTestCase; /** @@ -1428,6 +1436,116 @@ public void widthAuto() throws Exception { + ""; loadPageVerifyTitle2(html); } + + @Alerts("40") + @Test + public void widthBlockElements() throws Exception { + final String content = "\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
\n" + + "
"; + loadPageVerifyTitle2(content); + } + + @Alerts("4") + @Test + public void widthImageElements() throws Exception { + try (InputStream is = getClass().getClassLoader().getResourceAsStream("testfiles/4x7.jpg")) { + final byte[] directBytes = IOUtils.toByteArray(is); + final URL urlImage = new URL(URL_FIRST, "4x7.jpg"); + final List emptyList = Collections.emptyList(); + getMockWebConnection().setResponse(urlImage, directBytes, 200, "ok", "image/jpg", emptyList); + } + + getWebWindowOf((HtmlUnitDriver)getWebDriver()).getWebClient().getOptions().setDownloadImages(true); + + final String content = "\n" + + "\n" + + ""; + loadPageVerifyTitle2(content); + } + + @Alerts("0") + @Test + public void widthEmptyInlineContent() throws Exception { + final String content = "\n" + + "
\n" + + "
hidden elements should have zero width
\n" + + " \n" + + "
"; + loadPageVerifyTitle2(content); + } + + @Alerts("55") + @Test + public void widthExplicitInlineContent() throws Exception { + final String content = "\n" + + "
\n" + + "
" + + "
\n" + + "
" + + "
"; + loadPageVerifyTitle2(content); + } + + @Alerts("55") + @Test + public void widthWhitespaceBetweenTags() throws Exception { + final String content = "\n" + + "
\n " + + "
" + + "
"; + loadPageVerifyTitle2(content); + } + + @Alerts("" + 11 * (int) (16 / 1.8f)) + @Test + public void widthWhitespaceSequence() throws Exception { + final String content = "\n" + + "
\n" + + " Hello World " // browsers usually trim leading/trailing whitespace sequences, and replace mid-text whitespace them with a single space + + "
"; + loadPageVerifyTitle2(content); + } /** * @throws Exception if the test fails