@nimbus21.ai/chrome-devtools-mcp 0.17.6 → 0.17.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/src/McpContext.js +56 -8
- package/package.json +1 -1
package/build/src/McpContext.js
CHANGED
|
@@ -72,9 +72,18 @@ export class McpContext {
|
|
|
72
72
|
// Idle page reaper: tracks last activity time per page.
|
|
73
73
|
static PAGE_IDLE_TIMEOUT_MS = 5 * 60 * 1000; // 5 minutes
|
|
74
74
|
static IDLE_CHECK_INTERVAL_MS = 60 * 1000; // 60 seconds
|
|
75
|
+
static #INTERNAL_URL_PREFIXES = [
|
|
76
|
+
'about:',
|
|
77
|
+
'chrome://',
|
|
78
|
+
'chrome-extension://',
|
|
79
|
+
'chrome-untrusted://',
|
|
80
|
+
'devtools://',
|
|
81
|
+
];
|
|
75
82
|
#pageLastActivity = new Map();
|
|
76
83
|
#idleReaperTimer;
|
|
77
84
|
#onIdleBrowserEmpty;
|
|
85
|
+
// Tracks when we first noticed only internal pages remaining.
|
|
86
|
+
#onlyInternalPagesSince;
|
|
78
87
|
#uniqueBackendNodeIdToMcpId = new Map();
|
|
79
88
|
constructor(browser, logger, options, locatorClass) {
|
|
80
89
|
this.browser = browser;
|
|
@@ -114,6 +123,8 @@ export class McpContext {
|
|
|
114
123
|
*/
|
|
115
124
|
touchPage(page) {
|
|
116
125
|
this.#pageLastActivity.set(page, Date.now());
|
|
126
|
+
// A user is interacting with a page — reset the internal-only timer.
|
|
127
|
+
this.#onlyInternalPagesSince = undefined;
|
|
117
128
|
}
|
|
118
129
|
/**
|
|
119
130
|
* Record activity on all currently tracked pages.
|
|
@@ -149,6 +160,10 @@ export class McpContext {
|
|
|
149
160
|
this.#idleReaperTimer = undefined;
|
|
150
161
|
}
|
|
151
162
|
}
|
|
163
|
+
static #isInternalPage(page) {
|
|
164
|
+
const url = page.url();
|
|
165
|
+
return _a.#INTERNAL_URL_PREFIXES.some(prefix => url.startsWith(prefix));
|
|
166
|
+
}
|
|
152
167
|
async #reapIdlePages() {
|
|
153
168
|
const now = Date.now();
|
|
154
169
|
const idleTimeout = _a.PAGE_IDLE_TIMEOUT_MS;
|
|
@@ -160,8 +175,21 @@ export class McpContext {
|
|
|
160
175
|
catch {
|
|
161
176
|
return; // Browser may be disconnected.
|
|
162
177
|
}
|
|
178
|
+
// Also get ALL pages from the browser (including those filtered by
|
|
179
|
+
// createPagesSnapshot) so we can detect internal-only state.
|
|
180
|
+
let allBrowserPages;
|
|
181
|
+
try {
|
|
182
|
+
allBrowserPages = await this.browser.pages(true);
|
|
183
|
+
}
|
|
184
|
+
catch {
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
163
187
|
const pagesToClose = [];
|
|
164
188
|
for (const page of pages) {
|
|
189
|
+
// Skip internal pages — they can't be closed and will respawn.
|
|
190
|
+
if (_a.#isInternalPage(page)) {
|
|
191
|
+
continue;
|
|
192
|
+
}
|
|
165
193
|
const lastActivity = this.#pageLastActivity.get(page);
|
|
166
194
|
// Pages without a recorded timestamp get one now (first seen).
|
|
167
195
|
if (lastActivity === undefined) {
|
|
@@ -172,10 +200,7 @@ export class McpContext {
|
|
|
172
200
|
pagesToClose.push(page);
|
|
173
201
|
}
|
|
174
202
|
}
|
|
175
|
-
|
|
176
|
-
return;
|
|
177
|
-
}
|
|
178
|
-
// Close idle pages. If ALL pages are idle, close them all.
|
|
203
|
+
// Close idle user pages.
|
|
179
204
|
for (const page of pagesToClose) {
|
|
180
205
|
try {
|
|
181
206
|
this.logger(`Closing idle page ${this.#pageIdMap.get(page)}: ${page.url()} (idle ${Math.round((now - (this.#pageLastActivity.get(page) ?? now)) / 1000)}s)`);
|
|
@@ -186,16 +211,39 @@ export class McpContext {
|
|
|
186
211
|
// Page may already be closed.
|
|
187
212
|
}
|
|
188
213
|
}
|
|
189
|
-
//
|
|
214
|
+
// Re-check all browser pages to determine if only internal pages remain.
|
|
190
215
|
try {
|
|
191
|
-
|
|
216
|
+
allBrowserPages = await this.browser.pages(true);
|
|
192
217
|
}
|
|
193
218
|
catch {
|
|
194
|
-
|
|
219
|
+
allBrowserPages = [];
|
|
220
|
+
}
|
|
221
|
+
const hasUserPages = allBrowserPages.some(p => !p.isClosed() && !_a.#isInternalPage(p));
|
|
222
|
+
if (hasUserPages) {
|
|
223
|
+
// User pages still exist — reset the internal-only timer.
|
|
224
|
+
this.#onlyInternalPagesSince = undefined;
|
|
225
|
+
return;
|
|
226
|
+
}
|
|
227
|
+
// Only internal pages (or no pages) remain.
|
|
228
|
+
if (!this.#onIdleBrowserEmpty) {
|
|
229
|
+
return;
|
|
195
230
|
}
|
|
196
|
-
if (
|
|
231
|
+
if (allBrowserPages.length === 0) {
|
|
232
|
+
// Truly empty — kill immediately.
|
|
197
233
|
this.logger('All pages closed by idle reaper, triggering browser cleanup');
|
|
198
234
|
this.#onIdleBrowserEmpty();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
// Internal pages only — start or check the grace timer.
|
|
238
|
+
if (this.#onlyInternalPagesSince === undefined) {
|
|
239
|
+
this.#onlyInternalPagesSince = now;
|
|
240
|
+
this.logger(`Only internal pages remain (${allBrowserPages.length}), starting ${idleTimeout / 1000}s grace period`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
if (now - this.#onlyInternalPagesSince > idleTimeout) {
|
|
244
|
+
this.logger(`Only internal pages for ${Math.round((now - this.#onlyInternalPagesSince) / 1000)}s, triggering browser cleanup`);
|
|
245
|
+
this.#onlyInternalPagesSince = undefined;
|
|
246
|
+
this.#onIdleBrowserEmpty();
|
|
199
247
|
}
|
|
200
248
|
}
|
|
201
249
|
static async from(browser, logger, opts,
|