-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathwebkit2-ffi.ts
More file actions
172 lines (158 loc) · 9.27 KB
/
Copy pathwebkit2-ffi.ts
File metadata and controls
172 lines (158 loc) · 9.27 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
import { FFIType, ptr } from 'bun:ffi';
import { dlopen } from '../dlopen';
import { existsSync } from 'node:fs';
import { dirname, join } from 'node:path';
import { FFIError } from '../../../common/errors';
import { type ResolveDeps, resolveEngineWith } from '../../engine/resolve';
import { winLibraryAccessor, wstr } from './win32';
import { loadKernel32 } from './win32-ffi';
/**
* WinCairo WebKit2 C API FFI. Opaque `WK*Ref` handles are plain pointers (`ptr`) and
* `size_t` is `u64` on x64. Unlike Linux there is NO system-WebKit fallback — Windows
* ships none — so any `system` resolution outcome means "no engine" here.
*/
const WEBKIT2_SYMBOLS = {
// ── Context + configuration ──────────────────────────────────────────────
WKContextConfigurationCreate: { args: [], returns: FFIType.ptr },
WKContextCreateWithConfiguration: { args: [FFIType.ptr], returns: FFIType.ptr },
WKPageConfigurationCreate: { args: [], returns: FFIType.ptr },
WKPageConfigurationSetContext: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
WKPageConfigurationSetUserContentController: {
args: [FFIType.ptr, FFIType.ptr],
returns: FFIType.void,
},
WKPageConfigurationGetPreferences: { args: [FFIType.ptr], returns: FFIType.ptr },
WKPreferencesSetJavaScriptEnabled: { args: [FFIType.ptr, FFIType.u8], returns: FFIType.void },
// ── View (hosted in an HWND) ─────────────────────────────────────────────
// WKViewCreate(RECT rect, WKPageConfigurationRef, HWND parent): RECT is 16
// bytes -> passed by hidden pointer on the Win64 ABI, so `rect` binds as ptr.
WKViewCreate: { args: [FFIType.ptr, FFIType.ptr, FFIType.u64], returns: FFIType.ptr },
WKViewGetPage: { args: [FFIType.ptr], returns: FFIType.ptr },
WKViewGetWindow: { args: [FFIType.ptr], returns: FFIType.u64 },
WKViewSetIsInWindow: { args: [FFIType.ptr, FFIType.u8], returns: FFIType.void },
WKViewSetParentWindow: { args: [FFIType.ptr, FFIType.u64], returns: FFIType.void },
// ── Navigation + history ─────────────────────────────────────────────────
WKPageLoadURL: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
WKPageLoadHTMLString: { args: [FFIType.ptr, FFIType.ptr, FFIType.ptr], returns: FFIType.void },
WKPageReload: { args: [FFIType.ptr], returns: FFIType.void },
WKPageReloadFromOrigin: { args: [FFIType.ptr], returns: FFIType.void },
WKPageStopLoading: { args: [FFIType.ptr], returns: FFIType.void },
WKPageGoBack: { args: [FFIType.ptr], returns: FFIType.void },
WKPageGoForward: { args: [FFIType.ptr], returns: FFIType.void },
WKPageCanGoBack: { args: [FFIType.ptr], returns: FFIType.bool },
WKPageCanGoForward: { args: [FFIType.ptr], returns: FFIType.bool },
WKPageCopyActiveURL: { args: [FFIType.ptr], returns: FFIType.ptr },
WKPageCopyTitle: { args: [FFIType.ptr], returns: FFIType.ptr },
// (page, script, void* context, completion) — context+completion passed NULL for
// fire-and-forget eval; executeJavaScript results return out-of-band (D022).
WKPageEvaluateJavaScriptInMainFrame: {
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
returns: FFIType.void,
},
WKPageSetPageZoomFactor: { args: [FFIType.ptr, FFIType.f64], returns: FFIType.void },
WKPageSetCustomUserAgent: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
// (page, const WKPageNavigationClientBase*) — register navigation lifecycle callbacks.
WKPageSetPageNavigationClient: { args: [FFIType.ptr, FFIType.ptr], returns: FFIType.void },
// ── Errors (for did-fail-load) ───────────────────────────────────────────
WKErrorGetErrorCode: { args: [FFIType.ptr], returns: FFIType.i32 },
WKErrorCopyLocalizedDescription: { args: [FFIType.ptr], returns: FFIType.ptr },
// ── User content: document-start injection + the renderer->main bridge ────
WKUserContentControllerCreate: { args: [], returns: FFIType.ptr },
WKUserContentControllerAddUserScript: {
args: [FFIType.ptr, FFIType.ptr],
returns: FFIType.void,
},
WKUserContentControllerRemoveAllUserScripts: { args: [FFIType.ptr], returns: FFIType.void },
// (ucc, WKStringRef name, WKScriptMessageHandlerCallback, const void* context)
WKUserContentControllerAddScriptMessageHandler: {
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr, FFIType.ptr],
returns: FFIType.void,
},
WKUserContentControllerRemoveAllUserMessageHandlers: {
args: [FFIType.ptr],
returns: FFIType.void,
},
// (WKStringRef source, _WKUserScriptInjectionTime, bool forMainFrameOnly)
WKUserScriptCreateWithSource: {
args: [FFIType.ptr, FFIType.i32, FFIType.u8],
returns: FFIType.ptr,
},
WKScriptMessageGetBody: { args: [FFIType.ptr], returns: FFIType.ptr },
// ── Strings / URLs ───────────────────────────────────────────────────────
WKStringCreateWithUTF8CString: { args: [FFIType.cstring], returns: FFIType.ptr },
WKStringGetMaximumUTF8CStringSize: { args: [FFIType.ptr], returns: FFIType.u64 },
WKStringGetUTF8CString: { args: [FFIType.ptr, FFIType.ptr, FFIType.u64], returns: FFIType.u64 },
WKURLCreateWithUTF8CString: { args: [FFIType.cstring], returns: FFIType.ptr },
WKURLCopyString: { args: [FFIType.ptr], returns: FFIType.ptr },
// ── Website data (used by the session backend) ───────────────────────────
// () -> WKWebsiteDataStoreRef — the process-wide default store.
WKWebsiteDataStoreGetDefaultDataStore: { args: [], returns: FFIType.ptr },
// (WKWebsiteDataStoreRef) -> WKHTTPCookieStoreRef
WKWebsiteDataStoreGetHTTPCookieStore: { args: [FFIType.ptr], returns: FFIType.ptr },
// (WKHTTPCookieStoreRef, void* context, callback(void* context)) -> void — async.
WKHTTPCookieStoreDeleteAllCookies: {
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr],
returns: FFIType.void,
},
// (WKWebsiteDataStoreRef, void* context, callback(void* context)) -> void — async.
WKWebsiteDataStoreRemoveAllFetchCaches: {
args: [FFIType.ptr, FFIType.ptr, FFIType.ptr],
returns: FFIType.void,
},
// ── Reference counting ───────────────────────────────────────────────────
WKRetain: { args: [FFIType.ptr], returns: FFIType.ptr },
WKRelease: { args: [FFIType.ptr], returns: FFIType.void },
} as const;
/** `_WKUserScriptInjectionTime`: inject before the page's own scripts run. */
export const WK_INJECT_AT_DOCUMENT_START = 0;
/** `_WKUserScriptInjectionTime`: inject after the document has parsed. */
export const WK_INJECT_AT_DOCUMENT_END = 1;
/** The subdir an embedded engine is bundled into (must match `build-windows.ts`). */
const BUNDLED_ENGINE_DIRNAME = 'webkit';
/**
* A WinCairo engine bundled next to the executable — `<exeDir>/webkit/` with a
* `WebKit2.dll` (what `bunmaska build --embed-engine` produces) — or `undefined`.
* This is what lets a packaged `.exe` run with no environment variables. Pure;
* `exists` is a test seam.
*/
export const bundledEngineDir = (
execPath: string,
exists: (path: string) => boolean,
): string | undefined => {
const dir = join(dirname(execPath), BUNDLED_ENGINE_DIRNAME);
return exists(join(dir, 'WebKit2.dll')) ? dir : undefined;
};
/**
* The directory of the WinCairo WebKit engine this process loads, or `undefined`
* when none is available (there is no system WebKit to fall back to on Windows).
* Precedence: an explicit/store pin via {@link resolveEngineWith} (the engine's
* `lib/`, or the verbatim `BUNMASKA_WEBKIT_PATH`), then a {@link bundledEngineDir}
* shipped next to the executable. `deps` is a test seam.
*/
export const resolveWindowsEngineDir = (deps: ResolveDeps = {}): string | undefined => {
const resolution = resolveEngineWith(deps);
if (resolution.mode === 'pinned') {
return resolution.libDir;
}
return bundledEngineDir(process.execPath, existsSync);
};
/**
* Open the engine's `WebKit2.dll` and return its symbol table. Memoised;
* import-safe (throws on non-Windows via the accessor). Puts the engine dir on
* the DLL search path first so the bundled closure resolves beside `WebKit2.dll`.
* A pinned-but-uninstalled engine surfaces the resolver's warning in the error.
*/
export const loadWebKit2 = winLibraryAccessor('WebKit2', () => {
// Use the full resolution (store/explicit pin AND an engine bundled next to the
// executable) — NOT resolveEngineWith alone, which misses the bundled fallback.
const dir = resolveWindowsEngineDir();
if (dir === undefined) {
const detail = resolveEngineWith().warnings.join('; ');
throw new FFIError(
`no WinCairo WebKit engine configured${detail.length > 0 ? ` (${detail})` : ''}; bundle one ` +
'with `bunmaska build --embed-engine`, set BUNMASKA_WEBKIT_PATH, or pin an installed engine',
);
}
loadKernel32().symbols.SetDllDirectoryW(ptr(wstr(dir)));
return dlopen(`${dir}\\WebKit2.dll`, WEBKIT2_SYMBOLS);
});