@mozilla/firefox-devtools-mcp 0.10.0 → 0.10.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/dist/index.js +353 -328
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -14841,7 +14841,7 @@ var init_constants = __esm({
|
|
|
14841
14841
|
"src/config/constants.ts"() {
|
|
14842
14842
|
"use strict";
|
|
14843
14843
|
SERVER_NAME = true ? "@mozilla/firefox-devtools-mcp" : "firefox-devtools";
|
|
14844
|
-
SERVER_VERSION = true ? "0.10.
|
|
14844
|
+
SERVER_VERSION = true ? "0.10.1" : "dev";
|
|
14845
14845
|
}
|
|
14846
14846
|
});
|
|
14847
14847
|
|
|
@@ -15348,68 +15348,6 @@ var init_core3 = __esm({
|
|
|
15348
15348
|
getOptions() {
|
|
15349
15349
|
return this.options;
|
|
15350
15350
|
}
|
|
15351
|
-
/**
|
|
15352
|
-
* Wait for WebSocket to be in OPEN state
|
|
15353
|
-
*/
|
|
15354
|
-
async waitForWebSocketOpen(ws, timeout = 5e3) {
|
|
15355
|
-
if (ws.readyState === 1) {
|
|
15356
|
-
return;
|
|
15357
|
-
}
|
|
15358
|
-
if (ws.readyState === 0) {
|
|
15359
|
-
return new Promise((resolve4, reject) => {
|
|
15360
|
-
const timeoutId = setTimeout(() => {
|
|
15361
|
-
ws.off("open", onOpen);
|
|
15362
|
-
reject(new Error("Timeout waiting for WebSocket to open"));
|
|
15363
|
-
}, timeout);
|
|
15364
|
-
const onOpen = () => {
|
|
15365
|
-
clearTimeout(timeoutId);
|
|
15366
|
-
ws.off("open", onOpen);
|
|
15367
|
-
resolve4();
|
|
15368
|
-
};
|
|
15369
|
-
ws.on("open", onOpen);
|
|
15370
|
-
});
|
|
15371
|
-
}
|
|
15372
|
-
throw new Error(`WebSocket is not open: readyState ${ws.readyState}`);
|
|
15373
|
-
}
|
|
15374
|
-
/**
|
|
15375
|
-
* Send raw BiDi command and get response
|
|
15376
|
-
*/
|
|
15377
|
-
async sendBiDiCommand(method, params = {}) {
|
|
15378
|
-
if (!this.driver) {
|
|
15379
|
-
throw new Error("Driver not connected");
|
|
15380
|
-
}
|
|
15381
|
-
const bidi = await this.driver.getBidi();
|
|
15382
|
-
const ws = bidi.socket;
|
|
15383
|
-
await this.waitForWebSocketOpen(ws);
|
|
15384
|
-
const id = Math.floor(Math.random() * 1e6);
|
|
15385
|
-
return new Promise((resolve4, reject) => {
|
|
15386
|
-
const messageHandler = (data) => {
|
|
15387
|
-
try {
|
|
15388
|
-
const payload = JSON.parse(data.toString());
|
|
15389
|
-
if (payload.id === id) {
|
|
15390
|
-
ws.off("message", messageHandler);
|
|
15391
|
-
if (payload.error) {
|
|
15392
|
-
reject(new Error(`BiDi error: ${JSON.stringify(payload.error)}`));
|
|
15393
|
-
} else {
|
|
15394
|
-
resolve4(payload.result);
|
|
15395
|
-
}
|
|
15396
|
-
}
|
|
15397
|
-
} catch {
|
|
15398
|
-
}
|
|
15399
|
-
};
|
|
15400
|
-
ws.on("message", messageHandler);
|
|
15401
|
-
const command = {
|
|
15402
|
-
id,
|
|
15403
|
-
method,
|
|
15404
|
-
params
|
|
15405
|
-
};
|
|
15406
|
-
ws.send(JSON.stringify(command));
|
|
15407
|
-
setTimeout(() => {
|
|
15408
|
-
ws.off("message", messageHandler);
|
|
15409
|
-
reject(new Error(`BiDi command timeout: ${method}`));
|
|
15410
|
-
}, 1e4);
|
|
15411
|
-
});
|
|
15412
|
-
}
|
|
15413
15351
|
/**
|
|
15414
15352
|
* Close driver and cleanup.
|
|
15415
15353
|
* - Tries graceful quit() with a timeout; on timeout, force-kills via onQuit_().
|
|
@@ -15479,6 +15417,107 @@ var init_core3 = __esm({
|
|
|
15479
15417
|
}
|
|
15480
15418
|
});
|
|
15481
15419
|
|
|
15420
|
+
// src/firefox/bidi.ts
|
|
15421
|
+
import EventEmitter from "events";
|
|
15422
|
+
var BiDiFacade;
|
|
15423
|
+
var init_bidi = __esm({
|
|
15424
|
+
"src/firefox/bidi.ts"() {
|
|
15425
|
+
"use strict";
|
|
15426
|
+
init_logger();
|
|
15427
|
+
BiDiFacade = class extends EventEmitter {
|
|
15428
|
+
constructor(driver) {
|
|
15429
|
+
super();
|
|
15430
|
+
this.driver = driver;
|
|
15431
|
+
}
|
|
15432
|
+
listening = false;
|
|
15433
|
+
nextCommandId = 1;
|
|
15434
|
+
async subscribe(events) {
|
|
15435
|
+
const bidi = await this.driver.getBidi();
|
|
15436
|
+
if (!this.listening) {
|
|
15437
|
+
this.listenForEvents(bidi.socket);
|
|
15438
|
+
this.listening = true;
|
|
15439
|
+
}
|
|
15440
|
+
await bidi.subscribe(events);
|
|
15441
|
+
}
|
|
15442
|
+
async sendCommand(method, params = {}) {
|
|
15443
|
+
const bidi = await this.driver.getBidi();
|
|
15444
|
+
const ws = bidi.socket;
|
|
15445
|
+
await this.waitForWebSocketOpen(ws);
|
|
15446
|
+
const id = this.nextCommandId++;
|
|
15447
|
+
return new Promise((resolve4, reject) => {
|
|
15448
|
+
const messageHandler = (data) => {
|
|
15449
|
+
try {
|
|
15450
|
+
const payload = JSON.parse(data.toString());
|
|
15451
|
+
if (payload.id === id) {
|
|
15452
|
+
ws.off("message", messageHandler);
|
|
15453
|
+
if (payload.error) {
|
|
15454
|
+
reject(new Error(`BiDi error: ${JSON.stringify(payload.error)}`));
|
|
15455
|
+
} else {
|
|
15456
|
+
resolve4(payload.result);
|
|
15457
|
+
}
|
|
15458
|
+
}
|
|
15459
|
+
} catch {
|
|
15460
|
+
}
|
|
15461
|
+
};
|
|
15462
|
+
ws.on("message", messageHandler);
|
|
15463
|
+
const command = {
|
|
15464
|
+
id,
|
|
15465
|
+
method,
|
|
15466
|
+
params
|
|
15467
|
+
};
|
|
15468
|
+
ws.send(JSON.stringify(command));
|
|
15469
|
+
setTimeout(() => {
|
|
15470
|
+
ws.off("message", messageHandler);
|
|
15471
|
+
reject(new Error(`BiDi command timeout: ${method}`));
|
|
15472
|
+
}, 1e4);
|
|
15473
|
+
});
|
|
15474
|
+
}
|
|
15475
|
+
listenForEvents(ws) {
|
|
15476
|
+
ws.on("message", (data) => {
|
|
15477
|
+
let payload;
|
|
15478
|
+
try {
|
|
15479
|
+
payload = JSON.parse(data.toString());
|
|
15480
|
+
} catch {
|
|
15481
|
+
return;
|
|
15482
|
+
}
|
|
15483
|
+
if (payload?.type === "event" && payload.method) {
|
|
15484
|
+
try {
|
|
15485
|
+
this.emit(payload.method, payload.params);
|
|
15486
|
+
} catch (error2) {
|
|
15487
|
+
logDebug(
|
|
15488
|
+
`Error emitting ${payload.method} event: ${error2 instanceof Error ? error2.message : String(error2)}`
|
|
15489
|
+
);
|
|
15490
|
+
}
|
|
15491
|
+
}
|
|
15492
|
+
});
|
|
15493
|
+
}
|
|
15494
|
+
/**
|
|
15495
|
+
* Wait for WebSocket to be in OPEN state
|
|
15496
|
+
*/
|
|
15497
|
+
async waitForWebSocketOpen(ws, timeout = 5e3) {
|
|
15498
|
+
if (ws.readyState === 1) {
|
|
15499
|
+
return;
|
|
15500
|
+
}
|
|
15501
|
+
if (ws.readyState === 0) {
|
|
15502
|
+
return new Promise((resolve4, reject) => {
|
|
15503
|
+
const timeoutId = setTimeout(() => {
|
|
15504
|
+
ws.off("open", onOpen);
|
|
15505
|
+
reject(new Error("Timeout waiting for WebSocket to open"));
|
|
15506
|
+
}, timeout);
|
|
15507
|
+
const onOpen = () => {
|
|
15508
|
+
clearTimeout(timeoutId);
|
|
15509
|
+
ws.off("open", onOpen);
|
|
15510
|
+
resolve4();
|
|
15511
|
+
};
|
|
15512
|
+
ws.on("open", onOpen);
|
|
15513
|
+
});
|
|
15514
|
+
}
|
|
15515
|
+
throw new Error(`WebSocket is not open: readyState ${ws.readyState}`);
|
|
15516
|
+
}
|
|
15517
|
+
};
|
|
15518
|
+
}
|
|
15519
|
+
});
|
|
15520
|
+
|
|
15482
15521
|
// src/utils/remote-value.ts
|
|
15483
15522
|
function remoteValueToNative(rv) {
|
|
15484
15523
|
if (!rv || typeof rv !== "object") {
|
|
@@ -15549,8 +15588,8 @@ var init_console = __esm({
|
|
|
15549
15588
|
MAX_CONSOLE_MESSAGES = 1e3;
|
|
15550
15589
|
CONSOLE_TTL_MS = 5 * 60 * 1e3;
|
|
15551
15590
|
ConsoleEvents = class {
|
|
15552
|
-
constructor(
|
|
15553
|
-
this.
|
|
15591
|
+
constructor(bidi, options = {}) {
|
|
15592
|
+
this.bidi = bidi;
|
|
15554
15593
|
this.options = {
|
|
15555
15594
|
autoClearOnNavigate: false,
|
|
15556
15595
|
// Changed default to false to preserve logs across tabs
|
|
@@ -15563,44 +15602,27 @@ var init_console = __esm({
|
|
|
15563
15602
|
/**
|
|
15564
15603
|
* Subscribe to BiDi console events and navigation lifecycle
|
|
15565
15604
|
*/
|
|
15566
|
-
async subscribe(
|
|
15605
|
+
async subscribe() {
|
|
15567
15606
|
if (this.subscribed) {
|
|
15568
15607
|
return;
|
|
15569
15608
|
}
|
|
15570
|
-
|
|
15571
|
-
await bidi.subscribe("
|
|
15572
|
-
|
|
15573
|
-
|
|
15574
|
-
|
|
15575
|
-
|
|
15576
|
-
|
|
15577
|
-
|
|
15578
|
-
|
|
15579
|
-
|
|
15580
|
-
|
|
15581
|
-
|
|
15582
|
-
try {
|
|
15583
|
-
const payload = JSON.parse(data.toString());
|
|
15584
|
-
if (payload?.method === "log.entryAdded") {
|
|
15585
|
-
const entry = payload.params;
|
|
15586
|
-
const message = {
|
|
15587
|
-
level: entry.level || "info",
|
|
15588
|
-
text: entry.text || (entry.args ? JSON.stringify(entry.args) : ""),
|
|
15589
|
-
timestamp: entry.timestamp || Date.now(),
|
|
15590
|
-
source: entry.source?.realm,
|
|
15591
|
-
args: entry.args
|
|
15592
|
-
};
|
|
15593
|
-
this.consoleMessages.push(message);
|
|
15594
|
-
logDebug(`Console [${message.level}]: ${message.text}`);
|
|
15595
|
-
}
|
|
15596
|
-
if (payload?.method === "browsingContext.load" || payload?.method === "browsingContext.domContentLoaded") {
|
|
15597
|
-
if (this.options.autoClearOnNavigate) {
|
|
15598
|
-
this.clearMessages();
|
|
15599
|
-
}
|
|
15600
|
-
}
|
|
15601
|
-
} catch {
|
|
15602
|
-
}
|
|
15609
|
+
await this.bidi.subscribe("log.entryAdded");
|
|
15610
|
+
await this.bidi.subscribe(["browsingContext.load", "browsingContext.domContentLoaded"]);
|
|
15611
|
+
this.bidi.on("log.entryAdded", (entry) => {
|
|
15612
|
+
const message = {
|
|
15613
|
+
level: entry.level || "info",
|
|
15614
|
+
text: entry.text || (entry.args ? JSON.stringify(entry.args) : ""),
|
|
15615
|
+
timestamp: entry.timestamp || Date.now(),
|
|
15616
|
+
source: entry.source?.realm,
|
|
15617
|
+
args: entry.args
|
|
15618
|
+
};
|
|
15619
|
+
this.consoleMessages.push(message);
|
|
15620
|
+
logDebug(`Console [${message.level}]: ${message.text}`);
|
|
15603
15621
|
});
|
|
15622
|
+
if (this.options.autoClearOnNavigate) {
|
|
15623
|
+
this.bidi.on("browsingContext.load", () => this.clearMessages());
|
|
15624
|
+
this.bidi.on("browsingContext.domContentLoaded", () => this.clearMessages());
|
|
15625
|
+
}
|
|
15604
15626
|
this.subscribed = true;
|
|
15605
15627
|
logDebug("Console listener active with lifecycle hooks");
|
|
15606
15628
|
}
|
|
@@ -15648,9 +15670,8 @@ var init_network = __esm({
|
|
|
15648
15670
|
NETWORK_TTL_MS = 5 * 60 * 1e3;
|
|
15649
15671
|
MAX_ENCODED_DATA_SIZE = 10 * 1e3 * 1e3;
|
|
15650
15672
|
NetworkEvents = class {
|
|
15651
|
-
constructor(
|
|
15652
|
-
this.
|
|
15653
|
-
this.sendCommand = sendCommand;
|
|
15673
|
+
constructor(bidi, options = {}) {
|
|
15674
|
+
this.bidi = bidi;
|
|
15654
15675
|
this.options = {
|
|
15655
15676
|
autoClearOnNavigate: true,
|
|
15656
15677
|
...options
|
|
@@ -15666,90 +15687,81 @@ var init_network = __esm({
|
|
|
15666
15687
|
* Subscribe to BiDi network events and navigation lifecycle
|
|
15667
15688
|
* Enables monitoring by default (always-on capture)
|
|
15668
15689
|
*/
|
|
15669
|
-
async subscribe(
|
|
15690
|
+
async subscribe() {
|
|
15670
15691
|
if (this.subscribed) {
|
|
15671
15692
|
return;
|
|
15672
15693
|
}
|
|
15673
|
-
|
|
15674
|
-
|
|
15675
|
-
|
|
15676
|
-
|
|
15677
|
-
|
|
15678
|
-
|
|
15679
|
-
|
|
15680
|
-
|
|
15681
|
-
|
|
15682
|
-
|
|
15683
|
-
|
|
15684
|
-
|
|
15685
|
-
|
|
15686
|
-
|
|
15687
|
-
|
|
15688
|
-
|
|
15689
|
-
|
|
15690
|
-
|
|
15691
|
-
|
|
15692
|
-
|
|
15693
|
-
|
|
15694
|
-
|
|
15695
|
-
|
|
15696
|
-
|
|
15697
|
-
|
|
15698
|
-
|
|
15699
|
-
|
|
15700
|
-
|
|
15701
|
-
|
|
15702
|
-
|
|
15703
|
-
|
|
15704
|
-
|
|
15705
|
-
const record2 = {
|
|
15706
|
-
id: requestId,
|
|
15707
|
-
url: req.request?.url || "",
|
|
15708
|
-
method: req.request?.method || "GET",
|
|
15709
|
-
timestamp: Date.now(),
|
|
15710
|
-
resourceType: this.guessResourceType(req.request?.url || ""),
|
|
15711
|
-
isXHR: req.initiator?.type === "xmlhttprequest" || req.initiator?.type === "fetch",
|
|
15712
|
-
requestHeaders: this.parseHeaders(req.request?.headers || []),
|
|
15713
|
-
timings: {
|
|
15714
|
-
requestTime: Date.now()
|
|
15715
|
-
}
|
|
15716
|
-
};
|
|
15717
|
-
this.networkRecords.set(requestId, record2);
|
|
15718
|
-
logDebug(`Network request [${record2.method}]: ${record2.url}`);
|
|
15719
|
-
}
|
|
15720
|
-
if (payload?.method === "network.responseStarted") {
|
|
15721
|
-
const resp = payload.params;
|
|
15722
|
-
const requestId = resp.request?.request || resp.requestId;
|
|
15723
|
-
if (!requestId) {
|
|
15724
|
-
return;
|
|
15725
|
-
}
|
|
15726
|
-
const existing = this.networkRecords.get(requestId);
|
|
15727
|
-
if (existing) {
|
|
15728
|
-
existing.status = resp.response?.status;
|
|
15729
|
-
existing.statusText = resp.response?.statusText || "";
|
|
15730
|
-
existing.responseHeaders = this.parseHeaders(resp.response?.headers || []);
|
|
15731
|
-
}
|
|
15694
|
+
await this.bidi.subscribe([
|
|
15695
|
+
"network.beforeRequestSent",
|
|
15696
|
+
"network.responseStarted",
|
|
15697
|
+
"network.responseCompleted"
|
|
15698
|
+
]);
|
|
15699
|
+
await this.bidi.subscribe(["browsingContext.load", "browsingContext.domContentLoaded"]);
|
|
15700
|
+
const onLoadEvent = () => {
|
|
15701
|
+
if (this.enabled && this.options.autoClearOnNavigate) {
|
|
15702
|
+
this.clearRequests();
|
|
15703
|
+
}
|
|
15704
|
+
};
|
|
15705
|
+
this.bidi.on("browsingContext.domContentLoaded", onLoadEvent);
|
|
15706
|
+
this.bidi.on("browsingContext.load", onLoadEvent);
|
|
15707
|
+
this.bidi.on("network.beforeRequestSent", (req) => {
|
|
15708
|
+
if (!this.enabled) {
|
|
15709
|
+
return;
|
|
15710
|
+
}
|
|
15711
|
+
const requestId = req.request?.request || req.requestId;
|
|
15712
|
+
if (!requestId) {
|
|
15713
|
+
return;
|
|
15714
|
+
}
|
|
15715
|
+
this.requestStartTimes.set(requestId, Date.now());
|
|
15716
|
+
const record2 = {
|
|
15717
|
+
id: requestId,
|
|
15718
|
+
url: req.request?.url || "",
|
|
15719
|
+
method: req.request?.method || "GET",
|
|
15720
|
+
timestamp: Date.now(),
|
|
15721
|
+
resourceType: this.guessResourceType(req.request?.url || ""),
|
|
15722
|
+
isXHR: req.initiator?.type === "xmlhttprequest" || req.initiator?.type === "fetch",
|
|
15723
|
+
requestHeaders: this.parseHeaders(req.request?.headers || []),
|
|
15724
|
+
timings: {
|
|
15725
|
+
requestTime: Date.now()
|
|
15732
15726
|
}
|
|
15733
|
-
|
|
15734
|
-
|
|
15735
|
-
|
|
15736
|
-
|
|
15737
|
-
|
|
15738
|
-
|
|
15739
|
-
|
|
15740
|
-
|
|
15741
|
-
|
|
15742
|
-
|
|
15743
|
-
|
|
15744
|
-
|
|
15745
|
-
|
|
15746
|
-
|
|
15747
|
-
|
|
15748
|
-
|
|
15749
|
-
|
|
15727
|
+
};
|
|
15728
|
+
this.networkRecords.set(requestId, record2);
|
|
15729
|
+
logDebug(`Network request [${record2.method}]: ${record2.url}`);
|
|
15730
|
+
});
|
|
15731
|
+
this.bidi.on("network.responseStarted", (resp) => {
|
|
15732
|
+
if (!this.enabled) {
|
|
15733
|
+
return;
|
|
15734
|
+
}
|
|
15735
|
+
const requestId = resp.request?.request || resp.requestId;
|
|
15736
|
+
if (!requestId) {
|
|
15737
|
+
return;
|
|
15738
|
+
}
|
|
15739
|
+
const existing = this.networkRecords.get(requestId);
|
|
15740
|
+
if (existing) {
|
|
15741
|
+
existing.status = resp.response?.status;
|
|
15742
|
+
existing.statusText = resp.response?.statusText || "";
|
|
15743
|
+
existing.responseHeaders = this.parseHeaders(resp.response?.headers || []);
|
|
15744
|
+
}
|
|
15745
|
+
});
|
|
15746
|
+
this.bidi.on("network.responseCompleted", (resp) => {
|
|
15747
|
+
if (!this.enabled) {
|
|
15748
|
+
return;
|
|
15749
|
+
}
|
|
15750
|
+
const requestId = resp.request?.request || resp.requestId;
|
|
15751
|
+
if (!requestId) {
|
|
15752
|
+
return;
|
|
15753
|
+
}
|
|
15754
|
+
const existing = this.networkRecords.get(requestId);
|
|
15755
|
+
const startTime = this.requestStartTimes.get(requestId);
|
|
15756
|
+
if (existing && startTime) {
|
|
15757
|
+
existing.timings.responseTime = Date.now();
|
|
15758
|
+
existing.timings.duration = Date.now() - startTime;
|
|
15759
|
+
if (!existing.status && resp.response?.status) {
|
|
15760
|
+
existing.status = resp.response.status;
|
|
15761
|
+
existing.statusText = resp.response.statusText || "";
|
|
15750
15762
|
}
|
|
15751
|
-
} catch {
|
|
15752
15763
|
}
|
|
15764
|
+
this.requestStartTimes.delete(requestId);
|
|
15753
15765
|
});
|
|
15754
15766
|
await this.registerDataCollector();
|
|
15755
15767
|
this.subscribed = true;
|
|
@@ -15765,11 +15777,8 @@ var init_network = __esm({
|
|
|
15765
15777
|
logDebug("Network body capture disabled, skipping data collector registration");
|
|
15766
15778
|
return;
|
|
15767
15779
|
}
|
|
15768
|
-
if (!this.sendCommand) {
|
|
15769
|
-
return;
|
|
15770
|
-
}
|
|
15771
15780
|
try {
|
|
15772
|
-
const result = await this.sendCommand("network.addDataCollector", {
|
|
15781
|
+
const result = await this.bidi.sendCommand("network.addDataCollector", {
|
|
15773
15782
|
dataTypes: ["request", "response"],
|
|
15774
15783
|
maxEncodedDataSize: MAX_ENCODED_DATA_SIZE
|
|
15775
15784
|
});
|
|
@@ -15789,11 +15798,11 @@ var init_network = __esm({
|
|
|
15789
15798
|
* when the body was never collected, evicted, or the browser lacks support.
|
|
15790
15799
|
*/
|
|
15791
15800
|
async fetchBody(requestId, dataType) {
|
|
15792
|
-
if (!this.
|
|
15801
|
+
if (!this.collectorId) {
|
|
15793
15802
|
return { ok: false, reason: "unsupported" };
|
|
15794
15803
|
}
|
|
15795
15804
|
try {
|
|
15796
|
-
const result = await this.sendCommand("network.getData", {
|
|
15805
|
+
const result = await this.bidi.sendCommand("network.getData", {
|
|
15797
15806
|
request: requestId,
|
|
15798
15807
|
dataType
|
|
15799
15808
|
});
|
|
@@ -15975,46 +15984,35 @@ var init_debugging = __esm({
|
|
|
15975
15984
|
init_logger();
|
|
15976
15985
|
MAX_LOGPOINT_RESULTS = 100;
|
|
15977
15986
|
DebuggingEvents = class {
|
|
15978
|
-
constructor(
|
|
15979
|
-
this.
|
|
15980
|
-
this.sendBiDiCommand = sendBiDiCommand;
|
|
15987
|
+
constructor(bidi) {
|
|
15988
|
+
this.bidi = bidi;
|
|
15981
15989
|
}
|
|
15982
15990
|
logpoints = /* @__PURE__ */ new Map();
|
|
15983
15991
|
subscribed = false;
|
|
15984
15992
|
/**
|
|
15985
15993
|
* Subscribe to moz:debugging events
|
|
15986
15994
|
*/
|
|
15987
|
-
async subscribe(
|
|
15995
|
+
async subscribe() {
|
|
15988
15996
|
if (this.subscribed) {
|
|
15989
15997
|
return;
|
|
15990
15998
|
}
|
|
15991
|
-
const bidi = await this.driver.getBidi();
|
|
15992
15999
|
try {
|
|
15993
|
-
await bidi.subscribe("moz:debugging.paused",
|
|
15994
|
-
await bidi.subscribe("moz:debugging.resumed", contextId ? [contextId] : void 0);
|
|
16000
|
+
await this.bidi.subscribe(["moz:debugging.paused", "moz:debugging.resumed"]);
|
|
15995
16001
|
} catch {
|
|
15996
16002
|
logDebug(
|
|
15997
16003
|
"Debugging events subscription skipped (may not be available in this Firefox version)"
|
|
15998
16004
|
);
|
|
15999
16005
|
}
|
|
16000
|
-
|
|
16001
|
-
|
|
16002
|
-
|
|
16003
|
-
|
|
16004
|
-
|
|
16005
|
-
const { context, url, line, column } = payload.params;
|
|
16006
|
-
const logpointId = this.findLogpointByLocation(url, line);
|
|
16007
|
-
if (logpointId) {
|
|
16008
|
-
void this.handleLogpointPause(context, logpointId);
|
|
16009
|
-
return;
|
|
16010
|
-
}
|
|
16011
|
-
logDebug(`moz:Debugging paused in context: ${context} at ${url}:${line}:${column}`);
|
|
16012
|
-
}
|
|
16013
|
-
if (payload?.method === "moz:debugging.resumed") {
|
|
16014
|
-
logDebug(`moz:Debugging resumed in context: ${payload.params.context}`);
|
|
16015
|
-
}
|
|
16016
|
-
} catch {
|
|
16006
|
+
this.bidi.on("moz:debugging.paused", ({ context, url, line, column }) => {
|
|
16007
|
+
const logpointId = this.findLogpointByLocation(url, line);
|
|
16008
|
+
if (logpointId) {
|
|
16009
|
+
void this.handleLogpointPause(context, logpointId);
|
|
16010
|
+
return;
|
|
16017
16011
|
}
|
|
16012
|
+
logDebug(`moz:Debugging paused in context: ${context} at ${url}:${line}:${column}`);
|
|
16013
|
+
});
|
|
16014
|
+
this.bidi.on("moz:debugging.resumed", (entry) => {
|
|
16015
|
+
logDebug(`moz:Debugging resumed in context: ${entry.context}`);
|
|
16018
16016
|
});
|
|
16019
16017
|
this.subscribed = true;
|
|
16020
16018
|
logDebug("moz:debugging listener active");
|
|
@@ -16048,7 +16046,7 @@ var init_debugging = __esm({
|
|
|
16048
16046
|
}
|
|
16049
16047
|
logDebug(`Logpoint hit: ${logpointId} in context ${contextId}`);
|
|
16050
16048
|
try {
|
|
16051
|
-
const result = await this.
|
|
16049
|
+
const result = await this.bidi.sendCommand("script.evaluate", {
|
|
16052
16050
|
expression: entry.expression,
|
|
16053
16051
|
target: { context: contextId },
|
|
16054
16052
|
awaitPromise: false
|
|
@@ -16080,7 +16078,7 @@ var init_debugging = __esm({
|
|
|
16080
16078
|
logDebug(`Logpoint ${logpointId}: result buffer capped at ${MAX_LOGPOINT_RESULTS}`);
|
|
16081
16079
|
}
|
|
16082
16080
|
}
|
|
16083
|
-
await this.
|
|
16081
|
+
await this.bidi.sendCommand("moz:debugging.resume", { context: contextId }).catch((err) => {
|
|
16084
16082
|
logDebug(`Failed to resume after logpoint: ${String(err)}`);
|
|
16085
16083
|
});
|
|
16086
16084
|
}
|
|
@@ -16098,8 +16096,8 @@ var init_downloads = __esm({
|
|
|
16098
16096
|
MAX_DOWNLOADS = 500;
|
|
16099
16097
|
DOWNLOAD_TTL_MS = 30 * 60 * 1e3;
|
|
16100
16098
|
DownloadEvents = class {
|
|
16101
|
-
constructor(
|
|
16102
|
-
this.
|
|
16099
|
+
constructor(bidi) {
|
|
16100
|
+
this.bidi = bidi;
|
|
16103
16101
|
}
|
|
16104
16102
|
downloads = /* @__PURE__ */ new Map();
|
|
16105
16103
|
subscribed = false;
|
|
@@ -16111,65 +16109,53 @@ var init_downloads = __esm({
|
|
|
16111
16109
|
/**
|
|
16112
16110
|
* Subscribe to BiDi download events.
|
|
16113
16111
|
*/
|
|
16114
|
-
async subscribe(
|
|
16112
|
+
async subscribe() {
|
|
16115
16113
|
if (this.subscribed) {
|
|
16116
16114
|
return;
|
|
16117
16115
|
}
|
|
16118
|
-
|
|
16119
|
-
|
|
16120
|
-
|
|
16121
|
-
|
|
16122
|
-
|
|
16123
|
-
|
|
16124
|
-
|
|
16125
|
-
|
|
16126
|
-
|
|
16127
|
-
|
|
16128
|
-
|
|
16129
|
-
|
|
16130
|
-
|
|
16131
|
-
|
|
16132
|
-
|
|
16133
|
-
|
|
16134
|
-
|
|
16135
|
-
|
|
16136
|
-
|
|
16137
|
-
|
|
16138
|
-
|
|
16139
|
-
|
|
16140
|
-
|
|
16141
|
-
|
|
16142
|
-
|
|
16143
|
-
|
|
16144
|
-
|
|
16145
|
-
|
|
16146
|
-
|
|
16147
|
-
|
|
16148
|
-
|
|
16149
|
-
|
|
16150
|
-
|
|
16151
|
-
|
|
16152
|
-
|
|
16153
|
-
|
|
16154
|
-
|
|
16155
|
-
|
|
16156
|
-
suggestedFilename: "",
|
|
16157
|
-
status: "in_progress",
|
|
16158
|
-
startTimestamp: p.timestamp ?? Date.now()
|
|
16159
|
-
};
|
|
16160
|
-
existing.status = p.status;
|
|
16161
|
-
existing.endTimestamp = p.timestamp ?? Date.now();
|
|
16162
|
-
existing.durationMs = existing.endTimestamp - existing.startTimestamp;
|
|
16163
|
-
if (p.status === "complete" && p.filepath) {
|
|
16164
|
-
existing.filepath = p.filepath;
|
|
16165
|
-
}
|
|
16166
|
-
this.downloads.set(key, existing);
|
|
16167
|
-
logDebug(
|
|
16168
|
-
`Download ${p.status}: filepath=${existing.filepath}, url=${existing.url}, id=${key}`
|
|
16169
|
-
);
|
|
16170
|
-
}
|
|
16171
|
-
} catch {
|
|
16116
|
+
await this.bidi.subscribe(["browsingContext.downloadWillBegin", "browsingContext.downloadEnd"]);
|
|
16117
|
+
this.bidi.on("browsingContext.downloadWillBegin", (p) => {
|
|
16118
|
+
let key = p.download ?? p.navigation;
|
|
16119
|
+
if (!key) {
|
|
16120
|
+
key = `download-${this.fallbackCounter++}`;
|
|
16121
|
+
this.pendingFallbackKey = key;
|
|
16122
|
+
}
|
|
16123
|
+
this.downloads.set(key, {
|
|
16124
|
+
id: key,
|
|
16125
|
+
context: p.context,
|
|
16126
|
+
navigation: p.navigation ?? null,
|
|
16127
|
+
url: p.url || "",
|
|
16128
|
+
suggestedFilename: p.suggestedFilename || "",
|
|
16129
|
+
status: "in_progress",
|
|
16130
|
+
startTimestamp: p.timestamp ?? Date.now()
|
|
16131
|
+
});
|
|
16132
|
+
logDebug(`Download started: filename=${p.suggestedFilename}, url=${p.url}, id=${key}`);
|
|
16133
|
+
});
|
|
16134
|
+
this.bidi.on("browsingContext.downloadEnd", (p) => {
|
|
16135
|
+
let key = p?.download ?? p?.navigation;
|
|
16136
|
+
if (!key) {
|
|
16137
|
+
key = this.pendingFallbackKey ?? `download-${this.fallbackCounter++}`;
|
|
16138
|
+
this.pendingFallbackKey = null;
|
|
16139
|
+
}
|
|
16140
|
+
const existing = this.downloads.get(key) ?? {
|
|
16141
|
+
id: key,
|
|
16142
|
+
context: p.context,
|
|
16143
|
+
navigation: p.navigation ?? null,
|
|
16144
|
+
url: p.url || "",
|
|
16145
|
+
suggestedFilename: "",
|
|
16146
|
+
status: "in_progress",
|
|
16147
|
+
startTimestamp: p.timestamp ?? Date.now()
|
|
16148
|
+
};
|
|
16149
|
+
existing.status = p.status;
|
|
16150
|
+
existing.endTimestamp = p.timestamp ?? Date.now();
|
|
16151
|
+
existing.durationMs = existing.endTimestamp - existing.startTimestamp;
|
|
16152
|
+
if (p.status === "complete" && p.filepath) {
|
|
16153
|
+
existing.filepath = p.filepath;
|
|
16172
16154
|
}
|
|
16155
|
+
this.downloads.set(key, existing);
|
|
16156
|
+
logDebug(
|
|
16157
|
+
`Download ${p.status}: filepath=${existing.filepath}, url=${existing.url}, id=${key}`
|
|
16158
|
+
);
|
|
16173
16159
|
});
|
|
16174
16160
|
this.subscribed = true;
|
|
16175
16161
|
logDebug("Download listener ready");
|
|
@@ -17052,6 +17038,7 @@ var init_firefox = __esm({
|
|
|
17052
17038
|
"src/firefox/index.ts"() {
|
|
17053
17039
|
"use strict";
|
|
17054
17040
|
init_core3();
|
|
17041
|
+
init_bidi();
|
|
17055
17042
|
init_logger();
|
|
17056
17043
|
init_remote_value();
|
|
17057
17044
|
init_events();
|
|
@@ -17060,6 +17047,7 @@ var init_firefox = __esm({
|
|
|
17060
17047
|
init_snapshot();
|
|
17061
17048
|
FirefoxClient = class {
|
|
17062
17049
|
core;
|
|
17050
|
+
bidi = null;
|
|
17063
17051
|
consoleEvents = null;
|
|
17064
17052
|
networkEvents = null;
|
|
17065
17053
|
debuggingEvents = null;
|
|
@@ -17070,31 +17058,52 @@ var init_firefox = __esm({
|
|
|
17070
17058
|
constructor(options) {
|
|
17071
17059
|
this.core = new FirefoxCore(options);
|
|
17072
17060
|
}
|
|
17061
|
+
getBidi() {
|
|
17062
|
+
if (!this.bidi) {
|
|
17063
|
+
throw new Error("Not connected");
|
|
17064
|
+
}
|
|
17065
|
+
return this.bidi;
|
|
17066
|
+
}
|
|
17073
17067
|
/**
|
|
17074
17068
|
* Connect and initialize all modules
|
|
17075
17069
|
*/
|
|
17076
17070
|
async connect() {
|
|
17077
17071
|
await this.core.connect();
|
|
17078
17072
|
const driver = this.core.getDriver();
|
|
17073
|
+
this.bidi = new BiDiFacade(driver);
|
|
17079
17074
|
this.snapshot = new SnapshotManager(driver);
|
|
17080
|
-
|
|
17081
|
-
|
|
17082
|
-
|
|
17083
|
-
|
|
17084
|
-
|
|
17085
|
-
|
|
17086
|
-
|
|
17087
|
-
|
|
17088
|
-
|
|
17089
|
-
|
|
17090
|
-
|
|
17091
|
-
|
|
17092
|
-
|
|
17093
|
-
|
|
17094
|
-
|
|
17095
|
-
|
|
17096
|
-
);
|
|
17097
|
-
this.
|
|
17075
|
+
this.consoleEvents = new ConsoleEvents(this.bidi, {
|
|
17076
|
+
autoClearOnNavigate: false
|
|
17077
|
+
});
|
|
17078
|
+
try {
|
|
17079
|
+
await this.consoleEvents.subscribe();
|
|
17080
|
+
} catch {
|
|
17081
|
+
logDebug("Unable to subscribe to console events");
|
|
17082
|
+
this.consoleEvents = null;
|
|
17083
|
+
}
|
|
17084
|
+
this.networkEvents = new NetworkEvents(this.bidi, {
|
|
17085
|
+
autoClearOnNavigate: false,
|
|
17086
|
+
captureBodies: this.core.getOptions().captureNetworkBodies !== false
|
|
17087
|
+
});
|
|
17088
|
+
try {
|
|
17089
|
+
await this.networkEvents.subscribe();
|
|
17090
|
+
} catch {
|
|
17091
|
+
logDebug("Unable to subscribe to network events");
|
|
17092
|
+
this.networkEvents = null;
|
|
17093
|
+
}
|
|
17094
|
+
this.debuggingEvents = new DebuggingEvents(this.bidi);
|
|
17095
|
+
try {
|
|
17096
|
+
await this.debuggingEvents.subscribe();
|
|
17097
|
+
} catch {
|
|
17098
|
+
logDebug("Unable to subscribe to debugging events");
|
|
17099
|
+
this.debuggingEvents = null;
|
|
17100
|
+
}
|
|
17101
|
+
this.downloadEvents = new DownloadEvents(this.bidi);
|
|
17102
|
+
try {
|
|
17103
|
+
await this.downloadEvents.subscribe();
|
|
17104
|
+
} catch {
|
|
17105
|
+
logDebug("Unable to subscribe to download events");
|
|
17106
|
+
this.downloadEvents = null;
|
|
17098
17107
|
}
|
|
17099
17108
|
this.dom = new DomInteractions(
|
|
17100
17109
|
driver,
|
|
@@ -17104,40 +17113,8 @@ var init_firefox = __esm({
|
|
|
17104
17113
|
driver,
|
|
17105
17114
|
() => this.core.getCurrentContextId(),
|
|
17106
17115
|
(id) => this.core.setCurrentContextId(id),
|
|
17107
|
-
(method, params) => this.
|
|
17116
|
+
(method, params) => this.getBidi().sendCommand(method, params)
|
|
17108
17117
|
);
|
|
17109
|
-
if (this.consoleEvents) {
|
|
17110
|
-
try {
|
|
17111
|
-
await this.consoleEvents.subscribe(void 0);
|
|
17112
|
-
} catch {
|
|
17113
|
-
logDebug("Unable to subscribe to console events");
|
|
17114
|
-
this.consoleEvents = null;
|
|
17115
|
-
}
|
|
17116
|
-
}
|
|
17117
|
-
if (this.networkEvents) {
|
|
17118
|
-
try {
|
|
17119
|
-
await this.networkEvents.subscribe(void 0);
|
|
17120
|
-
} catch {
|
|
17121
|
-
logDebug("Unable to subscribe to network events");
|
|
17122
|
-
this.networkEvents = null;
|
|
17123
|
-
}
|
|
17124
|
-
}
|
|
17125
|
-
if (this.debuggingEvents) {
|
|
17126
|
-
try {
|
|
17127
|
-
await this.debuggingEvents.subscribe();
|
|
17128
|
-
} catch {
|
|
17129
|
-
logDebug("Unable to subscribe to debugging events");
|
|
17130
|
-
this.debuggingEvents = null;
|
|
17131
|
-
}
|
|
17132
|
-
}
|
|
17133
|
-
if (this.downloadEvents) {
|
|
17134
|
-
try {
|
|
17135
|
-
await this.downloadEvents.subscribe(void 0);
|
|
17136
|
-
} catch {
|
|
17137
|
-
logDebug("Unable to subscribe to download events");
|
|
17138
|
-
this.downloadEvents = null;
|
|
17139
|
-
}
|
|
17140
|
-
}
|
|
17141
17118
|
}
|
|
17142
17119
|
// ============================================================================
|
|
17143
17120
|
// DOM / Evaluate
|
|
@@ -17149,7 +17126,7 @@ var init_firefox = __esm({
|
|
|
17149
17126
|
* native value; throws on a script exception.
|
|
17150
17127
|
*/
|
|
17151
17128
|
async evaluate(expression) {
|
|
17152
|
-
const result = await this.
|
|
17129
|
+
const result = await this.getBidi().sendCommand("script.evaluate", {
|
|
17153
17130
|
expression,
|
|
17154
17131
|
awaitPromise: true,
|
|
17155
17132
|
target: { context: this.core.getCurrentContextId() }
|
|
@@ -17394,7 +17371,7 @@ var init_firefox = __esm({
|
|
|
17394
17371
|
*/
|
|
17395
17372
|
async setDownloadBehavior(behavior) {
|
|
17396
17373
|
const downloadBehavior = behavior === "default" ? null : behavior === "allowed" ? { type: "allowed" } : { type: "denied" };
|
|
17397
|
-
await this.
|
|
17374
|
+
await this.getBidi().sendCommand("browser.setDownloadBehavior", { downloadBehavior });
|
|
17398
17375
|
}
|
|
17399
17376
|
// ============================================================================
|
|
17400
17377
|
// Snapshot
|
|
@@ -17446,7 +17423,7 @@ var init_firefox = __esm({
|
|
|
17446
17423
|
* @internal
|
|
17447
17424
|
*/
|
|
17448
17425
|
async sendBiDiCommand(method, params = {}) {
|
|
17449
|
-
return await this.
|
|
17426
|
+
return await this.getBidi().sendCommand(method, params);
|
|
17450
17427
|
}
|
|
17451
17428
|
/**
|
|
17452
17429
|
* Get WebDriver instance (for advanced operations)
|
|
@@ -17491,7 +17468,7 @@ var init_firefox = __esm({
|
|
|
17491
17468
|
if (!this.debuggingEvents) {
|
|
17492
17469
|
throw new Error("Debugging events not available");
|
|
17493
17470
|
}
|
|
17494
|
-
const result = await this.
|
|
17471
|
+
const result = await this.getBidi().sendCommand("moz:debugging.setBreakpoint", {
|
|
17495
17472
|
location: { url, line }
|
|
17496
17473
|
});
|
|
17497
17474
|
const logpointId = result.breakpoint;
|
|
@@ -17505,7 +17482,7 @@ var init_firefox = __esm({
|
|
|
17505
17482
|
if (!this.debuggingEvents) {
|
|
17506
17483
|
throw new Error("Debugging events not available");
|
|
17507
17484
|
}
|
|
17508
|
-
await this.
|
|
17485
|
+
await this.getBidi().sendCommand("moz:debugging.removeBreakpoint", {
|
|
17509
17486
|
breakpoint: logpointId
|
|
17510
17487
|
});
|
|
17511
17488
|
this.debuggingEvents.removeLogpoint(logpointId);
|
|
@@ -17559,11 +17536,56 @@ var init_firefox = __esm({
|
|
|
17559
17536
|
}
|
|
17560
17537
|
});
|
|
17561
17538
|
|
|
17539
|
+
// src/tools/instructions.ts
|
|
17540
|
+
function buildInstructions(moduleNames, toolNames) {
|
|
17541
|
+
const byName = new Map(MODULES.map((m) => [m.name, m]));
|
|
17542
|
+
const capabilities = moduleNames.flatMap((name) => {
|
|
17543
|
+
const module17 = byName.get(name);
|
|
17544
|
+
return module17 ? [`- ${module17.name}: ${module17.description}`] : [];
|
|
17545
|
+
});
|
|
17546
|
+
const sections = [INSTRUCTIONS_INTRO, ["Enabled capabilities:", ...capabilities].join("\n")];
|
|
17547
|
+
const core = CORE_TOOLS.filter((name) => toolNames.has(name));
|
|
17548
|
+
if (core.length > 0) {
|
|
17549
|
+
const groups = TASK_TOOLS.flatMap(({ purpose, tools }) => {
|
|
17550
|
+
const available = tools.filter((name) => toolNames.has(name));
|
|
17551
|
+
return available.length > 0 ? [`${available.join(" / ")} for ${purpose}`] : [];
|
|
17552
|
+
});
|
|
17553
|
+
const extra = groups.length > 0 ? ` Add task-specific tools to the same search: ${groups.join(", ")}.` : "";
|
|
17554
|
+
sections.push(
|
|
17555
|
+
`When these tools are deferred, load the core set in one search rather than one at a time: ${core.join(", ")}.${extra}`
|
|
17556
|
+
);
|
|
17557
|
+
}
|
|
17558
|
+
return sections.join("\n\n");
|
|
17559
|
+
}
|
|
17560
|
+
var INSTRUCTIONS_INTRO, CORE_TOOLS, TASK_TOOLS;
|
|
17561
|
+
var init_instructions = __esm({
|
|
17562
|
+
"src/tools/instructions.ts"() {
|
|
17563
|
+
"use strict";
|
|
17564
|
+
init_tools();
|
|
17565
|
+
INSTRUCTIONS_INTRO = [
|
|
17566
|
+
"Browse and debug the web with Firefox.",
|
|
17567
|
+
"Use this for any browser or web page task: opening a URL, automating a web flow,",
|
|
17568
|
+
"or debugging a page (console errors, network requests, DOM state).",
|
|
17569
|
+
"The task does not need to name Firefox, reach for these tools unless another",
|
|
17570
|
+
"browser is explicitly requested.",
|
|
17571
|
+
"Chrome-only browser tools do not control Firefox, do not assume they cover this."
|
|
17572
|
+
].join(" ");
|
|
17573
|
+
CORE_TOOLS = ["list_pages", "new_page", "navigate_page", "take_snapshot", "get_page_text"];
|
|
17574
|
+
TASK_TOOLS = [
|
|
17575
|
+
{ purpose: "interaction", tools: ["click_by_uid", "fill_by_uid", "hover_by_uid"] },
|
|
17576
|
+
{ purpose: "debugging", tools: ["list_console_messages", "list_network_requests"] },
|
|
17577
|
+
{ purpose: "visuals", tools: ["screenshot_page", "screenshot_by_uid"] },
|
|
17578
|
+
{ purpose: "one-off JS", tools: ["evaluate_script"] }
|
|
17579
|
+
];
|
|
17580
|
+
}
|
|
17581
|
+
});
|
|
17582
|
+
|
|
17562
17583
|
// src/tools/registry.ts
|
|
17563
17584
|
function buildToolset(options) {
|
|
17564
17585
|
const { moduleNames, warnings } = selectModules(options);
|
|
17565
17586
|
const { toolDefinitions, handlers } = collectTools(moduleNames);
|
|
17566
|
-
|
|
17587
|
+
const instructions = buildInstructions(moduleNames, new Set(handlers.keys()));
|
|
17588
|
+
return { moduleNames, warnings, toolDefinitions, handlers, instructions };
|
|
17567
17589
|
}
|
|
17568
17590
|
function selectModules(options) {
|
|
17569
17591
|
const { tools: requested, preset, enableScript, enablePrivilegedContext } = options;
|
|
@@ -17651,6 +17673,7 @@ var init_registry = __esm({
|
|
|
17651
17673
|
"src/tools/registry.ts"() {
|
|
17652
17674
|
"use strict";
|
|
17653
17675
|
init_tools();
|
|
17676
|
+
init_instructions();
|
|
17654
17677
|
privilegedModuleNames = new Set(MODULES.filter((m) => m.privileged).map((m) => m.name));
|
|
17655
17678
|
}
|
|
17656
17679
|
});
|
|
@@ -17771,7 +17794,8 @@ async function run(parseArgsFn, importMetaUrl, allowPrivileged = false) {
|
|
|
17771
17794
|
moduleNames,
|
|
17772
17795
|
warnings,
|
|
17773
17796
|
toolDefinitions: allTools,
|
|
17774
|
-
handlers: toolHandlers
|
|
17797
|
+
handlers: toolHandlers,
|
|
17798
|
+
instructions
|
|
17775
17799
|
} = buildToolset({
|
|
17776
17800
|
tools: args.tools,
|
|
17777
17801
|
preset: args.toolPreset,
|
|
@@ -17801,7 +17825,8 @@ async function run(parseArgsFn, importMetaUrl, allowPrivileged = false) {
|
|
|
17801
17825
|
{
|
|
17802
17826
|
capabilities: {
|
|
17803
17827
|
tools: {}
|
|
17804
|
-
}
|
|
17828
|
+
},
|
|
17829
|
+
instructions
|
|
17805
17830
|
}
|
|
17806
17831
|
);
|
|
17807
17832
|
server.setRequestHandler(ListToolsRequestSchema, async () => {
|