electrobun 1.18.4-beta.6 → 2.0.1-beta.13
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/LICENSE +21 -0
- package/README.md +31 -170
- package/bin/electrobun.cjs +230 -153
- package/package.json +18 -49
- package/bun.lock +0 -119
- package/dist/api/browser/builtinrpcSchema.ts +0 -19
- package/dist/api/browser/global.d.ts +0 -36
- package/dist/api/browser/index.ts +0 -234
- package/dist/api/browser/webviewtag.ts +0 -88
- package/dist/api/browser/wgputag.ts +0 -48
- package/dist/api/bun/ElectrobunConfig.ts +0 -530
- package/dist/api/bun/__tests__/ffi-contract.test.ts +0 -105
- package/dist/api/bun/core/ApplicationMenu.ts +0 -70
- package/dist/api/bun/core/BrowserView.ts +0 -419
- package/dist/api/bun/core/BrowserWindow.ts +0 -400
- package/dist/api/bun/core/BuildConfig.ts +0 -71
- package/dist/api/bun/core/ContextMenu.ts +0 -75
- package/dist/api/bun/core/GpuWindow.ts +0 -289
- package/dist/api/bun/core/Paths.ts +0 -5
- package/dist/api/bun/core/Socket.ts +0 -22
- package/dist/api/bun/core/Tray.ts +0 -197
- package/dist/api/bun/core/Updater.ts +0 -1162
- package/dist/api/bun/core/Utils.ts +0 -487
- package/dist/api/bun/core/WGPUView.ts +0 -167
- package/dist/api/bun/core/menuRoles.ts +0 -181
- package/dist/api/bun/events/ApplicationEvents.ts +0 -22
- package/dist/api/bun/events/event.ts +0 -29
- package/dist/api/bun/events/eventEmitter.ts +0 -45
- package/dist/api/bun/events/trayEvents.ts +0 -11
- package/dist/api/bun/events/webviewEvents.ts +0 -39
- package/dist/api/bun/events/windowEvents.ts +0 -23
- package/dist/api/bun/index.ts +0 -298
- package/dist/api/bun/preload/.generated/compiled.ts +0 -8
- package/dist/api/bun/preload/build.ts +0 -65
- package/dist/api/bun/preload/dragRegions.ts +0 -41
- package/dist/api/bun/preload/encryption.ts +0 -86
- package/dist/api/bun/preload/events.ts +0 -171
- package/dist/api/bun/preload/globals.d.ts +0 -45
- package/dist/api/bun/preload/index-sandboxed.ts +0 -28
- package/dist/api/bun/preload/index.ts +0 -77
- package/dist/api/bun/preload/internalRpc.ts +0 -80
- package/dist/api/bun/preload/overlaySync.ts +0 -107
- package/dist/api/bun/preload/webviewTag.ts +0 -451
- package/dist/api/bun/preload/wgpuTag.ts +0 -246
- package/dist/api/bun/proc/linux.md +0 -43
- package/dist/api/bun/proc/native.ts +0 -3385
- package/dist/api/bun/webGPU.ts +0 -346
- package/dist/api/bun/webgpuAdapter.ts +0 -3011
- package/dist/api/shared/bun-version.ts +0 -3
- package/dist/api/shared/cef-version.ts +0 -5
- package/dist/api/shared/electrobun-version.ts +0 -2
- package/dist/api/shared/naming.test.ts +0 -327
- package/dist/api/shared/naming.ts +0 -188
- package/dist/api/shared/platform.ts +0 -48
- package/dist/api/shared/rpc.ts +0 -541
- package/dist/main.js +0 -168
- package/dist/preload-full.js +0 -913
- package/dist/preload-sandboxed.js +0 -111
- package/dist/zig-sdk/electrobun.zig +0 -1993
- package/src/cli/bun.lockb +0 -0
- package/src/cli/index.ts +0 -5689
- package/src/cli/package-lock.json +0 -81
- package/src/cli/package.json +0 -11
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
import "./globals.d.ts";
|
|
2
|
-
|
|
3
|
-
export interface Rect {
|
|
4
|
-
x: number;
|
|
5
|
-
y: number;
|
|
6
|
-
width: number;
|
|
7
|
-
height: number;
|
|
8
|
-
}
|
|
9
|
-
|
|
10
|
-
export type MaskRect = Rect;
|
|
11
|
-
|
|
12
|
-
export type OverlaySyncOptions = {
|
|
13
|
-
onSync: (rect: Rect, masksJson: string) => void;
|
|
14
|
-
getMasks?: () => MaskRect[];
|
|
15
|
-
burstIntervalMs?: number;
|
|
16
|
-
baseIntervalMs?: number;
|
|
17
|
-
burstDurationMs?: number;
|
|
18
|
-
};
|
|
19
|
-
|
|
20
|
-
export class OverlaySyncController {
|
|
21
|
-
private element: HTMLElement;
|
|
22
|
-
private options: Required<OverlaySyncOptions>;
|
|
23
|
-
private lastRect: Rect = { x: 0, y: 0, width: 0, height: 0 };
|
|
24
|
-
private resizeObserver: ResizeObserver | null = null;
|
|
25
|
-
private positionLoop: ReturnType<typeof setTimeout> | null = null;
|
|
26
|
-
private resizeHandler: (() => void) | null = null;
|
|
27
|
-
private burstUntil = 0;
|
|
28
|
-
|
|
29
|
-
constructor(element: HTMLElement, options: OverlaySyncOptions) {
|
|
30
|
-
this.element = element;
|
|
31
|
-
this.options = {
|
|
32
|
-
onSync: options.onSync,
|
|
33
|
-
getMasks: options.getMasks ?? (() => []),
|
|
34
|
-
burstIntervalMs: options.burstIntervalMs ?? 50,
|
|
35
|
-
baseIntervalMs: options.baseIntervalMs ?? 100,
|
|
36
|
-
burstDurationMs: options.burstDurationMs ?? 500,
|
|
37
|
-
};
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
start() {
|
|
41
|
-
this.resizeObserver = new ResizeObserver(() => this.sync());
|
|
42
|
-
this.resizeObserver.observe(this.element);
|
|
43
|
-
|
|
44
|
-
const loop = () => {
|
|
45
|
-
this.sync();
|
|
46
|
-
const now = performance.now();
|
|
47
|
-
const interval =
|
|
48
|
-
now < this.burstUntil
|
|
49
|
-
? this.options.burstIntervalMs
|
|
50
|
-
: this.options.baseIntervalMs;
|
|
51
|
-
this.positionLoop = setTimeout(loop, interval);
|
|
52
|
-
};
|
|
53
|
-
this.positionLoop = setTimeout(loop, this.options.baseIntervalMs);
|
|
54
|
-
|
|
55
|
-
this.resizeHandler = () => this.sync(true);
|
|
56
|
-
window.addEventListener("resize", this.resizeHandler);
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
stop() {
|
|
60
|
-
if (this.resizeObserver) this.resizeObserver.disconnect();
|
|
61
|
-
if (this.positionLoop) clearTimeout(this.positionLoop);
|
|
62
|
-
if (this.resizeHandler) {
|
|
63
|
-
window.removeEventListener("resize", this.resizeHandler);
|
|
64
|
-
}
|
|
65
|
-
this.resizeObserver = null;
|
|
66
|
-
this.positionLoop = null;
|
|
67
|
-
this.resizeHandler = null;
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
forceSync() {
|
|
71
|
-
this.sync(true);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
setLastRect(rect: Rect) {
|
|
75
|
-
this.lastRect = rect;
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
private sync(force = false) {
|
|
79
|
-
const rect = this.element.getBoundingClientRect();
|
|
80
|
-
const newRect: Rect = {
|
|
81
|
-
x: rect.x,
|
|
82
|
-
y: rect.y,
|
|
83
|
-
width: rect.width,
|
|
84
|
-
height: rect.height,
|
|
85
|
-
};
|
|
86
|
-
|
|
87
|
-
if (newRect.width === 0 && newRect.height === 0) {
|
|
88
|
-
return;
|
|
89
|
-
}
|
|
90
|
-
|
|
91
|
-
if (
|
|
92
|
-
!force &&
|
|
93
|
-
newRect.x === this.lastRect.x &&
|
|
94
|
-
newRect.y === this.lastRect.y &&
|
|
95
|
-
newRect.width === this.lastRect.width &&
|
|
96
|
-
newRect.height === this.lastRect.height
|
|
97
|
-
) {
|
|
98
|
-
return;
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
this.burstUntil = performance.now() + this.options.burstDurationMs;
|
|
102
|
-
this.lastRect = newRect;
|
|
103
|
-
|
|
104
|
-
const masks = this.options.getMasks();
|
|
105
|
-
this.options.onSync(newRect, JSON.stringify(masks));
|
|
106
|
-
}
|
|
107
|
-
}
|
|
@@ -1,451 +0,0 @@
|
|
|
1
|
-
// <electrobun-webview> Custom Element
|
|
2
|
-
// Provides OOPIF (out-of-process iframe) functionality
|
|
3
|
-
|
|
4
|
-
import "./globals.d.ts";
|
|
5
|
-
import { send, request } from "./internalRpc";
|
|
6
|
-
import { OverlaySyncController, type Rect } from "./overlaySync";
|
|
7
|
-
|
|
8
|
-
type WebviewEventType =
|
|
9
|
-
| "will-navigate"
|
|
10
|
-
| "did-navigate"
|
|
11
|
-
| "did-navigate-in-page"
|
|
12
|
-
| "did-commit-navigation"
|
|
13
|
-
| "dom-ready"
|
|
14
|
-
| "new-window-open"
|
|
15
|
-
| "host-message"
|
|
16
|
-
| "download-started"
|
|
17
|
-
| "download-progress"
|
|
18
|
-
| "download-completed"
|
|
19
|
-
| "download-failed"
|
|
20
|
-
| "load-started"
|
|
21
|
-
| "load-committed"
|
|
22
|
-
| "load-finished";
|
|
23
|
-
|
|
24
|
-
// Registry for webview instances (for event routing from bun)
|
|
25
|
-
export const webviewRegistry: Record<number, ElectrobunWebviewTag> = {};
|
|
26
|
-
|
|
27
|
-
export class ElectrobunWebviewTag extends HTMLElement {
|
|
28
|
-
webviewId: number | null = null;
|
|
29
|
-
maskSelectors: Set<string> = new Set();
|
|
30
|
-
private _sync: OverlaySyncController | null = null;
|
|
31
|
-
transparent = false;
|
|
32
|
-
passthroughEnabled = false;
|
|
33
|
-
hidden = false;
|
|
34
|
-
// Sandbox mode: when true, disables RPC and only allows event emission in the child webview
|
|
35
|
-
sandboxed = false;
|
|
36
|
-
private _eventListeners: Record<string, Array<(event: CustomEvent) => void>> =
|
|
37
|
-
{};
|
|
38
|
-
|
|
39
|
-
static get observedAttributes() {
|
|
40
|
-
return ["src", "html"];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
constructor() {
|
|
44
|
-
super();
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
connectedCallback() {
|
|
48
|
-
requestAnimationFrame(() => this.initWebview());
|
|
49
|
-
}
|
|
50
|
-
|
|
51
|
-
attributeChangedCallback(
|
|
52
|
-
name: string,
|
|
53
|
-
oldValue: string | null,
|
|
54
|
-
newValue: string | null,
|
|
55
|
-
) {
|
|
56
|
-
if (oldValue === newValue) return;
|
|
57
|
-
if (newValue === null) return;
|
|
58
|
-
if (this.webviewId === null) return;
|
|
59
|
-
if (name === "src") this.loadURL(newValue);
|
|
60
|
-
else if (name === "html") this.loadHTML(newValue);
|
|
61
|
-
}
|
|
62
|
-
|
|
63
|
-
disconnectedCallback() {
|
|
64
|
-
if (this.webviewId !== null) {
|
|
65
|
-
send("webviewTagRemove", { id: this.webviewId });
|
|
66
|
-
delete webviewRegistry[this.webviewId];
|
|
67
|
-
}
|
|
68
|
-
if (this._sync) this._sync.stop();
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
private getInitialNavigationRules(): string[] | null {
|
|
72
|
-
const rawRules = this.getAttribute("navigation-rules");
|
|
73
|
-
if (rawRules === null) {
|
|
74
|
-
return null;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const trimmed = rawRules.trim();
|
|
78
|
-
if (!trimmed) {
|
|
79
|
-
return [];
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
try {
|
|
83
|
-
const parsed = JSON.parse(trimmed);
|
|
84
|
-
if (!Array.isArray(parsed) || !parsed.every((rule) => typeof rule === "string")) {
|
|
85
|
-
throw new Error("navigation-rules must be a JSON string array");
|
|
86
|
-
}
|
|
87
|
-
return parsed;
|
|
88
|
-
} catch (error) {
|
|
89
|
-
console.error("Invalid navigation-rules attribute:", error);
|
|
90
|
-
return [];
|
|
91
|
-
}
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
async initWebview() {
|
|
95
|
-
const rect = this.getBoundingClientRect();
|
|
96
|
-
const initialRect = {
|
|
97
|
-
x: rect.x,
|
|
98
|
-
y: rect.y,
|
|
99
|
-
width: rect.width,
|
|
100
|
-
height: rect.height,
|
|
101
|
-
};
|
|
102
|
-
|
|
103
|
-
const url = this.getAttribute("src");
|
|
104
|
-
const html = this.getAttribute("html");
|
|
105
|
-
const preload = this.getAttribute("preload");
|
|
106
|
-
const partition = this.getAttribute("partition");
|
|
107
|
-
const renderer = (this.getAttribute("renderer") || "native") as
|
|
108
|
-
| "native"
|
|
109
|
-
| "cef";
|
|
110
|
-
const masks = this.getAttribute("masks");
|
|
111
|
-
const navigationRules = this.getInitialNavigationRules();
|
|
112
|
-
// Sandbox attribute: when present, the child webview is sandboxed (no RPC, events only)
|
|
113
|
-
const sandbox = this.hasAttribute("sandbox");
|
|
114
|
-
this.sandboxed = sandbox;
|
|
115
|
-
// Read transparent/passthrough attributes for initial state (avoids flash)
|
|
116
|
-
const transparent = this.hasAttribute("transparent");
|
|
117
|
-
const passthrough = this.hasAttribute("passthrough");
|
|
118
|
-
this.transparent = transparent;
|
|
119
|
-
this.passthroughEnabled = passthrough;
|
|
120
|
-
if (transparent) this.style.opacity = "0";
|
|
121
|
-
if (passthrough) this.style.pointerEvents = "none";
|
|
122
|
-
|
|
123
|
-
if (masks) {
|
|
124
|
-
masks.split(",").forEach((s) => this.maskSelectors.add(s.trim()));
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
try {
|
|
128
|
-
const webviewInitParams = {
|
|
129
|
-
hostWebviewId: window.__electrobunWebviewId,
|
|
130
|
-
windowId: window.__electrobunWindowId,
|
|
131
|
-
renderer,
|
|
132
|
-
url,
|
|
133
|
-
html,
|
|
134
|
-
preload,
|
|
135
|
-
partition,
|
|
136
|
-
frame: {
|
|
137
|
-
width: rect.width,
|
|
138
|
-
height: rect.height,
|
|
139
|
-
x: rect.x,
|
|
140
|
-
y: rect.y,
|
|
141
|
-
},
|
|
142
|
-
sandbox,
|
|
143
|
-
transparent,
|
|
144
|
-
passthrough,
|
|
145
|
-
...(navigationRules === null ? {} : { navigationRules }),
|
|
146
|
-
};
|
|
147
|
-
|
|
148
|
-
const webviewId = (await request(
|
|
149
|
-
"webviewTagInit",
|
|
150
|
-
webviewInitParams,
|
|
151
|
-
)) as number;
|
|
152
|
-
|
|
153
|
-
this.webviewId = webviewId;
|
|
154
|
-
this.id = `electrobun-webview-${webviewId}`;
|
|
155
|
-
webviewRegistry[webviewId] = this;
|
|
156
|
-
|
|
157
|
-
this.setupObservers(initialRect);
|
|
158
|
-
// Force immediate sync after initialization
|
|
159
|
-
this.syncDimensions(true);
|
|
160
|
-
|
|
161
|
-
// When adding a new webview, force all existing webviews to re-sync their positions
|
|
162
|
-
// This handles layout changes caused by the new webview
|
|
163
|
-
// Use requestAnimationFrame to ensure DOM layout is complete
|
|
164
|
-
requestAnimationFrame(() => {
|
|
165
|
-
Object.values(webviewRegistry).forEach((webview) => {
|
|
166
|
-
if (webview !== this && webview.webviewId !== null) {
|
|
167
|
-
webview.syncDimensions(true);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
});
|
|
171
|
-
} catch (err) {
|
|
172
|
-
console.error("Failed to init webview:", err);
|
|
173
|
-
}
|
|
174
|
-
}
|
|
175
|
-
|
|
176
|
-
setupObservers(initialRect: Rect) {
|
|
177
|
-
const getMasks = () => {
|
|
178
|
-
const rect = this.getBoundingClientRect();
|
|
179
|
-
const masks: Rect[] = [];
|
|
180
|
-
this.maskSelectors.forEach((selector) => {
|
|
181
|
-
try {
|
|
182
|
-
document.querySelectorAll(selector).forEach((el) => {
|
|
183
|
-
const mr = el.getBoundingClientRect();
|
|
184
|
-
masks.push({
|
|
185
|
-
x: mr.x - rect.x,
|
|
186
|
-
y: mr.y - rect.y,
|
|
187
|
-
width: mr.width,
|
|
188
|
-
height: mr.height,
|
|
189
|
-
});
|
|
190
|
-
});
|
|
191
|
-
} catch (_e) {
|
|
192
|
-
// Invalid selector, ignore
|
|
193
|
-
}
|
|
194
|
-
});
|
|
195
|
-
return masks;
|
|
196
|
-
};
|
|
197
|
-
|
|
198
|
-
this._sync = new OverlaySyncController(this, {
|
|
199
|
-
onSync: (rect, masksJson) => {
|
|
200
|
-
if (this.webviewId === null) return;
|
|
201
|
-
send("webviewTagResize", {
|
|
202
|
-
id: this.webviewId,
|
|
203
|
-
frame: rect,
|
|
204
|
-
masks: masksJson,
|
|
205
|
-
});
|
|
206
|
-
},
|
|
207
|
-
getMasks,
|
|
208
|
-
burstIntervalMs: 10,
|
|
209
|
-
baseIntervalMs: 100,
|
|
210
|
-
burstDurationMs: 50,
|
|
211
|
-
});
|
|
212
|
-
this._sync.setLastRect(initialRect);
|
|
213
|
-
this._sync.start();
|
|
214
|
-
}
|
|
215
|
-
|
|
216
|
-
syncDimensions(force = false) {
|
|
217
|
-
if (!this._sync) return;
|
|
218
|
-
if (force) {
|
|
219
|
-
this._sync.forceSync();
|
|
220
|
-
}
|
|
221
|
-
}
|
|
222
|
-
|
|
223
|
-
// Navigation methods
|
|
224
|
-
loadURL(url: string) {
|
|
225
|
-
if (this.webviewId === null) return;
|
|
226
|
-
this.setAttribute("src", url);
|
|
227
|
-
send("webviewTagUpdateSrc", { id: this.webviewId, url });
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
loadHTML(html: string) {
|
|
231
|
-
if (this.webviewId === null) return;
|
|
232
|
-
send("webviewTagUpdateHtml", { id: this.webviewId, html });
|
|
233
|
-
}
|
|
234
|
-
|
|
235
|
-
reload() {
|
|
236
|
-
if (this.webviewId !== null)
|
|
237
|
-
send("webviewTagReload", { id: this.webviewId });
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
goBack() {
|
|
241
|
-
if (this.webviewId !== null)
|
|
242
|
-
send("webviewTagGoBack", { id: this.webviewId });
|
|
243
|
-
}
|
|
244
|
-
|
|
245
|
-
goForward() {
|
|
246
|
-
if (this.webviewId !== null)
|
|
247
|
-
send("webviewTagGoForward", { id: this.webviewId });
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
async canGoBack(): Promise<boolean> {
|
|
251
|
-
if (this.webviewId === null) return false;
|
|
252
|
-
return (await request("webviewTagCanGoBack", {
|
|
253
|
-
id: this.webviewId,
|
|
254
|
-
})) as boolean;
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
async canGoForward(): Promise<boolean> {
|
|
258
|
-
if (this.webviewId === null) return false;
|
|
259
|
-
return (await request("webviewTagCanGoForward", {
|
|
260
|
-
id: this.webviewId,
|
|
261
|
-
})) as boolean;
|
|
262
|
-
}
|
|
263
|
-
|
|
264
|
-
// Visibility methods
|
|
265
|
-
toggleTransparent(value?: boolean) {
|
|
266
|
-
if (this.webviewId === null) return;
|
|
267
|
-
this.transparent = value !== undefined ? value : !this.transparent;
|
|
268
|
-
this.style.opacity = this.transparent ? "0" : "";
|
|
269
|
-
send("webviewTagSetTransparent", {
|
|
270
|
-
id: this.webviewId,
|
|
271
|
-
transparent: this.transparent,
|
|
272
|
-
});
|
|
273
|
-
}
|
|
274
|
-
|
|
275
|
-
togglePassthrough(value?: boolean) {
|
|
276
|
-
if (this.webviewId === null) return;
|
|
277
|
-
this.passthroughEnabled =
|
|
278
|
-
value !== undefined ? value : !this.passthroughEnabled;
|
|
279
|
-
this.style.pointerEvents = this.passthroughEnabled ? "none" : "";
|
|
280
|
-
send("webviewTagSetPassthrough", {
|
|
281
|
-
id: this.webviewId,
|
|
282
|
-
enablePassthrough: this.passthroughEnabled,
|
|
283
|
-
});
|
|
284
|
-
}
|
|
285
|
-
|
|
286
|
-
toggleHidden(value?: boolean) {
|
|
287
|
-
if (this.webviewId === null) return;
|
|
288
|
-
this.hidden = value !== undefined ? value : !this.hidden;
|
|
289
|
-
send("webviewTagSetHidden", { id: this.webviewId, hidden: this.hidden });
|
|
290
|
-
}
|
|
291
|
-
|
|
292
|
-
// Mask management
|
|
293
|
-
addMaskSelector(selector: string) {
|
|
294
|
-
this.maskSelectors.add(selector);
|
|
295
|
-
this.syncDimensions(true);
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
removeMaskSelector(selector: string) {
|
|
299
|
-
this.maskSelectors.delete(selector);
|
|
300
|
-
this.syncDimensions(true);
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
// Navigation rules
|
|
304
|
-
setNavigationRules(rules: string[]) {
|
|
305
|
-
if (this.webviewId !== null) {
|
|
306
|
-
send("webviewTagSetNavigationRules", { id: this.webviewId, rules });
|
|
307
|
-
}
|
|
308
|
-
}
|
|
309
|
-
|
|
310
|
-
// Find in page
|
|
311
|
-
findInPage(
|
|
312
|
-
searchText: string,
|
|
313
|
-
options?: { forward?: boolean; matchCase?: boolean },
|
|
314
|
-
) {
|
|
315
|
-
if (this.webviewId === null) return;
|
|
316
|
-
const forward = options?.forward !== false;
|
|
317
|
-
const matchCase = options?.matchCase || false;
|
|
318
|
-
send("webviewTagFindInPage", {
|
|
319
|
-
id: this.webviewId,
|
|
320
|
-
searchText,
|
|
321
|
-
forward,
|
|
322
|
-
matchCase,
|
|
323
|
-
});
|
|
324
|
-
}
|
|
325
|
-
|
|
326
|
-
stopFindInPage() {
|
|
327
|
-
if (this.webviewId !== null)
|
|
328
|
-
send("webviewTagStopFind", { id: this.webviewId });
|
|
329
|
-
}
|
|
330
|
-
|
|
331
|
-
// DevTools
|
|
332
|
-
openDevTools() {
|
|
333
|
-
if (this.webviewId !== null)
|
|
334
|
-
send("webviewTagOpenDevTools", { id: this.webviewId });
|
|
335
|
-
}
|
|
336
|
-
|
|
337
|
-
closeDevTools() {
|
|
338
|
-
if (this.webviewId !== null)
|
|
339
|
-
send("webviewTagCloseDevTools", { id: this.webviewId });
|
|
340
|
-
}
|
|
341
|
-
|
|
342
|
-
toggleDevTools() {
|
|
343
|
-
if (this.webviewId !== null)
|
|
344
|
-
send("webviewTagToggleDevTools", { id: this.webviewId });
|
|
345
|
-
}
|
|
346
|
-
|
|
347
|
-
// JavaScript execution
|
|
348
|
-
executeJavascript(js: string) {
|
|
349
|
-
if (this.webviewId === null) return;
|
|
350
|
-
send("webviewTagExecuteJavascript", { id: this.webviewId, js });
|
|
351
|
-
}
|
|
352
|
-
|
|
353
|
-
// Event handling
|
|
354
|
-
on(event: WebviewEventType, listener: (event: CustomEvent) => void) {
|
|
355
|
-
if (!this._eventListeners[event]) this._eventListeners[event] = [];
|
|
356
|
-
this._eventListeners[event].push(listener);
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
off(event: WebviewEventType, listener: (event: CustomEvent) => void) {
|
|
360
|
-
if (!this._eventListeners[event]) return;
|
|
361
|
-
const idx = this._eventListeners[event].indexOf(listener);
|
|
362
|
-
if (idx !== -1) this._eventListeners[event].splice(idx, 1);
|
|
363
|
-
}
|
|
364
|
-
|
|
365
|
-
emit(event: WebviewEventType, detail: unknown) {
|
|
366
|
-
const listeners = this._eventListeners[event];
|
|
367
|
-
if (listeners) {
|
|
368
|
-
const customEvent = new CustomEvent(event, { detail });
|
|
369
|
-
listeners.forEach((fn) => fn(customEvent));
|
|
370
|
-
}
|
|
371
|
-
}
|
|
372
|
-
|
|
373
|
-
// Property getters/setters
|
|
374
|
-
get src(): string | null {
|
|
375
|
-
return this.getAttribute("src");
|
|
376
|
-
}
|
|
377
|
-
set src(value: string | null) {
|
|
378
|
-
if (value) {
|
|
379
|
-
this.setAttribute("src", value);
|
|
380
|
-
} else {
|
|
381
|
-
this.removeAttribute("src");
|
|
382
|
-
}
|
|
383
|
-
}
|
|
384
|
-
|
|
385
|
-
get html(): string | null {
|
|
386
|
-
return this.getAttribute("html");
|
|
387
|
-
}
|
|
388
|
-
set html(value: string | null) {
|
|
389
|
-
if (value) {
|
|
390
|
-
this.setAttribute("html", value);
|
|
391
|
-
} else {
|
|
392
|
-
this.removeAttribute("html");
|
|
393
|
-
}
|
|
394
|
-
}
|
|
395
|
-
|
|
396
|
-
get preload(): string | null {
|
|
397
|
-
return this.getAttribute("preload");
|
|
398
|
-
}
|
|
399
|
-
set preload(value: string | null) {
|
|
400
|
-
if (value) this.setAttribute("preload", value);
|
|
401
|
-
else this.removeAttribute("preload");
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
get renderer(): "native" | "cef" {
|
|
405
|
-
return (this.getAttribute("renderer") as "native" | "cef") || "native";
|
|
406
|
-
}
|
|
407
|
-
set renderer(value: "native" | "cef") {
|
|
408
|
-
this.setAttribute("renderer", value);
|
|
409
|
-
}
|
|
410
|
-
|
|
411
|
-
// Sandbox is read-only after creation (set via attribute before adding to DOM)
|
|
412
|
-
get sandbox(): boolean {
|
|
413
|
-
return this.sandboxed;
|
|
414
|
-
}
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
export function initWebviewTag() {
|
|
418
|
-
// Register the custom element if not already registered
|
|
419
|
-
if (!customElements.get("electrobun-webview")) {
|
|
420
|
-
customElements.define("electrobun-webview", ElectrobunWebviewTag);
|
|
421
|
-
}
|
|
422
|
-
|
|
423
|
-
// Add default styles for <electrobun-webview> elements
|
|
424
|
-
// These can be easily overridden in the host document
|
|
425
|
-
const injectStyles = () => {
|
|
426
|
-
const style = document.createElement("style");
|
|
427
|
-
style.textContent = `
|
|
428
|
-
electrobun-webview {
|
|
429
|
-
display: block;
|
|
430
|
-
width: 800px;
|
|
431
|
-
height: 300px;
|
|
432
|
-
background: #fff;
|
|
433
|
-
background-repeat: no-repeat !important;
|
|
434
|
-
overflow: hidden;
|
|
435
|
-
}
|
|
436
|
-
`;
|
|
437
|
-
// Insert at the beginning of <head> so app styles take precedence
|
|
438
|
-
if (document.head?.firstChild) {
|
|
439
|
-
document.head.insertBefore(style, document.head.firstChild);
|
|
440
|
-
} else if (document.head) {
|
|
441
|
-
document.head.appendChild(style);
|
|
442
|
-
}
|
|
443
|
-
};
|
|
444
|
-
|
|
445
|
-
// document.head may not exist at document start, defer if needed
|
|
446
|
-
if (document.head) {
|
|
447
|
-
injectStyles();
|
|
448
|
-
} else {
|
|
449
|
-
document.addEventListener("DOMContentLoaded", injectStyles);
|
|
450
|
-
}
|
|
451
|
-
}
|