Il JavaFX docs stato che un WebView
è pronto quando Worker.State.SUCCEEDED
viene raggiunto tuttavia, a meno che non si aspetta un po '(vale a dire Animation
, Transition
, PauseTransition
, ecc), una pagina vuota viene eseguito il rendering.
Ciò suggerisce che si verifica un evento all'interno del WebView che lo sta preparando per un'acquisizione, ma che cos'è?
Ci sono oltre 7.000 frammenti di codice su GitHub che usano,SwingFXUtils.fromFXImage
ma la maggior parte di essi sembra non avere alcuna relazione WebView
, sono interattivi (maschere umane la condizione della razza) o usano Transizioni arbitrarie (da 100ms a 2.000ms).
Ho provato:
Ascoltare
changed(...)
dall'interno delleWebView
dimensioni (DoubleProperty
implementa le proprietà di altezza e larghezzaObservableValue
, che possono monitorare queste cose)- 🚫Non praticabile. A volte, il valore sembra cambiare separatamente dalla routine di pittura, portando a un contenuto parziale.
Dire ciecamente tutto e tutto
runLater(...)
sul thread dell'applicazione FX.- 🚫Molte tecniche usano questo, ma i miei test unitari (oltre ad alcuni ottimi feedback da parte di altri sviluppatori) spiegano che gli eventi sono spesso già sul thread giusto e questa chiamata è ridondante. Il meglio che mi viene in mente è di aggiungere un ritardo sufficiente per fare la fila che funziona per alcuni.
Aggiunta di un listener / trigger DOM o listener / trigger JavaScript a
WebView
- Oth Sia JavaScript che il DOM sembrano caricati correttamente quando
SUCCEEDED
viene chiamato nonostante l'acquisizione del bianco. I listener DOM / JavaScript non sembrano aiutare.
- Oth Sia JavaScript che il DOM sembrano caricati correttamente quando
Usando un
Animation
oTransition
per "dormire" efficacemente senza bloccare il thread FX principale.- ⚠️ Questo approccio funziona e se il ritardo è abbastanza lungo, può produrre fino al 100% dei test unitari, ma i tempi di transizione sembrano essere un momento futuro che stiamo solo supponendo e progettando male. Per applicazioni performanti o mission-critical, questo costringe il programmatore a fare un compromesso tra velocità o affidabilità, entrambe potenzialmente potenzialmente dannose per l'utente.
Quando è un buon momento per chiamare WebView.snapshot(...)
?
Uso:
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.
*/
Snippet di codice:
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();
}
}
Relazionato:
- Schermata dell'intera pagina Web caricata nel componente WebView JavaFX, non solo parte visibile
- Posso acquisire un'istantanea della scena a livello di codice?
- Schermata intera pagina, Java
- JavaFX 2.0+ WebView / WebEngine esegue il rendering della pagina Web su un'immagine
- Imposta altezza e larghezza del palcoscenico e della scena in javafx
- JavaFX: come ridimensionare lo stage quando si utilizza Webview
- Dimensionamento corretto di Webview incorporato in 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
è stato testato e non lo risolve. Per favore, provalo tu stesso se non sei d'accordo. Sarei felice di sbagliarmi, chiuderebbe il problema.
SUCCEEDED
stato (di cui l'ascoltatore spara sul thread FX) è la tecnica corretta. Se c'è un modo per mostrare eventi in coda, sarei euforico di provare. Ho trovato suggerimenti sparsi attraverso i commenti sui forum Oracle e alcune domande SO che WebView
devono essere eseguite nel suo thread in base alla progettazione, quindi dopo giorni di test sto concentrando l'energia lì. Se tale presupposto è sbagliato, ottimo. Sono aperto a qualsiasi suggerimento ragionevole che risolva il problema senza tempi di attesa arbitrari.
loadContent
metodo o quando si carica un URL di file.