Saltar al contenido
CrossFly es 100% open sourceSíguenos en GitHub
CrossFlyCrossFly
Empezar
Todos los ejemplos
RecetasDispositivomedia120 líneas · 1 archivos

Embedded web page

A native browser over the canvas, at this component's rect. On the web it is an <iframe>, and script only reaches a page from your own origin.

Qué muestra esto

Embedding a web page — the third native surface, and the one with a limit worth knowing.

WebContainer puts the platform's own browser over the GL canvas at this component's rect: WebView on Android, WKWebView on iOS, the system engine on Windows, a WKWebView through JNA on macOS, and — since 17/09/2026 — an

Compilado a JavaScript por TeaVM: es el mismo código, no un vídeo de él. · Todos los ejemplos

Qué usa

Tipos del framework que toca este ejemplo. Los enlazados tienen página de referencia.

ButtonCapabilityCardComponentDebugDeviceGridLayoutLabelPlatformUtilsScreenStylesVerticalLayoutWebContainerWidgets

El código

Completo, sin recortes. Es el archivo, no un fragmento.

Crudo
dev/cf/recipe/R33WebView.java
package dev.cf.recipe;

import dev.cf.Tones;

import dev.crossfly.Capability;
import dev.crossfly.PlatformUtils;
import dev.crossfly.util.Debug;
import dev.crossfly.ui.*;
import dev.crossfly.ui.layout.*;
import dev.crossfly.ui.style.*;

import static dev.crossfly.ui.Widgets.*;

/**
 * Embedding a web page — the third native surface, and the one with a limit worth knowing.
 *
 * <p>{@link WebContainer} puts the platform's own browser over the GL canvas at this component's
 * rect: {@code WebView} on Android, {@code WKWebView} on iOS, the system engine on Windows, a
 * {@code WKWebView} through JNA on macOS, and — since 17/09/2026 — an {@code <iframe>} on the web,
 * which had been the only one of the five without one.</p>
 *
 * <p><b>The limit is on the web and it is not a defect.</b> The four native backends can inject
 * script into any page they load. A browser cannot: reaching into a frame from another origin
 * raises {@code SecurityError}, by design, with no flag that changes it. So
 * {@link WebContainer#evaluateJavaScript} works on a page from the app's own origin and is refused
 * — out loud, in the log — on anything else. The page talks back the way a framed page always has,
 * with {@code postMessage}, and it arrives at the same listener the native bridge feeds.</p>
 *
 * <p>Two more things that bite whoever embeds one for the first time, on every platform: a native
 * surface does not clip to a scrolling parent and shows as a blank rectangle during a window
 * transition; and plenty of sites refuse to be framed at all ({@code X-Frame-Options}), which looks
 * exactly like a page that did not load, because from outside the frame it is indistinguishable.</p>
 */
public class R33WebView extends Recipe
{
   private Label answer;
   private WebContainer web;

   public String title()
   {
      return "Embedded web page";
   }

   public String note()
   {
      return "A native browser over the canvas, at this component's rect. On the web it is an"
         + " <iframe>, and script only reaches a page from your own origin.";
   }

   public void build(Component body) throws Exception
   {
      boolean has = PlatformUtils.isSupported(Capability.WEB_VIEW);

      answer = label(has ? "Ready." : "This platform has no embedded web view.", 1f, false,
         has ? Styles.captionColor : Tones.danger(), Position.START);
      answer.testTag("webview.answer");
      // Pinned below the scrolling body, so the answer stays in view (see Recipe.pin).
      pin(answer);

      if (!has)
      {
         Card c = new Card();
         c.setLayout(new VerticalLayout(Position.START, Position.START));
         c.add(LAY_AUTO, label("No WEB_VIEW on " + dev.crossfly.device.Device.platform, 1.2f, true,
            Styles.textColor, Position.START));
         c.add(LAY_AUTO, label("Ask the capability before building a WebContainer: its constructor"
            + " refuses by name, which is what lets an app open the page in the system browser"
            + " instead.", 1f, false, Styles.captionColor, Position.START));
         body.add(LAY_AUTO, c);
         return;
      }

      web = new WebContainer();
      // The page reaches the app through one listener on every platform: the native backends inject
      // window.DesktopBridge, and the web build installs the same name into a same-origin page.
      web.setFormSubmitListener(data -> said("the page said: " + data));
      body.add(.6f, web);

      Component buttons = new Component();
      buttons.setLayout(new GridLayout(Position.START, Position.CENTER));
      buttons.add(.33f, .33f, .33f,
         button("Load", "webview.load", () ->
         {
            web.show("https://example.com");
            said("Loading example.com…");
         }),
         button("Ask the page", "webview.ask", () ->
         {
            // On the four native platforms this comes back through the bridge. On the web it is
            // refused, because example.com is another origin -- and the refusal is logged rather
            // than being the silence that would send you debugging the page.
            web.evaluateJavaScript("window.DesktopBridge && window.DesktopBridge.onFormSubmit("
               + "'title=' + document.title)");
            said("Asked. On the web this is refused unless the page is from this origin.");
         }),
         button("Hide", "webview.hide", () ->
         {
            web.hide();
            said("Hidden — the page stays loaded.");
         }));
      body.add(LAY_AUTO, buttons);
   }

   private Button button(String text, String tag, Runnable go) throws Exception
   {
      Button b = new Button(text);
      b.testTag(tag);
      b.onAction(e -> go.run());
      return b;
   }

   private void said(String what)
   {
      Debug.log("[webview] " + what);
      answer.setText(what);
      answer.invalidateMeasure(); // the pinned band must grow with it (see Recipe.pin)
      Screen.repaint();
   }
}
Para una IA

Versiones de esta página legibles por máquina, para que un modelo lea el estante en vez de adivinar la API.

Este ejemplo, en JSONEste ejemplo, en Markdown
AnteriorVideo playbackSiguienteReading an odometer (OCR)