Cette page a été traduite automatiquement et peut contenir des erreurs. Lire l'original en anglais

Éléments

WebView

Une vue web affiche une page web, ou du HTML que vous lui transmettez, dans l’application : WKWebView sur iOS et Mac Catalyst, android.webkit.WebView sur Android. Comme NSH effectue son rendu avec des vues natives, une vue web s’insère dans la mise en page comme n’importe quelle autre vue, dans l’ordre par rapport aux vues voisines. En Rust, on en crée une avec web_view(url) ou web_view_html(html, base_url), en TSX avec <WebView> depuis nsh.

el-web-view.tsx
el-web-view.tsxLive
import { mount, signal, Text, View, WebView, type WebViewNavigation, type WebViewRef } from "nsh";
import { platform, windowSize } from "nsh";
import { button, buttonText, ui } from "./palette";

// The page: a button that posts to the app, and a line that shows what the
// app posts back.
const page = (c: ReturnType<typeof ui>) => `<!doctype html>
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Inline page</title>
<style>
  body { font: 15px system-ui, sans-serif; margin: 0; padding: 16px; background: ${c.page}; color: ${c.text}; }
  button { font: inherit; font-weight: 600; padding: 10px 14px; border-radius: 10px; border: 1px solid ${c.border}; background: ${c.accent}; color: ${c.page}; }
  .muted { color: ${c.muted}; }
  #got { margin-top: 12px; }
  #box { margin-top: 12px; height: 48px; border-radius: 10px; background: ${c.border}; transition: background 0.3s; }
</style>
<p class="muted">This is HTML in a web view.</p>
<button id="post">postMessage to the app</button>
<p id="got" class="muted">Nothing from the app yet.</p>
<div id="box"></div>
<script>
  let taps = 0;
  document.getElementById("post").onclick = () =>
    window.nsh.postMessage("tap " + ++taps + " from the page");
  window.addEventListener("message", (e) => { document.getElementById("got").textContent = "App says: " + e.data; });
</script>`;

function Button(props: { label: string; onPress: () => void }) {
  return (
    <View
      onPress={props.onPress}
      {...button()}
    >
      <Text style={buttonText()}>{props.label}</Text>
    </View>
  );
}

const colors = () => [ui().accent, ui().info, ui().violet, ui().down, ui().warn];

function App() {
  const messages = signal<string[]>([]);
  const nav = signal<WebViewNavigation | null>(null);
  const sent = signal(0);
  let painted = 0;
  let web!: WebViewRef;
  const wide = () => windowSize().width >= 700;

  return (
    <View style={{ flex: 1, backgroundColor: ui().page, padding: 20, paddingTop: 24, gap: 14 }}>
      <Text style={{ fontSize: 22, fontWeight: "700", color: ui().text }}>web-view</Text>
      <View style={{ flex: 1, flexDirection: wide() ? "row" : "column", gap: 16 }}>
        <WebView
          ref={web}
          html={page(ui())}
          // Runs after each load: the page reports its title.
          injectedJavaScript={`window.nsh.postMessage("loaded: " + document.title)`}
          onLoad={(e) => nav.set(e)}
          onMessage={(e) => messages.set([e.data, ...messages()].slice(0, 4))}
          style={{ flex: 1, minHeight: 240, borderRadius: 14, overflow: "hidden", borderWidth: 1, borderColor: ui().border }}
        />
        <View style={{ gap: 12, width: wide() ? 320 : undefined }}>
          <Text style={{ color: ui().muted }}>
            {platform.desktop ? "Click" : "Tap"} the button in the page to post to the app; these buttons call the ref's methods.
          </Text>
          <View style={{ flexDirection: "row", flexWrap: "wrap", gap: 8 }}>
            <Button
              label="postMessage"
              onPress={() => {
                sent.update((n) => n + 1);
                web.postMessage(`hello #${sent()}`);
              }}
            />
            <Button
              label="injectJavaScript"
              onPress={() => {
                const color = colors()[painted++ % colors().length];
                web.injectJavaScript(`document.getElementById("box").style.background = "${color}"`);
              }}
            />
            <Button label="reload" onPress={() => web.reload()} />
          </View>
          <View style={{ ...ui().card, borderRadius: 12, padding: 12, gap: 4 }}>
            <Text style={{ color: ui().muted, fontSize: 12 }}>onLoad</Text>
            <Text style={{ numberOfLines: 1, color: ui().text, fontFamily: "monospace", fontSize: 13 }}>
              {nav() ? `title "${nav()!.title}"` : "…"}
            </Text>
            <Text style={{ color: ui().muted, fontSize: 12, marginTop: 6 }}>onMessage, newest first</Text>
            <Each of={messages()}>
              {(m) => <Text style={{ color: ui().text, fontFamily: "monospace", fontSize: 13 }}>{m()}</Text>}
            </Each>
          </View>
        </View>
      </View>
    </View>
  );
}

mount(App);
use nsh_core::prelude::*;

fn browser() -> impl IntoElement {
    let title = signal(SharedString::from("Loading…"));
    view()
        .flex_1()
        .child(text(title).number_of_lines(1))
        .child(
            web_view("https://example.com")
                .flex_1()
                .on_navigation_state_change(move |e: &WebNavigation| title.set(e.title.clone().into())),
        )
}

En TSX, une vue web accepte soit src, soit html (voir l’API WebView). Le guide des vues web et vidéo présente les modèles de messagerie et les capacités.

#Props

Une vue web accepte les props d’une vue (elle n’a pas d’enfants), plus :

