Skip to content

feat(native-ui): locked-down webview element#7

Merged
simonhamp merged 11 commits into
mainfrom
feat/webview-element
Jul 22, 2026
Merged

feat(native-ui): locked-down webview element#7
simonhamp merged 11 commits into
mainfrom
feat/webview-element

Conversation

@simonhamp

Copy link
Copy Markdown
Member

What

Adds a <native:webview> leaf element (self-closing, <x-webview /> in Blade) for embedding web content on both platforms — WKWebView on iOS, android.webkit.WebView on Android.

Security posture

Defaults are paranoid by design; hosts opt back into individual capabilities via Blade attributes:

  • JavaScript off by default (javascript attribute to enable)
  • DOM storage off by default (dom-storage attribute to enable)
  • No JS bridge to the host app
  • No new windows: target=_blank / window.open() silently denied on both platforms
  • Non-persistent data store (iOS) / non-persistent cookies + no cache (Android)
  • No file/content access (Android), no link previews or back/forward swipe (iOS)
  • Mixed content blocked (Android MIXED_CONTENT_NEVER_ALLOW)
  • Media playback requires a user gesture
  • Top-frame navigations restricted to https / http / data / about; external schemes (mailto, tel, intent, …) are dropped rather than dispatched to the system

API

<x-webview src="https://example.com" javascript @navigated="onUrlChange" />
<x-webview :html="$inlineHtml" />
  • src — URL to load (loadable schemes only)
  • html — inline HTML, loaded with a null base URL so it gets an opaque origin and can't make same-origin requests against the host app
  • @navigated — fires once per committed top-frame navigation with the resolved URL (didCommit on iOS, onPageCommitVisible on Android)

Content reloads only when src/html actually change (signature check), so recompositions/re-renders don't reset scroll position or flicker.

Notes

  • Registered as self_closing: true — the renderers intentionally ignore children.
  • applyA11yAttributes() wired in like the other elements.
  • Test suite doesn't currently run against the released nativephp/mobile (pre-existing: main already targets unreleased Edge classes like Native\Mobile\Edge\Element), so this was validated by manifest/class cross-check rather than pest.

🤖 Generated with Claude Code

simonhamp and others added 3 commits July 15, 2026 16:11
Adds a <native:webview> leaf element for embedding web content, with a
paranoid-by-default posture: JS off, no DOM storage, no file access, no
JS bridge to the host, no new windows (target=_blank / window.open
denied), mixed content blocked, non-persistent cookies/data store, and
media playback requiring a user gesture. Hosts opt back into individual
capabilities via `javascript` / `dom-storage` attributes.

Content comes from `src` (https/http/data/about schemes only) or inline
`html` (loaded with a null base URL, so it gets an opaque origin).
Top-frame navigations fire `@navigated` with the resolved URL once
committed; external schemes (mailto, tel, intent, ...) are dropped.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Package renamed nativephp/native-ui -> nativephp/mobile-ui and the PHP
namespace Nativephp\NativeUi -> Native\Mobile\UI, matching the repo's
new home and bringing the plugin under the framework's Native\Mobile
namespace root (alongside Native\Mobile\Edge from nativephp/mobile).

Touches every class in src/, the PSR-4 mapping + provider FQCN in
composer.json, element/blade FQCNs and the plugin name in nativephp.json,
tests, docs, and namespace mentions in native-side comments. Platform
identifiers are intentionally unchanged: the Kotlin package
(com.nativephp.plugins.native_ui), Swift NativeUI* renderer names, the
manifest bridge namespace ("NativeUI"), and the native-ui config
key/publish tags.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
simonhamp and others added 3 commits July 15, 2026 17:53
The framework's PluginDiscovery and PluginValidateCommand both require
type "nativephp-plugin" exactly; the old "nativephp-ui-plugin" value
this test asserted would never be discovered.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
refactor(mobile-ui)!: rename package to nativephp/mobile-ui
`<x-webview php />` swaps the locked-down sandbox for the app's own
enriched Laravel webview — pages served by the embedded PHP runtime with
the full window.Native bridge, shared session store, and asset pipeline.
`src` becomes an app route path; empty/relative falls back to the app's
configured start URL.

iOS builds an independent WKWebView wired like the shell's classic one
(PHPSchemeHandler on php://, shared WebView.dataStore, addNativeHelper
user scripts borrowed from the shell). Android runs the full
WebViewManager.setup() wiring on a renderer-owned WebView, restoring the
process-wide `shared` slot afterwards and wrapping the stock client so
its "page load = web mode" isActive flip can't unmount the native tree
this webview lives inside.

`<x-webview fullscreen />` gives the v3 default-webview presentation:
fill layout from the PHP side plus ignoresSafeArea on iOS.

Also fixes `@navigated` never firing: `_navigated` isn't in the
collector's callback allowlist, so the element now wires it in
applyAttributes itself.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
simonhamp and others added 5 commits July 21, 2026 22:05
Device testing surfaced the real blocker for php mode: the persistent
runtime's single PHP executor is parked in the native screen's event
loop while a Route::native screen is live, so it can never answer the
embedded webview's requests. Each php-mode webview now gets a dedicated
WebviewPHPRuntime (its own PHP thread/context), wired through
PHPSchemeHandler.dedicatedRuntime on iOS and
PHPBridge.dedicatedWebviewRuntime on Android, and released the moment
the webview leaves the view hierarchy.

Also: reload once when WebKit's content process dies (transient kills
self-heal), and diagnostic logging across the php-mode load/navigation
path.

Requires the nativephp/mobile counterpart that ships WebviewPHPRuntime
(not in 4.0.0-rc.1).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Android side of the per-webview PHP runtime (mobile-air
feat/webview-php-runtimes), plus the rendering fixes that made the
embedded webview livable inside a native screen:

- PhpWebView creates a WebviewPHPRuntime per instance, hands its
  PHPBridge to WebViewManager with embedded = true (no app-level mode
  flips or chrome clearing from an embedded page), and releases the
  runtime in onRelease so its PHP thread dies with the webview.
- setBackgroundColor(TRANSPARENT) on the php webview — a fresh WebView
  surface otherwise paints opaque black over the window until
  Chromium's first paint (the sandbox factory already had this).
- clipToBounds() on both AndroidView sites + a requestLayout() nudge on
  page commit: a WebView created mid-composition can transiently paint
  its layer at a stale offset covering sibling composables (tree and
  view bounds verified correct while it happened); clipping makes the
  race invisible and the nudge snaps the surface home. Verified with
  514 recorded frames across 8 create/destroy cycles — zero artifacts.
- K2 compile fixes: labeled return in the AndroidView factory
  restructured to an explicit branch; isLoadableScheme(String) renamed
  isLoadableUrl (JVM signature clash with the String? overload).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
feat(mobile-ui): webview php + fullscreen modes
The main merge brought in files predating the mobile-ui rename and the
branch's Pint pass: ThemeFontsTest and README still referenced
Nativephp\NativeUi, and six PHP files carried style drift. Renames
applied, Pint run; suite green (98 passed).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@simonhamp
simonhamp merged commit 92e5d7e into main Jul 22, 2026
3 checks passed
@simonhamp
simonhamp deleted the feat/webview-element branch July 22, 2026 15:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant