L'état du JavaFX que WebView
est prêt quand Worker.State.SUCCEEDED
est atteint cependant, à moins que vous attendez un certain temps (c. -à Animation
, Transition
, PauseTransition
, etc.), une page vierge est rendue.
Cela suggère qu'il y a un événement qui se produit à l'intérieur du WebView le préparant pour une capture, mais qu'est-ce que c'est?
Il y a plus de 7000 extraits de code sur GitHub qui utilisentSwingFXUtils.fromFXImage
mais la plupart d'entre eux semblent être sans rapport avec WebView
, sont interactifs (l'homme masque la condition de concurrence) ou utilisent des transitions arbitraires (de 100 à 2000 ms).
J'ai essayé:
Écoute à
changed(...)
partirWebView
des dimensions du (DoubleProperty
implémentation des propriétés de hauteur et de largeurObservableValue
, qui peuvent surveiller ces choses)- 🚫Non viable. Parfois, la valeur semble changer indépendamment de la routine de peinture, conduisant à un contenu partiel.
Dire aveuglément tout et n'importe quoi
runLater(...)
sur le fil d'application FX.- Any De nombreuses techniques utilisent cela, mais mes propres tests unitaires (ainsi que d'excellents commentaires d'autres développeurs) expliquent que les événements sont souvent déjà sur le bon thread, et cet appel est redondant. Le mieux que je puisse penser est d'ajouter juste assez de retard dans la mise en file d'attente pour que cela fonctionne pour certains.
Ajouter un écouteur / déclencheur DOM ou un écouteur / déclencheur JavaScript au
WebView
- JavaScript Les deux JavaScript et le DOM semblent être chargés correctement lorsqu'ils
SUCCEEDED
sont appelés malgré la capture vierge. Les écouteurs DOM / JavaScript ne semblent pas aider.
- JavaScript Les deux JavaScript et le DOM semblent être chargés correctement lorsqu'ils
Utiliser un
Animation
ouTransition
pour "dormir" efficacement sans bloquer le thread FX principal.- ⚠️ Cette approche fonctionne et si le retard est suffisamment long, peut produire jusqu'à 100% des tests unitaires, mais les temps de transition semblent être un moment futur que nous devinons juste et une mauvaise conception. Pour les applications performantes ou critiques, cela oblige le programmeur à faire un compromis entre la vitesse ou la fiabilité, une expérience potentiellement mauvaise pour l'utilisateur.
Quel est le bon moment pour appeler WebView.snapshot(...)
?
Usage:
SnapshotRaceCondition.initialize();
BufferedImage bufferedImage = SnapshotRaceCondition.capture("<html style='background-color: red;'><h1>TEST</h1></html>");
/**
* Notes:
* - The color is to observe the otherwise non-obvious cropping that occurs
* with some techniques, such as `setPrefWidth`, `autosize`, etc.
* - Call this function in a loop and then display/write `BufferedImage` to
* to see strange behavior on subsequent calls.
* - Recommended, modify `<h1>TEST</h1` with a counter to see content from
* previous captures render much later.
*/
Extrait de code:
import javafx.application.Application;
import javafx.application.Platform;
import javafx.beans.value.ChangeListener;
import javafx.beans.value.ObservableValue;
import javafx.concurrent.Worker;
import javafx.embed.swing.SwingFXUtils;
import javafx.scene.Scene;
import javafx.scene.SnapshotParameters;
import javafx.scene.image.WritableImage;
import javafx.scene.web.WebView;
import javafx.stage.Stage;
import java.awt.image.BufferedImage;
import java.io.IOException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicReference;
import java.util.logging.Logger;
public class SnapshotRaceCondition extends Application {
private static final Logger log = Logger.getLogger(SnapshotRaceCondition.class.getName());
// self reference
private static SnapshotRaceCondition instance = null;
// concurrent-safe containers for flags/exceptions/image data
private static AtomicBoolean started = new AtomicBoolean(false);
private static AtomicBoolean finished = new AtomicBoolean(true);
private static AtomicReference<Throwable> thrown = new AtomicReference<>(null);
private static AtomicReference<BufferedImage> capture = new AtomicReference<>(null);
// main javafx objects
private static WebView webView = null;
private static Stage stage = null;
// frequency for checking fx is started
private static final int STARTUP_TIMEOUT= 10; // seconds
private static final int STARTUP_SLEEP_INTERVAL = 250; // millis
// frequency for checking capture has occured
private static final int CAPTURE_SLEEP_INTERVAL = 10; // millis
/** Called by JavaFX thread */
public SnapshotRaceCondition() {
instance = this;
}
/** Starts JavaFX thread if not already running */
public static synchronized void initialize() throws IOException {
if (instance == null) {
new Thread(() -> Application.launch(SnapshotRaceCondition.class)).start();
}
for(int i = 0; i < (STARTUP_TIMEOUT * 1000); i += STARTUP_SLEEP_INTERVAL) {
if (started.get()) { break; }
log.fine("Waiting for JavaFX...");
try { Thread.sleep(STARTUP_SLEEP_INTERVAL); } catch(Exception ignore) {}
}
if (!started.get()) {
throw new IOException("JavaFX did not start");
}
}
@Override
public void start(Stage primaryStage) {
started.set(true);
log.fine("Started JavaFX, creating WebView...");
stage = primaryStage;
primaryStage.setScene(new Scene(webView = new WebView()));
// Add listener for SUCCEEDED
Worker<Void> worker = webView.getEngine().getLoadWorker();
worker.stateProperty().addListener(stateListener);
// Prevents JavaFX from shutting down when hiding window, useful for calling capture(...) in succession
Platform.setImplicitExit(false);
}
/** Listens for a SUCCEEDED state to activate image capture **/
private static ChangeListener<Worker.State> stateListener = (ov, oldState, newState) -> {
if (newState == Worker.State.SUCCEEDED) {
WritableImage snapshot = webView.snapshot(new SnapshotParameters(), null);
capture.set(SwingFXUtils.fromFXImage(snapshot, null));
finished.set(true);
stage.hide();
}
};
/** Listen for failures **/
private static ChangeListener<Throwable> exceptListener = new ChangeListener<Throwable>() {
@Override
public void changed(ObservableValue<? extends Throwable> obs, Throwable oldExc, Throwable newExc) {
if (newExc != null) { thrown.set(newExc); }
}
};
/** Loads the specified HTML, triggering stateListener above **/
public static synchronized BufferedImage capture(final String html) throws Throwable {
capture.set(null);
thrown.set(null);
finished.set(false);
// run these actions on the JavaFX thread
Platform.runLater(new Thread(() -> {
try {
webView.getEngine().loadContent(html, "text/html");
stage.show(); // JDK-8087569: will not capture without showing stage
stage.toBack();
}
catch(Throwable t) {
thrown.set(t);
}
}));
// wait for capture to complete by monitoring our own finished flag
while(!finished.get() && thrown.get() == null) {
log.fine("Waiting on capture...");
try {
Thread.sleep(CAPTURE_SLEEP_INTERVAL);
}
catch(InterruptedException e) {
log.warning(e.getLocalizedMessage());
}
}
if (thrown.get() != null) {
throw thrown.get();
}
return capture.get();
}
}
En relation:
- Capture d'écran de la page Web complète chargée dans le composant JavaFX WebView, non seulement partie visible
- Puis-je capturer un instantané de scène par programme?
- Capture d'écran de la page entière, Java
- Page Web de rendu JavaFX 2.0+ WebView / WebEngine en image
- Définir la hauteur et la largeur de la scène et de la scène dans javafx
- JavaFX: comment redimensionner la scène lors de l'utilisation de WebView
- Dimensionnement correct de Webview intégré dans Tabelcell
- https://docs.oracle.com/javase/8/javafx/embedded-browser-tutorial/add-browser.htm#CEGDIBBI
- http://docs.oracle.com/javafx/2/swing/swing-fx-interoperability.htm#CHDIEEJE
- https://bugs.openjdk.java.net/browse/JDK-8126854
- https://bugs.openjdk.java.net/browse/JDK-8087569
Platform.runLater
a été testé et ne le résout pas. Veuillez l'essayer par vous-même si vous n'êtes pas d'accord. Je serais heureux de me tromper, cela résoudrait le problème.
SUCCEEDED
état (dont l'auditeur se déclenche sur le thread FX) est la bonne technique. S'il y a un moyen de montrer les événements en file d'attente, je serais ravi d'essayer. J'ai trouvé des suggestions clairsemées à travers des commentaires sur les forums Oracle et quelques questions SO qui WebView
doivent fonctionner dans son propre fil de conception, donc après des jours de test, je concentre l'énergie là-bas. Si cette hypothèse est fausse, tant mieux. Je suis ouvert à toutes suggestions raisonnables pour résoudre le problème sans temps d'attente arbitraires.
loadContent
méthode ou lors du chargement d'une URL de fichier.