electrobun 0.0.3 → 0.0.5

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/dist/webview CHANGED
Binary file
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "electrobun",
3
- "version": "0.0.3",
3
+ "version": "0.0.5",
4
4
  "description": "Build ultra fast, tiny, and cross-platform desktop apps with Typescript.",
5
5
  "license": "MIT",
6
6
  "author": "Blackboard Technologies Inc.",
@@ -45,6 +45,7 @@ type WebviewTagHandlers = RPCSchema<{
45
45
  width: number;
46
46
  height: number;
47
47
  };
48
+ masks: string;
48
49
  };
49
50
  webviewTagUpdateSrc: {
50
51
  id: number;
@@ -77,6 +78,10 @@ type WebviewTagHandlers = RPCSchema<{
77
78
  id: number;
78
79
  transparent: boolean;
79
80
  };
81
+ webviewTagToggleMirroring: {
82
+ id: number;
83
+ enable: boolean;
84
+ };
80
85
  webviewTagSetPassthrough: {
81
86
  id: number;
82
87
  enablePassthrough: boolean;
@@ -236,24 +241,32 @@ class Electroview<T> {
236
241
  }
237
242
 
238
243
  bunBridge(msg: string) {
239
- // Note: zig sets up this custom message handler bridge
240
- window.webkit.messageHandlers.bunBridge.postMessage(msg);
241
- }
242
-
243
- bunBridgeWithReply(msg: string) {
244
- // Note: zig sets up this custom message handler bridge
245
- // Note: Since post message is async in the browser context and bun will reply async
246
- // We're using postMessage handler (via bunBridge above) without a reply, and then letting bun reply
247
- // via pipesin and evaluateJavascript.
248
- // addScriptMessageHandlerWithReply is just here as reference and for future use cases.
249
- return window.webkit.messageHandlers.bunBridgeWithReply.postMessage(msg);
244
+ // Note: messageHandlers seem to freeze when sending large messages
245
+ // but xhr to views://rpc can run into CORS issues on non views://
246
+ // loaded content (eg: when writing extensions/preload scripts for
247
+ // remote content).
248
+
249
+ // Since most messages--especially those on remote content, are small
250
+ // we can solve most use cases by having a fallback to xhr for
251
+ // large messages
252
+
253
+ // TEMP: disable the fallback for now. for some reason suddenly can't
254
+ // repro now that other places are chunking messages and laptop restart
255
+ if (true || msg.length < 8 * 1024) {
256
+ window.webkit.messageHandlers.bunBridge.postMessage(msg);
257
+ } else {
258
+ var xhr = new XMLHttpRequest();
259
+
260
+ // Note: we're only using synchronouse http on this async
261
+ // call to get around CORS for now
262
+ // Note: DO NOT use postMessage handlers since it
263
+ // freezes the process when sending lots of large messages
264
+
265
+ xhr.open("POST", "views://rpc", false); // sychronous call
266
+ xhr.send(msg);
267
+ }
250
268
  }
251
269
 
252
- // webviewTagBridge(msg) {
253
- // // Note: zig sets up this custom message handler bridge
254
- // window.webkit.messageHandlers.webviewTagBridge.postMessage(msg);
255
- // }
256
-
257
270
  receiveMessageFromBun(msg) {
258
271
  // NOTE: in the webview messages are passed by executing ElectrobunView.receiveMessageFromBun(object)
259
272
  // so they're already parsed into an object here
@@ -4,6 +4,8 @@ type WebviewEventTypes =
4
4
  | "did-commit-navigation"
5
5
  | "dom-ready";
6
6
 
7
+ type Rect = { x: number; y: number; width: number; height: number };
8
+
7
9
  const ConfigureWebviewTags = (
8
10
  enableWebviewTags: boolean,
9
11
  zigRpc: (params: any) => any,
@@ -24,6 +26,10 @@ const ConfigureWebviewTags = (
24
26
  zigRpc: any;
25
27
  syncRpc: any;
26
28
 
29
+ // querySelectors for elements that you want to appear
30
+ // in front of the webview.
31
+ maskSelectors: Set<string> = new Set();
32
+
27
33
  // observers
28
34
  resizeObserver?: ResizeObserver;
29
35
  // intersectionObserver?: IntersectionObserver;
@@ -39,12 +45,16 @@ const ConfigureWebviewTags = (
39
45
  height: 0,
40
46
  };
41
47
 
48
+ lastMasksJSON: string = "";
49
+ lastMasks: Rect[] = [];
50
+
42
51
  transparent: boolean = false;
43
52
  passthroughEnabled: boolean = false;
44
53
  hidden: boolean = false;
45
54
  delegateMode: boolean = false;
46
55
  hiddenMirrorMode: boolean = false;
47
56
  wasZeroRect: boolean = false;
57
+ isMirroring: boolean = false;
48
58
 
49
59
  partition: string | null = null;
50
60
 
@@ -59,6 +69,16 @@ const ConfigureWebviewTags = (
59
69
  });
60
70
  }
61
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
+
62
82
  initWebview() {
63
83
  const rect = this.getBoundingClientRect();
64
84
  this.lastRect = rect;
@@ -206,6 +226,10 @@ const ConfigureWebviewTags = (
206
226
  // know that they're chaning something in order to eliminate the lag that the
207
227
  // catch all loop will catch
208
228
  syncDimensions(force: boolean = false) {
229
+ if (!force && this.hidden) {
230
+ return;
231
+ }
232
+
209
233
  const rect = this.getBoundingClientRect();
210
234
  const { x, y, width, height } =
211
235
  this.adjustDimensionsForHiddenMirrorMode(rect);
@@ -215,24 +239,49 @@ const ConfigureWebviewTags = (
215
239
  if (this.wasZeroRect === false) {
216
240
  this.wasZeroRect = true;
217
241
  this.toggleHidden(true, true);
218
- this.stopMirroring();
219
242
  }
220
243
  return;
221
244
  }
222
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
+
223
271
  if (
224
272
  force ||
225
273
  lastRect.x !== x ||
226
274
  lastRect.y !== y ||
227
275
  lastRect.width !== width ||
228
- lastRect.height !== height
276
+ lastRect.height !== height ||
277
+ this.lastMasksJSON !== masksJson
229
278
  ) {
230
- // if we're not already in an accelerated loop then accelerate it
231
- if (!this.positionCheckLoopReset) {
232
- this.setPositionCheckLoop(true);
233
- }
279
+ // let it know we're still accelerating
280
+ this.setPositionCheckLoop(true);
234
281
 
235
282
  this.lastRect = rect;
283
+ this.lastMasks = masks;
284
+ this.lastMasksJSON = masksJson;
236
285
 
237
286
  this.zigRpc.send.webviewTagResize({
238
287
  id: this.webviewId,
@@ -242,6 +291,7 @@ const ConfigureWebviewTags = (
242
291
  x: x,
243
292
  y: y,
244
293
  },
294
+ masks: masksJson,
245
295
  });
246
296
  }
247
297
 
@@ -265,13 +315,12 @@ const ConfigureWebviewTags = (
265
315
  this.positionCheckLoopReset = undefined;
266
316
  }
267
317
 
268
- const delay = accelerate ? 100 : 400;
318
+ const delay = accelerate ? 0 : 300;
269
319
 
270
320
  if (accelerate) {
271
- clearTimeout(this.positionCheckLoopReset);
272
321
  this.positionCheckLoopReset = setTimeout(() => {
273
322
  this.setPositionCheckLoop(false);
274
- }, 600);
323
+ }, 2000);
275
324
  }
276
325
  // Note: Since there's not catch all way to listen for x/y changes
277
326
  // we have a 400ms interval to check
@@ -300,7 +349,6 @@ const ConfigureWebviewTags = (
300
349
  // we still need to send it to objc to calculate from its bottom left position
301
350
  // otherwise it'll move around unexpectedly.
302
351
  window.addEventListener("resize", this.boundForceSyncDimensions);
303
-
304
352
  window.addEventListener("scroll", this.boundSyncDimensions);
305
353
 
306
354
  // todo: For chromium webviews (windows native or chromium bundled)
@@ -311,7 +359,6 @@ const ConfigureWebviewTags = (
311
359
  disconnectedCallback() {
312
360
  // removed from the dom
313
361
  clearInterval(this.positionCheckLoop);
314
- this.stopMirroring();
315
362
 
316
363
  this.resizeObserver?.disconnect();
317
364
  // this.intersectionObserver?.disconnect();
@@ -407,9 +454,8 @@ const ConfigureWebviewTags = (
407
454
  DEFAULT_FRAME_RATE = Math.round(1000 / 30); // 30fps
408
455
  streamScreenInterval?: Timer;
409
456
 
410
- // todo: add a flag to disable automirroring since it's cpu intensive and
411
- // developers may want to disable it in specific cases
412
- startMirroring(frameRate: number = this.DEFAULT_FRAME_RATE) {
457
+ // NOTE: This is very cpu intensive, Prefer startMirroring where possible
458
+ startMirroringToDom(frameRate: number = this.DEFAULT_FRAME_RATE) {
413
459
  if (this.streamScreenInterval) {
414
460
  clearInterval(this.streamScreenInterval);
415
461
  }
@@ -419,13 +465,39 @@ const ConfigureWebviewTags = (
419
465
  }, frameRate);
420
466
  }
421
467
 
422
- stopMirroring() {
468
+ stopMirroringToDom() {
423
469
  if (this.streamScreenInterval) {
424
470
  clearInterval(this.streamScreenInterval);
425
471
  this.streamScreenInterval = undefined;
426
472
  }
427
473
  }
428
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
+
429
501
  clearScreenImage() {
430
502
  this.style.backgroundImage = "";
431
503
  }
@@ -503,11 +575,11 @@ const ConfigureWebviewTags = (
503
575
  this.syncScreenshot(() => {
504
576
  this.delegateMode = true;
505
577
  this.toggleTransparent(true, true);
506
- this.startMirroring();
578
+ this.startMirroringToDom();
507
579
  });
508
580
  } else {
509
581
  this.delegateMode = false;
510
- this.stopMirroring();
582
+ this.stopMirroringToDom();
511
583
  this.toggleTransparent(this.transparent);
512
584
  this.tryClearScreenImage();
513
585
  }
@@ -527,10 +599,10 @@ const ConfigureWebviewTags = (
527
599
  this.hiddenMirrorMode = true;
528
600
  this.toggleHidden(true, true);
529
601
  this.togglePassthrough(true, true);
530
- this.startMirroring();
602
+ this.startMirroringToDom();
531
603
  });
532
604
  } else {
533
- this.stopMirroring();
605
+ this.stopMirroringToDom();
534
606
  this.toggleHidden(this.hidden);
535
607
  this.togglePassthrough(this.passthroughEnabled);
536
608
  this.tryClearScreenImage();
@@ -16,7 +16,7 @@ import type { BuiltinBunToWebviewSchema } from "../../browser/builtinrpcSchema";
16
16
  const BrowserViewMap = {};
17
17
  let nextWebviewId = 1;
18
18
 
19
- const CHUNK_SIZE = 4096; // 4KB
19
+ const CHUNK_SIZE = 1024 * 4; // 4KB
20
20
 
21
21
  type BrowserViewOptions<T = undefined> = {
22
22
  url: string | null;
@@ -32,6 +32,7 @@ type BrowserViewOptions<T = undefined> = {
32
32
  rpc: T;
33
33
  syncRpc: { [method: string]: (params: any) => any };
34
34
  hostWebviewId: number;
35
+ autoResize: boolean;
35
36
  };
36
37
 
37
38
  interface ElectrobunWebviewRPCSChema {
@@ -68,6 +69,7 @@ const internalSyncRpcHandlers = {
68
69
  partition,
69
70
  frame,
70
71
  hostWebviewId,
72
+ autoResize: false,
71
73
  });
72
74
 
73
75
  // Note: we have to give it a couple of ticks to fully create the browserview
@@ -104,6 +106,7 @@ export class BrowserView<T> {
104
106
  html: string | null = null;
105
107
  preload: string | null = null;
106
108
  partition: string | null = null;
109
+ autoResize: boolean = true;
107
110
  frame: {
108
111
  x: number;
109
112
  y: number;
@@ -135,6 +138,7 @@ export class BrowserView<T> {
135
138
  // file exists first
136
139
  this.pipePrefix = `/private/tmp/electrobun_ipc_pipe_${hash}_${randomId}_${this.id}`;
137
140
  this.hostWebviewId = options.hostWebviewId;
141
+ this.autoResize = options.autoResize === false ? false : true;
138
142
 
139
143
  this.init();
140
144
  }
@@ -158,7 +162,7 @@ export class BrowserView<T> {
158
162
  x: this.frame.x,
159
163
  y: this.frame.y,
160
164
  },
161
- // autoResize: true,
165
+ autoResize: this.autoResize,
162
166
  });
163
167
 
164
168
  this.createStreams();
@@ -192,6 +192,7 @@ type ZigHandlers = RPCSchema<{
192
192
  width: number;
193
193
  height: number;
194
194
  };
195
+ autoResize: boolean;
195
196
  };
196
197
  response: void;
197
198
  };