@gjsify/webkit-native 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (2) hide show
  1. package/README.md +122 -0
  2. package/package.json +60 -0
package/README.md ADDED
@@ -0,0 +1,122 @@
1
+ # @gjsify/webkit-native
2
+
3
+ Apple's WebKit (`WKWebView`) behind a GObject API shaped like WebKitGTK 6.0 — the
4
+ darwin backend for [`@gjsify/iframe`](../iframe/README.md).
5
+
6
+ Decision and measurements: [ADR 0022](../../../docs/adr/0022-webkit-on-darwin.md).
7
+
8
+ ## Why this exists
9
+
10
+ `gi://WebKit` version 6.0 has no macOS provider, and that is upstream's position
11
+ rather than a packaging gap: Homebrew's `webkitgtk` formula carries an
12
+ unconditional `depends_on :linux` whose own comment says *"Use
13
+ JavaScriptCore.Framework on macOS."*, ships Linux-only bottles, and builds the
14
+ GTK**3** port anyway (`-DUSE_GTK4=OFF`). So `@gjsify/iframe` could not load on
15
+ macOS at all:
16
+
17
+ ```
18
+ JS ERROR: Error: Requiring WebKit, version 6.0:
19
+ Typelib file for namespace 'WebKit', version '6.0' not found
20
+ ```
21
+
22
+ macOS does have complete WebKit with a complete DOM — what it lacks is a
23
+ GObject-Introspection binding and a GTK widget. This package supplies both.
24
+
25
+ ## What it is
26
+
27
+ One Objective-C library exposing a **subset of WebKitGTK 6.0's API under the
28
+ same namespace**: `WebKit-6.0`. That is the whole integration strategy —
29
+ `@gjsify/iframe` keeps `import WebKit from 'gi://WebKit?version=6.0'` verbatim
30
+ and carries **no backend seam, no OS branch, and no `gjsify.os` declaration**.
31
+ Which typelib answers to that name is decided by what is on `GI_TYPELIB_PATH`,
32
+ i.e. by packaging.
33
+
34
+ | exposed | backed by |
35
+ |---|---|
36
+ | `WebKit.WebView` (a real `Gtk.Widget`, derivable) | `WKWebView`, offscreen |
37
+ | `WebKit.UserContentManager` / `UserScript` | `WKUserContentController` / `WKUserScript` |
38
+ | `WebKit.Settings` | `WKWebViewConfiguration` / `WKPreferences` |
39
+ | `load-changed` + `WebKit.LoadEvent` | `WKNavigationDelegate` |
40
+ | `script-message-received::<name>` | `window.webkit.messageHandlers.<name>.postMessage` |
41
+ | `evaluate_javascript()` → a value with `to_string()` | `evaluateJavaScript:completionHandler:` |
42
+ | `get_snapshot()` → `Gdk.Texture` | `takeSnapshotWithConfiguration:` |
43
+
44
+ ## The two things that are not obvious
45
+
46
+ **The run loop.** `WKWebView` delivers everything through a CFRunLoop; GJS runs a
47
+ GMainContext, and a GMainContext does not pump a CFRunLoop. Measured: a
48
+ `WKWebView` driven from a bare `g_main_loop_run()` never even reaches
49
+ `didFinishNavigation`. The library installs a drain source while at least one
50
+ view is alive — [`docs/poc/webkit-runloop-darwin.m`](../../../docs/poc/webkit-runloop-darwin.m)
51
+ is that measurement, and it fails loudly if either half stops being true.
52
+
53
+ **The widget is built, not embedded.** `WKWebView` is an `NSView` and GTK4 has no
54
+ foreign-window embedding, so the content renders offscreen and is presented as a
55
+ `GdkTexture` in the widget's `snapshot` vfunc. There is no "content changed"
56
+ signal from an out-of-process web view, so the widget pulls: it snapshots on a
57
+ tick while mapped, at most one in flight. Measured at 1024×768 on a GPU-less
58
+ Intel VM, `takeSnapshot` averages 15.8 ms — a ~63 fps ceiling before any GPU is
59
+ involved.
60
+
61
+ ## Building
62
+
63
+ Requires macOS, clang, meson and gobject-introspection.
64
+
65
+ ```bash
66
+ gjsify workspace @gjsify/webkit-native run build:prebuilds
67
+ ```
68
+
69
+ That runs meson and stages `libgjsifywebkit.dylib` + `WebKit-6.0.typelib` +
70
+ `WebKit-6.0.gir` into `prebuilds/<os>-<arch>/`, from where ADR 0017's per-target
71
+ split publishes them.
72
+
73
+ **In CI this needs the `ci:macos` label on the PR.** `prebuilds.yml`'s
74
+ `build-prebuilds-macos` job is opt-in (macOS runners bill at 10×), so without the
75
+ label every darwin leg skips and nothing here is built or uploaded — a PR can go
76
+ green while this package was never compiled. The label only takes effect on a run
77
+ created *after* it was added: neither workflow declares `types:`, so the default
78
+ `[opened, synchronize, reopened]` applies and `labeled` is not among them. Push a
79
+ commit (or reopen the PR) after labelling.
80
+
81
+ ## Input
82
+
83
+ Mouse, scroll and keyboard are forwarded from the GTK widget to the web content:
84
+ `GtkGestureClick`, motion, scroll and key controllers each re-synthesize an
85
+ `NSEvent`. A forwarded click focuses the element under it, so DOM focus follows
86
+ the pointer as it does on Linux, and no `GtkIMContext` is attached because WebKit
87
+ routes `keyDown:` through its own `NSTextInputClient` — which *is* the macOS
88
+ input-method path, so a second IM context would compose the input twice.
89
+
90
+ Two things worth knowing if you touch it, both measured
91
+ (`docs/poc/webkit-input-darwin.m`): the view needs **no window and no responder
92
+ chain** — an offscreen `NSWindow` was built and is indistinguishable except that
93
+ it breaks scrolling — and an `NSEvent`'s location is **bottom-left window space
94
+ even though `WKWebView` is flipped**, so a GTK y is flipped exactly once against
95
+ the widget height. Getting that wrong delivers every event to the wrong element
96
+ and nothing else.
97
+
98
+ Minimum deployment target is **macOS 11**, declared in `meson.build`, because
99
+ script worlds are `WKContentWorld`.
100
+
101
+ ## Not implemented
102
+
103
+ Deliberate, and each fails loudly rather than silently:
104
+
105
+ - **`document.hasFocus()` is always `false`,** so `window.onfocus` / `onblur`
106
+ never fire and no caret blinks. It is derived from a responder chain a
107
+ windowless view has no place in; an offscreen window does not fix it, which was
108
+ measured rather than assumed.
109
+ - **The pointer cursor never changes** over links or text — another thing WebKit
110
+ delivers through a window it does not have.
111
+ - **App Sandbox is unanswered.** The **hardened runtime works**
112
+ (`docs/poc/webkit-hardened-runtime-darwin.sh`, with
113
+ `com.apple.security.cs.allow-jit`), so a notarised app has no WebKit-specific
114
+ obstacle. The sandbox case dies before `main` because
115
+ `com.apple.security.app-sandbox` needs a bundled app with an
116
+ `application-identifier` that an ad-hoc signature cannot issue — a statement
117
+ about bare executables, not about WebKit. A `gjs` process also has no bundle
118
+ identifier, which some WebKit features key on.
119
+ - **No CI covers the input path**, on any platform. It is held by two by-hand
120
+ probes that both need a display, and `@gjsify/iframe`'s unit suite instantiates
121
+ no live WebView. See `status/open-todos.md` for the two event-injection routes
122
+ that were tried and are dead ends.
package/package.json ADDED
@@ -0,0 +1,60 @@
1
+ {
2
+ "name": "@gjsify/webkit-native",
3
+ "version": "0.30.0",
4
+ "description": "Apple WebKit (WKWebView) behind a GObject API shaped like WebKitGTK 6.0 — the darwin backend for @gjsify/iframe (ADR 0022). Contains no JavaScript; the prebuilt library and typelib arrive through per-target optionalDependencies.",
5
+ "type": "module",
6
+ "files": [],
7
+ "gjsify": {
8
+ "platforms": [
9
+ "darwin-x64",
10
+ "darwin-arm64"
11
+ ],
12
+ "runtimes": {
13
+ "gjs": "polyfill",
14
+ "node": "none",
15
+ "browser": "none",
16
+ "nativescript": "none"
17
+ },
18
+ "os": {
19
+ "linux": "none",
20
+ "darwin": "supported",
21
+ "win32": "none"
22
+ },
23
+ "osNotes": {
24
+ "linux": "Binds Apple's WebKit.framework, which exists only on macOS. Linux has the real thing: @gjsify/iframe imports gi://WebKit 6.0 there and never reaches this package.",
25
+ "win32": "Binds Apple's WebKit.framework, which exists only on macOS. Windows has no GJS host either (see @gjsify/os win32 notes)."
26
+ },
27
+ "tier": 1
28
+ },
29
+ "scripts": {
30
+ "clear": "gjsify clear build _install",
31
+ "check": "echo 'no TypeScript in this package'",
32
+ "init:meson": "meson setup build .",
33
+ "init:meson:wipe": "gjsify run init:meson --wipe",
34
+ "build": "echo 'native only — see build:prebuilds'",
35
+ "build:meson": "gjsify run init:meson && meson compile -C build",
36
+ "build:prebuilds": "gjsify run build:meson && node ../../../scripts/stage-prebuild.mjs .",
37
+ "test": "echo 'covered by @gjsify/iframe on darwin'"
38
+ },
39
+ "keywords": [
40
+ "gjs",
41
+ "webkit",
42
+ "wkwebview",
43
+ "darwin",
44
+ "macos"
45
+ ],
46
+ "license": "MIT",
47
+ "repository": {
48
+ "type": "git",
49
+ "url": "git+https://github.com/gjsify/gjsify.git",
50
+ "directory": "packages/framework/webkit-native"
51
+ },
52
+ "bugs": {
53
+ "url": "https://github.com/gjsify/gjsify/issues"
54
+ },
55
+ "homepage": "https://github.com/gjsify/gjsify/tree/main/packages/framework/webkit-native#readme",
56
+ "optionalDependencies": {
57
+ "@gjsify/webkit-native-darwin-arm64": "0.30.0",
58
+ "@gjsify/webkit-native-darwin-x64": "0.30.0"
59
+ }
60
+ }