bunite-core 0.14.0 → 0.16.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 +2 -1
- package/src/host/core/BrowserView.ts +345 -24
- package/src/host/core/SurfaceBrowserIPC.ts +10 -1
- package/src/host/core/SurfaceManager.ts +357 -16
- package/src/host/events/webviewEvents.ts +18 -1
- package/src/host/log.ts +6 -1
- package/src/host/native.ts +140 -1
- package/src/host/preloadBundle.ts +7 -2
- package/src/native/linux/bunite_linux_ffi.cpp +205 -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 +278 -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 +93 -30
- package/src/native/win/native_host_cef.cpp +102 -10
- package/src/native/win/native_host_ffi.cpp +818 -2
- package/src/native/win/native_host_internal.h +22 -0
- package/src/native/win-webview2/bunite_webview2_ffi.cpp +788 -4
- package/src/native/win-webview2/webview2_internal.h +14 -0
- package/src/native/win-webview2/webview2_runtime.cpp +276 -23
- package/src/preload/runtime.built.js +1 -1
- package/src/rpc/framework.ts +174 -11
- package/src/rpc/index.ts +11 -0
- package/src/webview/native.ts +142 -32
- package/src/webview/polyfill.ts +91 -14
package/src/webview/native.ts
CHANGED
|
@@ -3,9 +3,14 @@
|
|
|
3
3
|
import type { ClientOf } from "../rpc/index";
|
|
4
4
|
import type {
|
|
5
5
|
SurfaceCap, EvaluateResult, SurfaceCapabilities, ScreenshotResult,
|
|
6
|
-
SurfaceEvent, ConsoleEntry, WaitResult,
|
|
6
|
+
SurfaceEvent, ConsoleEntry, WaitResult, NavigationState,
|
|
7
|
+
AccessibilitySnapshotResult, BoundingRectResult, ListFramesResult,
|
|
8
|
+
DownloadEvent, DownloadPolicy, WaitForDownloadResult,
|
|
9
|
+
ResolveAndClickResult,
|
|
7
10
|
} from "../rpc/framework";
|
|
8
11
|
|
|
12
|
+
declare const __buniteWebviewId: number;
|
|
13
|
+
|
|
9
14
|
declare const host: {
|
|
10
15
|
runtime(): Promise<ClientOf<typeof import("../rpc/framework").RuntimeCap>>;
|
|
11
16
|
};
|
|
@@ -138,17 +143,24 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
138
143
|
this._unsubNavigate = () => ctrl.abort();
|
|
139
144
|
void (async () => {
|
|
140
145
|
try {
|
|
141
|
-
// Wait until *this* connection's init resolves, then capture surfaceId
|
|
142
|
-
// atomically before subscribing. Re-entrant connects abort prior loops
|
|
143
|
-
// via `ctrl`, so a stale resolve from a previous cycle can't leak in.
|
|
144
146
|
const s = await getSurfaceCap();
|
|
145
147
|
if (ctrl.signal.aborted) return;
|
|
146
148
|
await this._waitForSurfaceId(ctrl.signal);
|
|
147
149
|
const sid = this._surfaceId;
|
|
148
150
|
if (ctrl.signal.aborted || sid == null) return;
|
|
149
|
-
const
|
|
150
|
-
this._activeStreams.push(
|
|
151
|
-
|
|
151
|
+
const surfStream = s.surfaceEvents({ surfaceId: sid });
|
|
152
|
+
this._activeStreams.push(surfStream as { cancel?: () => void });
|
|
153
|
+
const dlStream = s.downloadEvents({ surfaceId: sid });
|
|
154
|
+
this._activeStreams.push(dlStream as { cancel?: () => void });
|
|
155
|
+
(async () => {
|
|
156
|
+
try {
|
|
157
|
+
for await (const event of dlStream) {
|
|
158
|
+
if (ctrl.signal.aborted) break;
|
|
159
|
+
this.dispatchEvent(new CustomEvent<DownloadEvent>("download-event", { detail: event }));
|
|
160
|
+
}
|
|
161
|
+
} catch { /* stream torn down */ }
|
|
162
|
+
})();
|
|
163
|
+
for await (const event of surfStream) {
|
|
152
164
|
if (ctrl.signal.aborted) break;
|
|
153
165
|
this.dispatchEvent(new CustomEvent<SurfaceEvent>("surface-event", { detail: event }));
|
|
154
166
|
}
|
|
@@ -264,10 +276,10 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
264
276
|
this.setAttribute("src", url);
|
|
265
277
|
}
|
|
266
278
|
|
|
267
|
-
async evaluate(script: string): Promise<EvaluateResult> {
|
|
279
|
+
async evaluate(script: string, opts?: { frameId?: string }): Promise<EvaluateResult> {
|
|
268
280
|
const sid = this._surfaceId;
|
|
269
281
|
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
270
|
-
return callSurfaceTyped((s) => s.evaluate({ surfaceId: sid, script }));
|
|
282
|
+
return callSurfaceTyped((s) => s.evaluate({ surfaceId: sid, script, frameId: opts?.frameId }));
|
|
271
283
|
}
|
|
272
284
|
|
|
273
285
|
async capabilities(): Promise<SurfaceCapabilities> {
|
|
@@ -277,7 +289,8 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
277
289
|
evaluate: false, crossOriginEval: false, surfaceEvents: false,
|
|
278
290
|
nativeInputTrusted: false, click: false, type: false, press: false,
|
|
279
291
|
scroll: false, mouse: false, dialogs: false, console: false,
|
|
280
|
-
screenshot: false,
|
|
292
|
+
screenshot: false, accessibilitySnapshot: false, getBoundingRect: false,
|
|
293
|
+
frames: false, downloads: false, popups: false, resolveAndClick: false,
|
|
281
294
|
};
|
|
282
295
|
}
|
|
283
296
|
return callSurfaceTyped((s) => s.capabilities({ surfaceId: sid }));
|
|
@@ -361,6 +374,64 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
361
374
|
return (await callSurfaceTyped((s) => s.getConsoleBuffer({ surfaceId: sid, clear: opts?.clear }))) ?? [];
|
|
362
375
|
}
|
|
363
376
|
|
|
377
|
+
async getNavigationState(): Promise<NavigationState> {
|
|
378
|
+
const sid = this._surfaceId;
|
|
379
|
+
if (sid == null) return { lastLoadEpoch: 0, isLoading: false, currentUrl: "" };
|
|
380
|
+
return callSurfaceTyped((s) => s.getNavigationState({ surfaceId: sid }));
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
async accessibilitySnapshot(opts?: { interestingOnly?: boolean }): Promise<AccessibilitySnapshotResult> {
|
|
384
|
+
const sid = this._surfaceId;
|
|
385
|
+
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
386
|
+
return callSurfaceTyped((s) => s.accessibilitySnapshot({ surfaceId: sid, interestingOnly: opts?.interestingOnly }));
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async getBoundingRect(selector: string, opts?: { frameId?: string }): Promise<BoundingRectResult> {
|
|
390
|
+
const sid = this._surfaceId;
|
|
391
|
+
if (sid == null) return { ok: false, code: "runtime_error", message: "surface not ready" };
|
|
392
|
+
return callSurfaceTyped((s) => s.getBoundingRect({ surfaceId: sid, selector, frameId: opts?.frameId }));
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
async listFrames(): Promise<ListFramesResult> {
|
|
396
|
+
const sid = this._surfaceId;
|
|
397
|
+
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
398
|
+
return callSurfaceTyped((s) => s.listFrames({ surfaceId: sid }));
|
|
399
|
+
}
|
|
400
|
+
|
|
401
|
+
async resolveAndClick(
|
|
402
|
+
selector: string,
|
|
403
|
+
opts?: {
|
|
404
|
+
frameId?: string;
|
|
405
|
+
button?: "left" | "middle" | "right";
|
|
406
|
+
clickCount?: number;
|
|
407
|
+
modifiers?: Array<"alt" | "ctrl" | "meta" | "shift">;
|
|
408
|
+
}
|
|
409
|
+
): Promise<ResolveAndClickResult> {
|
|
410
|
+
const sid = this._surfaceId;
|
|
411
|
+
if (sid == null) return { ok: false, code: "runtime_error", message: "surface not ready" };
|
|
412
|
+
return callSurfaceTyped((s) => s.resolveAndClick({ surfaceId: sid, selector, ...opts }));
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
async setDownloadPolicy(policy: DownloadPolicy, downloadDir?: string): Promise<void> {
|
|
416
|
+
const sid = this._surfaceId;
|
|
417
|
+
if (sid == null) return;
|
|
418
|
+
await callSurface((s) => s.setDownloadPolicy({ surfaceId: sid, policy, downloadDir }));
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
async waitForDownload(opts?: { timeoutMs?: number }): Promise<WaitForDownloadResult> {
|
|
422
|
+
const sid = this._surfaceId;
|
|
423
|
+
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
424
|
+
return callSurfaceTyped((s) => s.waitForDownload({ surfaceId: sid, timeoutMs: opts?.timeoutMs }));
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
async dismissPopup(newSurfaceId: number): Promise<void> {
|
|
428
|
+
await callSurface((s) => s.dismissPopup({ newSurfaceId }));
|
|
429
|
+
}
|
|
430
|
+
|
|
431
|
+
async extendAdoptionTimeout(newSurfaceId: number, gracePeriodMs: number) {
|
|
432
|
+
return callSurfaceTyped((s) => s.extendPopupTimeout({ newSurfaceId, gracePeriodMs }));
|
|
433
|
+
}
|
|
434
|
+
|
|
364
435
|
async screenshot(args?: { format?: "png" | "jpeg"; quality?: number }): Promise<ScreenshotResult> {
|
|
365
436
|
const sid = this._surfaceId;
|
|
366
437
|
if (sid == null) return { ok: false, code: "not_supported", message: "surface not ready" };
|
|
@@ -374,11 +445,71 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
374
445
|
void callSurface((s) => s.setHidden({ surfaceId: sid, hidden }));
|
|
375
446
|
}
|
|
376
447
|
|
|
448
|
+
private _setupSyncCtrl() {
|
|
449
|
+
this._syncCtrl = new OverlaySyncController(this, (rect) => {
|
|
450
|
+
const sid = this._surfaceId;
|
|
451
|
+
if (sid == null) return;
|
|
452
|
+
const isZero = rect.width === 0 && rect.height === 0;
|
|
453
|
+
if (isZero) {
|
|
454
|
+
if (!this._syncHidden) {
|
|
455
|
+
this._syncHidden = true;
|
|
456
|
+
this._applySurfaceHidden();
|
|
457
|
+
}
|
|
458
|
+
return;
|
|
459
|
+
}
|
|
460
|
+
if (this._syncHidden) {
|
|
461
|
+
this._syncHidden = false;
|
|
462
|
+
this._applySurfaceHidden();
|
|
463
|
+
}
|
|
464
|
+
void callSurface((s) => s.resize({
|
|
465
|
+
surfaceId: sid, x: rect.x, y: rect.y, w: rect.width, h: rect.height,
|
|
466
|
+
}));
|
|
467
|
+
});
|
|
468
|
+
this._syncCtrl.start();
|
|
469
|
+
}
|
|
470
|
+
|
|
377
471
|
private initSurface() {
|
|
378
472
|
if (this._surfaceId != null || this._initPromise != null) return;
|
|
379
473
|
|
|
380
474
|
const dpr = window.devicePixelRatio || 1;
|
|
381
475
|
const r = this.getBoundingClientRect();
|
|
476
|
+
const adoptAttr = this.getAttribute("adopt-popup-id");
|
|
477
|
+
const adoptId = adoptAttr ? Number(adoptAttr) : NaN;
|
|
478
|
+
if (Number.isFinite(adoptId)) {
|
|
479
|
+
// Popup adoption — bind a backend-minted surface (received via the parent
|
|
480
|
+
// page's `surface-event` `popup` arm) to this element.
|
|
481
|
+
const initPromise = getSurfaceCap().then(async (s) => {
|
|
482
|
+
const res = await s.acceptPopup({
|
|
483
|
+
newSurfaceId: adoptId,
|
|
484
|
+
hostViewId: __buniteWebviewId,
|
|
485
|
+
bounds: {
|
|
486
|
+
x: Math.round(r.x * dpr),
|
|
487
|
+
y: Math.round(r.y * dpr),
|
|
488
|
+
width: Math.round(r.width * dpr),
|
|
489
|
+
height: Math.round(r.height * dpr),
|
|
490
|
+
},
|
|
491
|
+
});
|
|
492
|
+
if (!res.ok) throw new Error(`acceptPopup failed: ${res.code}: ${res.message}`);
|
|
493
|
+
return { surfaceId: adoptId };
|
|
494
|
+
}) as Promise<SurfaceInitResponse>;
|
|
495
|
+
this._initPromise = initPromise;
|
|
496
|
+
initPromise.then((response) => {
|
|
497
|
+
if (this._initPromise !== initPromise) return;
|
|
498
|
+
if (this._aborted) {
|
|
499
|
+
void callSurface((s) => s.remove({ surfaceId: response.surfaceId }));
|
|
500
|
+
return;
|
|
501
|
+
}
|
|
502
|
+
this._surfaceId = response.surfaceId;
|
|
503
|
+
this._setupSyncCtrl();
|
|
504
|
+
}).catch((err) => {
|
|
505
|
+
if ((globalThis as { __BUNITE_DEBUG__?: boolean }).__BUNITE_DEBUG__) {
|
|
506
|
+
console.warn("[bunite] adopt-popup-id init failed", err);
|
|
507
|
+
}
|
|
508
|
+
this._initPromise = null;
|
|
509
|
+
});
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
|
|
382
513
|
const src = this._pendingSrc || this.getAttribute("src") || "";
|
|
383
514
|
this._pendingSrc = null;
|
|
384
515
|
|
|
@@ -415,28 +546,7 @@ class BuniteWebviewElement extends HTMLElement {
|
|
|
415
546
|
}
|
|
416
547
|
}
|
|
417
548
|
|
|
418
|
-
this.
|
|
419
|
-
const sid = this._surfaceId;
|
|
420
|
-
if (sid == null) return;
|
|
421
|
-
|
|
422
|
-
const isZero = rect.width === 0 && rect.height === 0;
|
|
423
|
-
if (isZero) {
|
|
424
|
-
if (!this._syncHidden) {
|
|
425
|
-
this._syncHidden = true;
|
|
426
|
-
this._applySurfaceHidden();
|
|
427
|
-
}
|
|
428
|
-
return;
|
|
429
|
-
}
|
|
430
|
-
if (this._syncHidden) {
|
|
431
|
-
this._syncHidden = false;
|
|
432
|
-
this._applySurfaceHidden();
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
void callSurface((s) => s.resize({
|
|
436
|
-
surfaceId: sid, x: rect.x, y: rect.y, w: rect.width, h: rect.height,
|
|
437
|
-
}));
|
|
438
|
-
});
|
|
439
|
-
this._syncCtrl.start();
|
|
549
|
+
this._setupSyncCtrl();
|
|
440
550
|
})
|
|
441
551
|
.catch(() => {})
|
|
442
552
|
.finally(() => {
|
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
|
}
|