@atom-js-org/runtime 0.5.3-alpha.0 → 0.5.3-alpha.1
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 -3
- package/src/app.cjs +2 -0
- package/src/browser-window.cjs +6 -2
- package/src/runtime/window-host.mjs +1 -1
- package/src/windows-native-host.cjs +209 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@atom-js-org/runtime",
|
|
3
|
-
"version": "0.5.3-alpha.
|
|
3
|
+
"version": "0.5.3-alpha.1",
|
|
4
4
|
"description": "Electron-like desktop runtime powered by the operating system WebView.",
|
|
5
5
|
"main": "src/index.cjs",
|
|
6
6
|
"types": "index.d.ts",
|
|
@@ -19,10 +19,11 @@
|
|
|
19
19
|
"LICENSE"
|
|
20
20
|
],
|
|
21
21
|
"engines": {
|
|
22
|
-
"node": ">=
|
|
22
|
+
"node": ">=24"
|
|
23
23
|
},
|
|
24
24
|
"dependencies": {
|
|
25
|
-
"ws": "^8.21.1"
|
|
25
|
+
"ws": "^8.21.1",
|
|
26
|
+
"@webviewjs/webview": "0.4.0"
|
|
26
27
|
},
|
|
27
28
|
"repository": {
|
|
28
29
|
"type": "git",
|
package/src/app.cjs
CHANGED
|
@@ -7,6 +7,7 @@ const fs = require('node:fs');
|
|
|
7
7
|
const state = require('./state.cjs');
|
|
8
8
|
const { BridgeServer } = require('./bridge-server.cjs');
|
|
9
9
|
const { stopNativeHost } = require('./native-host.cjs');
|
|
10
|
+
const { stopWindowsNativeHost } = require('./windows-native-host.cjs');
|
|
10
11
|
|
|
11
12
|
class App extends EventEmitter {
|
|
12
13
|
constructor() {
|
|
@@ -43,6 +44,7 @@ class App extends EventEmitter {
|
|
|
43
44
|
|
|
44
45
|
for (const win of [...state.windows.values()]) win.destroy();
|
|
45
46
|
await stopNativeHost();
|
|
47
|
+
await stopWindowsNativeHost();
|
|
46
48
|
if (state.bridgeServer) await state.bridgeServer.stop();
|
|
47
49
|
this.emit('will-quit');
|
|
48
50
|
this.emit('quit', {}, 0);
|
package/src/browser-window.cjs
CHANGED
|
@@ -10,6 +10,7 @@ const app = require('./app.cjs');
|
|
|
10
10
|
const { WebContents } = require('./web-contents.cjs');
|
|
11
11
|
const { generateBridgeScript } = require('./bridge-script.cjs');
|
|
12
12
|
const { getNativeHost } = require('./native-host.cjs');
|
|
13
|
+
const { getWindowsNativeHost } = require('./windows-native-host.cjs');
|
|
13
14
|
|
|
14
15
|
class BrowserWindow extends EventEmitter {
|
|
15
16
|
constructor(options = {}) {
|
|
@@ -136,6 +137,10 @@ class BrowserWindow extends EventEmitter {
|
|
|
136
137
|
this._nativeHost = getNativeHost(app.getName());
|
|
137
138
|
this._hostAttached = true;
|
|
138
139
|
await this._nativeHost.createWindow(this, config);
|
|
140
|
+
} else if (process.platform === 'win32') {
|
|
141
|
+
this._nativeHost = getWindowsNativeHost();
|
|
142
|
+
this._hostAttached = true;
|
|
143
|
+
await this._nativeHost.createWindow(this, config);
|
|
139
144
|
} else {
|
|
140
145
|
await this._startLegacyHost(config);
|
|
141
146
|
}
|
|
@@ -213,8 +218,7 @@ class BrowserWindow extends EventEmitter {
|
|
|
213
218
|
|
|
214
219
|
_sendHostCommand(command) {
|
|
215
220
|
if (this._nativeHost) {
|
|
216
|
-
this._nativeHost.send({ ...command, windowId: this.id });
|
|
217
|
-
return true;
|
|
221
|
+
return this._nativeHost.send({ ...command, windowId: this.id }) !== false;
|
|
218
222
|
}
|
|
219
223
|
return false;
|
|
220
224
|
}
|
|
@@ -29,7 +29,7 @@ try {
|
|
|
29
29
|
({ Webview, SizeHint } = await import('webview-nodejs'));
|
|
30
30
|
} catch (error) {
|
|
31
31
|
console.error('\nAtomJS could not load the system WebView binding.');
|
|
32
|
-
console.error('
|
|
32
|
+
console.error('Linux currently requires the webview-nodejs package.');
|
|
33
33
|
console.error('Run `atom doctor`, install the platform prerequisites, then install dependencies again.');
|
|
34
34
|
console.error(error && error.stack ? error.stack : error);
|
|
35
35
|
process.exit(1);
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
let singleton = null;
|
|
4
|
+
|
|
5
|
+
class WindowsNativeHost {
|
|
6
|
+
constructor() {
|
|
7
|
+
this.binding = null;
|
|
8
|
+
this.application = null;
|
|
9
|
+
this.startPromise = null;
|
|
10
|
+
this.windows = new Map();
|
|
11
|
+
this.stopping = false;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
async ensureStarted() {
|
|
15
|
+
if (process.platform !== 'win32') {
|
|
16
|
+
throw new Error('The in-process Windows native host can only run on Windows.');
|
|
17
|
+
}
|
|
18
|
+
if (!this.startPromise) {
|
|
19
|
+
this.startPromise = this._start().catch((error) => {
|
|
20
|
+
this.startPromise = null;
|
|
21
|
+
throw error;
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
await this.startPromise;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
async _start() {
|
|
28
|
+
let binding;
|
|
29
|
+
try {
|
|
30
|
+
binding = require('@webviewjs/webview');
|
|
31
|
+
} catch (error) {
|
|
32
|
+
const wrapped = new Error([
|
|
33
|
+
'AtomJS could not load the prebuilt Windows WebView binding.',
|
|
34
|
+
'Delete node_modules and package-lock.json, then run npm install again.',
|
|
35
|
+
'CMake and Visual Studio Build Tools are not required.',
|
|
36
|
+
error && error.message ? error.message : String(error)
|
|
37
|
+
].join('\n'));
|
|
38
|
+
wrapped.cause = error;
|
|
39
|
+
throw wrapped;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
this.binding = binding;
|
|
43
|
+
this.application = new binding.Application();
|
|
44
|
+
await this.application.whenReady({ interval: 16, ref: true });
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
async createWindow(atomWindow, config) {
|
|
48
|
+
await this.ensureStarted();
|
|
49
|
+
|
|
50
|
+
const parent = config.parentWindowId ? this.windows.get(Number(config.parentWindowId)) : null;
|
|
51
|
+
const nativeOptions = {
|
|
52
|
+
title: String(config.title || 'AtomJS App'),
|
|
53
|
+
width: positive(config.width, 800),
|
|
54
|
+
height: positive(config.height, 600),
|
|
55
|
+
resizable: config.resizable !== false,
|
|
56
|
+
visible: false,
|
|
57
|
+
decorations: config.frame !== false,
|
|
58
|
+
alwaysOnTop: Boolean(config.alwaysOnTop),
|
|
59
|
+
maximizable: config.maximizable !== false,
|
|
60
|
+
minimizable: config.minimizable !== false,
|
|
61
|
+
focused: config.focusable !== false,
|
|
62
|
+
transparent: Boolean(config.transparent),
|
|
63
|
+
windowsSkipTaskbar: Boolean(config.skipTaskbar),
|
|
64
|
+
windowsClassName: sanitizeWindowsClass(process.env.ATOM_APP_ID || process.env.ATOM_APP_NAME || config.title)
|
|
65
|
+
};
|
|
66
|
+
if (Number.isFinite(Number(config.x))) nativeOptions.x = Math.round(Number(config.x));
|
|
67
|
+
if (Number.isFinite(Number(config.y))) nativeOptions.y = Math.round(Number(config.y));
|
|
68
|
+
if (parent && parent.nativeWindow && typeof parent.nativeWindow.getNativeHandle === 'function') {
|
|
69
|
+
nativeOptions.windowsOwnerWindow = parent.nativeWindow.getNativeHandle();
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const nativeWindow = this.application.createBrowserWindow(nativeOptions);
|
|
73
|
+
nativeWindow.setClosable(config.closable !== false);
|
|
74
|
+
if (Number(config.minWidth) > 0 || Number(config.minHeight) > 0) {
|
|
75
|
+
nativeWindow.setMinSize(positive(config.minWidth, 1), positive(config.minHeight, 1), true);
|
|
76
|
+
}
|
|
77
|
+
if (Number(config.maxWidth) > 0 || Number(config.maxHeight) > 0) {
|
|
78
|
+
nativeWindow.setMaxSize(positive(config.maxWidth, 100000), positive(config.maxHeight, 100000), true);
|
|
79
|
+
}
|
|
80
|
+
if (config.center !== false && !Number.isFinite(Number(config.x)) && !Number.isFinite(Number(config.y))) {
|
|
81
|
+
nativeWindow.center();
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const webview = nativeWindow.createWebview({
|
|
85
|
+
url: String(config.url),
|
|
86
|
+
preload: String(config.bridgeScript || ''),
|
|
87
|
+
enableDevtools: Boolean(config.debug),
|
|
88
|
+
transparent: Boolean(config.transparent)
|
|
89
|
+
});
|
|
90
|
+
|
|
91
|
+
const record = { atomWindow, nativeWindow, webview };
|
|
92
|
+
this.windows.set(Number(config.windowId), record);
|
|
93
|
+
this._attachEvents(Number(config.windowId), record);
|
|
94
|
+
|
|
95
|
+
if (config.show === false) nativeWindow.hide();
|
|
96
|
+
else nativeWindow.show();
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
_attachEvents(windowId, record) {
|
|
100
|
+
const emit = (event) => {
|
|
101
|
+
if (!this.windows.has(windowId)) return;
|
|
102
|
+
try { record.atomWindow._handleHostEvent({ ...event, windowId }); } catch {}
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
record.nativeWindow.on('close', () => {
|
|
106
|
+
try { record.atomWindow._handleHostEvent({ type: 'closed', windowId }); } catch {}
|
|
107
|
+
this.windows.delete(windowId);
|
|
108
|
+
});
|
|
109
|
+
record.nativeWindow.on('focus', () => emit({ type: 'focus' }));
|
|
110
|
+
record.nativeWindow.on('blur', () => emit({ type: 'blur' }));
|
|
111
|
+
record.nativeWindow.on('move', (event) => emit({
|
|
112
|
+
type: 'bounds-changed',
|
|
113
|
+
reason: 'move',
|
|
114
|
+
bounds: { x: Number(event.x), y: Number(event.y) }
|
|
115
|
+
}));
|
|
116
|
+
record.nativeWindow.on('resize', (event) => emit({
|
|
117
|
+
type: 'bounds-changed',
|
|
118
|
+
reason: 'resize',
|
|
119
|
+
bounds: { width: Number(event.width), height: Number(event.height) }
|
|
120
|
+
}));
|
|
121
|
+
record.webview.on('page-load-started', (event) => emit({ type: 'did-start-loading', url: event.url || '' }));
|
|
122
|
+
record.webview.on('page-load-finished', (event) => emit({ type: 'did-finish-load', url: event.url || '' }));
|
|
123
|
+
record.webview.on('title-changed', (event) => emit({ type: 'page-title-updated', title: event.title || '' }));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
send(message) {
|
|
127
|
+
const record = this.windows.get(Number(message.windowId));
|
|
128
|
+
if (!record) return false;
|
|
129
|
+
const win = record.nativeWindow;
|
|
130
|
+
const view = record.webview;
|
|
131
|
+
|
|
132
|
+
switch (message.command) {
|
|
133
|
+
case 'navigate': view.loadUrl(String(message.url)); return true;
|
|
134
|
+
case 'show': win.show(); return true;
|
|
135
|
+
case 'hide': win.hide(); return true;
|
|
136
|
+
case 'focus': win.focus(); return true;
|
|
137
|
+
case 'close': win.close(); return true;
|
|
138
|
+
case 'destroy':
|
|
139
|
+
this.windows.delete(Number(message.windowId));
|
|
140
|
+
try { view.dispose(); } catch {}
|
|
141
|
+
try { win.dispose(); } catch {}
|
|
142
|
+
return true;
|
|
143
|
+
case 'set-title': win.setTitle(String(message.title || '')); return true;
|
|
144
|
+
case 'set-always-on-top': win.setAlwaysOnTop(Boolean(message.value)); return true;
|
|
145
|
+
case 'set-resizable': win.setResizable(Boolean(message.value)); return true;
|
|
146
|
+
case 'fullscreen':
|
|
147
|
+
win.setFullscreen(message.value ? this.binding.FullscreenType.Borderless : null);
|
|
148
|
+
return true;
|
|
149
|
+
case 'maximize': win.setMaximized(true); return true;
|
|
150
|
+
case 'unmaximize': win.setMaximized(false); return true;
|
|
151
|
+
case 'minimize': win.setMinimized(true); return true;
|
|
152
|
+
case 'restore':
|
|
153
|
+
win.setMinimized(false);
|
|
154
|
+
win.setMaximized(false);
|
|
155
|
+
win.show();
|
|
156
|
+
return true;
|
|
157
|
+
case 'set-bounds': {
|
|
158
|
+
const bounds = message.bounds || {};
|
|
159
|
+
if (Number.isFinite(Number(bounds.width)) && Number.isFinite(Number(bounds.height))) {
|
|
160
|
+
win.setSize(Math.round(Number(bounds.width)), Math.round(Number(bounds.height)), true);
|
|
161
|
+
}
|
|
162
|
+
if (Number.isFinite(Number(bounds.x)) && Number.isFinite(Number(bounds.y))) {
|
|
163
|
+
win.setPosition(Math.round(Number(bounds.x)), Math.round(Number(bounds.y)), true);
|
|
164
|
+
}
|
|
165
|
+
return true;
|
|
166
|
+
}
|
|
167
|
+
default: return false;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
async stop() {
|
|
172
|
+
if (this.stopping) return;
|
|
173
|
+
this.stopping = true;
|
|
174
|
+
for (const record of this.windows.values()) {
|
|
175
|
+
try { record.webview.dispose(); } catch {}
|
|
176
|
+
try { record.nativeWindow.dispose(); } catch {}
|
|
177
|
+
}
|
|
178
|
+
this.windows.clear();
|
|
179
|
+
if (this.application) {
|
|
180
|
+
try { this.application.exit(); } catch {}
|
|
181
|
+
}
|
|
182
|
+
this.application = null;
|
|
183
|
+
this.startPromise = null;
|
|
184
|
+
this.stopping = false;
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function positive(value, fallback) {
|
|
189
|
+
const number = Number(value);
|
|
190
|
+
return Number.isFinite(number) && number > 0 ? Math.round(number) : fallback;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
function sanitizeWindowsClass(value) {
|
|
194
|
+
return String(value || 'AtomJS.App').replace(/[^A-Za-z0-9._-]+/g, '.').slice(0, 120) || 'AtomJS.App';
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
function getWindowsNativeHost() {
|
|
198
|
+
if (!singleton) singleton = new WindowsNativeHost();
|
|
199
|
+
return singleton;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
async function stopWindowsNativeHost() {
|
|
203
|
+
if (!singleton) return;
|
|
204
|
+
const current = singleton;
|
|
205
|
+
singleton = null;
|
|
206
|
+
await current.stop();
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
module.exports = { WindowsNativeHost, getWindowsNativeHost, stopWindowsNativeHost };
|