ADDITIONAL SYSTEM INFORMATION :
Operating System: Ubuntu 18.04.3 LTS
Java version:
openjdk version "13.0.1" 2019-10-15
OpenJDK Runtime Environment (build 13.0.1+9)
OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)
JavaFX versions: 13 and 14ea+7
The issue is most likely independent of the operating system and independent of the Java version.
A DESCRIPTION OF THE PROBLEM :
When calling getElementsByTagNameNS(), the behavior is inconsistent with that of getElementsByTagName(), but only when the function is called from a Thread other than the JavaFX Application thread. See below.
In a bigger context, the DOM API of WebEngine is problematic because its thread behavior is different than that of other DOM APIs. Other DOM APIs do not impose restrictions on the caller thread. This makes the DOM API of WebEngine incompatible with existing as well as future code that spawns threads to perform DOM processing.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Load an XHTML document (media type "application/xhtml+xml") in a WebEngine.
2. Wait until loading is completed.
3. Get the document from the WebEngine
4. Call document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") and compare the return value with document.getElementsByTagName("html")
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expected result is that the return values of document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") and document.getElementsByTagName("html") are consistent with each other.
There are three possibilities for consistency:
* The return values are equal NodeLists.
* The return values are not equal but at least have equal length, that is 1, and the first item is the HTML document element.
* Both methods throw an exception when they are called from the wrong thread.
ACTUAL -
The actual result is that the return value of document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") is an empty NodeList, while that of document.getElementsByTagName("html") is a NodeList with one element (the expected document element).
The actual result differs from the expected result only if the DOM calls are made from another thread than the JavaFX Application thread. However, it is inconcievable why a wrong usage of the JavaFX API, that is, using its DOM API from a different thread than the JavaFX Application thread would lead to a difference in behavior regarding Namespaces in the DOM API. Instead, it would be expected that using the DOM API from the wrong thread would consistently throw an exception, as in other places of the API, or work correctly.
---------- BEGIN SOURCE ----------
package com.nelkinda.bugreports.jfx;
import javafx.application.Platform;
import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.w3c.dom.Document;
import javax.swing.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class GetElementsByTagNameNSTest {
private static WebEngine engine;
private NodeListLength monitor = new NodeListLength();
@BeforeAll
static void loadTestPage() throws InterruptedException {
final String testPage = loadTestPageFile();
Platform.startup(() -> {
});
final CompletionMonitor loading = new CompletionMonitor();
Platform.runLater(() -> {
final WebView webView = new WebView();
engine = webView.getEngine();
engine.getLoadWorker().stateProperty().addListener((observableValue, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED)
loading.setDone();
});
engine.loadContent(testPage, "application/xhtml+xml");
});
loading.waitForDone();
}
@NotNull
private static String loadTestPageFile() {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE html>\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n" +
"<head><title>Test Page</title></head>\n" +
"<body><h1>Test Page</h1></body>\n" +
"</html>";
}
@ParameterizedTest
@EnumSource(TestCase.class)
void getDocumentElement(final TestCase testCase) throws InterruptedException {
testCase.runner.accept(() -> monitor.getValues(engine.getDocument()));
monitor.verify();
}
// Used in @EnumSource
@SuppressWarnings("unused")
enum TestCase {
JFX(Platform::runLater),
SWING(SwingUtilities::invokeLater),
OTHER(CompletableFuture::runAsync);
final Consumer<Runnable> runner;
TestCase(final Consumer<Runnable> runner) {
this.runner = runner;
}
}
static class CompletionMonitor {
volatile boolean done;
void setDone() {
if (done) return;
done = true;
synchronized (this) {
notifyAll();
}
}
void waitForDone() throws InterruptedException {
if (!done)
synchronized (this) {
while (!done)
wait();
}
}
}
static class NodeListLength extends CompletionMonitor {
static final String XHTML_NS_URI = "http://www.w3.org/1999/xhtml";
int lengthOfNSNodeList;
int lengthOfNodeList;
void verify() throws InterruptedException {
waitForDone();
assertAll(
() -> assertEquals(1, lengthOfNodeList, "Without Namespace"),
() -> assertEquals(1, lengthOfNSNodeList, "With Namespace")
);
}
void getValues(final Document doc) {
lengthOfNodeList = doc.getElementsByTagName("html").getLength();
lengthOfNSNodeList = doc.getElementsByTagNameNS(XHTML_NS_URI, "html").getLength();
setDone();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Never call the JavaFX WebEngine DOM API from another thread than the JavaFX application thread. Do not rely on that
FREQUENCY : always
Operating System: Ubuntu 18.04.3 LTS
Java version:
openjdk version "13.0.1" 2019-10-15
OpenJDK Runtime Environment (build 13.0.1+9)
OpenJDK 64-Bit Server VM (build 13.0.1+9, mixed mode, sharing)
JavaFX versions: 13 and 14ea+7
The issue is most likely independent of the operating system and independent of the Java version.
A DESCRIPTION OF THE PROBLEM :
When calling getElementsByTagNameNS(), the behavior is inconsistent with that of getElementsByTagName(), but only when the function is called from a Thread other than the JavaFX Application thread. See below.
In a bigger context, the DOM API of WebEngine is problematic because its thread behavior is different than that of other DOM APIs. Other DOM APIs do not impose restrictions on the caller thread. This makes the DOM API of WebEngine incompatible with existing as well as future code that spawns threads to perform DOM processing.
STEPS TO FOLLOW TO REPRODUCE THE PROBLEM :
1. Load an XHTML document (media type "application/xhtml+xml") in a WebEngine.
2. Wait until loading is completed.
3. Get the document from the WebEngine
4. Call document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") and compare the return value with document.getElementsByTagName("html")
EXPECTED VERSUS ACTUAL BEHAVIOR :
EXPECTED -
The expected result is that the return values of document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") and document.getElementsByTagName("html") are consistent with each other.
There are three possibilities for consistency:
* The return values are equal NodeLists.
* The return values are not equal but at least have equal length, that is 1, and the first item is the HTML document element.
* Both methods throw an exception when they are called from the wrong thread.
ACTUAL -
The actual result is that the return value of document.getElementsByTagNameNS("http://www.w3.org/1999/xhtml", "html") is an empty NodeList, while that of document.getElementsByTagName("html") is a NodeList with one element (the expected document element).
The actual result differs from the expected result only if the DOM calls are made from another thread than the JavaFX Application thread. However, it is inconcievable why a wrong usage of the JavaFX API, that is, using its DOM API from a different thread than the JavaFX Application thread would lead to a difference in behavior regarding Namespaces in the DOM API. Instead, it would be expected that using the DOM API from the wrong thread would consistently throw an exception, as in other places of the API, or work correctly.
---------- BEGIN SOURCE ----------
package com.nelkinda.bugreports.jfx;
import javafx.application.Platform;
import javafx.concurrent.Worker;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;
import org.jetbrains.annotations.NotNull;
import org.junit.jupiter.api.BeforeAll;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.EnumSource;
import org.w3c.dom.Document;
import javax.swing.*;
import java.util.concurrent.CompletableFuture;
import java.util.function.Consumer;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
public class GetElementsByTagNameNSTest {
private static WebEngine engine;
private NodeListLength monitor = new NodeListLength();
@BeforeAll
static void loadTestPage() throws InterruptedException {
final String testPage = loadTestPageFile();
Platform.startup(() -> {
});
final CompletionMonitor loading = new CompletionMonitor();
Platform.runLater(() -> {
final WebView webView = new WebView();
engine = webView.getEngine();
engine.getLoadWorker().stateProperty().addListener((observableValue, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED)
loading.setDone();
});
engine.loadContent(testPage, "application/xhtml+xml");
});
loading.waitForDone();
}
@NotNull
private static String loadTestPageFile() {
return "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" +
"<!DOCTYPE html>\n" +
"<html xmlns=\"http://www.w3.org/1999/xhtml\" xml:lang=\"en\">\n" +
"<head><title>Test Page</title></head>\n" +
"<body><h1>Test Page</h1></body>\n" +
"</html>";
}
@ParameterizedTest
@EnumSource(TestCase.class)
void getDocumentElement(final TestCase testCase) throws InterruptedException {
testCase.runner.accept(() -> monitor.getValues(engine.getDocument()));
monitor.verify();
}
// Used in @EnumSource
@SuppressWarnings("unused")
enum TestCase {
JFX(Platform::runLater),
SWING(SwingUtilities::invokeLater),
OTHER(CompletableFuture::runAsync);
final Consumer<Runnable> runner;
TestCase(final Consumer<Runnable> runner) {
this.runner = runner;
}
}
static class CompletionMonitor {
volatile boolean done;
void setDone() {
if (done) return;
done = true;
synchronized (this) {
notifyAll();
}
}
void waitForDone() throws InterruptedException {
if (!done)
synchronized (this) {
while (!done)
wait();
}
}
}
static class NodeListLength extends CompletionMonitor {
static final String XHTML_NS_URI = "http://www.w3.org/1999/xhtml";
int lengthOfNSNodeList;
int lengthOfNodeList;
void verify() throws InterruptedException {
waitForDone();
assertAll(
() -> assertEquals(1, lengthOfNodeList, "Without Namespace"),
() -> assertEquals(1, lengthOfNSNodeList, "With Namespace")
);
}
void getValues(final Document doc) {
lengthOfNodeList = doc.getElementsByTagName("html").getLength();
lengthOfNSNodeList = doc.getElementsByTagNameNS(XHTML_NS_URI, "html").getLength();
setDone();
}
}
}
---------- END SOURCE ----------
CUSTOMER SUBMITTED WORKAROUND :
Never call the JavaFX WebEngine DOM API from another thread than the JavaFX application thread. Do not rely on that
FREQUENCY : always