bunite-core 0.14.0 → 0.17.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.
- package/package.json +4 -4
- package/src/host/core/App.ts +6 -3
- package/src/host/core/BrowserView.ts +345 -24
- package/src/host/core/BrowserWindow.ts +52 -6
- package/src/host/core/SurfaceBrowserIPC.ts +10 -1
- package/src/host/core/SurfaceManager.ts +357 -16
- package/src/host/core/windowCap.ts +69 -0
- package/src/host/events/webviewEvents.ts +18 -1
- package/src/host/log.ts +6 -1
- package/src/host/native.ts +145 -1
- package/src/host/preloadBundle.ts +7 -2
- package/src/native/linux/bunite_linux_ffi.cpp +225 -1
- package/src/native/linux/bunite_linux_internal.h +12 -0
- package/src/native/linux/bunite_linux_runtime.cpp +6 -1
- package/src/native/linux/bunite_linux_view.cpp +211 -5
- package/src/native/mac/bunite_mac_ffi.mm +293 -4
- package/src/native/mac/bunite_mac_internal.h +13 -0
- package/src/native/mac/bunite_mac_view.mm +227 -7
- package/src/native/shared/ffi_exports.h +97 -30
- package/src/native/win/native_host_cef.cpp +107 -13
- package/src/native/win/native_host_ffi.cpp +831 -2
- package/src/native/win/native_host_internal.h +22 -0
- package/src/native/win/native_host_runtime.cpp +34 -0
- package/src/native/win-webview2/bunite_webview2_ffi.cpp +827 -5
- package/src/native/win-webview2/webview2_internal.h +19 -0
- package/src/native/win-webview2/webview2_runtime.cpp +383 -31
- package/src/preload/runtime.built.js +1 -1
- package/src/preload/runtime.ts +39 -0
- package/src/rpc/framework.ts +194 -12
- package/src/rpc/index.ts +12 -0
- package/src/rpc/peer.ts +1 -1
- package/src/webview/native.ts +142 -32
- package/src/webview/polyfill.ts +91 -14
package/src/webview/polyfill.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// Iframe fallback for web (no-op if native already registered). HTMLElement deref'd lazily so module is import-safe in Node/Bun.
|
|
2
2
|
|
|
3
|
-
import type { SurfaceEvent } from "../rpc/framework";
|
|
3
|
+
import type { SurfaceEvent, SurfaceEventBase } from "../rpc/framework";
|
|
4
4
|
|
|
5
5
|
// Default sandbox omits allow-same-origin / allow-top-navigation / allow-modals /
|
|
6
6
|
// allow-popups-to-escape-sandbox — popup escape stays opt-in so a sandboxed page
|
|
@@ -31,6 +31,12 @@ function definePolyfillClass(): CustomElementConstructor {
|
|
|
31
31
|
private _iframe: HTMLIFrameElement | null = null;
|
|
32
32
|
private _titleObserver: MutationObserver | null = null;
|
|
33
33
|
private _lastTitle: string = "";
|
|
34
|
+
private _epoch: number = 0;
|
|
35
|
+
private _isLoading: boolean = false;
|
|
36
|
+
private _currentUrl: string = "";
|
|
37
|
+
// Local history stack for goBack — cross-origin contentWindow.history is
|
|
38
|
+
// inaccessible, so we track navigations ourselves.
|
|
39
|
+
private _history: string[] = [];
|
|
34
40
|
|
|
35
41
|
private isReachable(): boolean {
|
|
36
42
|
if (!this._iframe) return false;
|
|
@@ -50,8 +56,22 @@ function definePolyfillClass(): CustomElementConstructor {
|
|
|
50
56
|
};
|
|
51
57
|
}
|
|
52
58
|
|
|
53
|
-
private emit(event:
|
|
54
|
-
|
|
59
|
+
private emit(event: SurfaceEventBase) {
|
|
60
|
+
if (event.type === "navigate") {
|
|
61
|
+
this._epoch++;
|
|
62
|
+
// Avoid pushing duplicate / same-as-top entries (reload doesn't grow history).
|
|
63
|
+
if (this._currentUrl && this._currentUrl !== event.url &&
|
|
64
|
+
this._history[this._history.length - 1] !== this._currentUrl) {
|
|
65
|
+
this._history.push(this._currentUrl);
|
|
66
|
+
}
|
|
67
|
+
this._currentUrl = event.url;
|
|
68
|
+
} else if (event.type === "load-start") {
|
|
69
|
+
this._isLoading = true;
|
|
70
|
+
} else if (event.type === "load-finish" || event.type === "load-fail") {
|
|
71
|
+
this._isLoading = false;
|
|
72
|
+
}
|
|
73
|
+
const stamped: SurfaceEvent = { ...event, epoch: this._epoch };
|
|
74
|
+
this.dispatchEvent(new CustomEvent<SurfaceEvent>("surface-event", { detail: stamped }));
|
|
55
75
|
}
|
|
56
76
|
|
|
57
77
|
private setupTitleObserver() {
|
|
@@ -100,6 +120,7 @@ function definePolyfillClass(): CustomElementConstructor {
|
|
|
100
120
|
this.dispatchBlocked(src);
|
|
101
121
|
} else {
|
|
102
122
|
iframe.src = src;
|
|
123
|
+
this._currentUrl = src;
|
|
103
124
|
this.emit({ type: "load-start", url: src });
|
|
104
125
|
}
|
|
105
126
|
}
|
|
@@ -112,8 +133,9 @@ function definePolyfillClass(): CustomElementConstructor {
|
|
|
112
133
|
// Suppress the spurious about:blank load that fires after a blocked
|
|
113
134
|
// navigation (or before any explicit navigate).
|
|
114
135
|
if (isBlockedSrc(url)) return;
|
|
115
|
-
|
|
136
|
+
// navigate first so load-finish carries the bumped epoch.
|
|
116
137
|
this.emit({ type: "navigate", url });
|
|
138
|
+
this.emit({ type: "load-finish", url });
|
|
117
139
|
this.setupTitleObserver();
|
|
118
140
|
});
|
|
119
141
|
|
|
@@ -148,19 +170,22 @@ function definePolyfillClass(): CustomElementConstructor {
|
|
|
148
170
|
}
|
|
149
171
|
|
|
150
172
|
goBack() {
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
} catch {}
|
|
173
|
+
// Same-origin path uses native history. Cross-origin throws → fall back
|
|
174
|
+
// to our tracked stack (push on every navigate; pop here).
|
|
175
|
+
try { this._iframe?.contentWindow?.history.back(); return; } catch {}
|
|
176
|
+
const prev = this._history.pop();
|
|
177
|
+
if (prev && this._iframe) this._iframe.src = prev;
|
|
154
178
|
}
|
|
155
179
|
|
|
156
180
|
reload() {
|
|
157
|
-
try {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
181
|
+
try { this._iframe?.contentWindow?.location.reload(); return; } catch {}
|
|
182
|
+
// Cross-origin fallback: reassigning the same src is a no-op in WHATWG;
|
|
183
|
+
// cycle via about:blank to force a fresh navigation.
|
|
184
|
+
const iframe = this._iframe;
|
|
185
|
+
if (!iframe) return;
|
|
186
|
+
const url = this._currentUrl || iframe.src;
|
|
187
|
+
iframe.src = "about:blank";
|
|
188
|
+
requestAnimationFrame(() => { if (this._iframe) this._iframe.src = url; });
|
|
164
189
|
}
|
|
165
190
|
|
|
166
191
|
setHidden(hidden: boolean) {
|
|
@@ -219,6 +244,54 @@ function definePolyfillClass(): CustomElementConstructor {
|
|
|
219
244
|
target.dispatchEvent(new MouseEvent("click", init));
|
|
220
245
|
}
|
|
221
246
|
|
|
247
|
+
async resolveAndClick(_selector: string, _opts?: unknown) {
|
|
248
|
+
return { ok: false as const, code: "not_supported" as const, message: "polyfill iframe: atomic resolveAndClick not supported" };
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
async getBoundingRect(selector: string, _opts?: unknown) {
|
|
252
|
+
if (!this.isReachable()) return { ok: false as const, code: "not_supported" as const, message: "iframe not reachable" };
|
|
253
|
+
try {
|
|
254
|
+
const el = this._iframe!.contentDocument!.querySelector(selector) as Element | null;
|
|
255
|
+
if (!el) return { ok: false as const, code: "not_found" as const, message: `selector ${selector} not found` };
|
|
256
|
+
const r = el.getBoundingClientRect();
|
|
257
|
+
const win = this._iframe!.contentWindow!;
|
|
258
|
+
const visible = r.width > 0 && r.height > 0 && r.bottom > 0 && r.right > 0
|
|
259
|
+
&& r.top < win.innerHeight && r.left < win.innerWidth;
|
|
260
|
+
return { ok: true as const, rect: { x: r.x, y: r.y, width: r.width, height: r.height }, visible };
|
|
261
|
+
} catch (e: any) {
|
|
262
|
+
const code = e?.name === "SecurityError" ? "cross_origin" as const : "runtime_error" as const;
|
|
263
|
+
return { ok: false as const, code, message: e?.message ?? String(e) };
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
async listFrames() {
|
|
268
|
+
return { ok: false as const, code: "not_supported" as const, message: "polyfill: not implemented" };
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
async accessibilitySnapshot(_opts?: unknown) {
|
|
272
|
+
return { ok: false as const, code: "not_supported" as const, message: "polyfill: not implemented" };
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
async setDownloadPolicy(_policy: unknown, _dir?: unknown) {
|
|
276
|
+
// No-op — iframe has no download lifecycle hook.
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
async waitForDownload(_opts?: unknown) {
|
|
280
|
+
return { ok: false as const, code: "not_supported" as const, message: "polyfill: not implemented" };
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
async acceptPopup(_opts?: unknown) {
|
|
284
|
+
return { ok: false as const, code: "not_found" as const, message: "polyfill: no popup orchestration" };
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async dismissPopup(_id?: unknown) {
|
|
288
|
+
// No-op.
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
async extendAdoptionTimeout(_id?: unknown, _ms?: unknown) {
|
|
292
|
+
return { ok: false as const, code: "not_found" as const, message: "polyfill: no popup orchestration" };
|
|
293
|
+
}
|
|
294
|
+
|
|
222
295
|
async sendType(text: string) {
|
|
223
296
|
if (!this.isReachable()) return;
|
|
224
297
|
const doc = this._iframe!.contentDocument!;
|
|
@@ -313,6 +386,10 @@ function definePolyfillClass(): CustomElementConstructor {
|
|
|
313
386
|
return [] as { level: string; args: string[]; ts: number }[];
|
|
314
387
|
}
|
|
315
388
|
|
|
389
|
+
async getNavigationState() {
|
|
390
|
+
return { lastLoadEpoch: this._epoch, isLoading: this._isLoading, currentUrl: this._currentUrl };
|
|
391
|
+
}
|
|
392
|
+
|
|
316
393
|
async screenshot(_args?: { format?: "png" | "jpeg"; quality?: number }) {
|
|
317
394
|
return { ok: false as const, code: "not_supported" as const, message: "iframe polyfill does not support screenshot" };
|
|
318
395
|
}
|