@dimina-kit/devtools 0.4.0-dev.20260718143821 → 0.4.0-dev.20260728065133

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.
Files changed (68) hide show
  1. package/dist/main/app/app.js +67 -1
  2. package/dist/main/app/lifecycle.js +9 -0
  3. package/dist/main/index.bundle.js +928 -162
  4. package/dist/main/ipc/bridge-router.d.ts +36 -0
  5. package/dist/main/ipc/bridge-router.js +56 -0
  6. package/dist/main/ipc/index.d.ts +1 -0
  7. package/dist/main/ipc/index.js +1 -0
  8. package/dist/main/ipc/internal-devtools.d.ts +15 -0
  9. package/dist/main/ipc/internal-devtools.js +9 -0
  10. package/dist/main/services/compile-standby.js +4 -0
  11. package/dist/main/services/console-forward/cdp-inject.d.ts +18 -0
  12. package/dist/main/services/console-forward/cdp-inject.js +30 -0
  13. package/dist/main/services/console-forward/global-console-mirror.d.ts +33 -0
  14. package/dist/main/services/console-forward/global-console-mirror.js +71 -0
  15. package/dist/main/services/console-forward/global-diagnostics-mirror.d.ts +33 -0
  16. package/dist/main/services/console-forward/global-diagnostics-mirror.js +73 -0
  17. package/dist/main/services/console-forward/index.d.ts +15 -1
  18. package/dist/main/services/console-forward/index.js +42 -2
  19. package/dist/main/services/console-forward/internal-log.d.ts +11 -0
  20. package/dist/main/services/console-forward/internal-log.js +23 -0
  21. package/dist/main/services/console-forward/open-gated-relay.d.ts +67 -0
  22. package/dist/main/services/console-forward/open-gated-relay.js +55 -0
  23. package/dist/main/services/diagnostics/index.d.ts +10 -0
  24. package/dist/main/services/elements-forward/index.js +14 -96
  25. package/dist/main/services/network-forward/frontend-dispatch.d.ts +64 -0
  26. package/dist/main/services/network-forward/frontend-dispatch.js +113 -8
  27. package/dist/main/services/network-forward/global-body-gate.d.ts +46 -0
  28. package/dist/main/services/network-forward/global-body-gate.js +93 -0
  29. package/dist/main/services/network-forward/global-mirror-test-fixtures.d.ts +48 -0
  30. package/dist/main/services/network-forward/global-mirror-test-fixtures.js +96 -0
  31. package/dist/main/services/network-forward/index.d.ts +31 -0
  32. package/dist/main/services/network-forward/index.js +256 -8
  33. package/dist/main/services/network-forward/user-facing.d.ts +21 -0
  34. package/dist/main/services/network-forward/user-facing.js +50 -0
  35. package/dist/main/services/views/console-filter.d.ts +145 -0
  36. package/dist/main/services/views/console-filter.js +233 -0
  37. package/dist/main/services/views/frontend-bootstrap-gate.d.ts +59 -0
  38. package/dist/main/services/views/frontend-bootstrap-gate.js +117 -0
  39. package/dist/main/services/views/native-simulator-devtools-host.js +138 -30
  40. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.d.ts +76 -0
  41. package/dist/main/services/views/view-manager-devtools-host-test-fixtures.js +182 -0
  42. package/dist/main/services/workbench-context.d.ts +10 -0
  43. package/dist/main/utils/theme.d.ts +10 -6
  44. package/dist/main/utils/theme.js +10 -6
  45. package/dist/main/windows/internal-devtools-window/index.d.ts +66 -0
  46. package/dist/main/windows/internal-devtools-window/index.js +160 -0
  47. package/dist/native-host/render/render.js +89 -73
  48. package/dist/native-host/service/service.js +2 -2
  49. package/dist/preload/windows/host-toolbar-runtime.cjs.map +1 -1
  50. package/dist/preload/windows/simulator.cjs.map +1 -1
  51. package/dist/renderer/assets/index-M5NDq_vi.js +49 -0
  52. package/dist/renderer/assets/{input-c2OcrrZy.js → input-dsgeTmVX.js} +2 -2
  53. package/dist/renderer/assets/ipc-transport-BgFdT9bT.js +9 -0
  54. package/dist/renderer/assets/{popover-kv2qspWz.js → popover-Disfu1cx.js} +2 -2
  55. package/dist/renderer/assets/{select-DKqzMtTh.js → select-DAw1hzkl.js} +2 -2
  56. package/dist/renderer/assets/{settings-dJNKTGr8.js → settings-M_8GFW2M.js} +2 -2
  57. package/dist/renderer/assets/settings-api-DFR5eDl_.js +2 -0
  58. package/dist/renderer/assets/{workbenchSettings-DQ_VP5-W.js → workbenchSettings-CxdXeoOu.js} +2 -2
  59. package/dist/renderer/entries/main/index.html +5 -5
  60. package/dist/renderer/entries/popover/index.html +4 -4
  61. package/dist/renderer/entries/settings/index.html +4 -4
  62. package/dist/renderer/entries/workbench-settings/index.html +3 -3
  63. package/dist/shared/ipc-channels.d.ts +3 -0
  64. package/dist/shared/ipc-channels.js +6 -0
  65. package/package.json +7 -7
  66. package/dist/renderer/assets/index-DfDIvgvK.js +0 -49
  67. package/dist/renderer/assets/ipc-transport-CuDJ07aE.js +0 -9
  68. package/dist/renderer/assets/settings-api-qpXKDRXt.js +0 -2
