electrobun 0.0.19-beta.8 → 0.0.19-beta.81
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/BUILD.md +90 -0
- package/bin/electrobun.cjs +165 -0
- package/debug.js +5 -0
- package/dist/api/browser/builtinrpcSchema.ts +19 -0
- package/dist/api/browser/index.ts +409 -0
- package/dist/api/browser/rpc/webview.ts +79 -0
- package/dist/api/browser/stylesAndElements.ts +3 -0
- package/dist/api/browser/webviewtag.ts +534 -0
- package/dist/api/bun/core/ApplicationMenu.ts +66 -0
- package/dist/api/bun/core/BrowserView.ts +349 -0
- package/dist/api/bun/core/BrowserWindow.ts +191 -0
- package/dist/api/bun/core/ContextMenu.ts +67 -0
- package/dist/api/bun/core/Paths.ts +5 -0
- package/dist/api/bun/core/Socket.ts +181 -0
- package/dist/api/bun/core/Tray.ts +107 -0
- package/dist/api/bun/core/Updater.ts +547 -0
- package/dist/api/bun/core/Utils.ts +48 -0
- package/dist/api/bun/events/ApplicationEvents.ts +14 -0
- package/dist/api/bun/events/event.ts +29 -0
- package/dist/api/bun/events/eventEmitter.ts +45 -0
- package/dist/api/bun/events/trayEvents.ts +9 -0
- package/dist/api/bun/events/webviewEvents.ts +16 -0
- package/dist/api/bun/events/windowEvents.ts +12 -0
- package/dist/api/bun/index.ts +45 -0
- package/dist/api/bun/proc/linux.md +43 -0
- package/dist/api/bun/proc/native.ts +1220 -0
- package/dist/api/shared/platform.ts +48 -0
- package/dist/main.js +53 -0
- package/package.json +15 -7
- package/src/cli/index.ts +1034 -210
- package/templates/hello-world/README.md +57 -0
- package/templates/hello-world/bun.lock +63 -0
- package/templates/hello-world/electrobun.config +18 -0
- package/templates/hello-world/package.json +16 -0
- package/templates/hello-world/src/bun/index.ts +15 -0
- package/templates/hello-world/src/mainview/index.css +124 -0
- package/templates/hello-world/src/mainview/index.html +47 -0
- package/templates/hello-world/src/mainview/index.ts +5 -0
- package/bin/electrobun +0 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type RPCSchema,
|
|
3
|
+
} from "rpc-anywhere";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
// todo (yoav): move this stuff to browser/rpc/webview.ts
|
|
7
|
+
export type InternalWebviewHandlers = RPCSchema<{
|
|
8
|
+
requests: {
|
|
9
|
+
webviewTagCallAsyncJavaScript: {
|
|
10
|
+
params: {
|
|
11
|
+
messageId: string;
|
|
12
|
+
webviewId: number;
|
|
13
|
+
hostWebviewId: number;
|
|
14
|
+
script: string;
|
|
15
|
+
};
|
|
16
|
+
response: void;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
}>;
|
|
20
|
+
|
|
21
|
+
export type WebviewTagHandlers = RPCSchema<{
|
|
22
|
+
requests: {};
|
|
23
|
+
messages: {
|
|
24
|
+
webviewTagResize: {
|
|
25
|
+
id: number;
|
|
26
|
+
frame: {
|
|
27
|
+
x: number;
|
|
28
|
+
y: number;
|
|
29
|
+
width: number;
|
|
30
|
+
height: number;
|
|
31
|
+
};
|
|
32
|
+
masks: string;
|
|
33
|
+
};
|
|
34
|
+
webviewTagUpdateSrc: {
|
|
35
|
+
id: number;
|
|
36
|
+
url: string;
|
|
37
|
+
};
|
|
38
|
+
webviewTagUpdateHtml: {
|
|
39
|
+
id: number;
|
|
40
|
+
html: string;
|
|
41
|
+
}
|
|
42
|
+
webviewTagGoBack: {
|
|
43
|
+
id: number;
|
|
44
|
+
};
|
|
45
|
+
webviewTagGoForward: {
|
|
46
|
+
id: number;
|
|
47
|
+
};
|
|
48
|
+
webviewTagReload: {
|
|
49
|
+
id: number;
|
|
50
|
+
};
|
|
51
|
+
webviewTagRemove: {
|
|
52
|
+
id: number;
|
|
53
|
+
};
|
|
54
|
+
startWindowMove: {
|
|
55
|
+
id: number;
|
|
56
|
+
};
|
|
57
|
+
stopWindowMove: {
|
|
58
|
+
id: number;
|
|
59
|
+
};
|
|
60
|
+
moveWindowBy: {
|
|
61
|
+
id: number;
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
};
|
|
65
|
+
webviewTagSetTransparent: {
|
|
66
|
+
id: number;
|
|
67
|
+
transparent: boolean;
|
|
68
|
+
};
|
|
69
|
+
webviewTagSetPassthrough: {
|
|
70
|
+
id: number;
|
|
71
|
+
enablePassthrough: boolean;
|
|
72
|
+
};
|
|
73
|
+
webviewTagSetHidden: {
|
|
74
|
+
id: number;
|
|
75
|
+
hidden: boolean;
|
|
76
|
+
};
|
|
77
|
+
};
|
|
78
|
+
}>;
|
|
79
|
+
|
|
@@ -0,0 +1,534 @@
|
|
|
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
|
+
internalRpc: (params: any) => any,
|
|
12
|
+
bunRpc: (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
|
+
internalRpc: any;
|
|
27
|
+
bunRpc: 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
|
+
hiddenMirrorMode: boolean = false;
|
|
55
|
+
wasZeroRect: boolean = false;
|
|
56
|
+
isMirroring: boolean = false;
|
|
57
|
+
|
|
58
|
+
partition: string | null = null;
|
|
59
|
+
|
|
60
|
+
constructor() {
|
|
61
|
+
super();
|
|
62
|
+
this.internalRpc = internalRpc;
|
|
63
|
+
this.bunRpc = bunRpc;
|
|
64
|
+
|
|
65
|
+
// Give it a frame to be added to the dom and render before measuring
|
|
66
|
+
requestAnimationFrame(() => {
|
|
67
|
+
this.initWebview();
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
addMaskSelector(selector: string) {
|
|
72
|
+
this.maskSelectors.add(selector);
|
|
73
|
+
this.syncDimensions();
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
removeMaskSelector(selector: string) {
|
|
77
|
+
this.maskSelectors.delete(selector);
|
|
78
|
+
this.syncDimensions();
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async initWebview() {
|
|
82
|
+
const rect = this.getBoundingClientRect();
|
|
83
|
+
this.lastRect = rect;
|
|
84
|
+
|
|
85
|
+
const url = this.src || this.getAttribute("src");
|
|
86
|
+
const html = this.html || this.getAttribute("html");
|
|
87
|
+
|
|
88
|
+
const webviewId = await this.internalRpc.request.webviewTagInit({
|
|
89
|
+
hostWebviewId: window.__electrobunWebviewId,
|
|
90
|
+
windowId: window.__electrobunWindowId,
|
|
91
|
+
renderer: this.renderer,
|
|
92
|
+
url: url,
|
|
93
|
+
html: html,
|
|
94
|
+
preload: this.preload || this.getAttribute("preload") || null,
|
|
95
|
+
partition: this.partition || this.getAttribute("partition") || null,
|
|
96
|
+
frame: {
|
|
97
|
+
width: rect.width,
|
|
98
|
+
height: rect.height,
|
|
99
|
+
x: rect.x,
|
|
100
|
+
y: rect.y,
|
|
101
|
+
},
|
|
102
|
+
// todo: wire up to a param and a method to update them
|
|
103
|
+
navigationRules: null,
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
this.webviewId = webviewId;
|
|
107
|
+
this.id = `electrobun-webview-${webviewId}`;
|
|
108
|
+
// todo: replace bun -> webviewtag communication with a global instead of
|
|
109
|
+
// queryselector based on id
|
|
110
|
+
this.setAttribute("id", this.id);
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
asyncResolvers: {
|
|
114
|
+
[id: string]: { resolve: (arg: any) => void; reject: (arg: any) => void };
|
|
115
|
+
} = {};
|
|
116
|
+
|
|
117
|
+
callAsyncJavaScript({ script }: { script: string }) {
|
|
118
|
+
return new Promise((resolve, reject) => {
|
|
119
|
+
const messageId = "" + Date.now() + Math.random();
|
|
120
|
+
this.asyncResolvers[messageId] = {
|
|
121
|
+
resolve,
|
|
122
|
+
reject,
|
|
123
|
+
};
|
|
124
|
+
|
|
125
|
+
this.internalRpc.request.webviewTagCallAsyncJavaScript({
|
|
126
|
+
messageId,
|
|
127
|
+
webviewId: this.webviewId,
|
|
128
|
+
hostWebviewId: window.__electrobunWebviewId,
|
|
129
|
+
script,
|
|
130
|
+
});
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
setCallAsyncJavaScriptResponse(messageId: string, response: any) {
|
|
135
|
+
const resolvers = this.asyncResolvers[messageId];
|
|
136
|
+
delete this.asyncResolvers[messageId];
|
|
137
|
+
try {
|
|
138
|
+
response = JSON.parse(response);
|
|
139
|
+
|
|
140
|
+
if (response.result) {
|
|
141
|
+
resolvers.resolve(response.result);
|
|
142
|
+
} else {
|
|
143
|
+
resolvers.reject(response.error);
|
|
144
|
+
}
|
|
145
|
+
} catch (e: any) {
|
|
146
|
+
resolvers.reject(e.message);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
async canGoBack() {
|
|
151
|
+
return this.internalRpc.request.webviewTagCanGoBack({ id: this.webviewId });
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
async canGoForward() {
|
|
155
|
+
return this.internalRpc.request.webviewTagCanGoForward({
|
|
156
|
+
id: this.webviewId,
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// propertie setters/getters. keeps them in sync with dom attributes
|
|
161
|
+
updateAttr(name: string, value: string | null) {
|
|
162
|
+
if (value) {
|
|
163
|
+
this.setAttribute(name, value);
|
|
164
|
+
} else {
|
|
165
|
+
this.removeAttribute(name);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
get src() {
|
|
170
|
+
return this.getAttribute("src");
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
set src(value) {
|
|
174
|
+
this.updateAttr("src", value);
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
get html() {
|
|
178
|
+
return this.getAttribute("html");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
set html(value) {
|
|
182
|
+
this.updateAttr("html", value);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
get preload() {
|
|
186
|
+
return this.getAttribute("preload");
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
set preload(value) {
|
|
190
|
+
this.updateAttr("preload", value);
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
get renderer() {
|
|
194
|
+
const _renderer = this.getAttribute("renderer") === "cef" ? "cef" : "native";
|
|
195
|
+
return _renderer;
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
set renderer(value: 'cef' | 'native') {
|
|
199
|
+
const _renderer = value === "cef" ? "cef" : "native";
|
|
200
|
+
this.updateAttr("renderer", _renderer);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// Note: since <electrobun-webview> is an anchor for a native webview
|
|
204
|
+
// on osx even if we hide it, enable mouse passthrough etc. There
|
|
205
|
+
// are still events like drag events which are natively handled deep in the window manager
|
|
206
|
+
// and will be handled incorrectly. To get around this for now we need to
|
|
207
|
+
// move the webview off screen during delegate mode.
|
|
208
|
+
adjustDimensionsForHiddenMirrorMode(rect: DOMRect) {
|
|
209
|
+
if (this.hiddenMirrorMode) {
|
|
210
|
+
rect.x = 0 - rect.width;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
return rect;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Note: in the brwoser-context we can ride on the dom element's uilt in event emitter for managing custom events
|
|
217
|
+
on(event: WebviewEventTypes, listener: () => {}) {
|
|
218
|
+
this.addEventListener(event, listener);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
off(event: WebviewEventTypes, listener: () => {}) {
|
|
222
|
+
this.removeEventListener(event, listener);
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// This is typically called by injected js from bun
|
|
226
|
+
emit(event: WebviewEventTypes, detail: any) {
|
|
227
|
+
this.dispatchEvent(new CustomEvent(event, { detail }));
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
// Call this via document.querySelector('electrobun-webview').syncDimensions();
|
|
231
|
+
// That way the host can trigger an alignment with the nested webview when they
|
|
232
|
+
// know that they're chaning something in order to eliminate the lag that the
|
|
233
|
+
// catch all loop will catch
|
|
234
|
+
syncDimensions(force: boolean = false) {
|
|
235
|
+
if (!this.webviewId || (!force && this.hidden)) {
|
|
236
|
+
return;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const rect = this.getBoundingClientRect();
|
|
240
|
+
const { x, y, width, height } =
|
|
241
|
+
this.adjustDimensionsForHiddenMirrorMode(rect);
|
|
242
|
+
const lastRect = this.lastRect;
|
|
243
|
+
|
|
244
|
+
if (width === 0 && height === 0) {
|
|
245
|
+
if (this.wasZeroRect === false) {
|
|
246
|
+
this.wasZeroRect = true;
|
|
247
|
+
this.toggleHidden(true, true);
|
|
248
|
+
}
|
|
249
|
+
return;
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
const masks: Rect[] = [];
|
|
253
|
+
this.maskSelectors.forEach((selector) => {
|
|
254
|
+
const els = document.querySelectorAll(selector);
|
|
255
|
+
|
|
256
|
+
for (let i = 0; i < els.length; i++) {
|
|
257
|
+
const el = els[i];
|
|
258
|
+
|
|
259
|
+
if (el) {
|
|
260
|
+
const maskRect = el.getBoundingClientRect();
|
|
261
|
+
|
|
262
|
+
masks.push({
|
|
263
|
+
// reposition the bounding rect to be relative to the webview rect
|
|
264
|
+
// so objc can apply the mask correctly and handle the actual overlap
|
|
265
|
+
x: maskRect.x - x,
|
|
266
|
+
y: maskRect.y - y,
|
|
267
|
+
width: maskRect.width,
|
|
268
|
+
height: maskRect.height,
|
|
269
|
+
});
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// store jsonStringified last masks value to compare
|
|
275
|
+
const masksJson = masks.length ? JSON.stringify(masks) : "";
|
|
276
|
+
|
|
277
|
+
if (
|
|
278
|
+
force ||
|
|
279
|
+
lastRect.x !== x ||
|
|
280
|
+
lastRect.y !== y ||
|
|
281
|
+
lastRect.width !== width ||
|
|
282
|
+
lastRect.height !== height ||
|
|
283
|
+
this.lastMasksJSON !== masksJson
|
|
284
|
+
) {
|
|
285
|
+
// let it know we're still accelerating
|
|
286
|
+
this.setPositionCheckLoop(true);
|
|
287
|
+
|
|
288
|
+
this.lastRect = rect;
|
|
289
|
+
this.lastMasks = masks;
|
|
290
|
+
this.lastMasksJSON = masksJson;
|
|
291
|
+
|
|
292
|
+
this.internalRpc.send.webviewTagResize({
|
|
293
|
+
id: this.webviewId,
|
|
294
|
+
frame: {
|
|
295
|
+
width: width,
|
|
296
|
+
height: height,
|
|
297
|
+
x: x,
|
|
298
|
+
y: y,
|
|
299
|
+
},
|
|
300
|
+
masks: masksJson,
|
|
301
|
+
});
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
if (this.wasZeroRect) {
|
|
305
|
+
this.wasZeroRect = false;
|
|
306
|
+
this.toggleHidden(false, true);
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
boundSyncDimensions = () => this.syncDimensions();
|
|
311
|
+
boundForceSyncDimensions = () => this.syncDimensions(true);
|
|
312
|
+
|
|
313
|
+
setPositionCheckLoop(accelerate = false) {
|
|
314
|
+
if (this.positionCheckLoop) {
|
|
315
|
+
clearInterval(this.positionCheckLoop);
|
|
316
|
+
this.positionCheckLoop = undefined;
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
if (this.positionCheckLoopReset) {
|
|
320
|
+
clearTimeout(this.positionCheckLoopReset);
|
|
321
|
+
this.positionCheckLoopReset = undefined;
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
const delay = accelerate ? 0 : 300;
|
|
325
|
+
|
|
326
|
+
if (accelerate) {
|
|
327
|
+
this.positionCheckLoopReset = setTimeout(() => {
|
|
328
|
+
this.setPositionCheckLoop(false);
|
|
329
|
+
}, 2000);
|
|
330
|
+
}
|
|
331
|
+
// Note: Since there's not catch all way to listen for x/y changes
|
|
332
|
+
// we have a 400ms interval to check
|
|
333
|
+
// on m1 max this 400ms interval for one nested webview
|
|
334
|
+
// only uses around 0.1% cpu
|
|
335
|
+
|
|
336
|
+
// Note: We also listen for resize events and changes to
|
|
337
|
+
// certain properties to get reactive repositioning for
|
|
338
|
+
// many cases.
|
|
339
|
+
|
|
340
|
+
// todo: consider having an option to disable this and let user
|
|
341
|
+
// trigger position sync for high performance cases (like
|
|
342
|
+
// a browser with a hundred tabs)
|
|
343
|
+
this.positionCheckLoop = setInterval(() => this.syncDimensions(), delay);
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
connectedCallback() {
|
|
347
|
+
this.setPositionCheckLoop();
|
|
348
|
+
|
|
349
|
+
this.resizeObserver = new ResizeObserver(() => {
|
|
350
|
+
this.syncDimensions();
|
|
351
|
+
});
|
|
352
|
+
// Note: In objc the webview is positioned in the window from the bottom-left corner
|
|
353
|
+
// the html anchor is positioned in the webview from the top-left corner
|
|
354
|
+
// In those cases the getBoundingClientRect() will return the same value, but
|
|
355
|
+
// we still need to send it to objc to calculate from its bottom left position
|
|
356
|
+
// otherwise it'll move around unexpectedly.
|
|
357
|
+
window.addEventListener("resize", this.boundForceSyncDimensions);
|
|
358
|
+
window.addEventListener("scroll", this.boundSyncDimensions);
|
|
359
|
+
|
|
360
|
+
// todo: For chromium webviews (windows native or chromium bundled)
|
|
361
|
+
// should be able to use performanceObservers on layout-shift to
|
|
362
|
+
// call syncDimensions more reactively
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
disconnectedCallback() {
|
|
366
|
+
// removed from the dom
|
|
367
|
+
clearInterval(this.positionCheckLoop);
|
|
368
|
+
|
|
369
|
+
this.resizeObserver?.disconnect();
|
|
370
|
+
// this.intersectionObserver?.disconnect();
|
|
371
|
+
// this.mutationObserver?.disconnect();
|
|
372
|
+
window.removeEventListener("resize", this.boundForceSyncDimensions);
|
|
373
|
+
window.removeEventListener("scroll", this.boundSyncDimensions);
|
|
374
|
+
this.internalRpc.send.webviewTagRemove({ id: this.webviewId });
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
static get observedAttributes() {
|
|
378
|
+
// TODO: support html, preload, and other stuff here
|
|
379
|
+
return ["src", "html", "preload", "class", "style"];
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
attributeChangedCallback(name, oldValue, newValue) {
|
|
383
|
+
if (name === "src" && oldValue !== newValue) {
|
|
384
|
+
this.updateIFrameSrc(newValue);
|
|
385
|
+
} else if (name === "html" && oldValue !== newValue) {
|
|
386
|
+
this.updateIFrameHtml(newValue);
|
|
387
|
+
} else if (name === "preload" && oldValue !== newValue) {
|
|
388
|
+
this.updateIFramePreload(newValue);
|
|
389
|
+
} else {
|
|
390
|
+
this.syncDimensions();
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
updateIFrameSrc(src: string) {
|
|
395
|
+
if (!this.webviewId) {
|
|
396
|
+
return;
|
|
397
|
+
}
|
|
398
|
+
this.internalRpc.send.webviewTagUpdateSrc({
|
|
399
|
+
id: this.webviewId,
|
|
400
|
+
url: src,
|
|
401
|
+
});
|
|
402
|
+
}
|
|
403
|
+
|
|
404
|
+
updateIFrameHtml(html: string) {
|
|
405
|
+
if (!this.webviewId) {
|
|
406
|
+
return;
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
this.internalRpc.send.webviewTagUpdateHtml({
|
|
410
|
+
id: this.webviewId,
|
|
411
|
+
html: html,
|
|
412
|
+
});
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
updateIFramePreload(preload: string) {
|
|
416
|
+
if (!this.webviewId) {
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
this.internalRpc.send.webviewTagUpdatePreload({
|
|
420
|
+
id: this.webviewId,
|
|
421
|
+
preload,
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
goBack() {
|
|
426
|
+
this.internalRpc.send.webviewTagGoBack({ id: this.webviewId });
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
goForward() {
|
|
430
|
+
this.internalRpc.send.webviewTagGoForward({ id: this.webviewId });
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
reload() {
|
|
434
|
+
this.internalRpc.send.webviewTagReload({ id: this.webviewId });
|
|
435
|
+
}
|
|
436
|
+
loadURL(url: string) {
|
|
437
|
+
this.setAttribute("src", url);
|
|
438
|
+
this.internalRpc.send.webviewTagUpdateSrc({
|
|
439
|
+
id: this.webviewId,
|
|
440
|
+
url,
|
|
441
|
+
});
|
|
442
|
+
}
|
|
443
|
+
loadHTML(html: string) {
|
|
444
|
+
this.setAttribute("html", html);
|
|
445
|
+
this.internalRpc.send.webviewTagUpdateHtml({
|
|
446
|
+
id: this.webviewId,
|
|
447
|
+
html,
|
|
448
|
+
})
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
// This sets the native webview hovering over the dom to be transparent
|
|
452
|
+
toggleTransparent(transparent?: boolean, bypassState?: boolean) {
|
|
453
|
+
if (!bypassState) {
|
|
454
|
+
if (typeof transparent === "undefined") {
|
|
455
|
+
this.transparent = !this.transparent;
|
|
456
|
+
} else {
|
|
457
|
+
this.transparent = transparent;
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
this.internalRpc.send.webviewTagSetTransparent({
|
|
462
|
+
id: this.webviewId,
|
|
463
|
+
transparent: this.transparent || Boolean(transparent),
|
|
464
|
+
});
|
|
465
|
+
}
|
|
466
|
+
togglePassthrough(enablePassthrough?: boolean, bypassState?: boolean) {
|
|
467
|
+
if (!bypassState) {
|
|
468
|
+
if (typeof enablePassthrough === "undefined") {
|
|
469
|
+
this.passthroughEnabled = !this.passthroughEnabled;
|
|
470
|
+
} else {
|
|
471
|
+
this.passthroughEnabled = enablePassthrough;
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
this.internalRpc.send.webviewTagSetPassthrough({
|
|
476
|
+
id: this.webviewId,
|
|
477
|
+
enablePassthrough:
|
|
478
|
+
this.passthroughEnabled || Boolean(enablePassthrough),
|
|
479
|
+
});
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
toggleHidden(hidden?: boolean, bypassState?: boolean) {
|
|
483
|
+
if (!bypassState) {
|
|
484
|
+
if (typeof hidden === "undefined") {
|
|
485
|
+
this.hidden = !this.hidden;
|
|
486
|
+
} else {
|
|
487
|
+
this.hidden = hidden;
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
this.internalRpc.send.webviewTagSetHidden({
|
|
492
|
+
id: this.webviewId,
|
|
493
|
+
hidden: this.hidden || Boolean(hidden),
|
|
494
|
+
});
|
|
495
|
+
}
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
customElements.define("electrobun-webview", WebviewTag);
|
|
499
|
+
|
|
500
|
+
insertWebviewTagNormalizationStyles();
|
|
501
|
+
};
|
|
502
|
+
|
|
503
|
+
// Give <electrobun-webview>s some default styles that can
|
|
504
|
+
// be easily overridden in the host document
|
|
505
|
+
const insertWebviewTagNormalizationStyles = () => {
|
|
506
|
+
var style = document.createElement("style");
|
|
507
|
+
style.type = "text/css";
|
|
508
|
+
|
|
509
|
+
var css = `
|
|
510
|
+
electrobun-webview {
|
|
511
|
+
display: block;
|
|
512
|
+
width: 800px;
|
|
513
|
+
height: 300px;
|
|
514
|
+
background: #fff;
|
|
515
|
+
background-repeat: no-repeat!important;
|
|
516
|
+
overflow: hidden;
|
|
517
|
+
}
|
|
518
|
+
`;
|
|
519
|
+
|
|
520
|
+
style.appendChild(document.createTextNode(css));
|
|
521
|
+
|
|
522
|
+
var head = document.getElementsByTagName("head")[0];
|
|
523
|
+
if (!head) {
|
|
524
|
+
return;
|
|
525
|
+
}
|
|
526
|
+
|
|
527
|
+
if (head.firstChild) {
|
|
528
|
+
head.insertBefore(style, head.firstChild);
|
|
529
|
+
} else {
|
|
530
|
+
head.appendChild(style);
|
|
531
|
+
}
|
|
532
|
+
};
|
|
533
|
+
|
|
534
|
+
export { ConfigureWebviewTags };
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { ffi, type ApplicationMenuItemConfig } from "../proc/native";
|
|
2
|
+
import electrobunEventEmitter from "../events/eventEmitter";
|
|
3
|
+
|
|
4
|
+
export const setApplicationMenu = (menu: Array<ApplicationMenuItemConfig>) => {
|
|
5
|
+
const menuWithDefaults = menuConfigWithDefaults(menu);
|
|
6
|
+
ffi.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
|
+
};
|