Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Corrections to width calculations. #356

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
34 changes: 34 additions & 0 deletions src/main/java/com/gargoylesoftware/htmlunit/html/HtmlImage.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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;
}

/**
* <p>Returns the image's actual width (<b>not</b> the image's {@link #getWidthAttribute() width attribute}).</p>
Expand All @@ -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;
}

/**
* <p>Returns the <tt>ImageReader</tt> which can be used to read the image contained by this image element.</p>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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());
}
Expand Down Expand Up @@ -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();
Expand Down Expand Up @@ -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) {
Expand All @@ -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;
}
}
}
Expand Down Expand Up @@ -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.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -1428,6 +1436,116 @@ public void widthAuto() throws Exception {
+ "</body></html>";
loadPageVerifyTitle2(html);
}

@Alerts("40")
@Test
public void widthBlockElements() throws Exception {
final String content = "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " var myDiv = document.getElementById('myDiv');\n"
+ " log(myDiv.offsetWidth);\n"
+ " }\n"
+ "</script></head><body onload='test()'>\n"
+ "<div style='display: inline-block'>\n"
+ " <div id='myDiv'>\n"
+ " <div style='width: 40px'>\n"
+ " </div>\n"
+ " </div>\n"
+ "</div>";
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<NameValuePair> emptyList = Collections.emptyList();
getMockWebConnection().setResponse(urlImage, directBytes, 200, "ok", "image/jpg", emptyList);
}

getWebWindowOf((HtmlUnitDriver)getWebDriver()).getWebClient().getOptions().setDownloadImages(true);

final String content = "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " var myImage = document.getElementById('myImage');\n"
+ " log(myImage.offsetWidth);\n"
+ " }\n"
+ "</script></head><body onload='test()'>\n"
+ "<img id='myImage' src='4x7.jpg' >\n"
+ "</body></html>";
loadPageVerifyTitle2(content);
}

@Alerts("0")
@Test
public void widthEmptyInlineContent() throws Exception {
final String content = "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " var myDiv = document.getElementById('myDiv');\n"
+ " log(myDiv.offsetWidth);\n"
+ " }\n"
+ "</script></head><body onload='test()'>\n"
+ "<div id='myDiv' style='display: inline-block'>\n"
+ " <div style='display: none; width: 40px'>hidden elements should have zero width</div>\n"
+ " <script>console.log('script elements should have zero width')</script>\n"
+ "</div></body></html>";
loadPageVerifyTitle2(content);
}

@Alerts("55")
@Test
public void widthExplicitInlineContent() throws Exception {
final String content = "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " var myDiv = document.getElementById('myDiv');\n"
+ " log(myDiv.offsetWidth);\n"
+ " }\n"
+ "</script></head><body onload='test()'>\n"
+ "<div id='myDiv' style='display: inline-block'>\n"
+ " <div style='width: 55px'>"
+ " <div style='width: 40px'></div>\n"
+ " </div>"
+ "</div>";
loadPageVerifyTitle2(content);
}

@Alerts("55")
@Test
public void widthWhitespaceBetweenTags() throws Exception {
final String content = "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " var myDiv = document.getElementById('myDiv');\n"
+ " log(myDiv.offsetWidth);\n"
+ " }\n"
+ "</script></head><body onload='test()'>\n"
+ "<div id='myDiv' style='display: inline-block'>\n "
+ " <div style='width: 55px'></div> "
+ " </div>";
loadPageVerifyTitle2(content);
}

@Alerts("" + 11 * (int) (16 / 1.8f))
@Test
public void widthWhitespaceSequence() throws Exception {
final String content = "<html><head><script>\n"
+ LOG_TITLE_FUNCTION
+ " function test() {\n"
+ " var myDiv = document.getElementById('myDiv');\n"
+ " log(myDiv.offsetWidth);\n"
+ " }\n"
+ "</script></head><body onload='test()'>\n"
+ "<div id='myDiv' style='display: inline-block; font-size: 14px'>\n"
+ " Hello World " // browsers usually trim leading/trailing whitespace sequences, and replace mid-text whitespace them with a single space
+ "</div>";
loadPageVerifyTitle2(content);
}

/**
* @throws Exception if the test fails
Expand Down