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óreWebView
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.Odpowiedzi:
Wygląda na to, że jest to błąd występujący podczas korzystania z
loadContent
metod WebEngine . Występuje również przyload
ładowaniu pliku lokalnego, ale w takim przypadku wywołanie reload () zrekompensuje to.Ponadto, ponieważ stół montażowy musi się wyświetlać podczas robienia migawki, musisz wywołać
show()
przed załadowaniem zawartości. Ponieważ treść jest ładowana asynchronicznie, jest całkowicie możliwe, że zostanie załadowana przed instrukcją po wywołaniuload
lubloadContent
zakończeniu.Obejściem tego problemu jest umieszczenie zawartości w pliku i wywołanie metody WebEngine
reload()
dokładnie raz. Podczas drugiego ładowania treści można pomyślnie wykonać migawkę z obiektu nasłuchującego właściwości stanu pracownika ładującego.Zwykle byłoby to łatwe:
Ale ponieważ używasz
static
do wszystkiego, musisz dodać kilka pól:Możesz ich użyć tutaj:
A potem będziesz musiał go zresetować za każdym razem, gdy ładujesz zawartość:
Należy pamiętać, że istnieją lepsze sposoby wykonywania przetwarzania wielowątkowego. Zamiast używać klas atomowych, możesz po prostu użyć
volatile
pól:(pola boolowskie są domyślnie fałszywe, a pola obiektowe są domyślnie zerowe. W przeciwieństwie do programów w C, jest to twarda gwarancja złożona przez Javę; nie ma czegoś takiego jak niezainicjowana pamięć).
Zamiast odpytywania w pętli o zmiany dokonane w innym wątku, lepiej jest użyć synchronizacji, blokady lub klasy wyższego poziomu, takiej jak CountDownLatch, która używa tych rzeczy wewnętrznie:
reloaded
nie jest deklarowany jako niestabilny, ponieważ jest dostępny tylko w wątku aplikacji JavaFX.źródło
volatile
zmiennych. Niestety dzwonienieWebEngine.reload()
i oczekiwanie na kolejneSUCCEEDED
nie działa. Jeśli umieszczę licznik w treści HTML, otrzymuję:0, 0, 1, 3, 3, 5
zamiast0, 1, 2, 3, 4, 5
, sugerując, że tak naprawdę nie naprawia podstawowych warunków wyścigu.CountDownLatch
”. Uaktualnienie, ponieważ ta informacja nie była łatwa do znalezienia i pomaga w szybkości i prostocie kodu przy początkowym uruchomieniu FX.Aby dostosować rozmiar, jak również podstawowe zachowanie migawki, ja (my) opracowałem następujące działające rozwiązanie. Uwaga: testy te przeprowadzono 2000 razy (Windows, macOS i Linux), zapewniając losowe rozmiary WebView ze 100% powodzeniem.
Najpierw zacytuję jednego z deweloperów JavaFX. Jest to cytowane z prywatnego (sponsorowanego) raportu o błędzie:
600
jeśli wysokość jest dokładnie0
. Kod PonowneWebView
należy używaćsetMinHeight(1)
,setPrefHeight(1)
aby uniknąć tego problemu. Nie ma tego w poniższym kodzie, ale warto o tym wspomnieć dla każdego, kto dostosowuje go do swojego projektu.źródło