Skip to content

Custom URL schemes on macOS, Linux and Windows (story 8, Canvases 30–32) - #59

Merged
shannah merged 10 commits into
masterfrom
claude/local-database-agent-support-7sg3j6
Sep 26, 2026
Merged

shannah merged 10 commits into
masterfrom
claude/local-database-agent-support-7sg3j6

Conversation

@shannah

@shannah shannah commented Sep 26, 2026 •

Copy link
Copy Markdown
Collaborator

Lets an application register its own URL scheme, such as aaf, before the first WebView exists. Every request to aaf://… is then answered by a Java handler, with no local HTTP server, port or token.

Built with SPDD:

  • story requirements/[User-story-8]serve-pages-from-a-custom-url-scheme.md;
  • analysis spdd/analysis/GGQPA-XXX-202609261430-[Analysis]-serve-pages-from-a-custom-url-scheme.md;
  • Canvas 30: the Java API, the dispatcher, macOS and the demo;
  • Canvas 31: Linux (WebKitGTK);
  • Canvas 32: Windows (WebView2).

Java API (Canvas 30)

  • WebViewSchemes — register(scheme, handler), isSupported() and registeredSchemes().
    • A scheme name is 2–32 characters: a letter first, then letters, digits, +, - or ..
    • The web's own schemes are refused.
    • Registration is refused once the first engine exists.
    • The capability check starts AWT before loading the native library. Without that, JDK 8 on macOS crashed in libawt_lwawt.
    • The refusal messages' non-ASCII characters are written as \u escapes, so the source compiles under javac's Cp1252 default on Windows.
  • WebViewSchemeRequest, WebViewSchemeResponse, WebViewSchemeHandler and WebViewSchemeResponder.
  • SchemeDispatcher:
    • runs handlers on webview-scheme-N daemon threads;
    • answers each request exactly once: 500 if the handler throws, 504 after 30 s, 404 for an unknown scheme, 413 for a request over 16 MB, 500 for a response over 64 MB;
    • drops Content-Length, adds a default Content-Type, and sets Access-Control-Allow-Origin to the request's own origin.

Native

  • macOS: one WKURLSchemeHandler is installed on each engine's configuration, and popups inherit it.
  • Linux (Canvas 31):
    • each scheme is registered once on WebKitGTK's default web context, as secure and CORS-enabled, and every view and popup shares that context. Both component modes are covered;
    • new optional loader symbols cover the request method, headers and body and the response status and headers. They are used where the engine has them and never fail the load. There is still no link-time WebKit, JavaScriptCore or libsoup dependency.
  • Windows (Canvas 32):
    • the schemes are declared on the WebView2 environment through the SDK's own WRL helpers (secure, with a host, accepting requests from pages on the same scheme), so every other option keeps its SDK default;
    • with nothing registered, options stay null, as before;
    • a WebResourceRequested filter and handler go on the engine's webview and on every popup child;
    • each request is paused and completed on the engine thread with CreateWebResourceResponse.
  • Linux and Windows never cancel an abandoned request: neither engine reports one, so the 30 s timeout ends it.

Tests and verification

  • Java: WebViewSchemesTest, SchemeDispatcherTest and WebViewSchemeResponseTest. The full suite passes: 236 tests, 0 failures.
  • The demo has an optional automatic mode, SCHEMEDEMO_AUTO=1. It runs 8 checks the page can observe and exits 0 only if all pass.
  • macOS: the manual checklist passes.
  • Linux: the automatic demo passes in both modes (WebKitGTK 2.52), and the demo also passed on the author's machine.
  • Windows: the demo passes on the author's machine.
  • CI compiles all six native targets.

Motivation: the Agentic App Framework will serve its local-database browser at aaf://db/ (webliteca/agentic-app-framework#378).

🤖 Generated with Claude Code

