Created
January 18, 2020 06:52
-
-
Save tresf/35b4db3b75ea8b86fcd9d98d4ae38923 to your computer and use it in GitHub Desktop.
Trying to fix race conditions in WebView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import com.sun.javafx.tk.TKPulseListener; | |
import com.sun.javafx.tk.Toolkit; | |
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.AtomicInteger; | |
import java.util.concurrent.atomic.AtomicReference; | |
import java.util.logging.Logger; | |
public class WebViewResize extends Application { | |
private static WebViewResize instance = null; | |
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); | |
// our main objects | |
private static WebView webView = null; | |
private static Stage stage = null; | |
private static final int STARTUP_TIMEOUT= 10; // seconds | |
private static final int STARTUP_SLEEP_INTERVAL = 250; // millis | |
private static final int CAPTURE_SLEEP_INTERVAL = 10; // millis | |
private static final Logger log = Logger.getLogger(WebViewResize.class.getName()); | |
/** Called by JavaFX thread */ | |
public WebViewResize() { | |
instance = this; | |
} | |
/** Starts JavaFX thread if not already running */ | |
public static synchronized void initialize() throws IOException { | |
if (instance == null) { | |
new Thread() { | |
public void run() { | |
Application.launch(WebViewResize.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) throws Exception { | |
started.set(true); | |
log.fine("Started JavaFX, creating WebView..."); | |
stage = primaryStage; | |
primaryStage.setScene(new Scene(webView = new WebView())); | |
Worker<Void> worker = webView.getEngine().getLoadWorker(); | |
worker.stateProperty().addListener(stateListener); | |
//prevents JavaFX from shutting down when hiding window | |
Platform.setImplicitExit(false); | |
} | |
/** Listens for a SUCCEEDED state to activate image capture **/ | |
private static ChangeListener<Worker.State> stateListener = new ChangeListener<Worker.State>() { | |
@Override | |
public void changed(ObservableValue<? extends Worker.State> ov, Worker.State oldState, Worker.State newState) { | |
if (newState == Worker.State.SUCCEEDED) { | |
/*try { | |
Method m = stage.getScene().getClass().getDeclaredMethod("setNeedsRepaint"); | |
m.setAccessible(true); | |
m.invoke(stage.getScene()); | |
} catch(Throwable t) { | |
t.printStackTrace(); | |
}*/ | |
final AtomicInteger pulseCount = new AtomicInteger(0); | |
Toolkit.getToolkit().addStageTkPulseListener(new TKPulseListener() { | |
@Override | |
public void pulse() { | |
Toolkit.getToolkit().removeStageTkPulseListener(this); | |
System.out.println("1. stagePulse"); | |
snapshot(pulseCount.incrementAndGet()); | |
} | |
}); | |
Toolkit.getToolkit().addSceneTkPulseListener(new TKPulseListener() { | |
@Override | |
public void pulse() { | |
Toolkit.getToolkit().removeSceneTkPulseListener(this); | |
System.out.println("2. scenePulse"); | |
snapshot(pulseCount.incrementAndGet()); | |
} | |
}); | |
Toolkit.getToolkit().addPostSceneTkPulseListener(new TKPulseListener() { | |
@Override | |
public void pulse() { | |
System.out.println("3. postScenePulse"); | |
Toolkit.getToolkit().removePostSceneTkPulseListener(this); | |
snapshot(pulseCount.incrementAndGet()); | |
} | |
}); | |
} | |
} | |
}; | |
private static void snapshot(int pulseCount) { | |
if(pulseCount < 3) { | |
return; | |
} | |
System.out.println("4. snapshot"); | |
// start image capture | |
WritableImage snapshot = webView.snapshot(new SnapshotParameters(), null); | |
capture.set(SwingFXUtils.fromFXImage(snapshot, null)); | |
finished.set(true); | |
stage.hide(); | |
} | |
// TODO: Render when invisible | |
// https://stackoverflow.com/a/52977547/3196753 | |
// SNAPSHOT Example Code: | |
// https://stackoverflow.com/questions/30121204 | |
/** Loads the specified HTML, waits for all events, returns a snapshot **/ | |
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() { | |
public void run() { | |
try { | |
webView.getEngine().loadContent(html, "text/html"); | |
stage.show(); // will not capture without showing stage | |
stage.toBack(); | |
} | |
catch(Throwable t) { | |
thrown.set(t); | |
} | |
} | |
}); | |
// wait for capture to complete by monitoring finished state | |
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(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment