electrobun 0.0.18 → 0.0.19-beta.6
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/BETA_RELEASE.md +67 -0
- package/README.md +48 -21
- package/{dist → bin}/electrobun +0 -0
- package/package.json +23 -29
- package/src/cli/bun.lockb +0 -0
- package/src/cli/index.ts +1527 -0
- package/src/cli/package-lock.json +93 -0
- package/src/cli/package.json +14 -0
- package/test-npm-install.sh +34 -0
- package/dist/api/browser/builtinrpcSchema.ts +0 -10
- package/dist/api/browser/index.ts +0 -474
- package/dist/api/browser/stylesAndElements.ts +0 -3
- package/dist/api/browser/webviewtag.ts +0 -650
- package/dist/api/bun/core/ApplicationMenu.ts +0 -66
- package/dist/api/bun/core/BrowserView.ts +0 -417
- package/dist/api/bun/core/BrowserWindow.ts +0 -178
- package/dist/api/bun/core/ContextMenu.ts +0 -67
- package/dist/api/bun/core/Paths.ts +0 -5
- package/dist/api/bun/core/Socket.ts +0 -181
- package/dist/api/bun/core/Tray.ts +0 -105
- package/dist/api/bun/core/Updater.ts +0 -388
- package/dist/api/bun/core/Utils.ts +0 -48
- package/dist/api/bun/events/applicationEvents.ts +0 -14
- 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 -9
- package/dist/api/bun/events/webviewEvents.ts +0 -19
- package/dist/api/bun/events/windowEvents.ts +0 -12
- package/dist/api/bun/index.ts +0 -45
- package/dist/api/bun/proc/zig.ts +0 -622
- package/dist/bsdiff +0 -0
- package/dist/bspatch +0 -0
- package/dist/bun +0 -0
- package/dist/extractor +0 -0
- package/dist/launcher +0 -0
- package/dist/webview +0 -0
|
@@ -1,650 +0,0 @@
|
|
|
1
|
-
type WebviewEventTypes =
|
|
2
|
-
| "did-navigate"
|
|
3
|
-
| "did-navigate-in-page"
|
|
4
|
-
| "did-commit-navigation"
|
|
5
|
-
| "dom-ready";
|
|
6
|
-
|
|
7
|
-
type Rect = { x: number; y: number; width: number; height: number };
|
|
8
|
-
|
|
9
|
-
const ConfigureWebviewTags = (
|
|
10
|
-
enableWebviewTags: boolean,
|
|
11
|
-
zigRpc: (params: any) => any,
|
|
12
|
-
syncRpc: (params: any) => any
|
|
13
|
-
) => {
|
|
14
|
-
if (!enableWebviewTags) {
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
// todo: provide global types for <electrobun-webview> tag elements (like querySelector results etc.)
|
|
19
|
-
|
|
20
|
-
class WebviewTag extends HTMLElement {
|
|
21
|
-
// todo (yoav): come up with a better mechanism to eliminate collisions with bun created
|
|
22
|
-
// webviews
|
|
23
|
-
webviewId?: number; // = nextWebviewId++;
|
|
24
|
-
|
|
25
|
-
// rpc
|
|
26
|
-
zigRpc: any;
|
|
27
|
-
syncRpc: any;
|
|
28
|
-
|
|
29
|
-
// querySelectors for elements that you want to appear
|
|
30
|
-
// in front of the webview.
|
|
31
|
-
maskSelectors: Set<string> = new Set();
|
|
32
|
-
|
|
33
|
-
// observers
|
|
34
|
-
resizeObserver?: ResizeObserver;
|
|
35
|
-
// intersectionObserver?: IntersectionObserver;
|
|
36
|
-
// mutationObserver?: MutationObserver;
|
|
37
|
-
|
|
38
|
-
positionCheckLoop?: Timer;
|
|
39
|
-
positionCheckLoopReset?: Timer;
|
|
40
|
-
|
|
41
|
-
lastRect = {
|
|
42
|
-
x: 0,
|
|
43
|
-
y: 0,
|
|
44
|
-
width: 0,
|
|
45
|
-
height: 0,
|
|
46
|
-
};
|
|
47
|
-
|
|
48
|
-
lastMasksJSON: string = "";
|
|
49
|
-
lastMasks: Rect[] = [];
|
|
50
|
-
|
|
51
|
-
transparent: boolean = false;
|
|
52
|
-
passthroughEnabled: boolean = false;
|
|
53
|
-
hidden: boolean = false;
|
|
54
|
-
delegateMode: boolean = false;
|
|
55
|
-
hiddenMirrorMode: boolean = false;
|
|
56
|
-
wasZeroRect: boolean = false;
|
|
57
|
-
isMirroring: boolean = false;
|
|
58
|
-
|
|
59
|
-
partition: string | null = null;
|
|
60
|
-
|
|
61
|
-
constructor() {
|
|
62
|
-
super();
|
|
63
|
-
this.zigRpc = zigRpc;
|
|
64
|
-
this.syncRpc = syncRpc;
|
|
65
|
-
|
|
66
|
-
// Give it a frame to be added to the dom and render before measuring
|
|
67
|
-
requestAnimationFrame(() => {
|
|
68
|
-
this.initWebview();
|
|
69
|
-
});
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
addMaskSelector(selector: string) {
|
|
73
|
-
this.maskSelectors.add(selector);
|
|
74
|
-
this.syncDimensions();
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
removeMaskSelector(selector: string) {
|
|
78
|
-
this.maskSelectors.delete(selector);
|
|
79
|
-
this.syncDimensions();
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
initWebview() {
|
|
83
|
-
const rect = this.getBoundingClientRect();
|
|
84
|
-
this.lastRect = rect;
|
|
85
|
-
|
|
86
|
-
const webviewId = this.syncRpc({
|
|
87
|
-
method: "webviewTagInit",
|
|
88
|
-
params: {
|
|
89
|
-
hostWebviewId: window.__electrobunWebviewId,
|
|
90
|
-
windowId: window.__electrobunWindowId,
|
|
91
|
-
url: this.src || this.getAttribute("src") || null,
|
|
92
|
-
html: this.html || this.getAttribute("html") || null,
|
|
93
|
-
preload: this.preload || this.getAttribute("preload") || null,
|
|
94
|
-
partition: this.partition || this.getAttribute("partition") || null,
|
|
95
|
-
frame: {
|
|
96
|
-
width: rect.width,
|
|
97
|
-
height: rect.height,
|
|
98
|
-
x: rect.x,
|
|
99
|
-
y: rect.y,
|
|
100
|
-
},
|
|
101
|
-
},
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
this.webviewId = webviewId;
|
|
105
|
-
this.id = `electrobun-webview-${webviewId}`;
|
|
106
|
-
// todo: replace zig -> webviewtag communication with a global instead of
|
|
107
|
-
// queryselector based on id
|
|
108
|
-
this.setAttribute("id", this.id);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
asyncResolvers: {
|
|
112
|
-
[id: string]: { resolve: (arg: any) => void; reject: (arg: any) => void };
|
|
113
|
-
} = {};
|
|
114
|
-
|
|
115
|
-
callAsyncJavaScript({ script }: { script: string }) {
|
|
116
|
-
return new Promise((resolve, reject) => {
|
|
117
|
-
const messageId = "" + Date.now() + Math.random();
|
|
118
|
-
this.asyncResolvers[messageId] = {
|
|
119
|
-
resolve,
|
|
120
|
-
reject,
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
this.zigRpc.request.webviewTagCallAsyncJavaScript({
|
|
124
|
-
messageId,
|
|
125
|
-
webviewId: this.webviewId,
|
|
126
|
-
hostWebviewId: window.__electrobunWebviewId,
|
|
127
|
-
script,
|
|
128
|
-
});
|
|
129
|
-
});
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
setCallAsyncJavaScriptResponse(messageId: string, response: any) {
|
|
133
|
-
const resolvers = this.asyncResolvers[messageId];
|
|
134
|
-
delete this.asyncResolvers[messageId];
|
|
135
|
-
try {
|
|
136
|
-
response = JSON.parse(response);
|
|
137
|
-
|
|
138
|
-
if (response.result) {
|
|
139
|
-
resolvers.resolve(response.result);
|
|
140
|
-
} else {
|
|
141
|
-
resolvers.reject(response.error);
|
|
142
|
-
}
|
|
143
|
-
} catch (e: any) {
|
|
144
|
-
resolvers.reject(e.message);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
async canGoBack() {
|
|
149
|
-
const {
|
|
150
|
-
payload: { webviewTagCanGoBackResponse },
|
|
151
|
-
} = await this.zigRpc.request.webviewTagCanGoBack({ id: this.webviewId });
|
|
152
|
-
return webviewTagCanGoBackResponse;
|
|
153
|
-
}
|
|
154
|
-
|
|
155
|
-
async canGoForward() {
|
|
156
|
-
const {
|
|
157
|
-
payload: { webviewTagCanGoForwardResponse },
|
|
158
|
-
} = await this.zigRpc.request.webviewTagCanGoForward({
|
|
159
|
-
id: this.webviewId,
|
|
160
|
-
});
|
|
161
|
-
return webviewTagCanGoForwardResponse;
|
|
162
|
-
}
|
|
163
|
-
|
|
164
|
-
// propertie setters/getters. keeps them in sync with dom attributes
|
|
165
|
-
updateAttr(name: string, value: string | null) {
|
|
166
|
-
if (value) {
|
|
167
|
-
this.setAttribute(name, value);
|
|
168
|
-
} else {
|
|
169
|
-
this.removeAttribute(name);
|
|
170
|
-
}
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
get src() {
|
|
174
|
-
return this.getAttribute("src");
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
set src(value) {
|
|
178
|
-
this.updateAttr("src", value);
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
get html() {
|
|
182
|
-
return this.getAttribute("html");
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
set html(value) {
|
|
186
|
-
this.updateAttr("html", value);
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
get preload() {
|
|
190
|
-
return this.getAttribute("preload");
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
set preload(value) {
|
|
194
|
-
this.updateAttr("preload", value);
|
|
195
|
-
}
|
|
196
|
-
|
|
197
|
-
// Note: since <electrobun-webview> is an anchor for a native webview
|
|
198
|
-
// on osx even if we hide it, enable mouse passthrough etc. There
|
|
199
|
-
// are still events like drag events which are natively handled deep in the window manager
|
|
200
|
-
// and will be handled incorrectly. To get around this for now we need to
|
|
201
|
-
// move the webview off screen during delegate mode.
|
|
202
|
-
adjustDimensionsForHiddenMirrorMode(rect: DOMRect) {
|
|
203
|
-
if (this.hiddenMirrorMode) {
|
|
204
|
-
rect.x = 0 - rect.width;
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return rect;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
// Note: in the brwoser-context we can ride on the dom element's uilt in event emitter for managing custom events
|
|
211
|
-
on(event: WebviewEventTypes, listener: () => {}) {
|
|
212
|
-
this.addEventListener(event, listener);
|
|
213
|
-
}
|
|
214
|
-
|
|
215
|
-
off(event: WebviewEventTypes, listener: () => {}) {
|
|
216
|
-
this.removeEventListener(event, listener);
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
// This is typically called by injected js from zig
|
|
220
|
-
emit(event: WebviewEventTypes, detail: any) {
|
|
221
|
-
this.dispatchEvent(new CustomEvent(event, { detail }));
|
|
222
|
-
}
|
|
223
|
-
|
|
224
|
-
// Call this via document.querySelector('electrobun-webview').syncDimensions();
|
|
225
|
-
// That way the host can trigger an alignment with the nested webview when they
|
|
226
|
-
// know that they're chaning something in order to eliminate the lag that the
|
|
227
|
-
// catch all loop will catch
|
|
228
|
-
syncDimensions(force: boolean = false) {
|
|
229
|
-
if (!this.webviewId || (!force && this.hidden)) {
|
|
230
|
-
return;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
const rect = this.getBoundingClientRect();
|
|
234
|
-
const { x, y, width, height } =
|
|
235
|
-
this.adjustDimensionsForHiddenMirrorMode(rect);
|
|
236
|
-
const lastRect = this.lastRect;
|
|
237
|
-
|
|
238
|
-
if (width === 0 && height === 0) {
|
|
239
|
-
if (this.wasZeroRect === false) {
|
|
240
|
-
this.wasZeroRect = true;
|
|
241
|
-
this.toggleHidden(true, true);
|
|
242
|
-
}
|
|
243
|
-
return;
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
const masks: Rect[] = [];
|
|
247
|
-
this.maskSelectors.forEach((selector) => {
|
|
248
|
-
const els = document.querySelectorAll(selector);
|
|
249
|
-
|
|
250
|
-
for (let i = 0; i < els.length; i++) {
|
|
251
|
-
const el = els[i];
|
|
252
|
-
|
|
253
|
-
if (el) {
|
|
254
|
-
const maskRect = el.getBoundingClientRect();
|
|
255
|
-
|
|
256
|
-
masks.push({
|
|
257
|
-
// reposition the bounding rect to be relative to the webview rect
|
|
258
|
-
// so objc can apply the mask correctly and handle the actual overlap
|
|
259
|
-
x: maskRect.x - x,
|
|
260
|
-
y: maskRect.y - y,
|
|
261
|
-
width: maskRect.width,
|
|
262
|
-
height: maskRect.height,
|
|
263
|
-
});
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
});
|
|
267
|
-
|
|
268
|
-
// store jsonStringified last masks value to compare
|
|
269
|
-
const masksJson = masks.length ? JSON.stringify(masks) : "";
|
|
270
|
-
|
|
271
|
-
if (
|
|
272
|
-
force ||
|
|
273
|
-
lastRect.x !== x ||
|
|
274
|
-
lastRect.y !== y ||
|
|
275
|
-
lastRect.width !== width ||
|
|
276
|
-
lastRect.height !== height ||
|
|
277
|
-
this.lastMasksJSON !== masksJson
|
|
278
|
-
) {
|
|
279
|
-
// let it know we're still accelerating
|
|
280
|
-
this.setPositionCheckLoop(true);
|
|
281
|
-
|
|
282
|
-
this.lastRect = rect;
|
|
283
|
-
this.lastMasks = masks;
|
|
284
|
-
this.lastMasksJSON = masksJson;
|
|
285
|
-
|
|
286
|
-
this.zigRpc.send.webviewTagResize({
|
|
287
|
-
id: this.webviewId,
|
|
288
|
-
frame: {
|
|
289
|
-
width: width,
|
|
290
|
-
height: height,
|
|
291
|
-
x: x,
|
|
292
|
-
y: y,
|
|
293
|
-
},
|
|
294
|
-
masks: masksJson,
|
|
295
|
-
});
|
|
296
|
-
}
|
|
297
|
-
|
|
298
|
-
if (this.wasZeroRect) {
|
|
299
|
-
this.wasZeroRect = false;
|
|
300
|
-
this.toggleHidden(false, true);
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
|
|
304
|
-
boundSyncDimensions = () => this.syncDimensions();
|
|
305
|
-
boundForceSyncDimensions = () => this.syncDimensions(true);
|
|
306
|
-
|
|
307
|
-
setPositionCheckLoop(accelerate = false) {
|
|
308
|
-
if (this.positionCheckLoop) {
|
|
309
|
-
clearInterval(this.positionCheckLoop);
|
|
310
|
-
this.positionCheckLoop = undefined;
|
|
311
|
-
}
|
|
312
|
-
|
|
313
|
-
if (this.positionCheckLoopReset) {
|
|
314
|
-
clearTimeout(this.positionCheckLoopReset);
|
|
315
|
-
this.positionCheckLoopReset = undefined;
|
|
316
|
-
}
|
|
317
|
-
|
|
318
|
-
const delay = accelerate ? 0 : 300;
|
|
319
|
-
|
|
320
|
-
if (accelerate) {
|
|
321
|
-
this.positionCheckLoopReset = setTimeout(() => {
|
|
322
|
-
this.setPositionCheckLoop(false);
|
|
323
|
-
}, 2000);
|
|
324
|
-
}
|
|
325
|
-
// Note: Since there's not catch all way to listen for x/y changes
|
|
326
|
-
// we have a 400ms interval to check
|
|
327
|
-
// on m1 max this 400ms interval for one nested webview
|
|
328
|
-
// only uses around 0.1% cpu
|
|
329
|
-
|
|
330
|
-
// Note: We also listen for resize events and changes to
|
|
331
|
-
// certain properties to get reactive repositioning for
|
|
332
|
-
// many cases.
|
|
333
|
-
|
|
334
|
-
// todo: consider having an option to disable this and let user
|
|
335
|
-
// trigger position sync for high performance cases (like
|
|
336
|
-
// a browser with a hundred tabs)
|
|
337
|
-
this.positionCheckLoop = setInterval(() => this.syncDimensions(), delay);
|
|
338
|
-
}
|
|
339
|
-
|
|
340
|
-
connectedCallback() {
|
|
341
|
-
this.setPositionCheckLoop();
|
|
342
|
-
|
|
343
|
-
this.resizeObserver = new ResizeObserver(() => {
|
|
344
|
-
this.syncDimensions();
|
|
345
|
-
});
|
|
346
|
-
// Note: In objc the webview is positioned in the window from the bottom-left corner
|
|
347
|
-
// the html anchor is positioned in the webview from the top-left corner
|
|
348
|
-
// In those cases the getBoundingClientRect() will return the same value, but
|
|
349
|
-
// we still need to send it to objc to calculate from its bottom left position
|
|
350
|
-
// otherwise it'll move around unexpectedly.
|
|
351
|
-
window.addEventListener("resize", this.boundForceSyncDimensions);
|
|
352
|
-
window.addEventListener("scroll", this.boundSyncDimensions);
|
|
353
|
-
|
|
354
|
-
// todo: For chromium webviews (windows native or chromium bundled)
|
|
355
|
-
// should be able to use performanceObservers on layout-shift to
|
|
356
|
-
// call syncDimensions more reactively
|
|
357
|
-
}
|
|
358
|
-
|
|
359
|
-
disconnectedCallback() {
|
|
360
|
-
// removed from the dom
|
|
361
|
-
clearInterval(this.positionCheckLoop);
|
|
362
|
-
|
|
363
|
-
this.resizeObserver?.disconnect();
|
|
364
|
-
// this.intersectionObserver?.disconnect();
|
|
365
|
-
// this.mutationObserver?.disconnect();
|
|
366
|
-
window.removeEventListener("resize", this.boundForceSyncDimensions);
|
|
367
|
-
window.removeEventListener("scroll", this.boundSyncDimensions);
|
|
368
|
-
this.zigRpc.send.webviewTagRemove({ id: this.webviewId });
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
static get observedAttributes() {
|
|
372
|
-
// TODO: support html, preload, and other stuff here
|
|
373
|
-
return ["src", "html", "preload", "class", "style"];
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
attributeChangedCallback(name, oldValue, newValue) {
|
|
377
|
-
if (name === "src" && oldValue !== newValue) {
|
|
378
|
-
this.updateIFrameSrc(newValue);
|
|
379
|
-
} else if (name === "html" && oldValue !== newValue) {
|
|
380
|
-
this.updateIFrameHtml(newValue);
|
|
381
|
-
} else if (name === "preload" && oldValue !== newValue) {
|
|
382
|
-
this.updateIFramePreload(newValue);
|
|
383
|
-
} else {
|
|
384
|
-
this.syncDimensions();
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
updateIFrameSrc(src: string) {
|
|
389
|
-
if (!this.webviewId) {
|
|
390
|
-
return;
|
|
391
|
-
}
|
|
392
|
-
this.zigRpc.send.webviewTagUpdateSrc({
|
|
393
|
-
id: this.webviewId,
|
|
394
|
-
url: src,
|
|
395
|
-
});
|
|
396
|
-
}
|
|
397
|
-
|
|
398
|
-
updateIFrameHtml(html: string) {
|
|
399
|
-
if (!this.webviewId) {
|
|
400
|
-
return;
|
|
401
|
-
}
|
|
402
|
-
this.zigRpc.send.webviewTagUpdateHtml({
|
|
403
|
-
id: this.webviewId,
|
|
404
|
-
html,
|
|
405
|
-
});
|
|
406
|
-
}
|
|
407
|
-
|
|
408
|
-
updateIFramePreload(preload: string) {
|
|
409
|
-
if (!this.webviewId) {
|
|
410
|
-
return;
|
|
411
|
-
}
|
|
412
|
-
this.zigRpc.send.webviewTagUpdatePreload({
|
|
413
|
-
id: this.webviewId,
|
|
414
|
-
preload,
|
|
415
|
-
});
|
|
416
|
-
}
|
|
417
|
-
|
|
418
|
-
goBack() {
|
|
419
|
-
this.zigRpc.send.webviewTagGoBack({ id: this.webviewId });
|
|
420
|
-
}
|
|
421
|
-
|
|
422
|
-
goForward() {
|
|
423
|
-
this.zigRpc.send.webviewTagGoForward({ id: this.webviewId });
|
|
424
|
-
}
|
|
425
|
-
|
|
426
|
-
reload() {
|
|
427
|
-
this.zigRpc.send.webviewTagReload({ id: this.webviewId });
|
|
428
|
-
}
|
|
429
|
-
loadURL(url: string) {
|
|
430
|
-
this.setAttribute("src", url);
|
|
431
|
-
this.zigRpc.send.webviewTagUpdateSrc({
|
|
432
|
-
id: this.webviewId,
|
|
433
|
-
url,
|
|
434
|
-
});
|
|
435
|
-
}
|
|
436
|
-
// Note: you can set an interval and do this 60 times a second and it's pretty smooth
|
|
437
|
-
// but it uses quite a bit of cpu
|
|
438
|
-
// todo: change this to "mirror to dom" or something
|
|
439
|
-
syncScreenshot(callback?: () => void) {
|
|
440
|
-
const cacheBustString = `?${Date.now()}`;
|
|
441
|
-
const url = `views://screenshot/${this.webviewId}${cacheBustString}`;
|
|
442
|
-
const img = new Image();
|
|
443
|
-
img.src = url;
|
|
444
|
-
img.onload = () => {
|
|
445
|
-
this.style.backgroundImage = `url(${url})`;
|
|
446
|
-
if (callback) {
|
|
447
|
-
// We've preloaded the image, but we still want to give it a chance to render
|
|
448
|
-
// after setting the background style. give it quite a bit longer than a rafr
|
|
449
|
-
setTimeout(callback, 100);
|
|
450
|
-
}
|
|
451
|
-
};
|
|
452
|
-
}
|
|
453
|
-
|
|
454
|
-
DEFAULT_FRAME_RATE = Math.round(1000 / 30); // 30fps
|
|
455
|
-
streamScreenInterval?: Timer;
|
|
456
|
-
|
|
457
|
-
// NOTE: This is very cpu intensive, Prefer startMirroring where possible
|
|
458
|
-
startMirroringToDom(frameRate: number = this.DEFAULT_FRAME_RATE) {
|
|
459
|
-
if (this.streamScreenInterval) {
|
|
460
|
-
clearInterval(this.streamScreenInterval);
|
|
461
|
-
}
|
|
462
|
-
|
|
463
|
-
this.streamScreenInterval = setInterval(() => {
|
|
464
|
-
this.syncScreenshot();
|
|
465
|
-
}, frameRate);
|
|
466
|
-
}
|
|
467
|
-
|
|
468
|
-
stopMirroringToDom() {
|
|
469
|
-
if (this.streamScreenInterval) {
|
|
470
|
-
clearInterval(this.streamScreenInterval);
|
|
471
|
-
this.streamScreenInterval = undefined;
|
|
472
|
-
}
|
|
473
|
-
}
|
|
474
|
-
|
|
475
|
-
startMirroring() {
|
|
476
|
-
// TEMP: mirroring now happens automatically in objc
|
|
477
|
-
// when the mouse moves. I'm leaving this here for now
|
|
478
|
-
// because I suspect there may still be use cases to
|
|
479
|
-
// toggle it from the dom outside of the mouse moving.
|
|
480
|
-
return;
|
|
481
|
-
if (this.isMirroring === false) {
|
|
482
|
-
this.isMirroring = true;
|
|
483
|
-
this.zigRpc.send.webviewTagToggleMirroring({
|
|
484
|
-
id: this.webviewId,
|
|
485
|
-
enable: true,
|
|
486
|
-
});
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
|
|
490
|
-
stopMirroring() {
|
|
491
|
-
return;
|
|
492
|
-
if (this.isMirroring === true) {
|
|
493
|
-
this.isMirroring = false;
|
|
494
|
-
this.zigRpc.send.webviewTagToggleMirroring({
|
|
495
|
-
id: this.webviewId,
|
|
496
|
-
enable: false,
|
|
497
|
-
});
|
|
498
|
-
}
|
|
499
|
-
}
|
|
500
|
-
|
|
501
|
-
clearScreenImage() {
|
|
502
|
-
this.style.backgroundImage = "";
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
tryClearScreenImage() {
|
|
506
|
-
if (
|
|
507
|
-
!this.transparent &&
|
|
508
|
-
!this.hiddenMirrorMode &&
|
|
509
|
-
!this.delegateMode &&
|
|
510
|
-
!this.hidden
|
|
511
|
-
) {
|
|
512
|
-
this.clearScreenImage();
|
|
513
|
-
}
|
|
514
|
-
}
|
|
515
|
-
// This sets the native webview hovering over the dom to be transparent
|
|
516
|
-
toggleTransparent(transparent?: boolean, bypassState?: boolean) {
|
|
517
|
-
if (!bypassState) {
|
|
518
|
-
if (typeof transparent === "undefined") {
|
|
519
|
-
this.transparent = !this.transparent;
|
|
520
|
-
} else {
|
|
521
|
-
this.transparent = transparent;
|
|
522
|
-
}
|
|
523
|
-
}
|
|
524
|
-
|
|
525
|
-
if (!this.transparent && !transparent) {
|
|
526
|
-
this.tryClearScreenImage();
|
|
527
|
-
}
|
|
528
|
-
|
|
529
|
-
this.zigRpc.send.webviewTagSetTransparent({
|
|
530
|
-
id: this.webviewId,
|
|
531
|
-
transparent: this.transparent || Boolean(transparent),
|
|
532
|
-
});
|
|
533
|
-
}
|
|
534
|
-
togglePassthrough(enablePassthrough?: boolean, bypassState?: boolean) {
|
|
535
|
-
if (!bypassState) {
|
|
536
|
-
if (typeof enablePassthrough === "undefined") {
|
|
537
|
-
this.passthroughEnabled = !this.passthroughEnabled;
|
|
538
|
-
} else {
|
|
539
|
-
this.passthroughEnabled = enablePassthrough;
|
|
540
|
-
}
|
|
541
|
-
}
|
|
542
|
-
|
|
543
|
-
this.zigRpc.send.webviewTagSetPassthrough({
|
|
544
|
-
id: this.webviewId,
|
|
545
|
-
enablePassthrough:
|
|
546
|
-
this.passthroughEnabled || Boolean(enablePassthrough),
|
|
547
|
-
});
|
|
548
|
-
}
|
|
549
|
-
|
|
550
|
-
toggleHidden(hidden?: boolean, bypassState?: boolean) {
|
|
551
|
-
if (!bypassState) {
|
|
552
|
-
if (typeof hidden === "undefined") {
|
|
553
|
-
this.hidden = !this.hidden;
|
|
554
|
-
} else {
|
|
555
|
-
this.hidden = hidden;
|
|
556
|
-
}
|
|
557
|
-
}
|
|
558
|
-
|
|
559
|
-
this.zigRpc.send.webviewTagSetHidden({
|
|
560
|
-
id: this.webviewId,
|
|
561
|
-
hidden: this.hidden || Boolean(hidden),
|
|
562
|
-
});
|
|
563
|
-
}
|
|
564
|
-
|
|
565
|
-
// note: delegateMode and hiddenMirrorMode are experimental
|
|
566
|
-
// ideally delegate mode would move the webview off screen
|
|
567
|
-
// and delegate mouse and keyboard events to the webview while
|
|
568
|
-
// streaming the screen so it can be fully layered in the dom
|
|
569
|
-
// and fully interactive.
|
|
570
|
-
toggleDelegateMode(delegateMode?: boolean) {
|
|
571
|
-
const _newDelegateMode =
|
|
572
|
-
typeof delegateMode === "undefined" ? !this.delegateMode : delegateMode;
|
|
573
|
-
|
|
574
|
-
if (_newDelegateMode) {
|
|
575
|
-
this.syncScreenshot(() => {
|
|
576
|
-
this.delegateMode = true;
|
|
577
|
-
this.toggleTransparent(true, true);
|
|
578
|
-
this.startMirroringToDom();
|
|
579
|
-
});
|
|
580
|
-
} else {
|
|
581
|
-
this.delegateMode = false;
|
|
582
|
-
this.stopMirroringToDom();
|
|
583
|
-
this.toggleTransparent(this.transparent);
|
|
584
|
-
this.tryClearScreenImage();
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
|
|
588
|
-
// While hiddenMirroMode would be similar to delegate mode but non-interactive
|
|
589
|
-
// This is used while scrolling or resizing the <electrobun-webviewtag> to
|
|
590
|
-
// make it smoother (scrolls with the dom) but disables interaction so that
|
|
591
|
-
// during the scroll we don't need to worry about the webview being misaligned
|
|
592
|
-
// with the mirror and accidentlly clicking on the wrong thing.
|
|
593
|
-
toggleHiddenMirrorMode(force: boolean) {
|
|
594
|
-
const enable =
|
|
595
|
-
typeof force === "undefined" ? !this.hiddenMirrorMode : force;
|
|
596
|
-
|
|
597
|
-
if (enable === true) {
|
|
598
|
-
this.syncScreenshot(() => {
|
|
599
|
-
this.hiddenMirrorMode = true;
|
|
600
|
-
this.toggleHidden(true, true);
|
|
601
|
-
this.togglePassthrough(true, true);
|
|
602
|
-
this.startMirroringToDom();
|
|
603
|
-
});
|
|
604
|
-
} else {
|
|
605
|
-
this.stopMirroringToDom();
|
|
606
|
-
this.toggleHidden(this.hidden);
|
|
607
|
-
this.togglePassthrough(this.passthroughEnabled);
|
|
608
|
-
this.tryClearScreenImage();
|
|
609
|
-
this.hiddenMirrorMode = false;
|
|
610
|
-
}
|
|
611
|
-
}
|
|
612
|
-
}
|
|
613
|
-
|
|
614
|
-
customElements.define("electrobun-webview", WebviewTag);
|
|
615
|
-
|
|
616
|
-
insertWebviewTagNormalizationStyles();
|
|
617
|
-
};
|
|
618
|
-
|
|
619
|
-
// Give <electrobun-webview>s some default styles that can
|
|
620
|
-
// be easily overridden in the host document
|
|
621
|
-
const insertWebviewTagNormalizationStyles = () => {
|
|
622
|
-
var style = document.createElement("style");
|
|
623
|
-
style.type = "text/css";
|
|
624
|
-
|
|
625
|
-
var css = `
|
|
626
|
-
electrobun-webview {
|
|
627
|
-
display: block;
|
|
628
|
-
width: 800px;
|
|
629
|
-
height: 300px;
|
|
630
|
-
background: #fff;
|
|
631
|
-
background-repeat: no-repeat!important;
|
|
632
|
-
overflow: hidden;
|
|
633
|
-
}
|
|
634
|
-
`;
|
|
635
|
-
|
|
636
|
-
style.appendChild(document.createTextNode(css));
|
|
637
|
-
|
|
638
|
-
var head = document.getElementsByTagName("head")[0];
|
|
639
|
-
if (!head) {
|
|
640
|
-
return;
|
|
641
|
-
}
|
|
642
|
-
|
|
643
|
-
if (head.firstChild) {
|
|
644
|
-
head.insertBefore(style, head.firstChild);
|
|
645
|
-
} else {
|
|
646
|
-
head.appendChild(style);
|
|
647
|
-
}
|
|
648
|
-
};
|
|
649
|
-
|
|
650
|
-
export { ConfigureWebviewTags };
|
|
@@ -1,66 +0,0 @@
|
|
|
1
|
-
import { zigRPC, type ApplicationMenuItemConfig } from "../proc/zig";
|
|
2
|
-
import electrobunEventEmitter from "../events/eventEmitter";
|
|
3
|
-
|
|
4
|
-
export const setApplicationMenu = (menu: Array<ApplicationMenuItemConfig>) => {
|
|
5
|
-
const menuWithDefaults = menuConfigWithDefaults(menu);
|
|
6
|
-
zigRPC.request.setApplicationMenu({
|
|
7
|
-
menuConfig: JSON.stringify(menuWithDefaults),
|
|
8
|
-
});
|
|
9
|
-
};
|
|
10
|
-
|
|
11
|
-
export const on = (name: "application-menu-clicked", handler) => {
|
|
12
|
-
const specificName = `${name}`;
|
|
13
|
-
electrobunEventEmitter.on(specificName, handler);
|
|
14
|
-
};
|
|
15
|
-
|
|
16
|
-
const roleLabelMap = {
|
|
17
|
-
quit: "Quit",
|
|
18
|
-
hide: "Hide",
|
|
19
|
-
hideOthers: "Hide Others",
|
|
20
|
-
showAll: "Show All",
|
|
21
|
-
undo: "Undo",
|
|
22
|
-
redo: "Redo",
|
|
23
|
-
cut: "Cut",
|
|
24
|
-
copy: "Copy",
|
|
25
|
-
paste: "Paste",
|
|
26
|
-
pasteAndMatchStyle: "Paste And Match Style",
|
|
27
|
-
delete: "Delete",
|
|
28
|
-
selectAll: "Select All",
|
|
29
|
-
startSpeaking: "Start Speaking",
|
|
30
|
-
stopSpeaking: "Stop Speaking",
|
|
31
|
-
enterFullScreen: "Enter FullScreen",
|
|
32
|
-
exitFullScreen: "Exit FullScreen",
|
|
33
|
-
toggleFullScreen: "Toggle Full Screen",
|
|
34
|
-
minimize: "Minimize",
|
|
35
|
-
zoom: "Zoom",
|
|
36
|
-
bringAllToFront: "Bring All To Front",
|
|
37
|
-
close: "Close",
|
|
38
|
-
cycleThroughWindows: "Cycle Through Windows",
|
|
39
|
-
showHelp: "Show Help",
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const menuConfigWithDefaults = (
|
|
43
|
-
menu: Array<ApplicationMenuItemConfig>
|
|
44
|
-
): Array<ApplicationMenuItemConfig> => {
|
|
45
|
-
return menu.map((item) => {
|
|
46
|
-
if (item.type === "divider" || item.type === "separator") {
|
|
47
|
-
return { type: "divider" };
|
|
48
|
-
} else {
|
|
49
|
-
return {
|
|
50
|
-
label: item.label || roleLabelMap[item.role] || "",
|
|
51
|
-
type: item.type || "normal",
|
|
52
|
-
// application menus can either have an action or a role. not both.
|
|
53
|
-
...(item.role ? { role: item.role } : { action: item.action || "" }),
|
|
54
|
-
// default enabled to true unless explicitly set to false
|
|
55
|
-
enabled: item.enabled === false ? false : true,
|
|
56
|
-
checked: Boolean(item.checked),
|
|
57
|
-
hidden: Boolean(item.hidden),
|
|
58
|
-
tooltip: item.tooltip || undefined,
|
|
59
|
-
accelerator: item.accelerator || undefined,
|
|
60
|
-
...(item.submenu
|
|
61
|
-
? { submenu: menuConfigWithDefaults(item.submenu) }
|
|
62
|
-
: {}),
|
|
63
|
-
};
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
};
|