diff --git a/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/pom.xml b/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/pom.xml index f75a51e75..74e13e2ab 100644 --- a/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/pom.xml +++ b/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/pom.xml @@ -21,6 +21,12 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-kit-javafxgraphics-emul + 0.1.0-SNAPSHOT + + dev.webfx webfx-kit-javafxweb-emul @@ -44,12 +50,30 @@ 0.1.0-SNAPSHOT + + dev.webfx + webfx-kit-javafxweb-enginepeer + 0.1.0-SNAPSHOT + + dev.webfx webfx-kit-javafxweb-peers-base 0.1.0-SNAPSHOT + + dev.webfx + webfx-platform-scheduler + 0.1.0-SNAPSHOT + + + + dev.webfx + webfx-platform-uischeduler + 0.1.0-SNAPSHOT + + dev.webfx webfx-platform-util diff --git a/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/src/main/java/dev/webfx/kit/mapper/peers/javafxweb/spi/gwt/HtmlWebViewPeer.java b/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/src/main/java/dev/webfx/kit/mapper/peers/javafxweb/spi/gwt/HtmlWebViewPeer.java index 22b21d44a..98d1b2a7f 100644 --- a/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/src/main/java/dev/webfx/kit/mapper/peers/javafxweb/spi/gwt/HtmlWebViewPeer.java +++ b/webfx-kit/webfx-kit-javafxweb-peers-gwt-j2cl/src/main/java/dev/webfx/kit/mapper/peers/javafxweb/spi/gwt/HtmlWebViewPeer.java @@ -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; @@ -36,8 +38,8 @@ public HtmlWebViewPeer(NB base, HTMLElement webViewElement) { HtmlUtil.setStyleAttribute(iFrame, "width", "100%"); // 100% of HtmlUtil.setStyleAttribute(iFrame, "height", "100%"); // 100% of // 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()). @@ -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); } }); } @@ -90,6 +95,8 @@ public void updateUrl(String url) { if (!Strings.isEmpty(iFrame.src)) iFrame.src = ""; } else { + WorkerImpl worker = (WorkerImpl) 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 @@ -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; }); @@ -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 diff --git a/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/pom.xml b/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/pom.xml index 2df4695b2..1316d6848 100644 --- a/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/pom.xml +++ b/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/pom.xml @@ -21,12 +21,6 @@ 0.1.0-SNAPSHOT - - dev.webfx - webfx-kit-javafxgraphics-emul - 0.1.0-SNAPSHOT - - dev.webfx webfx-kit-javafxweb-emul @@ -68,7 +62,7 @@ dev.webfx - webfx-kit-util + webfx-platform-console 0.1.0-SNAPSHOT @@ -80,12 +74,6 @@ true - - dev.webfx - webfx-platform-scheduler - 0.1.0-SNAPSHOT - - dev.webfx webfx-platform-util diff --git a/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtJSObject.java b/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtJSObject.java index bb02cbb98..10e64be6c 100644 --- a/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtJSObject.java +++ b/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtJSObject.java @@ -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; diff --git a/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtWebEnginePeer.java b/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtWebEnginePeer.java index 733a6517d..68fa108f2 100644 --- a/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtWebEnginePeer.java +++ b/webfx-kit/webfx-kit-javafxweb-registry-gwt-j2cl/src/main/java/dev/webfx/kit/registry/javafxweb/GwtWebEnginePeer.java @@ -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; @@ -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() { @@ -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() { @@ -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