@@ -0,0 +1,145 @@
1
+ /**
2
+ * Negative text-filter for the right-panel (user-facing) Console — hides
3
+ * dimina framework internal log lines from the panel the mini-app author
4
+ * watches (see console-forward/internal-log.ts for
5
+ * the prefix judge this mirrors). The framework prints these via a plain
6
+ * `console.log` call INSIDE the inspected service host itself; main has no
7
+ * interception point for a native console line (see
8
+ * console-forward/index.ts's header comment) — the only lever available is
9
+ * the front-end's OWN Console filter setting.
10
+ *
11
+ * ── Mechanism ────────────────────────────────────────────────────────────
12
+ * The Console panel's text filter is a Global/Synced DevTools setting. On
13
+ * Chromium ~146 (post-M125 kebab-case renaming) that means TWO things a
14
+ * naive `localStorage['console.textFilter']` write gets both wrong:
15
+ * 1. The real setting name is kebab-case: `console.text-filter`.
16
+ * 2. Global/Synced settings are NOT backed by `window.localStorage` at
17
+ * all — they go through `InspectorFrontendHost.getPreferences`
18
+ * (async-callback read) / `.setPreference(key, jsonValue)` (sync
19
+ * write). Values are themselves JSON-stringified (e.g. `'"elements"'`
20
+ * for a string preference) — never a `localStorage` entry.
21
+ * (An earlier version of this comment cited `localStorage['panel-selectedTab']`
22
+ * as a "proven working" precedent for the localStorage approach — that was
23
+ * itself a misreading: the console-default tab selection actually works via
24
+ * a LIVE `UI.ViewManager.instance().showView('console')` call, not a
25
+ * persisted setting; see native-simulator-devtools-host.ts's console-default
26
+ * injection, which has since dropped the dead localStorage line alongside
27
+ * this fix.)
28
+ *
29
+ * ── Disk persistence: verified NOT to apply to this custom host ───────────
30
+ * A dedicated isolated e2e (`e2e/_diagnose-inspector-frontend-host.spec.ts`,
31
+ * clean `electronApp.close()` — not `kill -9`, which never lets Electron
32
+ * flush prefs and would confound the result) confirms `setPreference`/
33
+ * `getPreferences` DO round-trip correctly in-process (this function's
34
+ * output was read back byte-for-byte via the real production code path),
35
+ * but for THIS custom devtools:// front-end host (a plain BrowserWindow we
36
+ * point at `devtools://` ourselves, not Electron's native
37
+ * `webContents.openDevTools()`) nothing under `electron.devtools.preferences`
38
+ * — not even `panel-selected-tab` — survives to the on-disk Preferences
39
+ * file on a clean shutdown. That's fine for this function's purpose: every
40
+ * fresh launch starts with `existing === undefined`, so the `isUnset` branch
41
+ * always (re-)applies the default — exactly the desired "always filtered by
42
+ * default" behavior. The self-healing "stale mark" branch below still
43
+ * matters WITHIN a single running process (e.g. re-pointing the front-end at
44
+ * a new service host mid-session), where `getPreferences` reflects whatever
45
+ * was `setPreference`d earlier in that same process, including a real user
46
+ * customization typed into the filter box.
47
+ *
48
+ * `setPreference` values are themselves JSON-stringified (matching the
49
+ * `'"elements"'` shape observed above). A NEGATIVE filter (`-/regex/`) is
50
+ * DevTools' own "hide lines matching" syntax (the same `-` prefix a user
51
+ * types into the filter box themselves).
52
+ *
53
+ * Only overwrites when the user hasn't set their OWN filter — never clobber
54
+ * a real choice with this default. Re-injected on every (re)point like
55
+ * customizeDevtoolsTabs, so a service-host pool swap re-applies it.
56
+ *
57
+ * Self-healing default: a companion `…dimina-default` key marks the value WE
58
+ * last wrote. If `console.text-filter` still equals that mark, it is our own
59
+ * prior default going stale (e.g. this DEFAULT_INTERNAL_LOG_FILTER regex
60
+ * changing between releases), not a real user choice — safe to overwrite. A
61
+ * value that differs from the mark is the user's own customization and is
62
+ * left untouched. Without this, "only write when unset" permanently freezes
63
+ * whatever default first got written (real dev-machine finding: an older
64
+ * build's regex, missing the `[视图] ` prefix, survived across rebuilds and
65
+ * silently never updated).
66
+ *
67
+ * This covers the persisted setting a FRESH (not-yet-constructed) Console
68
+ * panel would read at boot. It does NOT by itself update an
69
+ * ALREADY-constructed Console panel's live filter box — real-machine
70
+ * evidence (empirical probe against the actual bundled Console panel, this
71
+ * session) showed the panel's own async bootstrap (Settings storage wiring,
72
+ * i18n, ActionRegistry, …) can still be constructing itself well after
73
+ * `applyConsoleFilter`'s injection point (right after `openDevTools()`,
74
+ * gated on `did-stop-loading` — the top document finishing load, NOT the
75
+ * front-end's own panel construction), so this preference write can land
76
+ * before OR after the panel reads it, and a "before" write does nothing for
77
+ * that already-visible filter box. `buildLiveConsoleFilterScript` below
78
+ * closes that gap by driving the panel's live filter object directly, with a
79
+ * bounded poll (no fixed delay — there is no public "Console panel
80
+ * constructed" event to await, only a checkable condition) standing in for
81
+ * the missing event this repo's own conventions call for.
82
+ */
83
+ /**
84
+ * DevTools negative-filter regex hiding the dimina framework's internal-log
85
+ * lines (see console-forward/internal-log.ts's prefix judge) that have NO
86
+ * other interception point: SERVICE-layer entries (`^\[service\]`) are
87
+ * captured directly by the native CDP `Runtime.consoleAPICalled` attach on
88
+ * the service host (service-host/preload.cjs deliberately does not
89
+ * monkeypatch `console.*` there — see its header comment — so there is no
90
+ * main-owned code between the framework's call and the attached DevTools).
91
+ * This front-end filter is genuinely the only lever available for that half.
92
+ *
93
+ * RENDER-layer framework noise (`[system]`) no longer needs this filter:
94
+ * `console-forward/index.ts`'s `forwardRenderToServiceHost` now gates on
95
+ * `isInternalLogMessage` and simply never injects those entries into the
96
+ * service host's console at all — filtered at the source (main-owned code),
97
+ * so they never reach the right panel regardless of what a user types into
98
+ * this box. `-/…/ ` is DevTools' own "exclude" filter syntax.
99
+ */
100
+ export declare const DEFAULT_INTERNAL_LOG_FILTER = "-/^\\[service\\]/";
101
+ /**
102
+ * Build the `executeJavaScript` source that seeds the Console panel's
103
+ * negative filter via `InspectorFrontendHost`, unless the user already set
104
+ * one of their own (or our own prior default has been customized away from).
105
+ */
106
+ export declare function buildConsoleFilterScript(negativeFilter?: string): string;
107
+ /**
108
+ * Build the `executeJavaScript` source that drives the Console panel's LIVE
109
+ * filter object directly — the fix for the gap `buildConsoleFilterScript`'s
110
+ * persisted-preference write cannot close (see this module's header
111
+ * comment). Empirically discovered live API (real-machine probe against the
112
+ * actual bundled `devtools://` Console panel, this session — not a guess):
113
+ * `Console.ConsoleView.instance().filter` exposes `textFilterUI` (the
114
+ * visible input box — `.setValue(text)` updates what the user sees) and
115
+ * `updateCurrentFilter()` / `onFilterChanged()` (re-parses the box's value
116
+ * into the filter actually applied to messages, and re-applies it to
117
+ * already-shown AND future messages). Verified end-to-end: after this
118
+ * sequence, a fresh `[service]`-prefixed message logged afterward is
119
+ * immediately hidden, matching the already-shown ones.
120
+ *
121
+ * Same "don't clobber a real user choice" contract as
122
+ * `buildConsoleFilterScript`, judged against the LIVE box value instead of a
123
+ * persisted one: only applies when the box currently reads empty (unset) or
124
+ * already equals this exact default (re-applying is a harmless no-op,
125
+ * correctly handles a re-point re-running this script against a panel that
126
+ * already has it applied).
127
+ *
128
+ * `Console.ConsoleView` has no lifecycle EVENT marking "fully constructed" —
129
+ * only the checkable condition of whether calling `.instance()` and reaching
130
+ * into `.filter.textFilterUI` throws. Retries on a bounded interval (see
131
+ * `LIVE_FILTER_POLL_MAX_ATTEMPTS`/`_INTERVAL_MS`) rather than a single fixed
132
+ * delay, and stops immediately on either success or a detected user
133
+ * customization — it does not keep polling once there is nothing left to do.
134
+ *
135
+ * Every tick gates on the front-end bootstrap probe BEFORE touching
136
+ * `ConsoleView.instance()`: an early construction transitively creates
137
+ * IssuesManager and permanently kills the front-end's own bootstrap
138
+ * (`ensureFirst` conflict in MainImpl.#createAppUI) — see
139
+ * frontend-bootstrap-gate.ts for the full mechanism. The main process
140
+ * additionally holds this whole injection behind `whenFrontendBootstrapped`;
141
+ * the in-realm probe is defense in depth for any other caller of this
142
+ * script builder.
143
+ */
144
+ export declare function buildLiveConsoleFilterScript(negativeFilter?: string): string;
145
+ //# sourceMappingURL=console-filter.d.ts.map
@@ -0,0 +1,233 @@
1
+ /**
2
+ * Negative text-filter for the right-panel (user-facing) Console — hides
3
+ * dimina framework internal log lines from the panel the mini-app author
4
+ * watches (see console-forward/internal-log.ts for
5
+ * the prefix judge this mirrors). The framework prints these via a plain
6
+ * `console.log` call INSIDE the inspected service host itself; main has no
7
+ * interception point for a native console line (see
8
+ * console-forward/index.ts's header comment) — the only lever available is
9
+ * the front-end's OWN Console filter setting.
10
+ *
11
+ * ── Mechanism ────────────────────────────────────────────────────────────
12
+ * The Console panel's text filter is a Global/Synced DevTools setting. On
13
+ * Chromium ~146 (post-M125 kebab-case renaming) that means TWO things a
14
+ * naive `localStorage['console.textFilter']` write gets both wrong:
15
+ * 1. The real setting name is kebab-case: `console.text-filter`.
16
+ * 2. Global/Synced settings are NOT backed by `window.localStorage` at
17
+ * all — they go through `InspectorFrontendHost.getPreferences`
18
+ * (async-callback read) / `.setPreference(key, jsonValue)` (sync
19
+ * write). Values are themselves JSON-stringified (e.g. `'"elements"'`
20
+ * for a string preference) — never a `localStorage` entry.
21
+ * (An earlier version of this comment cited `localStorage['panel-selectedTab']`
22
+ * as a "proven working" precedent for the localStorage approach — that was
23
+ * itself a misreading: the console-default tab selection actually works via
24
+ * a LIVE `UI.ViewManager.instance().showView('console')` call, not a
25
+ * persisted setting; see native-simulator-devtools-host.ts's console-default
26
+ * injection, which has since dropped the dead localStorage line alongside
27
+ * this fix.)
28
+ *
29
+ * ── Disk persistence: verified NOT to apply to this custom host ───────────
30
+ * A dedicated isolated e2e (`e2e/_diagnose-inspector-frontend-host.spec.ts`,
31
+ * clean `electronApp.close()` — not `kill -9`, which never lets Electron
32
+ * flush prefs and would confound the result) confirms `setPreference`/
33
+ * `getPreferences` DO round-trip correctly in-process (this function's
34
+ * output was read back byte-for-byte via the real production code path),
35
+ * but for THIS custom devtools:// front-end host (a plain BrowserWindow we
36
+ * point at `devtools://` ourselves, not Electron's native
37
+ * `webContents.openDevTools()`) nothing under `electron.devtools.preferences`
38
+ * — not even `panel-selected-tab` — survives to the on-disk Preferences
39
+ * file on a clean shutdown. That's fine for this function's purpose: every
40
+ * fresh launch starts with `existing === undefined`, so the `isUnset` branch
41
+ * always (re-)applies the default — exactly the desired "always filtered by
42
+ * default" behavior. The self-healing "stale mark" branch below still
43
+ * matters WITHIN a single running process (e.g. re-pointing the front-end at
44
+ * a new service host mid-session), where `getPreferences` reflects whatever
45
+ * was `setPreference`d earlier in that same process, including a real user
46
+ * customization typed into the filter box.
47
+ *
48
+ * `setPreference` values are themselves JSON-stringified (matching the
49
+ * `'"elements"'` shape observed above). A NEGATIVE filter (`-/regex/`) is
50
+ * DevTools' own "hide lines matching" syntax (the same `-` prefix a user
51
+ * types into the filter box themselves).
52
+ *
53
+ * Only overwrites when the user hasn't set their OWN filter — never clobber
54
+ * a real choice with this default. Re-injected on every (re)point like
55
+ * customizeDevtoolsTabs, so a service-host pool swap re-applies it.
56
+ *
57
+ * Self-healing default: a companion `…dimina-default` key marks the value WE
58
+ * last wrote. If `console.text-filter` still equals that mark, it is our own
59
+ * prior default going stale (e.g. this DEFAULT_INTERNAL_LOG_FILTER regex
60
+ * changing between releases), not a real user choice — safe to overwrite. A
61
+ * value that differs from the mark is the user's own customization and is
62
+ * left untouched. Without this, "only write when unset" permanently freezes
63
+ * whatever default first got written (real dev-machine finding: an older
64
+ * build's regex, missing the `[视图] ` prefix, survived across rebuilds and
65
+ * silently never updated).
66
+ *
67
+ * This covers the persisted setting a FRESH (not-yet-constructed) Console
68
+ * panel would read at boot. It does NOT by itself update an
69
+ * ALREADY-constructed Console panel's live filter box — real-machine
70
+ * evidence (empirical probe against the actual bundled Console panel, this
71
+ * session) showed the panel's own async bootstrap (Settings storage wiring,
72
+ * i18n, ActionRegistry, …) can still be constructing itself well after
73
+ * `applyConsoleFilter`'s injection point (right after `openDevTools()`,
74
+ * gated on `did-stop-loading` — the top document finishing load, NOT the
75
+ * front-end's own panel construction), so this preference write can land
76
+ * before OR after the panel reads it, and a "before" write does nothing for
77
+ * that already-visible filter box. `buildLiveConsoleFilterScript` below
78
+ * closes that gap by driving the panel's live filter object directly, with a
79
+ * bounded poll (no fixed delay — there is no public "Console panel
80
+ * constructed" event to await, only a checkable condition) standing in for
81
+ * the missing event this repo's own conventions call for.
82
+ */
83
+ /**
84
+ * DevTools negative-filter regex hiding the dimina framework's internal-log
85
+ * lines (see console-forward/internal-log.ts's prefix judge) that have NO
86
+ * other interception point: SERVICE-layer entries (`^\[service\]`) are
87
+ * captured directly by the native CDP `Runtime.consoleAPICalled` attach on
88
+ * the service host (service-host/preload.cjs deliberately does not
89
+ * monkeypatch `console.*` there — see its header comment — so there is no
90
+ * main-owned code between the framework's call and the attached DevTools).
91
+ * This front-end filter is genuinely the only lever available for that half.
92
+ *
93
+ * RENDER-layer framework noise (`[system]`) no longer needs this filter:
94
+ * `console-forward/index.ts`'s `forwardRenderToServiceHost` now gates on
95
+ * `isInternalLogMessage` and simply never injects those entries into the
96
+ * service host's console at all — filtered at the source (main-owned code),
97
+ * so they never reach the right panel regardless of what a user types into
98
+ * this box. `-/…/ ` is DevTools' own "exclude" filter syntax.
99
+ */
100
+ export const DEFAULT_INTERNAL_LOG_FILTER = '-/^\\[service\\]/';
101
+ /**
102
+ * Build the `executeJavaScript` source that seeds the Console panel's
103
+ * negative filter via `InspectorFrontendHost`, unless the user already set
104
+ * one of their own (or our own prior default has been customized away from).
105
+ */
106
+ export function buildConsoleFilterScript(negativeFilter = DEFAULT_INTERNAL_LOG_FILTER) {
107
+ const keyJson = JSON.stringify('console.text-filter');
108
+ const markKeyJson = JSON.stringify('console.text-filter.dimina-default');
109
+ // The stored preference value is itself JSON-stringified (mirrors the
110
+ // real host's own `'"elements"'`-shaped entries).
111
+ const valueJsonLiteral = JSON.stringify(JSON.stringify(negativeFilter));
112
+ return `(function(){try{
113
+ var IFH = globalThis.InspectorFrontendHost;
114
+ if (!IFH || typeof IFH.getPreferences !== 'function' || typeof IFH.setPreference !== 'function') return;
115
+ IFH.getPreferences(function(prefs){
116
+ try {
117
+ var KEY = ${keyJson};
118
+ var MARK_KEY = ${markKeyJson};
119
+ var VALUE = ${valueJsonLiteral};
120
+ var existing = prefs ? prefs[KEY] : undefined;
121
+ var mark = prefs ? prefs[MARK_KEY] : undefined;
122
+ var isUnset = existing === undefined || existing === null || existing === '' || existing === '""';
123
+ var isOurStaleDefault = !isUnset && mark !== undefined && mark !== null && existing === mark;
124
+ if (isUnset || isOurStaleDefault) {
125
+ IFH.setPreference(KEY, VALUE);
126
+ IFH.setPreference(MARK_KEY, VALUE);
127
+ }
128
+ } catch(_){}
129
+ });
130
+ }catch(_){}})()`;
131
+ }
132
+ // Bound on how long `buildLiveConsoleFilterScript`'s in-realm poll retries
133
+ // before giving up — mirrors this module's other bounded waits (e.g.
134
+ // internal-devtools-window's CLOSE_WATCHDOG_MS): wait for the real condition,
135
+ // but never forever. `Console.ConsoleView` was observed (real-machine probe,
136
+ // this session) fully constructible well within a few seconds of the
137
+ // front-end's document settling; this leaves generous headroom.
138
+ const LIVE_FILTER_POLL_MAX_ATTEMPTS = 100;
139
+ const LIVE_FILTER_POLL_INTERVAL_MS = 100;
140
+ /**
141
+ * Build the `executeJavaScript` source that drives the Console panel's LIVE
142
+ * filter object directly — the fix for the gap `buildConsoleFilterScript`'s
143
+ * persisted-preference write cannot close (see this module's header
144
+ * comment). Empirically discovered live API (real-machine probe against the
145
+ * actual bundled `devtools://` Console panel, this session — not a guess):
146
+ * `Console.ConsoleView.instance().filter` exposes `textFilterUI` (the
147
+ * visible input box — `.setValue(text)` updates what the user sees) and
148
+ * `updateCurrentFilter()` / `onFilterChanged()` (re-parses the box's value
149
+ * into the filter actually applied to messages, and re-applies it to
150
+ * already-shown AND future messages). Verified end-to-end: after this
151
+ * sequence, a fresh `[service]`-prefixed message logged afterward is
152
+ * immediately hidden, matching the already-shown ones.
153
+ *
154
+ * Same "don't clobber a real user choice" contract as
155
+ * `buildConsoleFilterScript`, judged against the LIVE box value instead of a
156
+ * persisted one: only applies when the box currently reads empty (unset) or
157
+ * already equals this exact default (re-applying is a harmless no-op,
158
+ * correctly handles a re-point re-running this script against a panel that
159
+ * already has it applied).
160
+ *
161
+ * `Console.ConsoleView` has no lifecycle EVENT marking "fully constructed" —
162
+ * only the checkable condition of whether calling `.instance()` and reaching
163
+ * into `.filter.textFilterUI` throws. Retries on a bounded interval (see
164
+ * `LIVE_FILTER_POLL_MAX_ATTEMPTS`/`_INTERVAL_MS`) rather than a single fixed
165
+ * delay, and stops immediately on either success or a detected user
166
+ * customization — it does not keep polling once there is nothing left to do.
167
+ *
168
+ * Every tick gates on the front-end bootstrap probe BEFORE touching
169
+ * `ConsoleView.instance()`: an early construction transitively creates
170
+ * IssuesManager and permanently kills the front-end's own bootstrap
171
+ * (`ensureFirst` conflict in MainImpl.#createAppUI) — see
172
+ * frontend-bootstrap-gate.ts for the full mechanism. The main process
173
+ * additionally holds this whole injection behind `whenFrontendBootstrapped`;
174
+ * the in-realm probe is defense in depth for any other caller of this
175
+ * script builder.
176
+ */
177
+ export function buildLiveConsoleFilterScript(negativeFilter = DEFAULT_INTERNAL_LOG_FILTER) {
178
+ const filterJson = JSON.stringify(negativeFilter);
179
+ const maxAttempts = JSON.stringify(LIVE_FILTER_POLL_MAX_ATTEMPTS);
180
+ const intervalMs = JSON.stringify(LIVE_FILTER_POLL_INTERVAL_MS);
181
+ return `(function(){
182
+ var VALUE = ${filterJson};
183
+ var attempts = 0;
184
+ function bootstrapReady() {
185
+ try {
186
+ var eui = globalThis.EUI;
187
+ if (!eui || !eui.ShortcutRegistry || !eui.ShortcutRegistry.ShortcutRegistry) return false;
188
+ eui.ShortcutRegistry.ShortcutRegistry.instance();
189
+ return true;
190
+ } catch (_) { return false; }
191
+ }
192
+ function tryApply() {
193
+ attempts++;
194
+ var scheduleRetry = attempts < ${maxAttempts};
195
+ // In-realm bootstrap probe FIRST (defense in depth on top of the
196
+ // main-process whenFrontendBootstrapped gate): touching
197
+ // Console.ConsoleView.instance() before the front-end's own
198
+ // MainImpl bootstrap completes constructs IssuesManager early and
199
+ // permanently kills that bootstrap (ensureFirst conflict) — the probe
200
+ // throws BEFORE constructing anything, so a not-ready tick costs
201
+ // nothing and never touches ConsoleView.
202
+ if (!bootstrapReady()) {
203
+ if (scheduleRetry) { setTimeout(tryApply, ${intervalMs}); return; }
204
+ console.warn('[console-filter] gave up applying the live Console filter after ' + attempts + ' attempts — the front-end bootstrap never completed');
205
+ return;
206
+ }
207
+ try {
208
+ var ConsoleNS = globalThis.Console;
209
+ var view = ConsoleNS && ConsoleNS.ConsoleView ? ConsoleNS.ConsoleView.instance() : null;
210
+ var f = view ? view.filter : null;
211
+ if (!f || !f.textFilterUI || typeof f.textFilterUI.setValue !== 'function' || typeof f.textFilterUI.value !== 'function') {
212
+ if (scheduleRetry) { setTimeout(tryApply, ${intervalMs}); return; }
213
+ console.warn('[console-filter] gave up applying the live Console filter after ' + attempts + ' attempts — Console.ConsoleView never became usable');
214
+ return;
215
+ }
216
+ var current = f.textFilterUI.value();
217
+ var isUnset = current === undefined || current === null || current === '';
218
+ var isOurDefault = current === VALUE;
219
+ if (isUnset || isOurDefault) {
220
+ f.textFilterUI.setValue(VALUE);
221
+ if (typeof f.updateCurrentFilter === 'function') f.updateCurrentFilter();
222
+ if (typeof f.onFilterChanged === 'function') f.onFilterChanged();
223
+ }
224
+ // Either applied, or a real user customization was detected — both
225
+ // are terminal: nothing left for a later retry to accomplish.
226
+ } catch(_) {
227
+ if (scheduleRetry) setTimeout(tryApply, ${intervalMs});
228
+ }
229
+ }
230
+ tryApply();
231
+ })()`;
232
+ }
233
+ //# sourceMappingURL=console-filter.js.map
@@ -0,0 +1,59 @@
1
+ import type { WebContents } from 'electron';
2
+ /**
3
+ * The single "front-end bootstrap finished" gate every DevTools front-end
4
+ * injector must pass BEFORE running any `.instance()`-style script in the
5
+ * front-end realm.
6
+ *
7
+ * Why this must exist: the bundled front-end's own `MainImpl.#createAppUI`
8
+ * constructs its singletons in a fixed order, and several of them assert
9
+ * first-creation (`IssuesManager.instance({ensureFirst: true})`). An injected
10
+ * script that constructs a panel singleton early — e.g. a bare
11
+ * `Console.ConsoleView.instance()`, whose field initializers transitively
12
+ * create IssuesManager — makes that assertion throw, and the front-end's
13
+ * bootstrap dies as an unhandled rejection it NEVER recovers from:
14
+ * ActionRegistry/ShortcutRegistry are then never created and the panel
15
+ * renders no tabs at all (the "Missing actionRegistry for shortcutRegistry"
16
+ * permanent-failure mode, confirmed against the real bundled front-end with
17
+ * first-creator stack captures and the matching Chromium 146 source).
18
+ *
19
+ * Iron rule for injectors: BEFORE this gate reports ready, an injected
20
+ * script may only touch registries and InspectorFrontendHost preferences —
21
+ * never any `.instance()` call. (`customizeDevtoolsTabs` is the one
22
+ * documented pre-gate exception: it must run before ViewManager snapshots
23
+ * the view registry, and it only edits the registry.)
24
+ */
25
+ /**
26
+ * Side-effect-free in-realm readiness probe, safe to feed directly to
27
+ * `wc.executeJavaScript`. `ShortcutRegistry.instance()` (no args) throws
28
+ * BEFORE constructing anything when the registry does not exist yet, so a
29
+ * false answer costs nothing — and a true answer proves `MainImpl` already
30
+ * passed the point where every bootstrap singleton (IssuesManager included)
31
+ * exists, making singleton-touching injections safe. `EUI` is the bundle's
32
+ * real exposure of the ui/legacy namespace (`globalThis.UI` does not exist
33
+ * on this front-end build). An expression, not a statement list: the value
34
+ * `executeJavaScript` resolves is the IIFE's completion value.
35
+ */
36
+ export declare const FRONTEND_BOOTSTRAP_PROBE_SCRIPT = "(() => {\n try {\n const eui = globalThis.EUI\n if (!eui || !eui.ShortcutRegistry || !eui.ShortcutRegistry.ShortcutRegistry) return false\n eui.ShortcutRegistry.ShortcutRegistry.instance()\n return true\n } catch (_) {\n return false\n }\n})()";
37
+ export interface WhenFrontendBootstrappedOptions {
38
+ /** Wall-clock give-up bound. Past it the promise resolves false with a
39
+ * single warn — callers degrade silently, matching the surrounding
40
+ * modules' convention. */
41
+ timeoutMs?: number;
42
+ /** Probe cadence while the front-end is still booting. */
43
+ intervalMs?: number;
44
+ }
45
+ /**
46
+ * Poll the probe against `wc` until the front-end bootstrap is provably
47
+ * complete. Resolves true on readiness; false on timeout (one warn) or a
48
+ * destroyed wc — never rejects, and never leaves a live timer behind after
49
+ * settling. A rejected probe call (execution context torn down mid-load,
50
+ * navigation in flight) counts as "not ready yet", not as an error.
51
+ *
52
+ * The timeout is an INDEPENDENT hard deadline racing the poll — not a check
53
+ * inside the poll chain. A probe `executeJavaScript` promise that never
54
+ * settles (frame destroyed mid-call / a wc permanently stuck loading) would
55
+ * stall a chain-embedded check forever; the deadline settles this promise
56
+ * regardless.
57
+ */
58
+ export declare function whenFrontendBootstrapped(wc: WebContents, opts?: WhenFrontendBootstrappedOptions): Promise<boolean>;
59
+ //# sourceMappingURL=frontend-bootstrap-gate.d.ts.map
@@ -0,0 +1,117 @@
1
+ /**
2
+ * The single "front-end bootstrap finished" gate every DevTools front-end
3
+ * injector must pass BEFORE running any `.instance()`-style script in the
4
+ * front-end realm.
5
+ *
6
+ * Why this must exist: the bundled front-end's own `MainImpl.#createAppUI`
7
+ * constructs its singletons in a fixed order, and several of them assert
8
+ * first-creation (`IssuesManager.instance({ensureFirst: true})`). An injected
9
+ * script that constructs a panel singleton early — e.g. a bare
10
+ * `Console.ConsoleView.instance()`, whose field initializers transitively
11
+ * create IssuesManager — makes that assertion throw, and the front-end's
12
+ * bootstrap dies as an unhandled rejection it NEVER recovers from:
13
+ * ActionRegistry/ShortcutRegistry are then never created and the panel
14
+ * renders no tabs at all (the "Missing actionRegistry for shortcutRegistry"
15
+ * permanent-failure mode, confirmed against the real bundled front-end with
16
+ * first-creator stack captures and the matching Chromium 146 source).
17
+ *
18
+ * Iron rule for injectors: BEFORE this gate reports ready, an injected
19
+ * script may only touch registries and InspectorFrontendHost preferences —
20
+ * never any `.instance()` call. (`customizeDevtoolsTabs` is the one
21
+ * documented pre-gate exception: it must run before ViewManager snapshots
22
+ * the view registry, and it only edits the registry.)
23
+ */
24
+ /**
25
+ * Side-effect-free in-realm readiness probe, safe to feed directly to
26
+ * `wc.executeJavaScript`. `ShortcutRegistry.instance()` (no args) throws
27
+ * BEFORE constructing anything when the registry does not exist yet, so a
28
+ * false answer costs nothing — and a true answer proves `MainImpl` already
29
+ * passed the point where every bootstrap singleton (IssuesManager included)
30
+ * exists, making singleton-touching injections safe. `EUI` is the bundle's
31
+ * real exposure of the ui/legacy namespace (`globalThis.UI` does not exist
32
+ * on this front-end build). An expression, not a statement list: the value
33
+ * `executeJavaScript` resolves is the IIFE's completion value.
34
+ */
35
+ export const FRONTEND_BOOTSTRAP_PROBE_SCRIPT = `(() => {
36
+ try {
37
+ const eui = globalThis.EUI
38
+ if (!eui || !eui.ShortcutRegistry || !eui.ShortcutRegistry.ShortcutRegistry) return false
39
+ eui.ShortcutRegistry.ShortcutRegistry.instance()
40
+ return true
41
+ } catch (_) {
42
+ return false
43
+ }
44
+ })()`;
45
+ // Matches the e2e suite's own bootstrap-wait window (console-filter-live's
46
+ // 45s probe poll) so a slow-but-alive bootstrap never lands in a band where
47
+ // production has given up while the spec still expects the injections.
48
+ const DEFAULT_TIMEOUT_MS = 45_000;
49
+ const DEFAULT_INTERVAL_MS = 150;
50
+ /**
51
+ * Poll the probe against `wc` until the front-end bootstrap is provably
52
+ * complete. Resolves true on readiness; false on timeout (one warn) or a
53
+ * destroyed wc — never rejects, and never leaves a live timer behind after
54
+ * settling. A rejected probe call (execution context torn down mid-load,
55
+ * navigation in flight) counts as "not ready yet", not as an error.
56
+ *
57
+ * The timeout is an INDEPENDENT hard deadline racing the poll — not a check
58
+ * inside the poll chain. A probe `executeJavaScript` promise that never
59
+ * settles (frame destroyed mid-call / a wc permanently stuck loading) would
60
+ * stall a chain-embedded check forever; the deadline settles this promise
61
+ * regardless.
62
+ */
63
+ export function whenFrontendBootstrapped(wc, opts) {
64
+ const timeoutMs = opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS;
65
+ const intervalMs = opts?.intervalMs ?? DEFAULT_INTERVAL_MS;
66
+ return new Promise((resolve) => {
67
+ let timer = null;
68
+ let settled = false;
69
+ const finish = (ready) => {
70
+ if (settled)
71
+ return;
72
+ settled = true;
73
+ clearTimeout(deadline);
74
+ if (timer) {
75
+ clearTimeout(timer);
76
+ timer = null;
77
+ }
78
+ resolve(ready);
79
+ };
80
+ const deadline = setTimeout(() => {
81
+ // Within one call "ready" was never observed — a dead bootstrap and a
82
+ // merely-slow one are indistinguishable from out here; say so.
83
+ console.warn(`[frontend-bootstrap-gate] front-end bootstrap probe never reported ready within ${timeoutMs}ms — bootstrap-gated injections stay skipped for this host`);
84
+ finish(false);
85
+ }, timeoutMs);
86
+ deadline.unref?.();
87
+ const attempt = () => {
88
+ if (wc.isDestroyed()) {
89
+ finish(false);
90
+ return;
91
+ }
92
+ Promise.resolve()
93
+ .then(() => wc.executeJavaScript(FRONTEND_BOOTSTRAP_PROBE_SCRIPT))
94
+ .then((ready) => ready === true, () => false)
95
+ .then((ready) => {
96
+ if (settled)
97
+ return;
98
+ // Destroyed-check BEFORE honoring a ready answer: a probe that was
99
+ // in flight when the wc got destroyed can still resolve true, and
100
+ // reporting "bootstrapped" for a dead target contradicts this
101
+ // function's contract (destroyed ⇒ false).
102
+ if (wc.isDestroyed()) {
103
+ finish(false);
104
+ return;
105
+ }
106
+ if (ready) {
107
+ finish(true);
108
+ return;
109
+ }
110
+ timer = setTimeout(attempt, intervalMs);
111
+ timer.unref?.();
112
+ });
113
+ };
114
+ attempt();
115
+ });
116
+ }
117
+ //# sourceMappingURL=frontend-bootstrap-gate.js.map