DESCRIPTION
-----------
JavaFX allows to print any Node through the Print API, even when the application is running in headless mode.
However, in headless mode if the `PrinterJob#printPage` method is called on a WebView after it has been resized, the job doesn't print anything. This only happens for the first page. Subsequent prints after the first print are correct.
The issue only happens in headless mode for the first print after the WebView is resized.
PLATFORM
--------
Can be reproduced on MacOS Catalina, Ubuntu 18.04 and Windows 10 Using Oracle Java 8 (64-bit) and AdoptOpenJDK 11
FREQUENCY
---------
Can always be reproduced
STEPS TO REPRODUCE THE PROBLEM
------------------------------
1 Set the default printer to a "document printer"
2 Run the attached application
3 The application tries to print 4 pages and shows a dialog to save each one of them
4 First and fourth print-jobs prints the content of the WebView. However, second and third doesn't
EXPECTED vs ACTUAL
------------------
EXPECTED
Print job should always print the content of the WebView
ACTUAL -
The first print job after WebView resize doesn't print anything
---------- BEGIN SOURCE ----------
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker;
import javafx.geometry.Dimension2D;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.concurrent.CountDownLatch;
public class WebViewPrintBug extends Application {
private static final String HTML_TEMPLATE = "<html><body>%s</body></html>";
private static final CountDownLatch startup = new CountDownLatch(1);
private static WebView webView;
private static Stage stage;
private static Dimension2D dimensions;
private static PauseTransition transition;
private static CountDownLatch printed;
public static void main(String[] args) throws Exception {
// start JFX
new Thread(() -> Application.launch(WebViewPrintBug.class)).start();
startup.await();
//try some prints
Printer printer = Printer.getDefaultPrinter();
//prints fine
printed = new CountDownLatch(1);
dimensions = new Dimension2D(720, 960);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 1</h2><div>Content goes here</div>"));
printed.await();
//prints blank
printed = new CountDownLatch(1);
dimensions = new Dimension2D(640, 960);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 2</h2><div>Can you see me?</div>"));
printed.await();
//also prints blank
printed = new CountDownLatch(1);
dimensions = new Dimension2D(640, 800);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 3</h2><div>Spooky ghost image</div>"));
printed.await();
//same dimensions > prints fine
printed = new CountDownLatch(1);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 4</h2><div>Visible content</div>"));
printed.await();
System.out.println("Done");
Platform.exit();
}
/** Called by jfx thread on startup */
@Override
public void start(Stage st) {
webView = new WebView();
st.setScene(new Scene(webView));
stage = st;
//setup load listeners
Worker<Void> worker = webView.getEngine().getLoadWorker();
worker.stateProperty().addListener(stateListener);
worker.exceptionProperty().addListener((obs, oldEx, newEx) -> {
if (newEx != null) { newEx.printStackTrace(); }
});
//prevents JavaFX from shutting down when hiding window
Platform.setImplicitExit(false);
System.out.println("Started JFX");
startup.countDown();
}
/** Called by jfx thread on content load */
private static ChangeListener<Worker.State> stateListener = (ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
//resize the view
System.out.println(dimensions);
webView.setMinSize(dimensions.getWidth(), dimensions.getHeight());
webView.setPrefSize(dimensions.getWidth(), dimensions.getHeight());
webView.setMaxSize(dimensions.getWidth(), dimensions.getHeight());
webView.autosize();
//hand off to print lambda
transition.playFromStart();
//showing stage at this point prevents blank pages, but breaks on headless environments
stage.show();
}
};
/** Our call to load pages and finalize printing */
private static void print(Printer printer, String content) {
//note - extending duration doesn't seem to have an effect on blank pages
transition = new PauseTransition(Duration.millis(100));
transition.setOnFinished(evt -> {
PrinterJob job = PrinterJob.createPrinterJob(printer);
Platform.runLater(() -> {
stage.hide();
System.out.println("Starting print");
System.out.println((String) webView.getEngine().executeScript("document.documentElement.outerHTML"));
job.printPage(webView);
job.endJob();
printed.countDown();
});
});
//reset webview and start loading
Platform.runLater(() -> {
webView.getTransforms().clear();
webView.getEngine().loadContent(content, "text/html");
});
}
}
---------- END SOURCE ----------
-----------
JavaFX allows to print any Node through the Print API, even when the application is running in headless mode.
However, in headless mode if the `PrinterJob#printPage` method is called on a WebView after it has been resized, the job doesn't print anything. This only happens for the first page. Subsequent prints after the first print are correct.
The issue only happens in headless mode for the first print after the WebView is resized.
PLATFORM
--------
Can be reproduced on MacOS Catalina, Ubuntu 18.04 and Windows 10 Using Oracle Java 8 (64-bit) and AdoptOpenJDK 11
FREQUENCY
---------
Can always be reproduced
STEPS TO REPRODUCE THE PROBLEM
------------------------------
1 Set the default printer to a "document printer"
2 Run the attached application
3 The application tries to print 4 pages and shows a dialog to save each one of them
4 First and fourth print-jobs prints the content of the WebView. However, second and third doesn't
EXPECTED vs ACTUAL
------------------
EXPECTED
Print job should always print the content of the WebView
ACTUAL -
The first print job after WebView resize doesn't print anything
---------- BEGIN SOURCE ----------
import javafx.animation.PauseTransition;
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.concurrent.Worker;
import javafx.geometry.Dimension2D;
import javafx.print.Printer;
import javafx.print.PrinterJob;
import javafx.scene.Scene;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import javafx.util.Duration;
import java.util.concurrent.CountDownLatch;
public class WebViewPrintBug extends Application {
private static final String HTML_TEMPLATE = "<html><body>%s</body></html>";
private static final CountDownLatch startup = new CountDownLatch(1);
private static WebView webView;
private static Stage stage;
private static Dimension2D dimensions;
private static PauseTransition transition;
private static CountDownLatch printed;
public static void main(String[] args) throws Exception {
// start JFX
new Thread(() -> Application.launch(WebViewPrintBug.class)).start();
startup.await();
//try some prints
Printer printer = Printer.getDefaultPrinter();
//prints fine
printed = new CountDownLatch(1);
dimensions = new Dimension2D(720, 960);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 1</h2><div>Content goes here</div>"));
printed.await();
//prints blank
printed = new CountDownLatch(1);
dimensions = new Dimension2D(640, 960);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 2</h2><div>Can you see me?</div>"));
printed.await();
//also prints blank
printed = new CountDownLatch(1);
dimensions = new Dimension2D(640, 800);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 3</h2><div>Spooky ghost image</div>"));
printed.await();
//same dimensions > prints fine
printed = new CountDownLatch(1);
print(printer, String.format(HTML_TEMPLATE, "<h2>Test Page 4</h2><div>Visible content</div>"));
printed.await();
System.out.println("Done");
Platform.exit();
}
/** Called by jfx thread on startup */
@Override
public void start(Stage st) {
webView = new WebView();
st.setScene(new Scene(webView));
stage = st;
//setup load listeners
Worker<Void> worker = webView.getEngine().getLoadWorker();
worker.stateProperty().addListener(stateListener);
worker.exceptionProperty().addListener((obs, oldEx, newEx) -> {
if (newEx != null) { newEx.printStackTrace(); }
});
//prevents JavaFX from shutting down when hiding window
Platform.setImplicitExit(false);
System.out.println("Started JFX");
startup.countDown();
}
/** Called by jfx thread on content load */
private static ChangeListener<Worker.State> stateListener = (ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
//resize the view
System.out.println(dimensions);
webView.setMinSize(dimensions.getWidth(), dimensions.getHeight());
webView.setPrefSize(dimensions.getWidth(), dimensions.getHeight());
webView.setMaxSize(dimensions.getWidth(), dimensions.getHeight());
webView.autosize();
//hand off to print lambda
transition.playFromStart();
//showing stage at this point prevents blank pages, but breaks on headless environments
stage.show();
}
};
/** Our call to load pages and finalize printing */
private static void print(Printer printer, String content) {
//note - extending duration doesn't seem to have an effect on blank pages
transition = new PauseTransition(Duration.millis(100));
transition.setOnFinished(evt -> {
PrinterJob job = PrinterJob.createPrinterJob(printer);
Platform.runLater(() -> {
stage.hide();
System.out.println("Starting print");
System.out.println((String) webView.getEngine().executeScript("document.documentElement.outerHTML"));
job.printPage(webView);
job.endJob();
printed.countDown();
});
});
//reset webview and start loading
Platform.runLater(() -> {
webView.getTransforms().clear();
webView.getEngine().loadContent(content, "text/html");
});
}
}
---------- END SOURCE ----------