JavaFX Docs państwo, że WebView
jest gotowy, kiedy Worker.State.SUCCEEDED
zostanie osiągnięta jednak chyba trochę poczekać (czyli Animation
, Transition
, PauseTransition
, itd.), Puste strony są renderowane.
Sugeruje to, że wewnątrz WebView występuje zdarzenie, które przygotowuje je do przechwytywania, ale co to jest?
Na GitHubSwingFXUtils.fromFXImage
znajduje się ponad 7000 fragmentów kodu, które wykorzystują, ale większość z nich wydaje się być niezwiązana z nimi WebView
, są interaktywne (człowiek maskuje warunki wyścigu) lub używają dowolnych przejść (od 100 ms do 2000 ms).
Próbowałem:
Słuchanie w
changed(...)
obrębieWebView
wymiarów (DoubleProperty
implementowane są właściwości wysokości i szerokościObservableValue
, które mogą monitorować te rzeczy)- OtNie opłacalne. Czasami wartość wydaje się zmieniać niezależnie od procedury malowania, co prowadzi do częściowej zawartości.
Ślepe mówienie wszystkiego i wszystkiego
runLater(...)
w wątku aplikacji FX.- Use Wykorzystuje to wiele technik, ale moje własne testy jednostkowe (a także świetne opinie od innych programistów) wyjaśniają, że zdarzenia często są już na właściwym wątku i to wywołanie jest zbędne. Najlepsze, co mogę wymyślić, to wystarczające opóźnienie w kolejce, które działa dla niektórych.
Dodanie detektora / wyzwalacza DOM lub detektora / wyzwalacza JavaScript do
WebView
- Oth Oba skrypty JavaScript i DOM wydają się być ładowane poprawnie, gdy
SUCCEEDED
są wywoływane pomimo pustego przechwytywania. Słuchacze DOM / JavaScript nie wydają się pomagać.
- Oth Oba skrypty JavaScript i DOM wydają się być ładowane poprawnie, gdy
Używanie
Animation
lubTransition
do efektywnego „uśpienia” bez blokowania głównego wątku FX.- Approach Takie podejście działa i jeśli opóźnienie jest wystarczająco długie, może dać do 100% testów jednostkowych, ale czasy przejścia wydają się być przyszłym momentem, który tylko zgadujemy i źle zaprojektowaliśmy. W przypadku wydajnych lub krytycznych aplikacji zmusza to programistę do kompromisu między szybkością a niezawodnością, co jest potencjalnie złym doświadczeniem dla użytkownika.
Kiedy jest dobry moment na telefon WebView.snapshot(...)
?
Stosowanie:
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.
*/
Fragment kodu:
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();
}
}
Związane z:
- Zrzut ekranu pełnej strony internetowej załadowanej do komponentu JavaFX WebView, nie tylko widocznej części
- Czy mogę programowo uchwycić migawkę sceny?
- Zrzut ekranu całej strony, Java
- JavaFX 2.0+ WebView / WebEngine renderuje stronę internetową do obrazu
- Ustaw wysokość i szerokość sceny i sceny w javafx
- JavaFX: jak zmienić rozmiar sceny podczas korzystania z webview
- Prawidłowy rozmiar Webview osadzony w 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
został przetestowany i nie naprawia go. Spróbuj tego sam, jeśli się nie zgadzasz. Byłbym szczęśliwy, że się mylę, to zamknę problem.
SUCCEEDED
stan (którego słuchacz strzela w wątku FX) jest właściwą techniką. Jeśli istnieje sposób na pokazanie wydarzeń w kolejce, byłbym podekscytowany, aby spróbować. Znalazłem rzadkie sugestie poprzez komentarze na forach Oracle i niektóre pytania SO, które WebView
muszą być projektowane w swoim własnym wątku, więc po dniach testowania skupiam tam energię. Jeśli to założenie jest błędne, świetnie. Jestem otwarty na wszelkie rozsądne sugestie, które naprawią problem bez arbitralnych czasów oczekiwania.
loadContent
metody lub ładowania pliku URL.