PropRustTypeDéfautDescription
srcweb_view(url)stringL’URL de la page à afficher.
htmlweb_view_html(html, base_url), .html(html, base_url)stringHTML en ligne, affiché à la place de src.
baseUrlbase_url de web_view_html / .htmlstringBase de résolution des liens relatifs de html.
injectedJavaScript.injected_java_script(..)stringS’exécute après le chargement de chaque page.
injectedJavaScriptBeforeContentLoaded.injected_java_script_before_content(..)stringS’exécute avant les scripts propres à chaque page.
userAgent.user_agent(..)stringLa chaîne user agent du navigateur.
javaScriptEnabled.java_script_enabled(..)booleantrueLes pages exécutent des scripts.
scrollable.scrollable(..)booleantrueL’utilisateur peut faire défiler la page.
Clé iosRustTypeDescription
allowsBackForwardNavigationGestures.back_forward_gestures(..)booleanBalayer depuis les bords pour revenir en arrière et avancer.
Clé desktopRustTypeDescription
allowsBackForwardNavigationGestures.back_forward_gestures(..)booleanmacOS : balayer avec deux doigts sur le trackpad pour revenir en arrière et avancer ; Windows : balayer sur un écran tactile ; Linux : balayer sur le pavé tactile.

#Événements

ÉvénementRustPayloadQuand
onLoadStart.on_load_startWebViewNavigationUne page a commencé à se charger.
onLoad.on_loadWebViewNavigationUne page a fini de se charger.
onError.on_error{ message, code? } (&str, le message)Échec du chargement ; code vaut "ERR_ACCESS_DENIED" pour une page ou html que les capacités de l’application n’accordent pas.
onNavigationStateChange.on_navigation_state_changeWebViewNavigationL’URL, le titre, l’état de chargement ou l’historique a changé.
onMessage.on_message{ data: string } (&str)La page a appelé window.nsh.postMessage(data).
onDownloadWebViewDownloadUn téléchargement a démarré, s’est terminé ou a échoué (macOS).

WebViewNavigation est { url, title, loading, canGoBack, canGoForward } ; en Rust, WebNavigation avec url, title, loading, can_go_back et can_go_forward.

WebViewDownload est { state, id, url, filename, path, bytes?, message? } : state vaut "started", "finished" (avec le bytes du fichier) ou "failed" (avec son message). Avec un écouteur onDownload, ce que la page télécharge (une pièce jointe, un type que la vue web n’affiche pas, un lien download) est enregistré dans le répertoire de données de l’application sous downloads/<id>/<filename>, là où pointe path, et l’application le lit avec fs/promises.

Sur macOS, l’ajout de l’écouteur permet à toute page chargée autorisée par la capacité webview d’enregistrer automatiquement des téléchargements. L’application est responsable de son budget de stockage et de la suppression des fichiers terminés de downloads/ lorsqu’elle a fini de les utiliser. La suppression de la vue web annule les téléchargements en cours et supprime leurs répertoires partiels. Les téléchargements sont signalés via onDownload ; la conversion d’une navigation en téléchargement réserve onError aux véritables échecs de navigation. Les caractères de contrôle et les contrôles de mise en forme bidirectionnelle sont retirés des noms de fichiers, et les noms longs sont raccourcis à 200 octets tout en conservant une extension qui tient dans cette limite.

#Méthodes de ref

MéthodeRustDescription
reload()reload()Recharge la page.
goBack() / goForward()go_back() / go_forward()Se déplace dans l’historique de la page.
stopLoading()stop_loading()Arrête le chargement.
injectJavaScript(script)inject_java_script(script)Exécute du JavaScript dans la page.
postMessage(data)post_message(data)Envoie une chaîne à la page, qui reçoit un événement message sur window.

#Messagerie

L’application et la page échangent des chaînes. La page envoie avec window.nsh.postMessage(data) ; l’application envoie avec postMessage(data) de la ref (post_message(..) en Rust). Envoyez du JSON pour des données structurées.

use nsh_core::prelude::*;

const PAGE: &str = r#"<!doctype html>
<meta name="viewport" content="width=device-width">
<button onclick="window.nsh.postMessage('hello from the page')">Post to the app</button>
<p id="got"></p>
<script>
  window.addEventListener("message", (e) => { document.getElementById("got").textContent = e.data; });
</script>"#;

fn bridge() -> impl IntoElement {
    let message = signal(String::new());
    let web = element_ref();
    view()
        .gap_3()
        .child(
            web_view_html(PAGE.to_string(), None)
                .element_ref(&web)
                .h(px(200.0))
                .on_message(move |data: &str| message.set(data.to_string())),
        )
        .child(
            text(move || format!("Post to the page · got: {}", message.get()))
                .on_press(move || web.post_message("hi from the app")),
        )
}

#Capacités

Lorsque l’application déclare des capacités dans nsh.json, une vue web affiche les hôtes listés sous webview et y navigue. Un src sur un autre hôte, ou html dans une application sans webview, affiche une vue vide ; une navigation de premier niveau vers un autre hôte (un lien sur lequel l’utilisateur appuie) est annulée de façon native. Chacun signale onError avec code "ERR_ACCESS_DENIED".

{
  "capabilities": {
    "webview": { "hosts": ["example.com", "*.example.com"] }
  }
}

#Notes par plateforme

  • iOS charge WebKit la première fois qu’une vue web est créée ; cela ne coûte donc quelque chose à une application que lorsqu’elle en affiche une.
  • Sur macOS, onDownload enregistre les téléchargements avec WKDownload de WebKit.
  • Sur Android, les appels postMessage de la page arrivent sur un thread d’arrière-plan et sont transmis à l’application dans l’ordre.
  • Pour arrondir les coins d’une vue web, donnez-lui un rayon de coin et overflow: "hidden" (.overflow_hidden()).