Skip to content

Commit

Permalink
Mapped iFrame state to WebEngine state
Browse files Browse the repository at this point in the history
  • Loading branch information
salmonb committed Jun 16, 2024
1 parent fa395f7 commit e9695ee
Show file tree
Hide file tree
Showing 5 changed files with 104 additions and 42 deletions.
24 changes: 24 additions & 0 deletions webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,12 @@
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-kit-javafxgraphics-emul</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-kit-javafxweb-emul</artifactId>
Expand All @@ -44,12 +50,30 @@
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-kit-javafxweb-enginepeer</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-kit-javafxweb-peers-base</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-scheduler</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-uischeduler</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-util</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@
import dev.webfx.kit.mapper.peers.javafxgraphics.gwtj2cl.html.HtmlNodePeer;
import dev.webfx.kit.mapper.peers.javafxgraphics.gwtj2cl.util.HtmlPaints;
import dev.webfx.kit.mapper.peers.javafxgraphics.gwtj2cl.util.HtmlUtil;
import dev.webfx.kit.mapper.peers.javafxweb.engine.WorkerImpl;
import dev.webfx.platform.scheduler.Scheduled;
import dev.webfx.platform.uischeduler.UiScheduler;
import dev.webfx.platform.util.Strings;
import elemental2.dom.CSSProperties;
import elemental2.dom.DomGlobal;
import elemental2.dom.HTMLElement;
import elemental2.dom.HTMLIFrameElement;
import elemental2.dom.*;
import javafx.concurrent.Worker;
import javafx.event.EventHandler;
import javafx.scene.Scene;
import javafx.scene.paint.Color;
import javafx.scene.web.WebErrorEvent;
import javafx.scene.web.WebView;
Expand All @@ -36,8 +38,8 @@ public HtmlWebViewPeer(NB base, HTMLElement webViewElement) {
HtmlUtil.setStyleAttribute(iFrame, "width", "100%"); // 100% of <fx-webview>
HtmlUtil.setStyleAttribute(iFrame, "height", "100%"); // 100% of <fx-webview>
// Allowing fullscreen and autoplay for videos
HtmlUtil.setAttribute(iFrame, "allowfullscreen", "true");
iFrame.allow = "fullscreen; autoplay";
iFrame.allow = "fullscreen; autoplay"; // new way
HtmlUtil.setAttribute(iFrame, "allowfullscreen", "true"); // old way (must be executed second otherwise warning)
// Error management. Actually this listener is never called by the browser for an unknown reason. So if it's
// important for the application code to be aware of errors (ex: network errors), webfx provides an alternative
// iFrame loading mode called prefetch which is able to report such errors (see updateUrl()).
Expand All @@ -51,15 +53,18 @@ public HtmlWebViewPeer(NB base, HTMLElement webViewElement) {
if (DomGlobal.document.activeElement == iFrame) { // and the active element should be the iFrame.
// Then, we set the WebView as the new focus owner in JavaFX
N webView = getNode();
webView.getScene().focusOwnerProperty().setValue(webView);
Scene scene = webView == null ? null : webView.getScene();
if (scene != null)
scene.focusOwnerProperty().setValue(webView);
}
});
// 2) Detecting when the iFrame lost focus
DomGlobal.window.addEventListener("focus", e -> { // when iFrame lost focus, the parent window gained focus
// If the WebView is still the focus owner in JavaFX, we clear that focus to report the WebView lost focus
N webView = getNode();
if (webView.getScene().getFocusOwner() == webView) {
webView.getScene().focusOwnerProperty().setValue(null);
Scene scene = webView == null ? null : webView.getScene();
if (scene != null && scene.getFocusOwner() == webView) {
scene.focusOwnerProperty().setValue(null);
}
});
}
Expand Down Expand Up @@ -90,6 +95,8 @@ public void updateUrl(String url) {
if (!Strings.isEmpty(iFrame.src))
iFrame.src = "";
} else {
WorkerImpl<Void> worker = (WorkerImpl<Void>) getNode().getEngine().getLoadWorker();
worker.setState(Worker.State.SCHEDULED);
// WebFX proposes different loading mode for the iFrame:
Object webfxLoadingMode = getNode().getProperties().get("webfx-loadingMode");
if ("prefetch".equals(webfxLoadingMode)) { // prefetch mode
Expand All @@ -98,14 +105,18 @@ public void updateUrl(String url) {
iFrame.contentWindow.fetch(url)
.then(response -> {
response.text().then(text -> {
worker.setState(Worker.State.RUNNING);
updateLoadContent(text);
worker.setState(Worker.State.SUCCEEDED);
return null;
}).catch_(error -> {
worker.setState(Worker.State.FAILED);
reportError();
return null;
});
return null;
}).catch_(error -> {
worker.setState(Worker.State.FAILED);
reportError();
return null;
});
Expand All @@ -115,7 +126,47 @@ public void updateUrl(String url) {
// in all situations (ex: embed YouTube videos are not loading in this mode).
iFrame.contentWindow.location.replace(url);
} else { // Standard loading mode
Scheduled iFrameStateChecker = UiScheduler.schedulePeriodic(100, scheduled -> {
// Note: iFrame.contentDocument can be inaccessible (returns null) with cross-origin
Document contentDocument = iFrame.contentDocument;
if (contentDocument != null) {
String readyState = contentDocument.readyState.toLowerCase();
//DomGlobal.console.log("iFrame readyState = " + readyState);
switch (readyState) {
case "uninitialized":
worker.setState(Worker.State.READY);
break;
case "loading":
worker.setState(Worker.State.SCHEDULED);
break;
case "loaded":
case "interactive":
worker.setState(Worker.State.RUNNING);
break;
case "complete":
DomGlobal.console.log("iFrame readyState = " + readyState);
worker.setState(Worker.State.SUCCEEDED);
scheduled.cancel();
break;
}
}
});
iFrame.onload = e -> {
worker.setState(Worker.State.SUCCEEDED);
iFrameStateChecker.cancel();
};
iFrame.onerror = e -> {
worker.setState(Worker.State.FAILED);
iFrameStateChecker.cancel();
return null;
};
iFrame.onabort = e -> {
worker.setState(Worker.State.CANCELLED);
iFrameStateChecker.cancel();
return null;
};
iFrame.src = url; // Standard way to load an iFrame

// But it has 2 downsides (which is why webfx proposes alternative loading modes):
// 1) it doesn't report any network errors (iFrame.onerror not called). Issue addressed by the webfx
// "prefetch" mode
Expand Down
14 changes: 1 addition & 13 deletions webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,6 @@
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-kit-javafxgraphics-emul</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-kit-javafxweb-emul</artifactId>
Expand Down Expand Up @@ -68,7 +62,7 @@

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-kit-util</artifactId>
<artifactId>webfx-platform-console</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

Expand All @@ -80,12 +74,6 @@
<optional>true</optional>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-scheduler</artifactId>
<version>0.1.0-SNAPSHOT</version>
</dependency>

<dependency>
<groupId>dev.webfx</groupId>
<artifactId>webfx-platform-util</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ private static void bindCallbackMethods(Object javaInstance) {
WebToJavaCallbacks.bindCallbackMethods(javaInstance);
}

private static Object wrapJSObject(Object o) {
public static Object wrapJSObject(Object o) {
if ("object".equals(Js.typeof(o)))
o = new GwtJSObject(o);
return o;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@

import dev.webfx.kit.mapper.peers.javafxweb.engine.WebEnginePeerBase;
import dev.webfx.kit.mapper.peers.javafxweb.spi.gwt.HtmlWebViewPeer;
import dev.webfx.kit.util.properties.FXProperties;
import dev.webfx.platform.scheduler.Scheduler;
import elemental2.dom.*;
import javafx.concurrent.Worker;
import dev.webfx.platform.console.Console;
import elemental2.dom.Document;
import elemental2.dom.DomGlobal;
import elemental2.dom.HTMLIFrameElement;
import elemental2.dom.Window;
import javafx.scene.web.WebEngine;
import javafx.scene.web.WebView;

Expand All @@ -14,12 +15,11 @@
*/
final class GwtWebEnginePeer extends WebEnginePeerBase {

// Note - WebFX convention: if webEngine.getWebView() is null, it's a webEngine that applies to the global window
private final WebEngine webEngine;

public GwtWebEnginePeer(WebEngine webEngine) {
this.webEngine = webEngine;
WebView webView = webEngine.getWebView();
FXProperties.runNowAndOnPropertiesChange(e -> updateState(), webView == null ? null : webView.sceneProperty());
}

private HTMLIFrameElement getIFrame() {
Expand All @@ -32,8 +32,11 @@ private HTMLIFrameElement getIFrame() {

private Window getScriptWindow() {
HTMLIFrameElement iFrame = getIFrame();
// contentWindow is set only once the iFrame is inserted to the DOM, before that it is null
Window iFrameWindow = iFrame == null ? null : iFrame.contentWindow;
return iFrameWindow != null ? iFrameWindow : DomGlobal.window;
if (iFrameWindow == null && webEngine.getWebView() == null)
iFrameWindow = DomGlobal.window;
return iFrameWindow;
}

private Document getDocument() {
Expand All @@ -42,20 +45,16 @@ private Document getDocument() {
return iFrameDocument != null ? iFrameDocument : DomGlobal.document;
}

private void updateState() {
Window scriptWindow = getScriptWindow();
if (scriptWindow != null)
worker.setState(Worker.State.READY);
else {
worker.setState(Worker.State.SCHEDULED);
if (webEngine.getWebView().getScene() != null)
Scheduler.scheduleDelay(100, this::updateState);
}
}

@Override
public Object executeScript(String script) {
return GwtJSObject.eval(getScriptWindow(), script);
Window scriptWindow = getScriptWindow();
if (scriptWindow != null) {
if ("window".equals(script))
return GwtJSObject.wrapJSObject(scriptWindow);
return GwtJSObject.eval(scriptWindow, script);
}
Console.log("⚠️ Couldn't execute script because the webEngine window is not ready (" + (getIFrame() == null ? "iFrame is null)" : getIFrame().contentWindow == null ? "iFrame.contentWindow is null)" : "???)"));
return null;
}

@Override
Expand Down

0 comments on commit e9695ee

Please sign in to comment.