@lvce-editor/main-process 1.0.3

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.
@@ -0,0 +1,1039 @@
1
+ // The process explorer tries to implement the ARIA TreeGrid Design Pattern https://w3c.github.io/aria-practices/examples/treegrid/treegrid-1.html
2
+
3
+ const state = {
4
+ displayProcesses: [],
5
+ collapsed: [],
6
+ processes: [],
7
+ $Tbody: undefined,
8
+ }
9
+
10
+ const formatMemory = (memory) => {
11
+ if (memory < 1000) {
12
+ return `${memory} B`
13
+ }
14
+ if (memory < 1000 ** 2) {
15
+ return `${(memory / 1000 ** 1).toFixed(1)} kB`
16
+ }
17
+ if (memory < 1000 ** 3) {
18
+ return `${(memory / 1000 ** 2).toFixed(1)} MB`
19
+ }
20
+ if (memory < 1000 ** 4) {
21
+ return `${(memory / 1000 ** 3).toFixed(1)} GB`
22
+ }
23
+ return `${(memory / 1000 ** 4).toFixed(1)} TB`
24
+ }
25
+
26
+ const getDisplayProcesses = (processes) => {
27
+ const displayProcesses = []
28
+
29
+ const getChildren = (process, depth) => {
30
+ const children = []
31
+ for (const otherProcess of processes) {
32
+ if (otherProcess.ppid === process.pid) {
33
+ children.push(...withChildren(otherProcess, depth))
34
+ }
35
+ }
36
+ return children
37
+ }
38
+
39
+ const withChildren = (process, depth) => {
40
+ const children = getChildren(process, depth + 1)
41
+ if (children.length === 0) {
42
+ const displayProcess = {
43
+ ...process,
44
+ flags: 0,
45
+ depth,
46
+ // posInSet,
47
+ // setSize,
48
+ }
49
+ return [displayProcess]
50
+ }
51
+ if (state.collapsed.includes(process.pid)) {
52
+ const displayProcess = {
53
+ ...process,
54
+ flags: 1,
55
+ depth,
56
+
57
+ // posInSet,
58
+ // setSize,
59
+ }
60
+ return [displayProcess]
61
+ }
62
+ const displayProcess = {
63
+ ...process,
64
+ flags: 2,
65
+ depth,
66
+
67
+ // posInSet,
68
+ // setSize,
69
+ }
70
+ return [displayProcess, ...children]
71
+ }
72
+
73
+ const topProcess = processes[0]
74
+ displayProcesses.push(...withChildren(topProcess, 1))
75
+ return displayProcesses
76
+ }
77
+
78
+ const focusElement = ($Element) => {
79
+ $Element.tabIndex = 0
80
+ $Element.focus()
81
+ }
82
+
83
+ const getNodeIndex = ($Node) => {
84
+ let index = 0
85
+ while (($Node = $Node.previousElementSibling)) {
86
+ index++
87
+ }
88
+ return index
89
+ }
90
+
91
+ const handleClickIndex = (index) => {
92
+ const displayProcess = state.displayProcesses[index]
93
+ const collapsedIndex = state.collapsed.indexOf(displayProcess.pid)
94
+ if (collapsedIndex === -1) {
95
+ state.collapsed.push(displayProcess.pid)
96
+ } else {
97
+ state.collapsed.splice(collapsedIndex, 1)
98
+ }
99
+ renderProcesses(state.processes)
100
+ const oldFocusedElement = state.$Tbody.querySelector('[tabIndex="0"]')
101
+ if (oldFocusedElement) {
102
+ oldFocusedElement.tabIndex = -1
103
+ }
104
+ const newFocusedElement = state.$Tbody.children[index]
105
+ if (newFocusedElement) {
106
+ newFocusedElement.tabIndex = 0
107
+ }
108
+ }
109
+
110
+ const handleDoubleClick = (event) => {
111
+ const $Target = event.target
112
+ switch ($Target.className) {
113
+ case 'Row': {
114
+ const index = getNodeIndex($Target)
115
+ handleClickIndex(index)
116
+ break
117
+ }
118
+ case 'Cell': {
119
+ const index = getNodeIndex($Target.parentNode)
120
+ handleClickIndex(index)
121
+ break
122
+ }
123
+ default:
124
+ throw new Error('unexpected class name')
125
+ }
126
+ }
127
+
128
+ const handleFocusIn = (event) => {
129
+ const oldFocusedElement = state.$Tbody.querySelector('[tabIndex="0"]')
130
+ const $Target = event.target
131
+ if (oldFocusedElement) {
132
+ oldFocusedElement.tabIndex = -1
133
+ }
134
+ $Target.tabIndex = 0
135
+ }
136
+
137
+ const handleHomeRow = ($ActiveElement) => {
138
+ const $FirstRow = $ActiveElement.parentElement.firstElementChild
139
+ $FirstRow.focus()
140
+ }
141
+
142
+ const handleHomeCell = ($ActiveElement, control) => {
143
+ if (control) {
144
+ const index = getNodeIndex($ActiveElement)
145
+ const $FirstRow = $ActiveElement.parentElement.parentElement.firstElementChild
146
+ const $TopCell = $FirstRow.children[index]
147
+ $TopCell.focus()
148
+ return
149
+ }
150
+ const $FirstCell = $ActiveElement.parentElement.firstElementChild
151
+ $FirstCell.focus()
152
+ }
153
+
154
+ const handleHome = (control) => {
155
+ const $ActiveElement = document.activeElement
156
+ switch ($ActiveElement.className) {
157
+ case 'Row':
158
+ handleHomeRow($ActiveElement)
159
+ break
160
+ case 'Cell':
161
+ handleHomeCell($ActiveElement, control)
162
+ break
163
+ default:
164
+ break
165
+ }
166
+ }
167
+
168
+ const handleArrowUpRow = ($ActiveElement) => {
169
+ const $RowAbove = $ActiveElement.previousElementSibling
170
+ if (!$RowAbove) {
171
+ return
172
+ }
173
+ // @ts-ignore
174
+ $RowAbove.focus()
175
+ }
176
+
177
+ const handleArrowUpCell = ($ActiveElement) => {
178
+ const index = getNodeIndex($ActiveElement)
179
+ const $RowAbove = $ActiveElement.parentElement.previousElementSibling
180
+ if (!$RowAbove) {
181
+ return
182
+ }
183
+ const $CellAbove = $RowAbove.children[index]
184
+ // @ts-ignore
185
+ $CellAbove.focus()
186
+ }
187
+
188
+ const handleArrowUp = () => {
189
+ const $ActiveElement = document.activeElement
190
+ switch ($ActiveElement.className) {
191
+ case 'Row':
192
+ handleArrowUpRow($ActiveElement)
193
+ break
194
+ case 'Cell':
195
+ handleArrowUpCell($ActiveElement)
196
+ break
197
+ default:
198
+ break
199
+ }
200
+ }
201
+
202
+ const handleArrowDownRow = ($ActiveElement) => {
203
+ const $RowBelow = $ActiveElement.nextElementSibling
204
+ if (!$RowBelow) {
205
+ return
206
+ }
207
+ // @ts-ignore
208
+ $RowBelow.focus()
209
+ }
210
+
211
+ const handleArrowDownCell = ($ActiveElement) => {
212
+ const index = getNodeIndex($ActiveElement)
213
+ const $RowBelow = $ActiveElement.parentElement.nextElementSibling
214
+ if (!$RowBelow) {
215
+ return
216
+ }
217
+ const $CellBelow = $RowBelow.children[index]
218
+ // @ts-ignore
219
+ $CellBelow.focus()
220
+ }
221
+
222
+ const handleArrowDown = () => {
223
+ const $ActiveElement = document.activeElement
224
+ switch ($ActiveElement.className) {
225
+ case 'Row':
226
+ handleArrowDownRow($ActiveElement)
227
+ break
228
+ case 'Cell':
229
+ handleArrowDownCell($ActiveElement)
230
+ break
231
+ default:
232
+ break
233
+ }
234
+ }
235
+
236
+ const handleEndRow = ($ActiveElement) => {
237
+ const $LastRow = $ActiveElement.parentElement.lastElementChild
238
+ $LastRow.focus()
239
+ }
240
+
241
+ const handleEndCell = ($ActiveElement, control) => {
242
+ if (control) {
243
+ const index = getNodeIndex($ActiveElement)
244
+ const $LastRow = $ActiveElement.parentElement.parentElement.lastElementChild
245
+ const $BottomCell = $LastRow.children[index]
246
+ $BottomCell.focus()
247
+ return
248
+ }
249
+ const $LastCell = $ActiveElement.parentElement.lastElementChild
250
+ $LastCell.focus()
251
+ }
252
+
253
+ const handleEnd = (control) => {
254
+ const $ActiveElement = document.activeElement
255
+ switch ($ActiveElement.className) {
256
+ case 'Row':
257
+ handleEndRow($ActiveElement)
258
+ break
259
+ case 'Cell':
260
+ handleEndCell($ActiveElement, control)
261
+ break
262
+ default:
263
+ break
264
+ }
265
+ }
266
+
267
+ const handleArrowRightRow = ($ActiveElement) => {
268
+ if ($ActiveElement.ariaExpanded === 'false') {
269
+ const index = getNodeIndex($ActiveElement)
270
+ const displayProcess = state.displayProcesses[index]
271
+ const collapsedIndex = state.collapsed.indexOf(displayProcess.pid)
272
+ state.collapsed.splice(collapsedIndex, 1)
273
+ renderProcesses(state.processes)
274
+ return
275
+ }
276
+ const $Cell = $ActiveElement.firstElementChild
277
+ // @ts-ignore
278
+ $Cell.focus()
279
+ // @ts-ignore
280
+ }
281
+
282
+ const handleArrowRightCell = ($ActiveElement) => {
283
+ // TODO focus next cell
284
+ const $NextCell = $ActiveElement.nextElementSibling
285
+ if (!$NextCell) {
286
+ return
287
+ }
288
+ // @ts-ignore
289
+ $NextCell.focus()
290
+ }
291
+
292
+ const handleArrowRight = () => {
293
+ const $ActiveElement = document.activeElement
294
+ switch ($ActiveElement.className) {
295
+ case 'Row':
296
+ handleArrowRightRow($ActiveElement)
297
+ break
298
+ case 'Cell':
299
+ handleArrowRightCell($ActiveElement)
300
+ break
301
+ default:
302
+ break
303
+ }
304
+ }
305
+
306
+ const handleArrowLeftRow = ($ActiveElement) => {
307
+ if ($ActiveElement.ariaExpanded === 'true') {
308
+ const index = getNodeIndex($ActiveElement)
309
+ const displayProcess = state.displayProcesses[index]
310
+ state.collapsed.push(displayProcess.pid)
311
+ renderProcesses(state.processes)
312
+ return
313
+ }
314
+ const index = getNodeIndex($ActiveElement)
315
+ const displayProcess = state.displayProcesses[index]
316
+ for (let i = index; i >= 0; i--) {
317
+ const otherProcess = state.displayProcesses[i]
318
+ if (otherProcess.depth === displayProcess.depth - 1) {
319
+ const $Row = state.$Tbody.children[i]
320
+ $Row.focus()
321
+ break
322
+ }
323
+ }
324
+ }
325
+
326
+ const handleArrowLeftCell = ($ActiveElement) => {
327
+ const $PreviousCell = $ActiveElement.previousElementSibling
328
+ if ($PreviousCell) {
329
+ // @ts-ignore
330
+ $PreviousCell.focus()
331
+ return
332
+ }
333
+ const $Row = $ActiveElement.parentNode
334
+ // @ts-ignore
335
+ $Row.focus()
336
+ }
337
+
338
+ const handleArrowLeft = () => {
339
+ const $ActiveElement = document.activeElement
340
+ switch ($ActiveElement.className) {
341
+ case 'Row':
342
+ handleArrowLeftRow($ActiveElement)
343
+ break
344
+ case 'Cell':
345
+ handleArrowLeftCell($ActiveElement)
346
+ break
347
+ default:
348
+ break
349
+ }
350
+ }
351
+
352
+ const handleMouseDownCell = () => {}
353
+
354
+ const handleMouseDown = (event) => {
355
+ const $Target = event.target
356
+ const $ActiveElement = document.activeElement
357
+ if ($Target === $ActiveElement) {
358
+ return
359
+ }
360
+ if ($Target.className === 'Cell') {
361
+ event.preventDefault()
362
+ $Target.parentElement.focus()
363
+ }
364
+ }
365
+
366
+ const IsDebuggable = {
367
+ isDebuggable(command) {
368
+ return command.includes('node ') || command.includes('node.exe') || command.includes('node.mojom.NodeService')
369
+ },
370
+ }
371
+
372
+ const UiStrings = {
373
+ KillProcess: 'Kill Process',
374
+ DebugProcess: 'Debug Process',
375
+ }
376
+
377
+ const getMenuItems = (displayProcess) => {
378
+ const menuItems = [
379
+ {
380
+ label: UiStrings.KillProcess,
381
+ },
382
+ ]
383
+ if (IsDebuggable.isDebuggable(displayProcess.cmd)) {
384
+ menuItems.push({
385
+ label: UiStrings.DebugProcess,
386
+ })
387
+ }
388
+ return menuItems
389
+ }
390
+
391
+ const handleContextMenuSelect = (label, displayProcess) => {
392
+ switch (label) {
393
+ case UiStrings.DebugProcess:
394
+ return ProcessExplorer.invoke('AttachDebugger.attachDebugger', displayProcess.pid)
395
+ case UiStrings.KillProcess:
396
+ return ProcessExplorer.invoke('Process.kill', displayProcess.pid)
397
+ default:
398
+ break
399
+ }
400
+ }
401
+
402
+ const processExplorerShowContextMenu = async (displayProcess, x, y) => {
403
+ const menuItems = getMenuItems(displayProcess)
404
+ const customData = displayProcess
405
+ const event = await ProcessExplorer.invoke('ElectronContextMenu.openContextMenu', menuItems, x, y, customData)
406
+ if (event.type === 'close') {
407
+ return
408
+ }
409
+ handleContextMenuSelect(event.data, displayProcess)
410
+ }
411
+
412
+ const handleContextMenu = async (event) => {
413
+ event.preventDefault()
414
+ const { clientX, clientY } = event
415
+ const $Target = event.target
416
+
417
+ const $Row = $Target.parentNode
418
+ const index = getNodeIndex($Row)
419
+
420
+ const displayProcess = state.displayProcesses[index]
421
+ await processExplorerShowContextMenu(displayProcess, clientX, clientY)
422
+ }
423
+
424
+ /**
425
+ *
426
+ * @param {KeyboardEvent} event
427
+ */
428
+ const handleKeyDown = (event) => {
429
+ const { key } = event
430
+ const control = event.ctrlKey
431
+ switch (key) {
432
+ case 'ArrowDown':
433
+ event.preventDefault()
434
+ handleArrowDown()
435
+ break
436
+ case 'ArrowUp':
437
+ event.preventDefault()
438
+ handleArrowUp()
439
+ break
440
+ case 'Home':
441
+ event.preventDefault()
442
+ handleHome(control)
443
+ break
444
+ case 'End':
445
+ event.preventDefault()
446
+ handleEnd(control)
447
+ break
448
+ case 'ArrowRight':
449
+ event.preventDefault()
450
+ handleArrowRight()
451
+ break
452
+ case 'ArrowLeft':
453
+ event.preventDefault()
454
+ handleArrowLeft()
455
+ break
456
+ default:
457
+ break
458
+ }
459
+ }
460
+
461
+ const ProcessFlag = {
462
+ None: 0,
463
+ Collapsed: 1,
464
+ Expanded: 2,
465
+ }
466
+
467
+ const getPaddingLeft = (process) => {
468
+ if (process.depth === 1) {
469
+ return '0'
470
+ }
471
+ const depthCh = (process.depth - 1) * 1.5
472
+ if (process.flags === ProcessFlag.None) {
473
+ return `calc(${depthCh}ch + 17px)`
474
+ }
475
+ return `${depthCh}ch`
476
+ }
477
+
478
+ const render$Process = ($Process, process) => {
479
+ $Process.ariaLevel = process.depth
480
+ $Process.firstChild.style.paddingLeft = getPaddingLeft(process)
481
+ $Process.title = process.cmd
482
+ switch (process.flags) {
483
+ case ProcessFlag.None:
484
+ $Process.removeAttribute('aria-expanded')
485
+ break
486
+ case ProcessFlag.Collapsed:
487
+ $Process.ariaExpanded = false
488
+ break
489
+ case ProcessFlag.Expanded:
490
+ $Process.ariaExpanded = true
491
+ break
492
+ default:
493
+ break
494
+ }
495
+ const $Name = $Process.children[0]
496
+ const $Id = $Process.children[1]
497
+ const $Memory = $Process.children[2]
498
+
499
+ const { name } = process
500
+ const id = `${process.pid}`
501
+ const memory = formatMemory(process.memory)
502
+
503
+ if ($Name.firstChild.nodeValue !== name) {
504
+ $Name.firstChild.nodeValue = name
505
+ }
506
+ if ($Id.firstChild.nodeValue !== id) {
507
+ $Id.firstChild.nodeValue = id
508
+ }
509
+ if ($Memory.firstChild.nodeValue !== memory) {
510
+ $Memory.firstChild.nodeValue = memory
511
+ }
512
+ }
513
+
514
+ const create$GridCell = () => {
515
+ const $Cell = document.createElement('td')
516
+ $Cell.className = 'Cell'
517
+ // @ts-ignore
518
+ $Cell.role = 'gridcell'
519
+ $Cell.tabIndex = -1
520
+ const $Text = document.createTextNode('')
521
+ $Cell.append($Text)
522
+ return $Cell
523
+ }
524
+
525
+ const create$Row = () => {
526
+ const $Row = document.createElement('tr')
527
+ $Row.className = 'Row'
528
+ // @ts-ignore
529
+ $Row.role = 'row'
530
+ $Row.tabIndex = -1
531
+ // Set aria-description to empty string so that screen readers don't read title as well
532
+ // More details https://github.com/microsoft/vscode/issues/95378
533
+ $Row.setAttribute('aria-description', '')
534
+ const $Name = create$GridCell()
535
+ const $Id = create$GridCell()
536
+ const $Memory = create$GridCell()
537
+ $Row.append($Name, $Id, $Memory)
538
+ return $Row
539
+ }
540
+
541
+ const render$ProcessesLess = ($Processes, processes) => {
542
+ for (let i = 0; i < $Processes.children.length; i++) {
543
+ render$Process($Processes.children[i], processes[i])
544
+ }
545
+ const fragment = document.createDocumentFragment()
546
+ for (let i = $Processes.children.length; i < processes.length; i++) {
547
+ const $Process = create$Row()
548
+ render$Process($Process, processes[i])
549
+ fragment.append($Process)
550
+ }
551
+ $Processes.append(fragment)
552
+ }
553
+
554
+ const render$ProcessesEqual = ($Processes, processes) => {
555
+ for (const [i, process_] of processes.entries()) {
556
+ render$Process($Processes.children[i], process_)
557
+ }
558
+ }
559
+
560
+ const render$ProcessesMore = ($Processes, processes) => {
561
+ for (const [i, process_] of processes.entries()) {
562
+ render$Process($Processes.children[i], process_)
563
+ }
564
+ const diff = $Processes.children.length - processes.length
565
+ for (let i = processes.length; i < processes.length + diff; i++) {
566
+ $Processes.lastChild.remove()
567
+ }
568
+ }
569
+
570
+ const renderProcesses = (processes) => {
571
+ const displayProcesses = getDisplayProcesses(processes)
572
+ state.processes = processes
573
+ state.displayProcesses = displayProcesses
574
+ const $Tbody = document.querySelector('tbody')
575
+ if (!$Tbody) {
576
+ return
577
+ }
578
+ if ($Tbody.children.length < displayProcesses.length) {
579
+ render$ProcessesLess($Tbody, displayProcesses)
580
+ } else if ($Tbody.children.length === displayProcesses.length) {
581
+ render$ProcessesEqual($Tbody, displayProcesses)
582
+ } else {
583
+ render$ProcessesMore($Tbody, displayProcesses)
584
+ }
585
+ $Tbody.ondblclick = handleDoubleClick
586
+ $Tbody.onkeydown = handleKeyDown
587
+ $Tbody.addEventListener('focusin', handleFocusIn, { capture: true })
588
+ $Tbody.onmousedown = handleMouseDown
589
+ $Tbody.oncontextmenu = handleContextMenu
590
+ state.$Tbody = $Tbody
591
+ }
592
+
593
+ const Id = {
594
+ id: 1,
595
+ create() {
596
+ return this.id++
597
+ },
598
+ }
599
+
600
+ const callbacks = Object.create(null)
601
+
602
+ const Callback = {
603
+ registerPromise() {
604
+ const id = Id.create()
605
+ const promise = new Promise((resolve, reject) => {
606
+ callbacks[id] = { resolve, reject }
607
+ })
608
+ return { id, promise }
609
+ },
610
+ resolve(id, message) {
611
+ callbacks[id].resolve(message)
612
+ delete callbacks[id]
613
+ },
614
+ }
615
+
616
+ const JsonRpcVersion = {
617
+ Two: '2.0',
618
+ }
619
+
620
+ const ErrorType = {
621
+ DomException: 'DOMException',
622
+ ReferenceError: 'ReferenceError',
623
+ SyntaxError: 'SyntaxError',
624
+ TypeError: 'TypeError',
625
+ }
626
+
627
+ const GetErrorConstructor = {
628
+ getErrorConstructor(message, type) {
629
+ if (type) {
630
+ switch (type) {
631
+ case ErrorType.DomException:
632
+ return DOMException
633
+ case ErrorType.TypeError:
634
+ return TypeError
635
+ case ErrorType.SyntaxError:
636
+ return SyntaxError
637
+ case ErrorType.ReferenceError:
638
+ return ReferenceError
639
+ default:
640
+ return Error
641
+ }
642
+ }
643
+ if (message.startsWith('TypeError: ')) {
644
+ return TypeError
645
+ }
646
+ if (message.startsWith('SyntaxError: ')) {
647
+ return SyntaxError
648
+ }
649
+ if (message.startsWith('ReferenceError: ')) {
650
+ return ReferenceError
651
+ }
652
+ return Error
653
+ },
654
+ }
655
+
656
+ const constructError = (message, type, name) => {
657
+ const ErrorConstructor = GetErrorConstructor.getErrorConstructor(message, type)
658
+ if (ErrorConstructor === DOMException && name) {
659
+ return new ErrorConstructor(message, name)
660
+ }
661
+ if (ErrorConstructor === Error) {
662
+ const error = new Error(message)
663
+ if (name && name !== 'VError') {
664
+ error.name = name
665
+ }
666
+ return error
667
+ }
668
+ return new ErrorConstructor(message)
669
+ }
670
+
671
+ const JsonRpcErrorCode = {
672
+ MethodNotFound: -32601,
673
+ Custom: -32001,
674
+ }
675
+
676
+ class JsonRpcError extends Error {
677
+ constructor(message) {
678
+ super(message)
679
+ this.name = 'JsonRpcError'
680
+ }
681
+ }
682
+
683
+ const RestoreJsonRpcError = {
684
+ restoreJsonRpcError(error) {
685
+ if (error && error instanceof Error) {
686
+ return error
687
+ }
688
+ if (error && error.code && error.code === JsonRpcErrorCode.MethodNotFound) {
689
+ const restoredError = new JsonRpcError(error.message)
690
+ restoredError.stack = error.stack
691
+ return restoredError
692
+ }
693
+ if (error && error.message) {
694
+ const restoredError = constructError(error.message, error.type, error.name)
695
+ if (error.data) {
696
+ if (error.data.stack) {
697
+ restoredError.stack = error.message + '\n' + error.data.stack
698
+
699
+ if (error.data.codeFrame) {
700
+ // @ts-ignore
701
+ restoredError.codeFrame = error.data.codeFrame
702
+ }
703
+ }
704
+ } else if (error.stack) {
705
+ // TODO accessing stack might be slow
706
+ const lowerStack = restoredError.stack
707
+ // @ts-ignore
708
+ const indexNewLine = lowerStack.indexOf('\n')
709
+ // @ts-ignore
710
+ restoredError.stack = error.stack + lowerStack.slice(indexNewLine)
711
+ }
712
+ return restoredError
713
+ }
714
+ if (typeof error === 'string') {
715
+ return new Error(`JsonRpc Error: ${error}`)
716
+ }
717
+ return new Error(`JsonRpc Error: ${error}`)
718
+ },
719
+ }
720
+
721
+ const JsonRpc = {
722
+ async invoke(ipc, method, ...params) {
723
+ const { id, promise } = Callback.registerPromise()
724
+ ipc.send({
725
+ jsonrpc: JsonRpcVersion.Two,
726
+ method,
727
+ params,
728
+ id,
729
+ })
730
+ const responseMessage = await promise
731
+ if ('error' in responseMessage) {
732
+ const restoredError = RestoreJsonRpcError.restoreJsonRpcError(responseMessage.error)
733
+ throw restoredError
734
+ }
735
+ if ('result' in responseMessage) {
736
+ return responseMessage.result
737
+ }
738
+
739
+ throw new Error('unexpected response message')
740
+ },
741
+ async invokeAndTransfer(ipc, method, ...params) {
742
+ const { id, promise } = Callback.registerPromise()
743
+ ipc.sendAndTransfer({
744
+ jsonrpc: JsonRpcVersion.Two,
745
+ method,
746
+ params,
747
+ id,
748
+ })
749
+ const responseMessage = await promise
750
+ if ('error' in responseMessage) {
751
+ const restoredError = RestoreJsonRpcError.restoreJsonRpcError(responseMessage.error)
752
+ throw restoredError
753
+ }
754
+ if ('result' in responseMessage) {
755
+ return responseMessage.result
756
+ }
757
+
758
+ throw new Error('unexpected response message')
759
+ },
760
+ }
761
+
762
+ const SharedProcess = {
763
+ /**
764
+ * @type {any}
765
+ */
766
+ ipc: undefined,
767
+ async invoke(method, ...params) {
768
+ return JsonRpc.invoke(this.ipc, method, ...params)
769
+ },
770
+ async invokeAndTransfer(method, ...params) {
771
+ return JsonRpc.invokeAndTransfer(this.ipc, method, ...params)
772
+ },
773
+ async listen() {
774
+ this.ipc = await IpcChild.listen({ module: IpcChildWithSharedProcess })
775
+ },
776
+ }
777
+
778
+ const ProcessExplorer = {
779
+ /**
780
+ * @type {any}
781
+ */
782
+ ipc: undefined,
783
+ async invoke(method, ...params) {
784
+ return JsonRpc.invoke(this.ipc, method, ...params)
785
+ },
786
+ async listen() {
787
+ this.ipc = await IpcChild.listen({ module: IpcChildWithProcessExplorer })
788
+ },
789
+ }
790
+
791
+ const listProcessesWithMemoryUsage = (rootPid) => {
792
+ return ProcessExplorer.invoke('ListProcessesWithMemoryUsage.listProcessesWithMemoryUsage', rootPid)
793
+ }
794
+
795
+ const handleMessageFromWindow = (event) => {
796
+ const { data } = event
797
+ if ('method' in data) {
798
+ return
799
+ }
800
+ Callback.resolve(data.id, data)
801
+ window.removeEventListener('message', handleMessageFromWindow)
802
+ }
803
+
804
+ const unwrapJsonRpcResult = (responseMessage) => {
805
+ if ('error' in responseMessage) {
806
+ const restoredError = RestoreJsonRpcError.restoreJsonRpcError(responseMessage.error)
807
+ throw restoredError
808
+ }
809
+ if ('result' in responseMessage) {
810
+ return responseMessage.result
811
+ }
812
+ return undefined
813
+ }
814
+
815
+ const IpcId = {
816
+ ProcessExplorerRenderer: 33,
817
+ }
818
+
819
+ const getPort = async (type, name) => {
820
+ // @ts-ignore
821
+ window.addEventListener('message', handleMessageFromWindow)
822
+ const { id, promise } = Callback.registerPromise()
823
+ const { port1, port2 } = new MessageChannel()
824
+ const message = {
825
+ jsonrpc: JsonRpcVersion.Two,
826
+ id,
827
+ method: 'CreateMessagePort.createMessagePort',
828
+ params: [IpcId.ProcessExplorerRenderer],
829
+ }
830
+ // @ts-ignore
831
+ if (!globalThis.isElectron) {
832
+ throw new Error('Electron api was requested but is not available')
833
+ }
834
+ window.postMessage(message, '*', [port1])
835
+ const responseMessage = await promise
836
+ unwrapJsonRpcResult(responseMessage)
837
+ return port2
838
+ }
839
+
840
+ const getTransfer = (params) => {
841
+ return params.filter((value) => value instanceof MessagePort)
842
+ }
843
+
844
+ const fixElectronParams = (message) => {
845
+ const { params } = message
846
+ const newParams = []
847
+ const transfer = getTransfer(params)
848
+ for (const param of params) {
849
+ if (!transfer.includes(param)) {
850
+ newParams.push(param)
851
+ }
852
+ }
853
+ return {
854
+ newValue: {
855
+ ...message,
856
+ params: newParams,
857
+ },
858
+ transfer,
859
+ }
860
+ }
861
+
862
+ const IpcChildWithSharedProcess = {
863
+ async create() {
864
+ const port = await getPort('shared-process', 'Shared Process')
865
+ return port
866
+ },
867
+ wrap(port) {
868
+ return {
869
+ port,
870
+ /**
871
+ * @type {any}
872
+ */
873
+ wrappedListener: null,
874
+ send(message) {
875
+ this.port.postMessage(message)
876
+ },
877
+ sendAndTransfer(message) {
878
+ const { newValue, transfer } = fixElectronParams(message)
879
+ this.port.postMessage(newValue, transfer)
880
+ },
881
+ set onmessage(listener) {
882
+ this.wrappedListener = (event) => {
883
+ listener(event.data)
884
+ }
885
+ this.port.onmessage = this.wrappedListener
886
+ },
887
+ get onmessage() {
888
+ return this.wrappedListener
889
+ },
890
+ }
891
+ },
892
+ }
893
+
894
+ const IpcChildWithProcessExplorer = {
895
+ async create() {
896
+ const { port1, port2 } = new MessageChannel()
897
+ await SharedProcess.invokeAndTransfer('HandleMessagePortForProcessExplorer.handleMessagePortForProcessExplorer', port1)
898
+ return port2
899
+ },
900
+ wrap(port) {
901
+ return {
902
+ port,
903
+ /**
904
+ * @type {any}
905
+ */
906
+ wrappedListener: null,
907
+ send(message) {
908
+ this.port.postMessage(message)
909
+ },
910
+ set onmessage(listener) {
911
+ this.wrappedListener = (event) => {
912
+ listener(event.data)
913
+ }
914
+ this.port.onmessage = this.wrappedListener
915
+ },
916
+ get onmessage() {
917
+ return this.wrappedListener
918
+ },
919
+ }
920
+ },
921
+ }
922
+
923
+ const IpcChild = {
924
+ async listen({ module }) {
925
+ const rawIpc = await module.create()
926
+ const ipc = module.wrap(rawIpc)
927
+ HandleIpc.handleIpc(ipc)
928
+ return ipc
929
+ },
930
+ }
931
+
932
+ const handleError = (event) => {
933
+ console.error(event)
934
+ document.body.textContent = `${event}`
935
+ }
936
+
937
+ const handleUnhandledRejection = (event) => {
938
+ console.error(event.reason)
939
+ document.body.textContent = `${event.reason}`
940
+ }
941
+
942
+ const isResultMessage = (message) => {
943
+ return 'result' in message
944
+ }
945
+
946
+ const isErrorMessage = (message) => {
947
+ return 'error' in message
948
+ }
949
+
950
+ const Signal = {
951
+ SIGTERM: 'SIGTERM',
952
+ }
953
+
954
+ const Process = {
955
+ kill(pid) {
956
+ return ProcessExplorer.invoke('Process.kill', pid, Signal.SIGTERM)
957
+ },
958
+ debug(pid) {
959
+ // TODO
960
+ console.log({ pid })
961
+ },
962
+ }
963
+
964
+ const getContextMenuFn = (label) => {
965
+ switch (label) {
966
+ case 'Kill Process':
967
+ return Process.kill
968
+ case 'Debug Process':
969
+ return Process.debug
970
+ default:
971
+ throw new Error(`context menu function not found ${label}`)
972
+ }
973
+ }
974
+
975
+ const ElectronContextMenu = {
976
+ handleSelect(label, customData) {
977
+ const { pid } = customData
978
+ const fn = getContextMenuFn(label)
979
+ return fn(pid)
980
+ },
981
+ }
982
+
983
+ const CommandMap = {
984
+ commandMap: {
985
+ 'ElectronContextMenu.handleSelect': ElectronContextMenu.handleSelect,
986
+ },
987
+ }
988
+
989
+ const Command = {
990
+ execute(method, ...params) {
991
+ const fn = CommandMap.commandMap[method]
992
+ if (!fn) {
993
+ return
994
+ }
995
+ return fn(...params)
996
+ },
997
+ }
998
+
999
+ const handleMessage = (message) => {
1000
+ if (message.id && (isResultMessage(message) || isErrorMessage(message))) {
1001
+ Callback.resolve(message.id, message)
1002
+ return
1003
+ }
1004
+ if (message.method) {
1005
+ return Command.execute(message.method, ...message.params)
1006
+ }
1007
+ }
1008
+
1009
+ const getPid = () => {
1010
+ return ProcessExplorer.invoke('ProcessId.getMainProcessId')
1011
+ }
1012
+
1013
+ const sleep = (timeout) => {
1014
+ return new Promise((resolve) => {
1015
+ setTimeout(resolve, timeout)
1016
+ })
1017
+ }
1018
+
1019
+ const HandleIpc = {
1020
+ handleIpc(ipc) {
1021
+ ipc.onmessage = handleMessage
1022
+ },
1023
+ }
1024
+
1025
+ const main = async () => {
1026
+ onerror = handleError
1027
+ onunhandledrejection = handleUnhandledRejection
1028
+ await SharedProcess.listen()
1029
+ await ProcessExplorer.listen()
1030
+ const pid = await getPid()
1031
+ const refreshInterval = 1000
1032
+ while (true) {
1033
+ const processesWithMemoryUsage = await listProcessesWithMemoryUsage(pid)
1034
+ renderProcesses(processesWithMemoryUsage)
1035
+ await sleep(refreshInterval)
1036
+ }
1037
+ }
1038
+
1039
+ main()