@tutti-os/browser-node 0.0.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/LICENSE +202 -0
- package/README.md +21 -0
- package/dist/assets/workspace-dock-website.d.ts +3 -0
- package/dist/assets/workspace-dock-website.png +0 -0
- package/dist/bridge/index.d.ts +15 -0
- package/dist/bridge/index.js +9 -0
- package/dist/bridge/index.js.map +1 -0
- package/dist/chunk-2GEY55PS.js +1179 -0
- package/dist/chunk-2GEY55PS.js.map +1 -0
- package/dist/chunk-IO5AJ2R5.js +111 -0
- package/dist/chunk-IO5AJ2R5.js.map +1 -0
- package/dist/chunk-IS2USG4D.js +93 -0
- package/dist/chunk-IS2USG4D.js.map +1 -0
- package/dist/chunk-LVVPDNEF.js +93 -0
- package/dist/chunk-LVVPDNEF.js.map +1 -0
- package/dist/chunk-UTXZLRPE.js +34 -0
- package/dist/chunk-UTXZLRPE.js.map +1 -0
- package/dist/electron-main/index.d.ts +158 -0
- package/dist/electron-main/index.js +1492 -0
- package/dist/electron-main/index.js.map +1 -0
- package/dist/electron-preload/index.d.ts +33 -0
- package/dist/electron-preload/index.js +140 -0
- package/dist/electron-preload/index.js.map +1 -0
- package/dist/i18n/index.d.ts +18 -0
- package/dist/i18n/index.js +13 -0
- package/dist/i18n/index.js.map +1 -0
- package/dist/index.d.ts +187 -0
- package/dist/index.js +165 -0
- package/dist/index.js.map +1 -0
- package/dist/react/index.d.ts +41 -0
- package/dist/react/index.js +11 -0
- package/dist/react/index.js.map +1 -0
- package/dist/types-Bmzz1Q84.d.ts +32 -0
- package/dist/workbench/index.d.ts +55 -0
- package/dist/workbench/index.js +194 -0
- package/dist/workbench/index.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1,1179 @@
|
|
|
1
|
+
import {
|
|
2
|
+
resolveBrowserSessionPartition
|
|
3
|
+
} from "./chunk-UTXZLRPE.js";
|
|
4
|
+
import {
|
|
5
|
+
normalizeBrowserComparableUrl,
|
|
6
|
+
normalizeHostBrowserComparableUrl
|
|
7
|
+
} from "./chunk-LVVPDNEF.js";
|
|
8
|
+
|
|
9
|
+
// src/react/BrowserNode.tsx
|
|
10
|
+
import {
|
|
11
|
+
ArrowLeftIcon,
|
|
12
|
+
ArrowRightIcon,
|
|
13
|
+
Badge,
|
|
14
|
+
Button,
|
|
15
|
+
Input,
|
|
16
|
+
LaunchIcon,
|
|
17
|
+
LoadingIcon,
|
|
18
|
+
RefreshIcon,
|
|
19
|
+
WarningLinedIcon,
|
|
20
|
+
ViewportMenuSurface,
|
|
21
|
+
cn,
|
|
22
|
+
menuItemClassName
|
|
23
|
+
} from "@tutti-os/ui-system";
|
|
24
|
+
import { useEffect as useEffect3, useRef, useState } from "react";
|
|
25
|
+
|
|
26
|
+
// src/react/useBrowserNodeController.ts
|
|
27
|
+
import { useEffect, useMemo } from "react";
|
|
28
|
+
import { useExternalStoreSnapshot } from "@tutti-os/ui-react-hooks";
|
|
29
|
+
|
|
30
|
+
// src/core/nodeController.ts
|
|
31
|
+
var controllerRegistry = /* @__PURE__ */ new Map();
|
|
32
|
+
function acquireBrowserNodeController(input) {
|
|
33
|
+
const existing = controllerRegistry.get(input.nodeId);
|
|
34
|
+
const entry = existing ?? createBrowserNodeControllerEntry({
|
|
35
|
+
defaultUrl: input.defaultUrl,
|
|
36
|
+
feature: input.feature,
|
|
37
|
+
navigationPolicy: input.navigationPolicy,
|
|
38
|
+
nodeId: input.nodeId,
|
|
39
|
+
profileId: input.profileId ?? null,
|
|
40
|
+
sessionMode: input.sessionMode ?? "shared",
|
|
41
|
+
sessionPartition: input.sessionPartition,
|
|
42
|
+
syncDefaultUrl: input.syncDefaultUrl ?? false
|
|
43
|
+
});
|
|
44
|
+
entry.context = {
|
|
45
|
+
defaultUrl: input.defaultUrl,
|
|
46
|
+
feature: input.feature,
|
|
47
|
+
navigationPolicy: input.navigationPolicy,
|
|
48
|
+
nodeId: input.nodeId,
|
|
49
|
+
profileId: input.profileId ?? null,
|
|
50
|
+
sessionMode: input.sessionMode ?? "shared",
|
|
51
|
+
sessionPartition: input.sessionPartition,
|
|
52
|
+
syncDefaultUrl: input.syncDefaultUrl ?? false
|
|
53
|
+
};
|
|
54
|
+
if (!existing) {
|
|
55
|
+
controllerRegistry.set(input.nodeId, entry);
|
|
56
|
+
}
|
|
57
|
+
reconcileBrowserNodeControllerState(entry, {
|
|
58
|
+
allowAutoActivate: false,
|
|
59
|
+
notifyListeners: false
|
|
60
|
+
});
|
|
61
|
+
return entry.controller;
|
|
62
|
+
}
|
|
63
|
+
function createBrowserNodeControllerEntry(context) {
|
|
64
|
+
const runtime = context.feature.runtimeStore.getNodeState(context.nodeId);
|
|
65
|
+
const displayUrl = resolveBrowserNodeDisplayUrl(runtime, context.defaultUrl);
|
|
66
|
+
const entry = {
|
|
67
|
+
connectedRelease: null,
|
|
68
|
+
controller: null,
|
|
69
|
+
context,
|
|
70
|
+
lastColdActivationUrl: resolveInitialLastColdActivationUrl(
|
|
71
|
+
runtime,
|
|
72
|
+
context.defaultUrl
|
|
73
|
+
),
|
|
74
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
75
|
+
pendingColdActivationUrl: null,
|
|
76
|
+
refCount: 0,
|
|
77
|
+
runtimeUnsubscribe: null,
|
|
78
|
+
state: {
|
|
79
|
+
displayUrl,
|
|
80
|
+
draftUrl: displayUrl,
|
|
81
|
+
runtime
|
|
82
|
+
}
|
|
83
|
+
};
|
|
84
|
+
entry.controller = {
|
|
85
|
+
getState() {
|
|
86
|
+
return entry.state;
|
|
87
|
+
},
|
|
88
|
+
goBack() {
|
|
89
|
+
return entry.context.feature.hostApi.goBack({
|
|
90
|
+
nodeId: entry.context.nodeId
|
|
91
|
+
});
|
|
92
|
+
},
|
|
93
|
+
goForward() {
|
|
94
|
+
return entry.context.feature.hostApi.goForward({
|
|
95
|
+
nodeId: entry.context.nodeId
|
|
96
|
+
});
|
|
97
|
+
},
|
|
98
|
+
reload() {
|
|
99
|
+
return entry.context.feature.hostApi.reload({
|
|
100
|
+
nodeId: entry.context.nodeId
|
|
101
|
+
});
|
|
102
|
+
},
|
|
103
|
+
release() {
|
|
104
|
+
entry.refCount = Math.max(0, entry.refCount - 1);
|
|
105
|
+
if (entry.refCount > 0) {
|
|
106
|
+
return;
|
|
107
|
+
}
|
|
108
|
+
entry.connectedRelease?.();
|
|
109
|
+
entry.connectedRelease = null;
|
|
110
|
+
entry.runtimeUnsubscribe?.();
|
|
111
|
+
entry.runtimeUnsubscribe = null;
|
|
112
|
+
controllerRegistry.delete(entry.context.nodeId);
|
|
113
|
+
},
|
|
114
|
+
retain() {
|
|
115
|
+
entry.refCount += 1;
|
|
116
|
+
if (entry.refCount > 1) {
|
|
117
|
+
return;
|
|
118
|
+
}
|
|
119
|
+
if (!controllerRegistry.has(entry.context.nodeId)) {
|
|
120
|
+
controllerRegistry.set(entry.context.nodeId, entry);
|
|
121
|
+
}
|
|
122
|
+
entry.connectedRelease = entry.context.feature.connect();
|
|
123
|
+
entry.runtimeUnsubscribe = entry.context.feature.runtimeStore.subscribe(
|
|
124
|
+
() => {
|
|
125
|
+
reconcileBrowserNodeControllerState(entry, {
|
|
126
|
+
allowAutoActivate: true,
|
|
127
|
+
notifyListeners: true
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
);
|
|
131
|
+
reconcileBrowserNodeControllerState(entry, {
|
|
132
|
+
allowAutoActivate: true,
|
|
133
|
+
notifyListeners: true
|
|
134
|
+
});
|
|
135
|
+
},
|
|
136
|
+
setDraftUrl(nextUrl) {
|
|
137
|
+
if (entry.state.draftUrl === nextUrl) {
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
entry.state = {
|
|
141
|
+
...entry.state,
|
|
142
|
+
draftUrl: nextUrl
|
|
143
|
+
};
|
|
144
|
+
notifyBrowserNodeControllerListeners(entry);
|
|
145
|
+
},
|
|
146
|
+
sync() {
|
|
147
|
+
reconcileBrowserNodeControllerState(entry, {
|
|
148
|
+
allowAutoActivate: true,
|
|
149
|
+
notifyListeners: true
|
|
150
|
+
});
|
|
151
|
+
},
|
|
152
|
+
subscribe(listener) {
|
|
153
|
+
entry.listeners.add(listener);
|
|
154
|
+
return () => {
|
|
155
|
+
entry.listeners.delete(listener);
|
|
156
|
+
};
|
|
157
|
+
},
|
|
158
|
+
async submitDraftUrl() {
|
|
159
|
+
const resolved = entry.context.feature.resolveAddressInput(
|
|
160
|
+
entry.state.draftUrl
|
|
161
|
+
);
|
|
162
|
+
if (!resolved.url) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
if (entry.state.draftUrl !== resolved.url) {
|
|
166
|
+
entry.state = {
|
|
167
|
+
...entry.state,
|
|
168
|
+
draftUrl: resolved.url
|
|
169
|
+
};
|
|
170
|
+
notifyBrowserNodeControllerListeners(entry);
|
|
171
|
+
}
|
|
172
|
+
await entry.context.feature.hostApi.navigate({
|
|
173
|
+
navigationPolicy: entry.context.navigationPolicy,
|
|
174
|
+
nodeId: entry.context.nodeId,
|
|
175
|
+
url: resolved.url
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
return entry;
|
|
180
|
+
}
|
|
181
|
+
function resolveInitialLastColdActivationUrl(runtime, defaultUrl) {
|
|
182
|
+
const trimmedUrl = defaultUrl.trim();
|
|
183
|
+
if (runtime.lifecycle === "cold" || runtime.error !== null || trimmedUrl.length === 0 || trimmedUrl === "about:blank") {
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
const comparableDefaultUrl = normalizeBrowserComparableUrl(trimmedUrl);
|
|
187
|
+
const comparableRuntimeUrl = runtime.url ? normalizeBrowserComparableUrl(runtime.url) : null;
|
|
188
|
+
return comparableDefaultUrl !== null && comparableDefaultUrl === comparableRuntimeUrl ? trimmedUrl : null;
|
|
189
|
+
}
|
|
190
|
+
function notifyBrowserNodeControllerListeners(entry) {
|
|
191
|
+
for (const listener of entry.listeners) {
|
|
192
|
+
listener();
|
|
193
|
+
}
|
|
194
|
+
}
|
|
195
|
+
function resolveBrowserNodeDisplayUrl(runtime, defaultUrl) {
|
|
196
|
+
const resolvedRuntimeUrl = runtime.url?.trim() ?? "";
|
|
197
|
+
return resolvedRuntimeUrl.length > 0 ? resolvedRuntimeUrl : defaultUrl;
|
|
198
|
+
}
|
|
199
|
+
function reconcileBrowserNodeControllerState(entry, options) {
|
|
200
|
+
const runtime = entry.context.feature.runtimeStore.getNodeState(
|
|
201
|
+
entry.context.nodeId
|
|
202
|
+
);
|
|
203
|
+
const displayUrl = resolveBrowserNodeDisplayUrl(
|
|
204
|
+
runtime,
|
|
205
|
+
entry.context.defaultUrl
|
|
206
|
+
);
|
|
207
|
+
const nextDraftUrl = displayUrl !== entry.state.displayUrl ? displayUrl : entry.state.draftUrl;
|
|
208
|
+
const changed = entry.state.runtime !== runtime || entry.state.displayUrl !== displayUrl || entry.state.draftUrl !== nextDraftUrl;
|
|
209
|
+
if (changed) {
|
|
210
|
+
entry.state = {
|
|
211
|
+
displayUrl,
|
|
212
|
+
draftUrl: nextDraftUrl,
|
|
213
|
+
runtime
|
|
214
|
+
};
|
|
215
|
+
if (options.notifyListeners) {
|
|
216
|
+
notifyBrowserNodeControllerListeners(entry);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
if (options.allowAutoActivate) {
|
|
220
|
+
void maybeActivateBrowserNodeDefaultUrl(entry).catch(() => void 0);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
async function maybeActivateBrowserNodeDefaultUrl(entry) {
|
|
224
|
+
const {
|
|
225
|
+
defaultUrl,
|
|
226
|
+
feature,
|
|
227
|
+
navigationPolicy,
|
|
228
|
+
nodeId,
|
|
229
|
+
profileId,
|
|
230
|
+
sessionMode,
|
|
231
|
+
sessionPartition,
|
|
232
|
+
syncDefaultUrl
|
|
233
|
+
} = entry.context;
|
|
234
|
+
const trimmedUrl = defaultUrl.trim();
|
|
235
|
+
const comparableDefaultUrl = normalizeHostBrowserComparableUrl(trimmedUrl);
|
|
236
|
+
const comparableRuntimeUrl = entry.state.runtime.url ? normalizeHostBrowserComparableUrl(entry.state.runtime.url) : null;
|
|
237
|
+
const shouldActivateColdNode = entry.state.runtime.lifecycle === "cold";
|
|
238
|
+
const shouldSyncDefaultUrl = syncDefaultUrl && entry.state.runtime.lifecycle !== "cold" && comparableDefaultUrl !== null && (comparableRuntimeUrl !== comparableDefaultUrl || entry.state.runtime.error !== null) && entry.lastColdActivationUrl !== trimmedUrl;
|
|
239
|
+
if (trimmedUrl.length === 0 || trimmedUrl === "about:blank" || entry.state.runtime.isLoading || entry.pendingColdActivationUrl === trimmedUrl || !shouldActivateColdNode && !shouldSyncDefaultUrl || shouldActivateColdNode && entry.state.runtime.error !== null && entry.lastColdActivationUrl === trimmedUrl) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
entry.pendingColdActivationUrl = trimmedUrl;
|
|
243
|
+
try {
|
|
244
|
+
await feature.hostApi.activate({
|
|
245
|
+
navigationPolicy,
|
|
246
|
+
nodeId,
|
|
247
|
+
profileId,
|
|
248
|
+
sessionMode,
|
|
249
|
+
sessionPartition,
|
|
250
|
+
url: trimmedUrl
|
|
251
|
+
});
|
|
252
|
+
entry.lastColdActivationUrl = trimmedUrl;
|
|
253
|
+
} finally {
|
|
254
|
+
if (entry.pendingColdActivationUrl === trimmedUrl) {
|
|
255
|
+
entry.pendingColdActivationUrl = null;
|
|
256
|
+
}
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
// src/react/useBrowserNodeController.ts
|
|
261
|
+
function useBrowserNodeController(input) {
|
|
262
|
+
const controller = useMemo(
|
|
263
|
+
() => acquireBrowserNodeController({
|
|
264
|
+
defaultUrl: input.defaultUrl,
|
|
265
|
+
feature: input.feature,
|
|
266
|
+
navigationPolicy: input.navigationPolicy,
|
|
267
|
+
nodeId: input.nodeId,
|
|
268
|
+
profileId: input.profileId ?? null,
|
|
269
|
+
sessionMode: input.sessionMode ?? "shared",
|
|
270
|
+
sessionPartition: input.sessionPartition,
|
|
271
|
+
syncDefaultUrl: input.syncDefaultUrl ?? false
|
|
272
|
+
}),
|
|
273
|
+
[
|
|
274
|
+
input.defaultUrl,
|
|
275
|
+
input.feature,
|
|
276
|
+
input.navigationPolicy,
|
|
277
|
+
input.nodeId,
|
|
278
|
+
input.profileId,
|
|
279
|
+
input.sessionMode,
|
|
280
|
+
input.sessionPartition,
|
|
281
|
+
input.syncDefaultUrl
|
|
282
|
+
]
|
|
283
|
+
);
|
|
284
|
+
useEffect(() => {
|
|
285
|
+
controller.retain();
|
|
286
|
+
return () => {
|
|
287
|
+
controller.release();
|
|
288
|
+
};
|
|
289
|
+
}, [controller]);
|
|
290
|
+
useEffect(() => {
|
|
291
|
+
controller.sync();
|
|
292
|
+
}, [
|
|
293
|
+
controller,
|
|
294
|
+
input.defaultUrl,
|
|
295
|
+
input.feature,
|
|
296
|
+
input.navigationPolicy,
|
|
297
|
+
input.nodeId,
|
|
298
|
+
input.profileId,
|
|
299
|
+
input.sessionMode,
|
|
300
|
+
input.sessionPartition,
|
|
301
|
+
input.syncDefaultUrl
|
|
302
|
+
]);
|
|
303
|
+
const state = useExternalStoreSnapshot({
|
|
304
|
+
getSnapshot() {
|
|
305
|
+
return controller.getState();
|
|
306
|
+
},
|
|
307
|
+
subscribe(listener) {
|
|
308
|
+
return controller.subscribe(listener);
|
|
309
|
+
}
|
|
310
|
+
});
|
|
311
|
+
return {
|
|
312
|
+
controller,
|
|
313
|
+
state
|
|
314
|
+
};
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
// src/react/useBrowserNodeWebview.ts
|
|
318
|
+
import { useCallback, useEffect as useEffect2, useMemo as useMemo2 } from "react";
|
|
319
|
+
import { useExternalStoreSnapshot as useExternalStoreSnapshot2 } from "@tutti-os/ui-react-hooks";
|
|
320
|
+
|
|
321
|
+
// src/core/webviewController.ts
|
|
322
|
+
var browserGuestUnregisterGraceMs = 250;
|
|
323
|
+
var browserNodeInitialWebviewSrc = "about:blank";
|
|
324
|
+
var webviewControllerRegistry = /* @__PURE__ */ new Map();
|
|
325
|
+
var pendingGuestIdsByNodeId = /* @__PURE__ */ new Map();
|
|
326
|
+
var pendingUnregisterTimersByNodeId = /* @__PURE__ */ new Map();
|
|
327
|
+
function acquireBrowserNodeWebviewController(input) {
|
|
328
|
+
const existing = webviewControllerRegistry.get(input.nodeId);
|
|
329
|
+
const entry = existing ?? createBrowserNodeWebviewControllerEntry({
|
|
330
|
+
feature: input.feature,
|
|
331
|
+
initialUrl: input.initialUrl,
|
|
332
|
+
lifecycle: input.lifecycle,
|
|
333
|
+
navigationPolicy: input.navigationPolicy,
|
|
334
|
+
nodeId: input.nodeId,
|
|
335
|
+
onGuestInteraction: input.onGuestInteraction,
|
|
336
|
+
profileId: input.profileId,
|
|
337
|
+
sessionMode: input.sessionMode,
|
|
338
|
+
sessionPartition: input.sessionPartition
|
|
339
|
+
});
|
|
340
|
+
entry.context = {
|
|
341
|
+
feature: input.feature,
|
|
342
|
+
initialUrl: input.initialUrl,
|
|
343
|
+
lifecycle: input.lifecycle,
|
|
344
|
+
navigationPolicy: input.navigationPolicy,
|
|
345
|
+
nodeId: input.nodeId,
|
|
346
|
+
onGuestInteraction: input.onGuestInteraction,
|
|
347
|
+
profileId: input.profileId,
|
|
348
|
+
sessionMode: input.sessionMode,
|
|
349
|
+
sessionPartition: input.sessionPartition
|
|
350
|
+
};
|
|
351
|
+
if (!existing) {
|
|
352
|
+
webviewControllerRegistry.set(input.nodeId, entry);
|
|
353
|
+
}
|
|
354
|
+
return entry.controller;
|
|
355
|
+
}
|
|
356
|
+
function createBrowserNodeWebviewControllerEntry(context) {
|
|
357
|
+
const state = resolveBrowserNodeWebviewControllerState(context);
|
|
358
|
+
const entry = {
|
|
359
|
+
attachedListeners: [],
|
|
360
|
+
context,
|
|
361
|
+
controller: null,
|
|
362
|
+
listeners: /* @__PURE__ */ new Set(),
|
|
363
|
+
refCount: 0,
|
|
364
|
+
registeredGuestId: null,
|
|
365
|
+
registeringGuestId: null,
|
|
366
|
+
state,
|
|
367
|
+
webview: null
|
|
368
|
+
};
|
|
369
|
+
entry.controller = {
|
|
370
|
+
dismissDevToolsContextMenu() {
|
|
371
|
+
setBrowserNodeDevToolsContextMenu(entry, null);
|
|
372
|
+
},
|
|
373
|
+
getState() {
|
|
374
|
+
return entry.state;
|
|
375
|
+
},
|
|
376
|
+
async openDevToolsFromContextMenu() {
|
|
377
|
+
setBrowserNodeDevToolsContextMenu(entry, null);
|
|
378
|
+
try {
|
|
379
|
+
await entry.context.feature.hostApi.openDevTools?.({
|
|
380
|
+
nodeId: entry.context.nodeId
|
|
381
|
+
});
|
|
382
|
+
} catch (error) {
|
|
383
|
+
reportBrowserNodeWebviewDiagnostic(
|
|
384
|
+
entry,
|
|
385
|
+
"devtools.open.failed",
|
|
386
|
+
{
|
|
387
|
+
error: error instanceof Error ? error.message : String(error),
|
|
388
|
+
webContentsId: readBrowserNodeWebContentsId(entry.webview),
|
|
389
|
+
webviewPartition: entry.state.webviewPartition
|
|
390
|
+
},
|
|
391
|
+
"warn"
|
|
392
|
+
);
|
|
393
|
+
throw error;
|
|
394
|
+
}
|
|
395
|
+
},
|
|
396
|
+
release() {
|
|
397
|
+
entry.refCount = Math.max(0, entry.refCount - 1);
|
|
398
|
+
if (entry.refCount > 0) {
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
scheduleBrowserNodeGuestUnregister(entry);
|
|
402
|
+
detachBrowserNodeWebview(entry);
|
|
403
|
+
webviewControllerRegistry.delete(entry.context.nodeId);
|
|
404
|
+
},
|
|
405
|
+
retain() {
|
|
406
|
+
entry.refCount += 1;
|
|
407
|
+
if (entry.refCount > 1) {
|
|
408
|
+
return;
|
|
409
|
+
}
|
|
410
|
+
reconcileBrowserNodeWebviewControllerState(entry, {
|
|
411
|
+
allowHostEffects: true,
|
|
412
|
+
notifyListeners: true,
|
|
413
|
+
rebindWebview: true
|
|
414
|
+
});
|
|
415
|
+
},
|
|
416
|
+
setWebview(element) {
|
|
417
|
+
if (entry.webview === element) {
|
|
418
|
+
return;
|
|
419
|
+
}
|
|
420
|
+
detachBrowserNodeWebview(entry);
|
|
421
|
+
entry.webview = element;
|
|
422
|
+
attachBrowserNodeWebview(entry);
|
|
423
|
+
},
|
|
424
|
+
sync() {
|
|
425
|
+
reconcileBrowserNodeWebviewControllerState(entry, {
|
|
426
|
+
allowHostEffects: true,
|
|
427
|
+
notifyListeners: true,
|
|
428
|
+
rebindWebview: true
|
|
429
|
+
});
|
|
430
|
+
},
|
|
431
|
+
subscribe(listener) {
|
|
432
|
+
entry.listeners.add(listener);
|
|
433
|
+
return () => {
|
|
434
|
+
entry.listeners.delete(listener);
|
|
435
|
+
};
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
return entry;
|
|
439
|
+
}
|
|
440
|
+
function resolveBrowserNodeWebviewControllerState(context) {
|
|
441
|
+
const webviewPartition = resolveBrowserSessionPartition({
|
|
442
|
+
profileId: context.profileId,
|
|
443
|
+
sessionMode: context.sessionMode,
|
|
444
|
+
sessionPartition: context.sessionPartition
|
|
445
|
+
});
|
|
446
|
+
return {
|
|
447
|
+
devToolsContextMenu: null,
|
|
448
|
+
shouldRenderWebview: context.lifecycle !== "cold",
|
|
449
|
+
webviewKey: `${context.nodeId}:${webviewPartition}`,
|
|
450
|
+
webviewPartition,
|
|
451
|
+
webviewSrc: browserNodeInitialWebviewSrc
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
function reconcileBrowserNodeWebviewControllerState(entry, options) {
|
|
455
|
+
const nextState = resolveBrowserNodeWebviewControllerState(entry.context);
|
|
456
|
+
const changed = entry.state.devToolsContextMenu !== nextState.devToolsContextMenu || entry.state.shouldRenderWebview !== nextState.shouldRenderWebview || entry.state.webviewKey !== nextState.webviewKey || entry.state.webviewPartition !== nextState.webviewPartition || entry.state.webviewSrc !== nextState.webviewSrc;
|
|
457
|
+
if (options.allowHostEffects) {
|
|
458
|
+
if (entry.context.lifecycle === "cold") {
|
|
459
|
+
scheduleBrowserNodeGuestUnregister(entry);
|
|
460
|
+
} else {
|
|
461
|
+
clearPendingBrowserNodeGuestUnregister(entry.context.nodeId);
|
|
462
|
+
void entry.context.feature.hostApi.prepareSession({
|
|
463
|
+
navigationPolicy: entry.context.navigationPolicy,
|
|
464
|
+
nodeId: entry.context.nodeId,
|
|
465
|
+
profileId: entry.context.profileId,
|
|
466
|
+
sessionMode: entry.context.sessionMode,
|
|
467
|
+
sessionPartition: entry.context.sessionPartition
|
|
468
|
+
}).catch(() => void 0);
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
if (!changed) {
|
|
472
|
+
if (options.rebindWebview && entry.webview && entry.attachedListeners.length === 0) {
|
|
473
|
+
attachBrowserNodeWebview(entry);
|
|
474
|
+
}
|
|
475
|
+
return;
|
|
476
|
+
}
|
|
477
|
+
entry.state = nextState;
|
|
478
|
+
detachBrowserNodeWebview(entry);
|
|
479
|
+
attachBrowserNodeWebview(entry);
|
|
480
|
+
if (options.notifyListeners) {
|
|
481
|
+
notifyBrowserNodeWebviewControllerListeners(entry);
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function setBrowserNodeDevToolsContextMenu(entry, devToolsContextMenu) {
|
|
485
|
+
if (entry.state.devToolsContextMenu?.x === devToolsContextMenu?.x && entry.state.devToolsContextMenu?.y === devToolsContextMenu?.y) {
|
|
486
|
+
return;
|
|
487
|
+
}
|
|
488
|
+
entry.state = {
|
|
489
|
+
...entry.state,
|
|
490
|
+
devToolsContextMenu
|
|
491
|
+
};
|
|
492
|
+
notifyBrowserNodeWebviewControllerListeners(entry);
|
|
493
|
+
}
|
|
494
|
+
function notifyBrowserNodeWebviewControllerListeners(entry) {
|
|
495
|
+
for (const listener of entry.listeners) {
|
|
496
|
+
listener();
|
|
497
|
+
}
|
|
498
|
+
}
|
|
499
|
+
function clearPendingBrowserNodeGuestUnregister(nodeId) {
|
|
500
|
+
const timerId = pendingUnregisterTimersByNodeId.get(nodeId);
|
|
501
|
+
if (timerId !== void 0) {
|
|
502
|
+
globalThis.clearTimeout(timerId);
|
|
503
|
+
pendingUnregisterTimersByNodeId.delete(nodeId);
|
|
504
|
+
}
|
|
505
|
+
pendingGuestIdsByNodeId.delete(nodeId);
|
|
506
|
+
}
|
|
507
|
+
function scheduleBrowserNodeGuestUnregister(entry) {
|
|
508
|
+
const guestId = entry.registeredGuestId;
|
|
509
|
+
const nodeId = entry.context.nodeId;
|
|
510
|
+
entry.registeringGuestId = null;
|
|
511
|
+
if (guestId === null) {
|
|
512
|
+
clearPendingBrowserNodeGuestUnregister(nodeId);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
entry.registeredGuestId = null;
|
|
516
|
+
clearPendingBrowserNodeGuestUnregister(nodeId);
|
|
517
|
+
pendingGuestIdsByNodeId.set(nodeId, guestId);
|
|
518
|
+
const timerId = globalThis.setTimeout(() => {
|
|
519
|
+
pendingUnregisterTimersByNodeId.delete(nodeId);
|
|
520
|
+
const pendingGuestId = pendingGuestIdsByNodeId.get(nodeId);
|
|
521
|
+
pendingGuestIdsByNodeId.delete(nodeId);
|
|
522
|
+
if (typeof pendingGuestId !== "number" || !Number.isFinite(pendingGuestId)) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
void entry.context.feature.hostApi.unregisterGuest({
|
|
526
|
+
nodeId: entry.context.nodeId,
|
|
527
|
+
webContentsId: pendingGuestId
|
|
528
|
+
}).catch(() => void 0);
|
|
529
|
+
}, browserGuestUnregisterGraceMs);
|
|
530
|
+
pendingUnregisterTimersByNodeId.set(nodeId, timerId);
|
|
531
|
+
}
|
|
532
|
+
function detachBrowserNodeWebview(entry) {
|
|
533
|
+
if (!entry.webview) {
|
|
534
|
+
entry.attachedListeners = [];
|
|
535
|
+
return;
|
|
536
|
+
}
|
|
537
|
+
for (const record of entry.attachedListeners) {
|
|
538
|
+
entry.webview.removeEventListener(record.event, record.listener);
|
|
539
|
+
}
|
|
540
|
+
entry.attachedListeners = [];
|
|
541
|
+
}
|
|
542
|
+
function attachBrowserNodeWebview(entry) {
|
|
543
|
+
const webview = entry.webview;
|
|
544
|
+
if (!webview || !entry.state.shouldRenderWebview) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const registerGuest = async () => {
|
|
548
|
+
const guestId = webview.getWebContentsId?.();
|
|
549
|
+
if (typeof guestId !== "number" || !Number.isFinite(guestId) || guestId <= 0 || entry.registeredGuestId === guestId || entry.registeringGuestId === guestId) {
|
|
550
|
+
return;
|
|
551
|
+
}
|
|
552
|
+
clearPendingBrowserNodeGuestUnregister(entry.context.nodeId);
|
|
553
|
+
entry.registeringGuestId = guestId;
|
|
554
|
+
try {
|
|
555
|
+
await entry.context.feature.hostApi.registerGuest({
|
|
556
|
+
navigationPolicy: entry.context.navigationPolicy,
|
|
557
|
+
nodeId: entry.context.nodeId,
|
|
558
|
+
profileId: entry.context.profileId,
|
|
559
|
+
sessionMode: entry.context.sessionMode,
|
|
560
|
+
sessionPartition: entry.context.sessionPartition,
|
|
561
|
+
webContentsId: guestId
|
|
562
|
+
});
|
|
563
|
+
entry.registeredGuestId = guestId;
|
|
564
|
+
} finally {
|
|
565
|
+
if (entry.registeringGuestId === guestId) {
|
|
566
|
+
entry.registeringGuestId = null;
|
|
567
|
+
}
|
|
568
|
+
}
|
|
569
|
+
};
|
|
570
|
+
const handleDidAttach = () => {
|
|
571
|
+
void registerGuest().catch(() => void 0);
|
|
572
|
+
};
|
|
573
|
+
const handleDomReady = () => {
|
|
574
|
+
void registerGuest().catch(() => void 0);
|
|
575
|
+
};
|
|
576
|
+
const handleGuestInteraction = () => {
|
|
577
|
+
entry.context.onGuestInteraction?.();
|
|
578
|
+
};
|
|
579
|
+
const handleDevToolsContextMenu = (event) => {
|
|
580
|
+
const hasNativeContextMenu = entry.context.feature.hostApi.showDevToolsContextMenu !== void 0;
|
|
581
|
+
const hasInlineContextMenu = entry.context.feature.hostApi.openDevTools !== void 0;
|
|
582
|
+
if (!hasNativeContextMenu && !hasInlineContextMenu) {
|
|
583
|
+
return;
|
|
584
|
+
}
|
|
585
|
+
event.preventDefault();
|
|
586
|
+
const point = resolveBrowserNodeContextMenuPoint(event, webview);
|
|
587
|
+
if (hasNativeContextMenu) {
|
|
588
|
+
void entry.context.feature.hostApi.showDevToolsContextMenu?.({
|
|
589
|
+
label: entry.context.feature.i18n.t("actions.openDevTools"),
|
|
590
|
+
nodeId: entry.context.nodeId,
|
|
591
|
+
point
|
|
592
|
+
}).catch((error) => {
|
|
593
|
+
reportBrowserNodeWebviewDiagnostic(
|
|
594
|
+
entry,
|
|
595
|
+
"devtools.native-context-menu.failed",
|
|
596
|
+
{
|
|
597
|
+
error: error instanceof Error ? error.message : String(error),
|
|
598
|
+
webContentsId: readBrowserNodeWebContentsId(webview),
|
|
599
|
+
webviewPartition: entry.state.webviewPartition
|
|
600
|
+
},
|
|
601
|
+
"warn"
|
|
602
|
+
);
|
|
603
|
+
});
|
|
604
|
+
return;
|
|
605
|
+
}
|
|
606
|
+
setBrowserNodeDevToolsContextMenu(entry, point);
|
|
607
|
+
};
|
|
608
|
+
const records = [
|
|
609
|
+
{ event: "did-attach", listener: handleDidAttach },
|
|
610
|
+
{ event: "dom-ready", listener: handleDomReady },
|
|
611
|
+
{ event: "context-menu", listener: handleDevToolsContextMenu },
|
|
612
|
+
{ event: "contextmenu", listener: handleDevToolsContextMenu },
|
|
613
|
+
{ event: "focus", listener: handleGuestInteraction },
|
|
614
|
+
{ event: "ipc-message", listener: handleGuestInteraction }
|
|
615
|
+
];
|
|
616
|
+
for (const record of records) {
|
|
617
|
+
webview.addEventListener(record.event, record.listener);
|
|
618
|
+
}
|
|
619
|
+
entry.attachedListeners = records;
|
|
620
|
+
}
|
|
621
|
+
function resolveBrowserNodeContextMenuPoint(event, webview) {
|
|
622
|
+
const eventWithPoint = event;
|
|
623
|
+
const clientX = readFiniteNumber(eventWithPoint.clientX);
|
|
624
|
+
const clientY = readFiniteNumber(eventWithPoint.clientY);
|
|
625
|
+
if (clientX !== null && clientY !== null) {
|
|
626
|
+
return { x: clientX, y: clientY };
|
|
627
|
+
}
|
|
628
|
+
const paramX = readFiniteNumber(eventWithPoint.params?.x);
|
|
629
|
+
const paramY = readFiniteNumber(eventWithPoint.params?.y);
|
|
630
|
+
if (paramX !== null && paramY !== null) {
|
|
631
|
+
return { x: paramX, y: paramY };
|
|
632
|
+
}
|
|
633
|
+
const rect = webview.getBoundingClientRect();
|
|
634
|
+
return {
|
|
635
|
+
x: rect.left,
|
|
636
|
+
y: rect.top
|
|
637
|
+
};
|
|
638
|
+
}
|
|
639
|
+
function readFiniteNumber(value) {
|
|
640
|
+
return typeof value === "number" && Number.isFinite(value) ? value : null;
|
|
641
|
+
}
|
|
642
|
+
function readBrowserNodeWebContentsId(webview) {
|
|
643
|
+
const webContentsId = webview?.getWebContentsId?.();
|
|
644
|
+
return typeof webContentsId === "number" && Number.isFinite(webContentsId) ? webContentsId : null;
|
|
645
|
+
}
|
|
646
|
+
function reportBrowserNodeWebviewDiagnostic(entry, event, details, level = "info") {
|
|
647
|
+
entry.context.feature.reportDiagnostic?.({
|
|
648
|
+
details: {
|
|
649
|
+
...details,
|
|
650
|
+
nodeId: entry.context.nodeId
|
|
651
|
+
},
|
|
652
|
+
event,
|
|
653
|
+
level
|
|
654
|
+
});
|
|
655
|
+
}
|
|
656
|
+
|
|
657
|
+
// src/react/useBrowserNodeWebview.ts
|
|
658
|
+
function useBrowserNodeWebview({
|
|
659
|
+
feature,
|
|
660
|
+
initialUrl,
|
|
661
|
+
lifecycle,
|
|
662
|
+
navigationPolicy,
|
|
663
|
+
nodeId,
|
|
664
|
+
onGuestInteraction,
|
|
665
|
+
profileId,
|
|
666
|
+
sessionMode,
|
|
667
|
+
sessionPartition
|
|
668
|
+
}) {
|
|
669
|
+
const controller = useMemo2(
|
|
670
|
+
() => acquireBrowserNodeWebviewController({
|
|
671
|
+
feature,
|
|
672
|
+
initialUrl,
|
|
673
|
+
lifecycle,
|
|
674
|
+
navigationPolicy,
|
|
675
|
+
nodeId,
|
|
676
|
+
onGuestInteraction,
|
|
677
|
+
profileId,
|
|
678
|
+
sessionMode,
|
|
679
|
+
sessionPartition
|
|
680
|
+
}),
|
|
681
|
+
[
|
|
682
|
+
feature,
|
|
683
|
+
initialUrl,
|
|
684
|
+
lifecycle,
|
|
685
|
+
navigationPolicy,
|
|
686
|
+
nodeId,
|
|
687
|
+
onGuestInteraction,
|
|
688
|
+
profileId,
|
|
689
|
+
sessionMode,
|
|
690
|
+
sessionPartition
|
|
691
|
+
]
|
|
692
|
+
);
|
|
693
|
+
useEffect2(() => {
|
|
694
|
+
controller.retain();
|
|
695
|
+
return () => {
|
|
696
|
+
controller.release();
|
|
697
|
+
};
|
|
698
|
+
}, [controller]);
|
|
699
|
+
useEffect2(() => {
|
|
700
|
+
controller.sync();
|
|
701
|
+
}, [
|
|
702
|
+
controller,
|
|
703
|
+
initialUrl,
|
|
704
|
+
lifecycle,
|
|
705
|
+
navigationPolicy,
|
|
706
|
+
nodeId,
|
|
707
|
+
onGuestInteraction,
|
|
708
|
+
profileId,
|
|
709
|
+
sessionMode,
|
|
710
|
+
sessionPartition
|
|
711
|
+
]);
|
|
712
|
+
const state = useExternalStoreSnapshot2({
|
|
713
|
+
getSnapshot() {
|
|
714
|
+
return controller.getState();
|
|
715
|
+
},
|
|
716
|
+
subscribe(listener) {
|
|
717
|
+
return controller.subscribe(listener);
|
|
718
|
+
}
|
|
719
|
+
});
|
|
720
|
+
const setWebviewRef = useCallback(
|
|
721
|
+
(element) => {
|
|
722
|
+
controller.setWebview(element);
|
|
723
|
+
},
|
|
724
|
+
[controller]
|
|
725
|
+
);
|
|
726
|
+
const dismissDevToolsContextMenu = useCallback(() => {
|
|
727
|
+
controller.dismissDevToolsContextMenu();
|
|
728
|
+
}, [controller]);
|
|
729
|
+
const openDevToolsFromContextMenu = useCallback(() => {
|
|
730
|
+
return controller.openDevToolsFromContextMenu();
|
|
731
|
+
}, [controller]);
|
|
732
|
+
return {
|
|
733
|
+
devToolsContextMenu: state.devToolsContextMenu,
|
|
734
|
+
dismissDevToolsContextMenu,
|
|
735
|
+
openDevToolsFromContextMenu,
|
|
736
|
+
shouldRenderWebview: state.shouldRenderWebview,
|
|
737
|
+
setWebviewRef,
|
|
738
|
+
webviewKey: state.webviewKey,
|
|
739
|
+
webviewPartition: state.webviewPartition,
|
|
740
|
+
webviewSrc: state.webviewSrc
|
|
741
|
+
};
|
|
742
|
+
}
|
|
743
|
+
|
|
744
|
+
// src/react/BrowserNode.tsx
|
|
745
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
746
|
+
var browserNodeAllowPopupsAttribute = "true";
|
|
747
|
+
function BrowserNode({
|
|
748
|
+
defaultUrl,
|
|
749
|
+
feature,
|
|
750
|
+
navigationPolicy = null,
|
|
751
|
+
nodeId,
|
|
752
|
+
onFocusRequest,
|
|
753
|
+
onNavigated,
|
|
754
|
+
profileId = null,
|
|
755
|
+
sessionMode = "shared",
|
|
756
|
+
sessionPartition = null,
|
|
757
|
+
showHeader = true,
|
|
758
|
+
syncDefaultUrl = false
|
|
759
|
+
}) {
|
|
760
|
+
const { controller, state } = useBrowserNodeController({
|
|
761
|
+
defaultUrl,
|
|
762
|
+
feature,
|
|
763
|
+
navigationPolicy,
|
|
764
|
+
nodeId,
|
|
765
|
+
profileId,
|
|
766
|
+
sessionMode,
|
|
767
|
+
sessionPartition,
|
|
768
|
+
syncDefaultUrl
|
|
769
|
+
});
|
|
770
|
+
const runtime = state.runtime;
|
|
771
|
+
const lastNavigatedUrlRef = useRef(
|
|
772
|
+
state.runtime.url?.trim() || null
|
|
773
|
+
);
|
|
774
|
+
const errorMessage = runtime.error ? formatBrowserNodeErrorMessage(feature, runtime.error) : null;
|
|
775
|
+
const errorStatus = runtime.error ? formatBrowserNodeErrorStatus(feature, runtime.error) : null;
|
|
776
|
+
const isShowingLoadError = errorMessage !== null;
|
|
777
|
+
const openExternalUrl = feature.hostApi.openExternal ? feature.resolveAddressInput(state.displayUrl).url : null;
|
|
778
|
+
const {
|
|
779
|
+
devToolsContextMenu,
|
|
780
|
+
dismissDevToolsContextMenu,
|
|
781
|
+
openDevToolsFromContextMenu,
|
|
782
|
+
shouldRenderWebview,
|
|
783
|
+
setWebviewRef,
|
|
784
|
+
webviewKey,
|
|
785
|
+
webviewPartition,
|
|
786
|
+
webviewSrc
|
|
787
|
+
} = useBrowserNodeWebview({
|
|
788
|
+
feature,
|
|
789
|
+
initialUrl: state.displayUrl,
|
|
790
|
+
lifecycle: runtime.lifecycle,
|
|
791
|
+
navigationPolicy,
|
|
792
|
+
nodeId,
|
|
793
|
+
onGuestInteraction: onFocusRequest,
|
|
794
|
+
profileId,
|
|
795
|
+
sessionMode,
|
|
796
|
+
sessionPartition
|
|
797
|
+
});
|
|
798
|
+
useEffect3(() => {
|
|
799
|
+
const navigatedUrl = state.runtime.url?.trim() ?? "";
|
|
800
|
+
if (!onNavigated || state.runtime.isLoading || state.runtime.error || navigatedUrl.length === 0 || navigatedUrl === "about:blank" || lastNavigatedUrlRef.current === navigatedUrl) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
lastNavigatedUrlRef.current = navigatedUrl;
|
|
804
|
+
onNavigated(navigatedUrl);
|
|
805
|
+
}, [
|
|
806
|
+
onNavigated,
|
|
807
|
+
state.runtime.error,
|
|
808
|
+
state.runtime.isLoading,
|
|
809
|
+
state.runtime.url
|
|
810
|
+
]);
|
|
811
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col overflow-hidden bg-[var(--background-panel)]", children: [
|
|
812
|
+
showHeader ? /* @__PURE__ */ jsx(
|
|
813
|
+
BrowserNodeHeader,
|
|
814
|
+
{
|
|
815
|
+
canGoBack: runtime.canGoBack,
|
|
816
|
+
canGoForward: runtime.canGoForward,
|
|
817
|
+
draftUrl: state.draftUrl,
|
|
818
|
+
feature,
|
|
819
|
+
isCold: runtime.lifecycle === "cold",
|
|
820
|
+
isLoading: runtime.isLoading,
|
|
821
|
+
onDraftUrlChange: (nextUrl) => controller.setDraftUrl(nextUrl),
|
|
822
|
+
onFocusRequest,
|
|
823
|
+
onSubmitUrl: () => {
|
|
824
|
+
void controller.submitDraftUrl().catch(() => void 0);
|
|
825
|
+
},
|
|
826
|
+
onOpenExternal: openExternalUrl ? () => {
|
|
827
|
+
void feature.hostApi.openExternal?.({ url: openExternalUrl }).catch(() => void 0);
|
|
828
|
+
} : void 0,
|
|
829
|
+
onGoBack: () => {
|
|
830
|
+
void controller.goBack().catch(() => void 0);
|
|
831
|
+
},
|
|
832
|
+
onGoForward: () => {
|
|
833
|
+
void controller.goForward().catch(() => void 0);
|
|
834
|
+
},
|
|
835
|
+
onReload: () => {
|
|
836
|
+
void controller.reload().catch(() => void 0);
|
|
837
|
+
}
|
|
838
|
+
}
|
|
839
|
+
) : null,
|
|
840
|
+
/* @__PURE__ */ jsxs("div", { className: "relative min-h-0 flex-1 overflow-hidden bg-[var(--background-panel)]", children: [
|
|
841
|
+
shouldRenderWebview ? /* @__PURE__ */ jsx(
|
|
842
|
+
"webview",
|
|
843
|
+
{
|
|
844
|
+
allowpopups: browserNodeAllowPopupsAttribute,
|
|
845
|
+
ref: setWebviewRef,
|
|
846
|
+
className: cn(
|
|
847
|
+
"absolute inset-0 h-full w-full border-0 bg-[var(--background-panel)]",
|
|
848
|
+
isShowingLoadError ? "hidden pointer-events-none" : "visible"
|
|
849
|
+
),
|
|
850
|
+
"data-browser-node-webview": "true",
|
|
851
|
+
partition: webviewPartition,
|
|
852
|
+
src: webviewSrc
|
|
853
|
+
},
|
|
854
|
+
webviewKey
|
|
855
|
+
) : null,
|
|
856
|
+
errorMessage ? /* @__PURE__ */ jsx("div", { className: "absolute inset-0 z-10 flex items-center justify-center bg-[var(--background-panel)] px-8 py-10 text-center", children: /* @__PURE__ */ jsxs(
|
|
857
|
+
"div",
|
|
858
|
+
{
|
|
859
|
+
className: "flex w-full max-w-[440px] flex-col items-center",
|
|
860
|
+
role: "status",
|
|
861
|
+
"aria-live": "polite",
|
|
862
|
+
children: [
|
|
863
|
+
/* @__PURE__ */ jsx("div", { className: "flex size-11 items-center justify-center rounded-full border border-[color-mix(in_srgb,var(--state-danger)_22%,transparent)] bg-[color-mix(in_srgb,var(--state-danger)_8%,transparent)] text-[var(--state-danger)]", children: /* @__PURE__ */ jsx(WarningLinedIcon, { className: "size-5" }) }),
|
|
864
|
+
/* @__PURE__ */ jsx("div", { className: "mt-4 text-lg font-semibold text-[var(--text-primary)]", children: feature.i18n.t("loadFailed") }),
|
|
865
|
+
/* @__PURE__ */ jsx("div", { className: "mt-1 max-w-[360px] text-sm leading-5 text-[var(--text-secondary)]", children: errorMessage }),
|
|
866
|
+
errorStatus ? /* @__PURE__ */ jsx("div", { className: "mt-4 rounded-full border border-border bg-[var(--transparency-block)] px-2.5 py-1 text-xs font-medium text-[var(--text-secondary)]", children: errorStatus }) : null,
|
|
867
|
+
openExternalUrl ? /* @__PURE__ */ jsx("div", { className: "mt-5 flex flex-wrap items-center justify-center gap-2", children: /* @__PURE__ */ jsxs(
|
|
868
|
+
Button,
|
|
869
|
+
{
|
|
870
|
+
size: "sm",
|
|
871
|
+
type: "button",
|
|
872
|
+
variant: "outline",
|
|
873
|
+
onClick: () => {
|
|
874
|
+
void feature.hostApi.openExternal?.({ url: openExternalUrl }).catch(() => void 0);
|
|
875
|
+
},
|
|
876
|
+
children: [
|
|
877
|
+
/* @__PURE__ */ jsx(LaunchIcon, { className: "size-3.5" }),
|
|
878
|
+
feature.i18n.t("actions.openExternal")
|
|
879
|
+
]
|
|
880
|
+
}
|
|
881
|
+
) }) : null
|
|
882
|
+
]
|
|
883
|
+
}
|
|
884
|
+
) }) : null,
|
|
885
|
+
devToolsContextMenu ? /* @__PURE__ */ jsx(
|
|
886
|
+
ViewportMenuSurface,
|
|
887
|
+
{
|
|
888
|
+
open: true,
|
|
889
|
+
className: "w-44",
|
|
890
|
+
dismissOnEscape: true,
|
|
891
|
+
dismissOnPointerDownOutside: true,
|
|
892
|
+
onDismiss: dismissDevToolsContextMenu,
|
|
893
|
+
placement: {
|
|
894
|
+
type: "point",
|
|
895
|
+
point: devToolsContextMenu,
|
|
896
|
+
alignX: "start",
|
|
897
|
+
alignY: "start",
|
|
898
|
+
estimatedSize: {
|
|
899
|
+
width: 176,
|
|
900
|
+
height: 40
|
|
901
|
+
}
|
|
902
|
+
},
|
|
903
|
+
children: /* @__PURE__ */ jsx(
|
|
904
|
+
"button",
|
|
905
|
+
{
|
|
906
|
+
className: cn(menuItemClassName, "w-full"),
|
|
907
|
+
type: "button",
|
|
908
|
+
onClick: () => {
|
|
909
|
+
void openDevToolsFromContextMenu().catch(() => void 0);
|
|
910
|
+
},
|
|
911
|
+
children: feature.i18n.t("actions.openDevTools")
|
|
912
|
+
}
|
|
913
|
+
)
|
|
914
|
+
}
|
|
915
|
+
) : null
|
|
916
|
+
] })
|
|
917
|
+
] });
|
|
918
|
+
}
|
|
919
|
+
function BrowserNodeWorkbenchHeader({
|
|
920
|
+
className,
|
|
921
|
+
defaultActions,
|
|
922
|
+
defaultUrl,
|
|
923
|
+
dragHandleProps,
|
|
924
|
+
feature,
|
|
925
|
+
nodeId,
|
|
926
|
+
onCloseRequest,
|
|
927
|
+
onFocusRequest
|
|
928
|
+
}) {
|
|
929
|
+
const { controller, state } = useBrowserNodeController({
|
|
930
|
+
defaultUrl,
|
|
931
|
+
feature,
|
|
932
|
+
nodeId
|
|
933
|
+
});
|
|
934
|
+
const runtime = state.runtime;
|
|
935
|
+
const openExternalUrl = feature.hostApi.openExternal ? feature.resolveAddressInput(state.displayUrl).url : null;
|
|
936
|
+
return /* @__PURE__ */ jsx(
|
|
937
|
+
BrowserNodeHeader,
|
|
938
|
+
{
|
|
939
|
+
canGoBack: runtime.canGoBack,
|
|
940
|
+
canGoForward: runtime.canGoForward,
|
|
941
|
+
className,
|
|
942
|
+
defaultActions,
|
|
943
|
+
draftUrl: state.draftUrl,
|
|
944
|
+
dragHandleProps,
|
|
945
|
+
feature,
|
|
946
|
+
isCold: runtime.lifecycle === "cold",
|
|
947
|
+
isLoading: runtime.isLoading,
|
|
948
|
+
onCloseRequest,
|
|
949
|
+
onDraftUrlChange: (nextUrl) => controller.setDraftUrl(nextUrl),
|
|
950
|
+
onFocusRequest,
|
|
951
|
+
onSubmitUrl: () => {
|
|
952
|
+
void controller.submitDraftUrl().catch(() => void 0);
|
|
953
|
+
},
|
|
954
|
+
onOpenExternal: openExternalUrl ? () => {
|
|
955
|
+
void feature.hostApi.openExternal?.({ url: openExternalUrl }).catch(() => void 0);
|
|
956
|
+
} : void 0,
|
|
957
|
+
onGoBack: () => {
|
|
958
|
+
void controller.goBack().catch(() => void 0);
|
|
959
|
+
},
|
|
960
|
+
onGoForward: () => {
|
|
961
|
+
void controller.goForward().catch(() => void 0);
|
|
962
|
+
},
|
|
963
|
+
onReload: () => {
|
|
964
|
+
void controller.reload().catch(() => void 0);
|
|
965
|
+
},
|
|
966
|
+
withBorder: false
|
|
967
|
+
}
|
|
968
|
+
);
|
|
969
|
+
}
|
|
970
|
+
function BrowserNodeHeader({
|
|
971
|
+
canGoBack,
|
|
972
|
+
canGoForward,
|
|
973
|
+
className,
|
|
974
|
+
defaultActions,
|
|
975
|
+
draftUrl,
|
|
976
|
+
dragHandleProps,
|
|
977
|
+
feature,
|
|
978
|
+
isCold = false,
|
|
979
|
+
isLoading,
|
|
980
|
+
onCloseRequest,
|
|
981
|
+
onDraftUrlChange,
|
|
982
|
+
onFocusRequest,
|
|
983
|
+
onGoBack,
|
|
984
|
+
onGoForward,
|
|
985
|
+
onOpenExternal,
|
|
986
|
+
onReload,
|
|
987
|
+
onSubmitUrl,
|
|
988
|
+
withBorder = true
|
|
989
|
+
}) {
|
|
990
|
+
const [reloadAnimationKey, setReloadAnimationKey] = useState(0);
|
|
991
|
+
const handleReload = () => {
|
|
992
|
+
setReloadAnimationKey((currentKey) => currentKey + 1);
|
|
993
|
+
onReload();
|
|
994
|
+
};
|
|
995
|
+
return /* @__PURE__ */ jsxs(
|
|
996
|
+
"div",
|
|
997
|
+
{
|
|
998
|
+
className: cn(
|
|
999
|
+
"flex h-[var(--workbench-header-height,38px)] min-h-[var(--workbench-header-height,38px)] items-center gap-2 bg-[var(--background-panel)] px-2 pl-3",
|
|
1000
|
+
withBorder ? "border-b border-border" : null,
|
|
1001
|
+
className
|
|
1002
|
+
),
|
|
1003
|
+
"data-browser-node-header": "true",
|
|
1004
|
+
onDoubleClick: (event) => {
|
|
1005
|
+
if (event.target instanceof Element && event.target.closest(".nodrag")) {
|
|
1006
|
+
return;
|
|
1007
|
+
}
|
|
1008
|
+
event.stopPropagation();
|
|
1009
|
+
dragHandleProps?.onDoubleClick?.(event);
|
|
1010
|
+
},
|
|
1011
|
+
children: [
|
|
1012
|
+
/* @__PURE__ */ jsxs("div", { className: "inline-flex items-center gap-1", children: [
|
|
1013
|
+
/* @__PURE__ */ jsx(
|
|
1014
|
+
BrowserNodeHeaderButton,
|
|
1015
|
+
{
|
|
1016
|
+
disabled: !canGoBack,
|
|
1017
|
+
label: feature.i18n.t("actions.back"),
|
|
1018
|
+
onClick: onGoBack,
|
|
1019
|
+
children: /* @__PURE__ */ jsx(ArrowLeftIcon, { className: "size-[15px]" })
|
|
1020
|
+
}
|
|
1021
|
+
),
|
|
1022
|
+
/* @__PURE__ */ jsx(
|
|
1023
|
+
BrowserNodeHeaderButton,
|
|
1024
|
+
{
|
|
1025
|
+
disabled: !canGoForward,
|
|
1026
|
+
label: feature.i18n.t("actions.forward"),
|
|
1027
|
+
onClick: onGoForward,
|
|
1028
|
+
children: /* @__PURE__ */ jsx(ArrowRightIcon, { className: "size-[15px]" })
|
|
1029
|
+
}
|
|
1030
|
+
),
|
|
1031
|
+
/* @__PURE__ */ jsx(
|
|
1032
|
+
BrowserNodeHeaderButton,
|
|
1033
|
+
{
|
|
1034
|
+
label: feature.i18n.t("actions.reload"),
|
|
1035
|
+
onClick: handleReload,
|
|
1036
|
+
children: /* @__PURE__ */ jsx(
|
|
1037
|
+
RefreshIcon,
|
|
1038
|
+
{
|
|
1039
|
+
className: cn(
|
|
1040
|
+
"size-[15px]",
|
|
1041
|
+
reloadAnimationKey > 0 && "motion-safe:animate-[spin_520ms_cubic-bezier(0.4,0,0.2,1)_1_reverse]"
|
|
1042
|
+
)
|
|
1043
|
+
},
|
|
1044
|
+
reloadAnimationKey
|
|
1045
|
+
)
|
|
1046
|
+
}
|
|
1047
|
+
)
|
|
1048
|
+
] }),
|
|
1049
|
+
/* @__PURE__ */ jsx(
|
|
1050
|
+
"div",
|
|
1051
|
+
{
|
|
1052
|
+
...dragHandleProps,
|
|
1053
|
+
className: "h-full w-8 shrink-0 cursor-grab active:cursor-grabbing",
|
|
1054
|
+
"data-browser-node-drag-gutter": "true",
|
|
1055
|
+
"data-node-drag-handle": "true",
|
|
1056
|
+
"aria-hidden": "true"
|
|
1057
|
+
}
|
|
1058
|
+
),
|
|
1059
|
+
/* @__PURE__ */ jsxs(
|
|
1060
|
+
"form",
|
|
1061
|
+
{
|
|
1062
|
+
className: "nodrag relative min-w-0 flex-1",
|
|
1063
|
+
onSubmit: (event) => {
|
|
1064
|
+
event.preventDefault();
|
|
1065
|
+
event.stopPropagation();
|
|
1066
|
+
onSubmitUrl();
|
|
1067
|
+
},
|
|
1068
|
+
children: [
|
|
1069
|
+
/* @__PURE__ */ jsx(
|
|
1070
|
+
Input,
|
|
1071
|
+
{
|
|
1072
|
+
"aria-label": feature.i18n.t("addressLabel"),
|
|
1073
|
+
className: "pr-8 focus-visible:border-input focus-visible:ring-0 focus-visible:ring-offset-0",
|
|
1074
|
+
placeholder: feature.i18n.t("addressPlaceholder"),
|
|
1075
|
+
size: "sm",
|
|
1076
|
+
value: draftUrl,
|
|
1077
|
+
onChange: (event) => onDraftUrlChange(event.target.value),
|
|
1078
|
+
onFocus: onFocusRequest
|
|
1079
|
+
}
|
|
1080
|
+
),
|
|
1081
|
+
isLoading ? /* @__PURE__ */ jsx(LoadingIcon, { className: "pointer-events-none absolute right-2 top-1/2 z-[1] size-4 -translate-y-1/2 animate-spin text-[var(--text-tertiary)]" }) : null
|
|
1082
|
+
]
|
|
1083
|
+
}
|
|
1084
|
+
),
|
|
1085
|
+
onOpenExternal ? /* @__PURE__ */ jsx(
|
|
1086
|
+
BrowserNodeHeaderButton,
|
|
1087
|
+
{
|
|
1088
|
+
label: feature.i18n.t("actions.openExternal"),
|
|
1089
|
+
onClick: onOpenExternal,
|
|
1090
|
+
children: /* @__PURE__ */ jsx(LaunchIcon, { className: "size-[15px]" })
|
|
1091
|
+
}
|
|
1092
|
+
) : null,
|
|
1093
|
+
defaultActions ? /* @__PURE__ */ jsxs("div", { className: "nodrag flex shrink-0 items-center gap-1.5", children: [
|
|
1094
|
+
isCold ? /* @__PURE__ */ jsx(
|
|
1095
|
+
Badge,
|
|
1096
|
+
{
|
|
1097
|
+
className: "h-[26px] min-w-7 rounded-md text-[10px] font-semibold lowercase tracking-[0.08em]",
|
|
1098
|
+
"aria-label": feature.i18n.t("coldStatus"),
|
|
1099
|
+
children: feature.i18n.t("coldStatus")
|
|
1100
|
+
}
|
|
1101
|
+
) : null,
|
|
1102
|
+
/* @__PURE__ */ jsx(
|
|
1103
|
+
"span",
|
|
1104
|
+
{
|
|
1105
|
+
className: "contents",
|
|
1106
|
+
onClickCapture: (event) => {
|
|
1107
|
+
if (!onCloseRequest || !(event.target instanceof Element) || !event.target.closest('[data-workbench-action="close"]')) {
|
|
1108
|
+
return;
|
|
1109
|
+
}
|
|
1110
|
+
onCloseRequest();
|
|
1111
|
+
},
|
|
1112
|
+
children: defaultActions
|
|
1113
|
+
}
|
|
1114
|
+
)
|
|
1115
|
+
] }) : null
|
|
1116
|
+
]
|
|
1117
|
+
}
|
|
1118
|
+
);
|
|
1119
|
+
}
|
|
1120
|
+
function formatBrowserNodeErrorMessage(feature, error) {
|
|
1121
|
+
switch (error.code) {
|
|
1122
|
+
case "invalid-url":
|
|
1123
|
+
return feature.i18n.t("errors.invalidUrl", error.params);
|
|
1124
|
+
case "navigation-failed":
|
|
1125
|
+
if (error.params && error.params.statusCode !== void 0) {
|
|
1126
|
+
return feature.i18n.t(
|
|
1127
|
+
"errors.navigationFailedWithStatus",
|
|
1128
|
+
error.params
|
|
1129
|
+
);
|
|
1130
|
+
}
|
|
1131
|
+
return feature.i18n.t("errors.navigationFailed", error.params);
|
|
1132
|
+
case "unsupported-protocol":
|
|
1133
|
+
return feature.i18n.t("errors.unsupportedProtocol", error.params);
|
|
1134
|
+
case "unsupported-url":
|
|
1135
|
+
return feature.i18n.t("errors.unsupportedUrl", error.params);
|
|
1136
|
+
}
|
|
1137
|
+
}
|
|
1138
|
+
function formatBrowserNodeErrorStatus(feature, error) {
|
|
1139
|
+
if (error.code !== "navigation-failed" || !error.params) {
|
|
1140
|
+
return null;
|
|
1141
|
+
}
|
|
1142
|
+
const statusCode = error.params.statusCode;
|
|
1143
|
+
if (typeof statusCode === "number") {
|
|
1144
|
+
return feature.i18n.t("errors.statusCode", { statusCode });
|
|
1145
|
+
}
|
|
1146
|
+
const errorCode = error.params.errorCode;
|
|
1147
|
+
if (typeof errorCode === "number") {
|
|
1148
|
+
return feature.i18n.t("errors.errorCode", { errorCode });
|
|
1149
|
+
}
|
|
1150
|
+
return null;
|
|
1151
|
+
}
|
|
1152
|
+
function BrowserNodeHeaderButton({
|
|
1153
|
+
children,
|
|
1154
|
+
disabled,
|
|
1155
|
+
label,
|
|
1156
|
+
onClick
|
|
1157
|
+
}) {
|
|
1158
|
+
return /* @__PURE__ */ jsx(
|
|
1159
|
+
Button,
|
|
1160
|
+
{
|
|
1161
|
+
"aria-label": label,
|
|
1162
|
+
className: "rounded-md",
|
|
1163
|
+
disabled,
|
|
1164
|
+
size: "icon-sm",
|
|
1165
|
+
title: label,
|
|
1166
|
+
type: "button",
|
|
1167
|
+
variant: "chrome",
|
|
1168
|
+
onClick,
|
|
1169
|
+
children
|
|
1170
|
+
}
|
|
1171
|
+
);
|
|
1172
|
+
}
|
|
1173
|
+
|
|
1174
|
+
export {
|
|
1175
|
+
BrowserNode,
|
|
1176
|
+
BrowserNodeWorkbenchHeader,
|
|
1177
|
+
BrowserNodeHeader
|
|
1178
|
+
};
|
|
1179
|
+
//# sourceMappingURL=chunk-2GEY55PS.js.map
|