https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
Applications can register a scheme (e.g. demo://) before the first
WebView and answer every request to it in Java, with no local server.

- WebViewSchemes: static registry with name rules, reserved web schemes,
  the freeze at the first engine, and an isSupported() capability probe.
- WebViewSchemeRequest/Response/Handler/Responder: the handler contract.
- SchemeDispatcher: runs handlers on webview-scheme-N daemon threads and
  answers each request exactly once (500 on throw, 504 after 30 s, 404
  unknown scheme, 413 over 16 MB, 500 over 64 MB); drops Content-Length,
  defaults Content-Type, sets CORS to the request's own origin.
- macOS: a WKURLSchemeHandler installed on each engine's configuration;
  responses applied on the main queue, never after a stop.
- Linux and Windows: stub exports (isSupported() is false) until
  Canvases 31 and 32.
- EmbeddedWebView/OffscreenWebView freeze the registry before native create.
- Tests: WebViewSchemesTest, SchemeDispatcherTest, WebViewSchemeResponseTest.
- Demo WebViewSchemeDemo with run-{mac,linux}-scheme-demo.sh and
  run-windows-scheme-demo.bat; README "Custom URL schemes" section.
- Canvas 30 O5.1 amended to name the Installer test seam.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
@shannah shannah changed the title Custom URL schemes: story 8 Custom URL schemes: Java API + macOS (story 8, Canvas 30) Sep 26, 2026
WebViewSchemes.isSupported() and register() are meant to be called at
start-up, before any window. Their probe loaded WebViewNative, which
loads libjawt; on macOS under JDK 8 that pulls in libawt_lwawt, whose
JNI_OnLoad segfaults when the AWT toolkit has not started. The demo hit
this in main().

The default probe now answers false when headless without loading the
native library, and otherwise calls Toolkit.getDefaultToolkit() before
touching WebViewNative. Canvas 30 D4, O5.2 and Safeguard 6 amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
Registers each scheme once on WebKitGTK's default web context (secure,
CORS-enabled) before the first view, answers requests through Canvas
30's dispatcher, and applies responses on the GTK pump thread. Adds an
optional-symbol list to the Linux loader so request bodies (2.40) and
response status/headers (2.36) are used where present without failing
the load on older engines. Adds an automatic mode to the scheme demo so
the engine can be checked under Xvfb.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
- Register each scheme once on WebKitGTK's default web context, as
  secure and CORS-enabled, on the pump thread before the first view;
  every view, popup and adopted popup shares that context. Both
  lightweight and heavyweight modes are covered.
- Requests are held (ref'd) by id and handed to Canvas 30's dispatcher;
  answers are applied on the pump thread only if still pending.
- The Linux loader gains an optional-symbol list (WK_WEBKIT_OPT_SYMS,
  WK_HAS): request method (2.12), headers and the response object with
  status/headers (2.36, plus libsoup via the WebKit handle) and request
  bodies (2.40) are used where present and never fail the load.
- webview_scheme_available() is true on GTK.
- Demo: SCHEMEDEMO_AUTO=1 runs 8 page-observable checks and exits
  0/1/2; the report travels as a GET so it works on every engine.
- READMEs: Linux coverage and limitations of older WebKitGTK.
- Canvas 31 D10 amended: same-origin 404 check instead of a cross-host
  fetch (blocked by CORS by design), and the report sent as a GET.

Verified under Xvfb on WebKitGTK 2.52: all checks pass in lightweight
and heavyweight modes; a build with the optional gates forced off loads,
serves pages and degrades as documented without crashing.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
@shannah shannah changed the title Custom URL schemes: Java API + macOS (story 8, Canvas 30) Custom URL schemes: Java API, macOS and Linux (story 8, Canvases 30–31) Sep 26, 2026
Declares the registered schemes on the WebView2 environment (secure,
with an authority, accepting requests from their own scheme) through the
SDK's own WRL options helper, only when something is registered; hooks
WebResourceRequested on the engine's webview and every popup child,
ignoring other schemes; answers through a deferral completed on the
engine thread with CreateWebResourceResponse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
- Declare registered schemes on the WebView2 environment through the
  SDK's own WRL helpers (CoreWebView2EnvironmentOptions and
  CoreWebView2CustomSchemeRegistration): secure, with an authority,
  accepting requests from pages on the same scheme. Null options, as
  before, when nothing is registered.
- Filter and hook WebResourceRequested on the engine's webview and on
  every popup child (both dispositions); adoption keeps the child's hook.
  The handler ignores requests of unregistered schemes.
- Each request is held by a deferral and handed to Canvas 30's
  dispatcher on a detached thread; the answer is built with
  CreateWebResourceResponse and completed on the engine thread, once.
- webview_scheme_available() is true on Windows.
- READMEs and the Windows run script: Windows coverage and notes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
@shannah shannah changed the title Custom URL schemes: Java API, macOS and Linux (story 8, Canvases 30–31) Custom URL schemes on macOS, Linux and Windows (story 8, Canvases 30–32) Sep 26, 2026
javac on Windows reads sources as Cp1252 by default, which cannot map
the closing curly quote (0x9D byte) in WebViewSchemes' refusal messages.

- WebViewSchemes: spell the three messages' quotes and dash as \u
  escapes; the strings are identical (tests assert them verbatim).
- run-windows-scheme-demo.bat: pass -encoding UTF-8 to both javac calls.
- /spdd-sync: Canvas 30 Norm 7 (source encoding) and O11 note.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01EvuvnSZ2qxgNJkF4T5eD6V
@shannah
shannah marked this pull request as ready for review September 26, 2026 15:24
@shannah
shannah merged commit 863db9b into master Sep 26, 2026
7 checks passed
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.

2 participants