@mozilla/firefox-devtools-mcp 0.9.4 → 0.9.6
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/README.md +23 -16
- package/dist/index.js +980 -175
- package/package.json +6 -5
- package/scripts/test-closed-window.js +216 -0
package/dist/index.js
CHANGED
|
@@ -128,8 +128,8 @@ var init_cli = __esm({
|
|
|
128
128
|
},
|
|
129
129
|
startUrl: {
|
|
130
130
|
type: "string",
|
|
131
|
-
description: "URL to open when Firefox starts (default: about:
|
|
132
|
-
default: process.env.START_URL ?? "about:
|
|
131
|
+
description: "URL to open when Firefox starts (default: about:blank)",
|
|
132
|
+
default: process.env.START_URL ?? "about:blank"
|
|
133
133
|
},
|
|
134
134
|
connectExisting: {
|
|
135
135
|
type: "boolean",
|
|
@@ -164,9 +164,13 @@ var init_cli = __esm({
|
|
|
164
164
|
description: "Android app package name (default: org.mozilla.firefox). Use org.mozilla.fenix for Nightly.",
|
|
165
165
|
default: process.env.ANDROID_PACKAGE ?? "org.mozilla.firefox"
|
|
166
166
|
},
|
|
167
|
+
logFile: {
|
|
168
|
+
type: "string",
|
|
169
|
+
description: "Path to a file where MCP server logs will be written. Set DEBUG=* to also enable verbose debug logs."
|
|
170
|
+
},
|
|
167
171
|
enableScript: {
|
|
168
172
|
type: "boolean",
|
|
169
|
-
description: "Enable the
|
|
173
|
+
description: "Enable the script tools such as script evaluation and logpoints (Firefox 153+ required).",
|
|
170
174
|
default: (process.env.ENABLE_SCRIPT ?? "false") === "true"
|
|
171
175
|
},
|
|
172
176
|
enablePrivilegedContext: {
|
|
@@ -17762,6 +17766,9 @@ var require_utils = __commonJS({
|
|
|
17762
17766
|
"use strict";
|
|
17763
17767
|
var isUUID = RegExp.prototype.test.bind(/^[\da-f]{8}-[\da-f]{4}-[\da-f]{4}-[\da-f]{4}-[\da-f]{12}$/iu);
|
|
17764
17768
|
var isIPv4 = RegExp.prototype.test.bind(/^(?:(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)\.){3}(?:25[0-5]|2[0-4]\d|1\d{2}|[1-9]\d|\d)$/u);
|
|
17769
|
+
var isHexPair = RegExp.prototype.test.bind(/^[\da-f]{2}$/iu);
|
|
17770
|
+
var isUnreserved = RegExp.prototype.test.bind(/^[\da-z\-._~]$/iu);
|
|
17771
|
+
var isPathCharacter = RegExp.prototype.test.bind(/^[\da-z\-._~!$&'()*+,;=:@/]$/iu);
|
|
17765
17772
|
function stringArrayToHexStripped(input) {
|
|
17766
17773
|
let acc = "";
|
|
17767
17774
|
let code = 0;
|
|
@@ -17954,27 +17961,77 @@ var require_utils = __commonJS({
|
|
|
17954
17961
|
}
|
|
17955
17962
|
return output.join("");
|
|
17956
17963
|
}
|
|
17957
|
-
|
|
17958
|
-
|
|
17959
|
-
|
|
17960
|
-
|
|
17961
|
-
|
|
17962
|
-
|
|
17963
|
-
|
|
17964
|
-
|
|
17965
|
-
|
|
17966
|
-
|
|
17964
|
+
var HOST_DELIMS = { "@": "%40", "/": "%2F", "?": "%3F", "#": "%23", ":": "%3A" };
|
|
17965
|
+
var HOST_DELIM_RE = /[@/?#:]/g;
|
|
17966
|
+
var HOST_DELIM_NO_COLON_RE = /[@/?#]/g;
|
|
17967
|
+
function reescapeHostDelimiters(host, isIP) {
|
|
17968
|
+
const re = isIP ? HOST_DELIM_NO_COLON_RE : HOST_DELIM_RE;
|
|
17969
|
+
re.lastIndex = 0;
|
|
17970
|
+
return host.replace(re, (ch) => HOST_DELIMS[ch]);
|
|
17971
|
+
}
|
|
17972
|
+
function normalizePercentEncoding(input, decodeUnreserved = false) {
|
|
17973
|
+
if (input.indexOf("%") === -1) {
|
|
17974
|
+
return input;
|
|
17967
17975
|
}
|
|
17968
|
-
|
|
17969
|
-
|
|
17976
|
+
let output = "";
|
|
17977
|
+
for (let i = 0; i < input.length; i++) {
|
|
17978
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
17979
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
17980
|
+
if (isHexPair(hex3)) {
|
|
17981
|
+
const normalizedHex = hex3.toUpperCase();
|
|
17982
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
17983
|
+
if (decodeUnreserved && isUnreserved(decoded)) {
|
|
17984
|
+
output += decoded;
|
|
17985
|
+
} else {
|
|
17986
|
+
output += "%" + normalizedHex;
|
|
17987
|
+
}
|
|
17988
|
+
i += 2;
|
|
17989
|
+
continue;
|
|
17990
|
+
}
|
|
17991
|
+
}
|
|
17992
|
+
output += input[i];
|
|
17970
17993
|
}
|
|
17971
|
-
|
|
17972
|
-
|
|
17994
|
+
return output;
|
|
17995
|
+
}
|
|
17996
|
+
function normalizePathEncoding(input) {
|
|
17997
|
+
let output = "";
|
|
17998
|
+
for (let i = 0; i < input.length; i++) {
|
|
17999
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
18000
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
18001
|
+
if (isHexPair(hex3)) {
|
|
18002
|
+
const normalizedHex = hex3.toUpperCase();
|
|
18003
|
+
const decoded = String.fromCharCode(parseInt(normalizedHex, 16));
|
|
18004
|
+
if (decoded !== "." && isUnreserved(decoded)) {
|
|
18005
|
+
output += decoded;
|
|
18006
|
+
} else {
|
|
18007
|
+
output += "%" + normalizedHex;
|
|
18008
|
+
}
|
|
18009
|
+
i += 2;
|
|
18010
|
+
continue;
|
|
18011
|
+
}
|
|
18012
|
+
}
|
|
18013
|
+
if (isPathCharacter(input[i])) {
|
|
18014
|
+
output += input[i];
|
|
18015
|
+
} else {
|
|
18016
|
+
output += escape(input[i]);
|
|
18017
|
+
}
|
|
17973
18018
|
}
|
|
17974
|
-
|
|
17975
|
-
|
|
18019
|
+
return output;
|
|
18020
|
+
}
|
|
18021
|
+
function escapePreservingEscapes(input) {
|
|
18022
|
+
let output = "";
|
|
18023
|
+
for (let i = 0; i < input.length; i++) {
|
|
18024
|
+
if (input[i] === "%" && i + 2 < input.length) {
|
|
18025
|
+
const hex3 = input.slice(i + 1, i + 3);
|
|
18026
|
+
if (isHexPair(hex3)) {
|
|
18027
|
+
output += "%" + hex3.toUpperCase();
|
|
18028
|
+
i += 2;
|
|
18029
|
+
continue;
|
|
18030
|
+
}
|
|
18031
|
+
}
|
|
18032
|
+
output += escape(input[i]);
|
|
17976
18033
|
}
|
|
17977
|
-
return
|
|
18034
|
+
return output;
|
|
17978
18035
|
}
|
|
17979
18036
|
function recomposeAuthority(component) {
|
|
17980
18037
|
const uriTokens = [];
|
|
@@ -17989,7 +18046,7 @@ var require_utils = __commonJS({
|
|
|
17989
18046
|
if (ipV6res.isIPV6 === true) {
|
|
17990
18047
|
host = `[${ipV6res.escapedHost}]`;
|
|
17991
18048
|
} else {
|
|
17992
|
-
host =
|
|
18049
|
+
host = reescapeHostDelimiters(host, false);
|
|
17993
18050
|
}
|
|
17994
18051
|
}
|
|
17995
18052
|
uriTokens.push(host);
|
|
@@ -18003,7 +18060,10 @@ var require_utils = __commonJS({
|
|
|
18003
18060
|
module.exports = {
|
|
18004
18061
|
nonSimpleDomain,
|
|
18005
18062
|
recomposeAuthority,
|
|
18006
|
-
|
|
18063
|
+
reescapeHostDelimiters,
|
|
18064
|
+
normalizePercentEncoding,
|
|
18065
|
+
normalizePathEncoding,
|
|
18066
|
+
escapePreservingEscapes,
|
|
18007
18067
|
removeDotSegments,
|
|
18008
18068
|
isIPv4,
|
|
18009
18069
|
isUUID,
|
|
@@ -18227,12 +18287,12 @@ var require_schemes = __commonJS({
|
|
|
18227
18287
|
var require_fast_uri = __commonJS({
|
|
18228
18288
|
"node_modules/fast-uri/index.js"(exports, module) {
|
|
18229
18289
|
"use strict";
|
|
18230
|
-
var { normalizeIPv6, removeDotSegments, recomposeAuthority,
|
|
18290
|
+
var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
|
|
18231
18291
|
var { SCHEMES, getSchemeHandler } = require_schemes();
|
|
18232
18292
|
function normalize(uri, options) {
|
|
18233
18293
|
if (typeof uri === "string") {
|
|
18234
18294
|
uri = /** @type {T} */
|
|
18235
|
-
|
|
18295
|
+
normalizeString(uri, options);
|
|
18236
18296
|
} else if (typeof uri === "object") {
|
|
18237
18297
|
uri = /** @type {T} */
|
|
18238
18298
|
parse3(serialize(uri, options), options);
|
|
@@ -18299,19 +18359,9 @@ var require_fast_uri = __commonJS({
|
|
|
18299
18359
|
return target;
|
|
18300
18360
|
}
|
|
18301
18361
|
function equal(uriA, uriB, options) {
|
|
18302
|
-
|
|
18303
|
-
|
|
18304
|
-
|
|
18305
|
-
} else if (typeof uriA === "object") {
|
|
18306
|
-
uriA = serialize(normalizeComponentEncoding(uriA, true), { ...options, skipEscape: true });
|
|
18307
|
-
}
|
|
18308
|
-
if (typeof uriB === "string") {
|
|
18309
|
-
uriB = unescape(uriB);
|
|
18310
|
-
uriB = serialize(normalizeComponentEncoding(parse3(uriB, options), true), { ...options, skipEscape: true });
|
|
18311
|
-
} else if (typeof uriB === "object") {
|
|
18312
|
-
uriB = serialize(normalizeComponentEncoding(uriB, true), { ...options, skipEscape: true });
|
|
18313
|
-
}
|
|
18314
|
-
return uriA.toLowerCase() === uriB.toLowerCase();
|
|
18362
|
+
const normalizedA = normalizeComparableURI(uriA, options);
|
|
18363
|
+
const normalizedB = normalizeComparableURI(uriB, options);
|
|
18364
|
+
return normalizedA !== void 0 && normalizedB !== void 0 && normalizedA.toLowerCase() === normalizedB.toLowerCase();
|
|
18315
18365
|
}
|
|
18316
18366
|
function serialize(cmpts, opts) {
|
|
18317
18367
|
const component = {
|
|
@@ -18336,12 +18386,12 @@ var require_fast_uri = __commonJS({
|
|
|
18336
18386
|
if (schemeHandler && schemeHandler.serialize) schemeHandler.serialize(component, options);
|
|
18337
18387
|
if (component.path !== void 0) {
|
|
18338
18388
|
if (!options.skipEscape) {
|
|
18339
|
-
component.path =
|
|
18389
|
+
component.path = escapePreservingEscapes(component.path);
|
|
18340
18390
|
if (component.scheme !== void 0) {
|
|
18341
18391
|
component.path = component.path.split("%3A").join(":");
|
|
18342
18392
|
}
|
|
18343
18393
|
} else {
|
|
18344
|
-
component.path =
|
|
18394
|
+
component.path = normalizePercentEncoding(component.path);
|
|
18345
18395
|
}
|
|
18346
18396
|
}
|
|
18347
18397
|
if (options.reference !== "suffix" && component.scheme) {
|
|
@@ -18376,7 +18426,16 @@ var require_fast_uri = __commonJS({
|
|
|
18376
18426
|
return uriTokens.join("");
|
|
18377
18427
|
}
|
|
18378
18428
|
var URI_PARSE = /^(?:([^#/:?]+):)?(?:\/\/((?:([^#/?@]*)@)?(\[[^#/?\]]+\]|[^#/:?]*)(?::(\d*))?))?([^#?]*)(?:\?([^#]*))?(?:#((?:.|[\n\r])*))?/u;
|
|
18379
|
-
function
|
|
18429
|
+
function getParseError(parsed, matches) {
|
|
18430
|
+
if (matches[2] !== void 0 && parsed.path && parsed.path[0] !== "/") {
|
|
18431
|
+
return 'URI path must start with "/" when authority is present.';
|
|
18432
|
+
}
|
|
18433
|
+
if (typeof parsed.port === "number" && (parsed.port < 0 || parsed.port > 65535)) {
|
|
18434
|
+
return "URI port is malformed.";
|
|
18435
|
+
}
|
|
18436
|
+
return void 0;
|
|
18437
|
+
}
|
|
18438
|
+
function parseWithStatus(uri, opts) {
|
|
18380
18439
|
const options = Object.assign({}, opts);
|
|
18381
18440
|
const parsed = {
|
|
18382
18441
|
scheme: void 0,
|
|
@@ -18387,6 +18446,7 @@ var require_fast_uri = __commonJS({
|
|
|
18387
18446
|
query: void 0,
|
|
18388
18447
|
fragment: void 0
|
|
18389
18448
|
};
|
|
18449
|
+
let malformedAuthorityOrPort = false;
|
|
18390
18450
|
let isIP = false;
|
|
18391
18451
|
if (options.reference === "suffix") {
|
|
18392
18452
|
if (options.scheme) {
|
|
@@ -18407,6 +18467,11 @@ var require_fast_uri = __commonJS({
|
|
|
18407
18467
|
if (isNaN(parsed.port)) {
|
|
18408
18468
|
parsed.port = matches[5];
|
|
18409
18469
|
}
|
|
18470
|
+
const parseError = getParseError(parsed, matches);
|
|
18471
|
+
if (parseError !== void 0) {
|
|
18472
|
+
parsed.error = parsed.error || parseError;
|
|
18473
|
+
malformedAuthorityOrPort = true;
|
|
18474
|
+
}
|
|
18410
18475
|
if (parsed.host) {
|
|
18411
18476
|
const ipv4result = isIPv4(parsed.host);
|
|
18412
18477
|
if (ipv4result === false) {
|
|
@@ -18445,14 +18510,18 @@ var require_fast_uri = __commonJS({
|
|
|
18445
18510
|
parsed.scheme = unescape(parsed.scheme);
|
|
18446
18511
|
}
|
|
18447
18512
|
if (parsed.host !== void 0) {
|
|
18448
|
-
parsed.host = unescape(parsed.host);
|
|
18513
|
+
parsed.host = reescapeHostDelimiters(unescape(parsed.host), isIP);
|
|
18449
18514
|
}
|
|
18450
18515
|
}
|
|
18451
18516
|
if (parsed.path) {
|
|
18452
|
-
parsed.path =
|
|
18517
|
+
parsed.path = normalizePathEncoding(parsed.path);
|
|
18453
18518
|
}
|
|
18454
18519
|
if (parsed.fragment) {
|
|
18455
|
-
|
|
18520
|
+
try {
|
|
18521
|
+
parsed.fragment = encodeURI(decodeURIComponent(parsed.fragment));
|
|
18522
|
+
} catch {
|
|
18523
|
+
parsed.error = parsed.error || "URI malformed";
|
|
18524
|
+
}
|
|
18456
18525
|
}
|
|
18457
18526
|
}
|
|
18458
18527
|
if (schemeHandler && schemeHandler.parse) {
|
|
@@ -18461,7 +18530,29 @@ var require_fast_uri = __commonJS({
|
|
|
18461
18530
|
} else {
|
|
18462
18531
|
parsed.error = parsed.error || "URI can not be parsed.";
|
|
18463
18532
|
}
|
|
18464
|
-
return parsed;
|
|
18533
|
+
return { parsed, malformedAuthorityOrPort };
|
|
18534
|
+
}
|
|
18535
|
+
function parse3(uri, opts) {
|
|
18536
|
+
return parseWithStatus(uri, opts).parsed;
|
|
18537
|
+
}
|
|
18538
|
+
function normalizeString(uri, opts) {
|
|
18539
|
+
return normalizeStringWithStatus(uri, opts).normalized;
|
|
18540
|
+
}
|
|
18541
|
+
function normalizeStringWithStatus(uri, opts) {
|
|
18542
|
+
const { parsed, malformedAuthorityOrPort } = parseWithStatus(uri, opts);
|
|
18543
|
+
return {
|
|
18544
|
+
normalized: malformedAuthorityOrPort ? uri : serialize(parsed, opts),
|
|
18545
|
+
malformedAuthorityOrPort
|
|
18546
|
+
};
|
|
18547
|
+
}
|
|
18548
|
+
function normalizeComparableURI(uri, opts) {
|
|
18549
|
+
if (typeof uri === "string") {
|
|
18550
|
+
const { normalized, malformedAuthorityOrPort } = normalizeStringWithStatus(uri, opts);
|
|
18551
|
+
return malformedAuthorityOrPort ? void 0 : normalized;
|
|
18552
|
+
}
|
|
18553
|
+
if (typeof uri === "object") {
|
|
18554
|
+
return serialize(uri, opts);
|
|
18555
|
+
}
|
|
18465
18556
|
}
|
|
18466
18557
|
var fastUri = {
|
|
18467
18558
|
SCHEMES,
|
|
@@ -27137,12 +27228,12 @@ var require_dist = __commonJS({
|
|
|
27137
27228
|
throw new Error(`Unknown format "${name}"`);
|
|
27138
27229
|
return f;
|
|
27139
27230
|
};
|
|
27140
|
-
function addFormats(ajv, list,
|
|
27231
|
+
function addFormats(ajv, list, fs2, exportName) {
|
|
27141
27232
|
var _a2;
|
|
27142
27233
|
var _b;
|
|
27143
27234
|
(_a2 = (_b = ajv.opts.code).formats) !== null && _a2 !== void 0 ? _a2 : _b.formats = (0, codegen_1._)`require("ajv-formats/dist/formats").${exportName}`;
|
|
27144
27235
|
for (const f of list)
|
|
27145
|
-
ajv.addFormat(f,
|
|
27236
|
+
ajv.addFormat(f, fs2[f]);
|
|
27146
27237
|
}
|
|
27147
27238
|
module.exports = exports = formatsPlugin;
|
|
27148
27239
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
@@ -27991,27 +28082,76 @@ var init_constants = __esm({
|
|
|
27991
28082
|
});
|
|
27992
28083
|
|
|
27993
28084
|
// src/utils/logger.ts
|
|
27994
|
-
|
|
27995
|
-
|
|
28085
|
+
import fs from "fs";
|
|
28086
|
+
function formatArgs(args2) {
|
|
28087
|
+
if (args2.length === 0) {
|
|
28088
|
+
return "";
|
|
28089
|
+
}
|
|
28090
|
+
return " " + args2.map((a) => {
|
|
28091
|
+
if (typeof a === "string") {
|
|
28092
|
+
return a;
|
|
28093
|
+
}
|
|
28094
|
+
try {
|
|
28095
|
+
return JSON.stringify(a);
|
|
28096
|
+
} catch {
|
|
28097
|
+
return String(a);
|
|
28098
|
+
}
|
|
28099
|
+
}).join(" ");
|
|
27996
28100
|
}
|
|
27997
|
-
function
|
|
27998
|
-
if (
|
|
27999
|
-
|
|
28000
|
-
|
|
28001
|
-
|
|
28101
|
+
function flushLogs(timeoutMs = 2e3) {
|
|
28102
|
+
if (!logStream) {
|
|
28103
|
+
return Promise.resolve();
|
|
28104
|
+
}
|
|
28105
|
+
return new Promise((resolve4, reject) => {
|
|
28106
|
+
const timeout = setTimeout(reject, timeoutMs);
|
|
28107
|
+
logStream.end(() => {
|
|
28108
|
+
clearTimeout(timeout);
|
|
28109
|
+
resolve4();
|
|
28110
|
+
});
|
|
28111
|
+
});
|
|
28112
|
+
}
|
|
28113
|
+
function write(message, args2, body) {
|
|
28114
|
+
if (logStream) {
|
|
28115
|
+
logStream.write(`${(/* @__PURE__ */ new Date()).toISOString()} ${message}${formatArgs(args2)}
|
|
28116
|
+
`);
|
|
28117
|
+
if (body) {
|
|
28118
|
+
logStream.write(`${body}
|
|
28119
|
+
`);
|
|
28002
28120
|
}
|
|
28003
28121
|
} else {
|
|
28004
|
-
console.error(
|
|
28122
|
+
console.error(message, ...args2);
|
|
28123
|
+
if (body) {
|
|
28124
|
+
console.error(body);
|
|
28125
|
+
}
|
|
28005
28126
|
}
|
|
28006
28127
|
}
|
|
28128
|
+
function log(message, ...args2) {
|
|
28129
|
+
write(`[firefox-devtools-mcp] ${message}`, args2);
|
|
28130
|
+
}
|
|
28007
28131
|
function logDebug(message, ...args2) {
|
|
28008
28132
|
if (process.env.DEBUG === "*" || process.env.DEBUG?.includes("firefox-devtools")) {
|
|
28009
|
-
|
|
28133
|
+
write(`[firefox-devtools-mcp] DEBUG: ${message}`, args2);
|
|
28010
28134
|
}
|
|
28011
28135
|
}
|
|
28136
|
+
function logError(message, error2) {
|
|
28137
|
+
if (error2 instanceof Error) {
|
|
28138
|
+
write(`[firefox-devtools-mcp] ERROR: ${message}`, [error2.message], error2.stack);
|
|
28139
|
+
} else {
|
|
28140
|
+
write(`[firefox-devtools-mcp] ERROR: ${message}`, [error2]);
|
|
28141
|
+
}
|
|
28142
|
+
}
|
|
28143
|
+
function setupLogFile(filePath) {
|
|
28144
|
+
logStream = fs.createWriteStream(filePath, { flags: "a" });
|
|
28145
|
+
logStream.on("error", (error2) => {
|
|
28146
|
+
console.error(`[firefox-devtools-mcp] Error writing to log file: ${error2.message}`);
|
|
28147
|
+
logStream = null;
|
|
28148
|
+
});
|
|
28149
|
+
}
|
|
28150
|
+
var logStream;
|
|
28012
28151
|
var init_logger = __esm({
|
|
28013
28152
|
"src/utils/logger.ts"() {
|
|
28014
28153
|
"use strict";
|
|
28154
|
+
logStream = null;
|
|
28015
28155
|
}
|
|
28016
28156
|
});
|
|
28017
28157
|
|
|
@@ -28123,11 +28263,12 @@ var init_core3 = __esm({
|
|
|
28123
28263
|
constructor(options) {
|
|
28124
28264
|
this.options = options;
|
|
28125
28265
|
}
|
|
28126
|
-
driver = null;
|
|
28127
28266
|
currentContextId = null;
|
|
28128
|
-
|
|
28129
|
-
|
|
28267
|
+
driver = null;
|
|
28268
|
+
firefoxVersion = null;
|
|
28130
28269
|
logFileFd;
|
|
28270
|
+
logFilePath;
|
|
28271
|
+
originalEnv = {};
|
|
28131
28272
|
profileWarning = null;
|
|
28132
28273
|
/**
|
|
28133
28274
|
* Launch Firefox (or connect to an existing instance) and establish BiDi connection
|
|
@@ -28219,6 +28360,9 @@ var init_core3 = __esm({
|
|
|
28219
28360
|
for (const [name, value] of Object.entries(this.options.prefs)) {
|
|
28220
28361
|
firefoxOptions.setPreference(name, value);
|
|
28221
28362
|
}
|
|
28363
|
+
if (this.options.prefs["remote.prefs.recommended"] === false && !("app.update.disabledForTesting" in this.options.prefs)) {
|
|
28364
|
+
firefoxOptions.setPreference("app.update.disabledForTesting", true);
|
|
28365
|
+
}
|
|
28222
28366
|
}
|
|
28223
28367
|
let serviceBuilder;
|
|
28224
28368
|
if (process.platform === "win32") {
|
|
@@ -28233,11 +28377,18 @@ var init_core3 = __esm({
|
|
|
28233
28377
|
serviceBuilder.setStdio(["ignore", this.logFileFd, this.logFileFd]);
|
|
28234
28378
|
log(`Capturing Firefox output to: ${this.logFilePath}`);
|
|
28235
28379
|
}
|
|
28380
|
+
const remoteLogLevel = this.options.prefs?.["remote.log.level"];
|
|
28381
|
+
if (remoteLogLevel && typeof remoteLogLevel === "string") {
|
|
28382
|
+
serviceBuilder.addArguments("--log", remoteLogLevel.toLowerCase());
|
|
28383
|
+
}
|
|
28236
28384
|
this.driver = await new Builder().forBrowser(Browser.FIREFOX).setFirefoxOptions(firefoxOptions).setFirefoxService(serviceBuilder).build();
|
|
28237
28385
|
}
|
|
28238
28386
|
log(
|
|
28239
28387
|
this.options.connectExisting ? "Connected to existing Firefox" : "Firefox launched with BiDi"
|
|
28240
28388
|
);
|
|
28389
|
+
const driverCapabilities = await this.driver.getCapabilities();
|
|
28390
|
+
this.firefoxVersion = driverCapabilities.get("browserVersion") ?? null;
|
|
28391
|
+
logDebug(`Browser version: ${this.firefoxVersion}`);
|
|
28241
28392
|
this.currentContextId = await this.driver.getWindowHandle();
|
|
28242
28393
|
logDebug(`Browsing context ID: ${this.currentContextId}`);
|
|
28243
28394
|
if (this.options.startUrl && !this.options.connectExisting) {
|
|
@@ -28271,24 +28422,6 @@ var init_core3 = __esm({
|
|
|
28271
28422
|
return false;
|
|
28272
28423
|
}
|
|
28273
28424
|
}
|
|
28274
|
-
/**
|
|
28275
|
-
* Reset driver state (used when Firefox is detected as closed)
|
|
28276
|
-
*/
|
|
28277
|
-
reset() {
|
|
28278
|
-
if (this.driver) {
|
|
28279
|
-
const d = this.driver;
|
|
28280
|
-
if (d._bidiConnection) {
|
|
28281
|
-
d._bidiConnection.close();
|
|
28282
|
-
d._bidiConnection = void 0;
|
|
28283
|
-
}
|
|
28284
|
-
if ("quit" in this.driver) {
|
|
28285
|
-
void this.driver.quit();
|
|
28286
|
-
}
|
|
28287
|
-
}
|
|
28288
|
-
this.driver = null;
|
|
28289
|
-
this.currentContextId = null;
|
|
28290
|
-
logDebug("Driver state reset");
|
|
28291
|
-
}
|
|
28292
28425
|
/**
|
|
28293
28426
|
* Get current browsing context ID
|
|
28294
28427
|
*/
|
|
@@ -28301,6 +28434,12 @@ var init_core3 = __esm({
|
|
|
28301
28434
|
setCurrentContextId(contextId) {
|
|
28302
28435
|
this.currentContextId = contextId;
|
|
28303
28436
|
}
|
|
28437
|
+
/**
|
|
28438
|
+
* Get the current firefox version, as a string (eg "153.0a1")
|
|
28439
|
+
*/
|
|
28440
|
+
getFirefoxVersion() {
|
|
28441
|
+
return this.firefoxVersion;
|
|
28442
|
+
}
|
|
28304
28443
|
/**
|
|
28305
28444
|
* Get log file path
|
|
28306
28445
|
*/
|
|
@@ -28386,20 +28525,47 @@ var init_core3 = __esm({
|
|
|
28386
28525
|
}
|
|
28387
28526
|
/**
|
|
28388
28527
|
* Close driver and cleanup.
|
|
28389
|
-
*
|
|
28390
|
-
*
|
|
28528
|
+
* - Tries graceful quit() with a timeout; on timeout, force-kills via onQuit_().
|
|
28529
|
+
* - Restores env vars, closes log fd, clears all state.
|
|
28530
|
+
* - Never throws — callers can rely on cleanup completing.
|
|
28391
28531
|
*/
|
|
28392
28532
|
async close() {
|
|
28393
|
-
if (this.driver) {
|
|
28394
|
-
|
|
28395
|
-
|
|
28396
|
-
|
|
28397
|
-
|
|
28533
|
+
if (!this.driver) {
|
|
28534
|
+
return;
|
|
28535
|
+
}
|
|
28536
|
+
const webdriver = this.driver;
|
|
28537
|
+
const webdriverQuitTimeout = 5e3;
|
|
28538
|
+
this.driver = null;
|
|
28539
|
+
this.currentContextId = null;
|
|
28540
|
+
this.logFilePath = void 0;
|
|
28541
|
+
this.profileWarning = null;
|
|
28542
|
+
if (webdriver._bidiConnection) {
|
|
28543
|
+
try {
|
|
28544
|
+
webdriver._bidiConnection.close();
|
|
28545
|
+
} catch {
|
|
28546
|
+
} finally {
|
|
28547
|
+
webdriver._bidiConnection = void 0;
|
|
28398
28548
|
}
|
|
28399
|
-
|
|
28400
|
-
|
|
28549
|
+
}
|
|
28550
|
+
if ("quit" in webdriver) {
|
|
28551
|
+
let timer;
|
|
28552
|
+
try {
|
|
28553
|
+
await Promise.race([
|
|
28554
|
+
webdriver.quit(),
|
|
28555
|
+
new Promise((_, reject) => {
|
|
28556
|
+
timer = setTimeout(() => reject(new Error("close timeout")), webdriverQuitTimeout);
|
|
28557
|
+
})
|
|
28558
|
+
]);
|
|
28559
|
+
} catch {
|
|
28560
|
+
const webdriverHasOnQuit = typeof webdriver.onQuit_ === "function";
|
|
28561
|
+
logDebug("WebDriver.quit() timed out or failed - force killing geckodriver");
|
|
28562
|
+
if (webdriverHasOnQuit) {
|
|
28563
|
+
void webdriver.onQuit_().catch(() => {
|
|
28564
|
+
});
|
|
28565
|
+
}
|
|
28566
|
+
} finally {
|
|
28567
|
+
clearTimeout(timer);
|
|
28401
28568
|
}
|
|
28402
|
-
this.driver = null;
|
|
28403
28569
|
}
|
|
28404
28570
|
if (this.logFileFd !== void 0) {
|
|
28405
28571
|
try {
|
|
@@ -28789,12 +28955,135 @@ var init_network = __esm({
|
|
|
28789
28955
|
}
|
|
28790
28956
|
});
|
|
28791
28957
|
|
|
28958
|
+
// src/firefox/events/debugging.ts
|
|
28959
|
+
var MAX_LOGPOINT_RESULTS, DebuggingEvents;
|
|
28960
|
+
var init_debugging = __esm({
|
|
28961
|
+
"src/firefox/events/debugging.ts"() {
|
|
28962
|
+
"use strict";
|
|
28963
|
+
init_logger();
|
|
28964
|
+
MAX_LOGPOINT_RESULTS = 100;
|
|
28965
|
+
DebuggingEvents = class {
|
|
28966
|
+
constructor(driver, sendBiDiCommand) {
|
|
28967
|
+
this.driver = driver;
|
|
28968
|
+
this.sendBiDiCommand = sendBiDiCommand;
|
|
28969
|
+
}
|
|
28970
|
+
logpoints = /* @__PURE__ */ new Map();
|
|
28971
|
+
subscribed = false;
|
|
28972
|
+
/**
|
|
28973
|
+
* Subscribe to moz:debugging events
|
|
28974
|
+
*/
|
|
28975
|
+
async subscribe(contextId) {
|
|
28976
|
+
if (this.subscribed) {
|
|
28977
|
+
return;
|
|
28978
|
+
}
|
|
28979
|
+
const bidi = await this.driver.getBidi();
|
|
28980
|
+
try {
|
|
28981
|
+
await bidi.subscribe("moz:debugging.paused", contextId ? [contextId] : void 0);
|
|
28982
|
+
await bidi.subscribe("moz:debugging.resumed", contextId ? [contextId] : void 0);
|
|
28983
|
+
} catch {
|
|
28984
|
+
logDebug(
|
|
28985
|
+
"Debugging events subscription skipped (may not be available in this Firefox version)"
|
|
28986
|
+
);
|
|
28987
|
+
}
|
|
28988
|
+
const ws = bidi.socket;
|
|
28989
|
+
ws.on("message", (data) => {
|
|
28990
|
+
try {
|
|
28991
|
+
const payload = JSON.parse(data.toString());
|
|
28992
|
+
if (payload?.method === "moz:debugging.paused") {
|
|
28993
|
+
const { context, url: url2, line, column } = payload.params;
|
|
28994
|
+
const logpointId = this.findLogpointByLocation(url2, line);
|
|
28995
|
+
if (logpointId) {
|
|
28996
|
+
void this.handleLogpointPause(context, logpointId);
|
|
28997
|
+
return;
|
|
28998
|
+
}
|
|
28999
|
+
logDebug(`moz:Debugging paused in context: ${context} at ${url2}:${line}:${column}`);
|
|
29000
|
+
}
|
|
29001
|
+
if (payload?.method === "moz:debugging.resumed") {
|
|
29002
|
+
logDebug(`moz:Debugging resumed in context: ${payload.params.context}`);
|
|
29003
|
+
}
|
|
29004
|
+
} catch {
|
|
29005
|
+
}
|
|
29006
|
+
});
|
|
29007
|
+
this.subscribed = true;
|
|
29008
|
+
logDebug("moz:debugging listener active");
|
|
29009
|
+
}
|
|
29010
|
+
addLogpoint(logpointId, url2, line, expression) {
|
|
29011
|
+
this.logpoints.set(logpointId, {
|
|
29012
|
+
location: { url: url2, line },
|
|
29013
|
+
expression,
|
|
29014
|
+
results: [],
|
|
29015
|
+
capped: false
|
|
29016
|
+
});
|
|
29017
|
+
}
|
|
29018
|
+
removeLogpoint(logpointId) {
|
|
29019
|
+
this.logpoints.delete(logpointId);
|
|
29020
|
+
}
|
|
29021
|
+
getLogpointResults(logpointId) {
|
|
29022
|
+
return this.logpoints.get(logpointId)?.results ?? null;
|
|
29023
|
+
}
|
|
29024
|
+
findLogpointByLocation(url2, line) {
|
|
29025
|
+
for (const [logpointId, entry] of this.logpoints) {
|
|
29026
|
+
if (entry.location.url === url2 && entry.location.line === line) {
|
|
29027
|
+
return logpointId;
|
|
29028
|
+
}
|
|
29029
|
+
}
|
|
29030
|
+
return null;
|
|
29031
|
+
}
|
|
29032
|
+
async handleLogpointPause(contextId, logpointId) {
|
|
29033
|
+
const entry = this.logpoints.get(logpointId);
|
|
29034
|
+
if (!entry) {
|
|
29035
|
+
return;
|
|
29036
|
+
}
|
|
29037
|
+
logDebug(`Logpoint hit: ${logpointId} in context ${contextId}`);
|
|
29038
|
+
try {
|
|
29039
|
+
const result = await this.sendBiDiCommand("script.evaluate", {
|
|
29040
|
+
expression: entry.expression,
|
|
29041
|
+
target: { context: contextId },
|
|
29042
|
+
awaitPromise: false
|
|
29043
|
+
});
|
|
29044
|
+
const evalResult = result;
|
|
29045
|
+
if (evalResult.type === "exception") {
|
|
29046
|
+
entry.results.push({
|
|
29047
|
+
value: null,
|
|
29048
|
+
error: evalResult.exceptionDetails?.text ?? "Unknown error",
|
|
29049
|
+
timestamp: Date.now()
|
|
29050
|
+
});
|
|
29051
|
+
} else {
|
|
29052
|
+
entry.results.push({
|
|
29053
|
+
value: evalResult.result,
|
|
29054
|
+
timestamp: Date.now()
|
|
29055
|
+
});
|
|
29056
|
+
}
|
|
29057
|
+
} catch (error2) {
|
|
29058
|
+
entry.results.push({
|
|
29059
|
+
value: null,
|
|
29060
|
+
error: String(error2),
|
|
29061
|
+
timestamp: Date.now()
|
|
29062
|
+
});
|
|
29063
|
+
} finally {
|
|
29064
|
+
if (entry.results.length > MAX_LOGPOINT_RESULTS) {
|
|
29065
|
+
entry.results.splice(0, entry.results.length - MAX_LOGPOINT_RESULTS);
|
|
29066
|
+
if (!entry.capped) {
|
|
29067
|
+
entry.capped = true;
|
|
29068
|
+
logDebug(`Logpoint ${logpointId}: result buffer capped at ${MAX_LOGPOINT_RESULTS}`);
|
|
29069
|
+
}
|
|
29070
|
+
}
|
|
29071
|
+
await this.sendBiDiCommand("moz:debugging.resume", { context: contextId }).catch((err) => {
|
|
29072
|
+
logDebug(`Failed to resume after logpoint: ${String(err)}`);
|
|
29073
|
+
});
|
|
29074
|
+
}
|
|
29075
|
+
}
|
|
29076
|
+
};
|
|
29077
|
+
}
|
|
29078
|
+
});
|
|
29079
|
+
|
|
28792
29080
|
// src/firefox/events/index.ts
|
|
28793
29081
|
var init_events = __esm({
|
|
28794
29082
|
"src/firefox/events/index.ts"() {
|
|
28795
29083
|
"use strict";
|
|
28796
29084
|
init_console();
|
|
28797
29085
|
init_network();
|
|
29086
|
+
init_debugging();
|
|
28798
29087
|
}
|
|
28799
29088
|
});
|
|
28800
29089
|
|
|
@@ -29706,6 +29995,7 @@ var init_firefox = __esm({
|
|
|
29706
29995
|
core;
|
|
29707
29996
|
consoleEvents = null;
|
|
29708
29997
|
networkEvents = null;
|
|
29998
|
+
debuggingEvents = null;
|
|
29709
29999
|
dom = null;
|
|
29710
30000
|
pages = null;
|
|
29711
30001
|
snapshot = null;
|
|
@@ -29734,6 +30024,10 @@ var init_firefox = __esm({
|
|
|
29734
30024
|
onNavigate,
|
|
29735
30025
|
autoClearOnNavigate: false
|
|
29736
30026
|
});
|
|
30027
|
+
this.debuggingEvents = new DebuggingEvents(
|
|
30028
|
+
driver,
|
|
30029
|
+
(method, params) => this.core.sendBiDiCommand(method, params)
|
|
30030
|
+
);
|
|
29737
30031
|
}
|
|
29738
30032
|
this.dom = new DomInteractions(
|
|
29739
30033
|
driver,
|
|
@@ -29761,6 +30055,14 @@ var init_firefox = __esm({
|
|
|
29761
30055
|
this.networkEvents = null;
|
|
29762
30056
|
}
|
|
29763
30057
|
}
|
|
30058
|
+
if (this.debuggingEvents) {
|
|
30059
|
+
try {
|
|
30060
|
+
await this.debuggingEvents.subscribe();
|
|
30061
|
+
} catch {
|
|
30062
|
+
logDebug("Debugging events unavailable (BiDi not supported by this Firefox session)");
|
|
30063
|
+
this.debuggingEvents = null;
|
|
30064
|
+
}
|
|
30065
|
+
}
|
|
29764
30066
|
}
|
|
29765
30067
|
// ============================================================================
|
|
29766
30068
|
// DOM / Evaluate
|
|
@@ -30054,6 +30356,48 @@ var init_firefox = __esm({
|
|
|
30054
30356
|
async isConnected() {
|
|
30055
30357
|
return await this.core.isConnected();
|
|
30056
30358
|
}
|
|
30359
|
+
/**
|
|
30360
|
+
* Get current browser version (eg "153.0a1").
|
|
30361
|
+
* @internal
|
|
30362
|
+
*/
|
|
30363
|
+
getFirefoxVersion() {
|
|
30364
|
+
return this.core.getFirefoxVersion();
|
|
30365
|
+
}
|
|
30366
|
+
/**
|
|
30367
|
+
* @internal
|
|
30368
|
+
*/
|
|
30369
|
+
async setLogpoint(url2, line, expression) {
|
|
30370
|
+
if (!this.debuggingEvents) {
|
|
30371
|
+
throw new Error("Debugging events not available");
|
|
30372
|
+
}
|
|
30373
|
+
const result = await this.core.sendBiDiCommand("moz:debugging.setBreakpoint", {
|
|
30374
|
+
location: { url: url2, line }
|
|
30375
|
+
});
|
|
30376
|
+
const logpointId = result.breakpoint;
|
|
30377
|
+
this.debuggingEvents.addLogpoint(logpointId, url2, line, expression);
|
|
30378
|
+
return logpointId;
|
|
30379
|
+
}
|
|
30380
|
+
/**
|
|
30381
|
+
* @internal
|
|
30382
|
+
*/
|
|
30383
|
+
async removeLogpoint(logpointId) {
|
|
30384
|
+
if (!this.debuggingEvents) {
|
|
30385
|
+
throw new Error("Debugging events not available");
|
|
30386
|
+
}
|
|
30387
|
+
await this.core.sendBiDiCommand("moz:debugging.removeBreakpoint", {
|
|
30388
|
+
breakpoint: logpointId
|
|
30389
|
+
});
|
|
30390
|
+
this.debuggingEvents.removeLogpoint(logpointId);
|
|
30391
|
+
}
|
|
30392
|
+
/**
|
|
30393
|
+
* @internal
|
|
30394
|
+
*/
|
|
30395
|
+
getLogpointResults(logpointId) {
|
|
30396
|
+
if (!this.debuggingEvents) {
|
|
30397
|
+
return null;
|
|
30398
|
+
}
|
|
30399
|
+
return this.debuggingEvents.getLogpointResults(logpointId);
|
|
30400
|
+
}
|
|
30057
30401
|
/**
|
|
30058
30402
|
* Get log file path (if logging is enabled)
|
|
30059
30403
|
*/
|
|
@@ -30073,23 +30417,22 @@ var init_firefox = __esm({
|
|
|
30073
30417
|
getOptions() {
|
|
30074
30418
|
return this.core.getOptions();
|
|
30075
30419
|
}
|
|
30076
|
-
|
|
30077
|
-
|
|
30078
|
-
|
|
30079
|
-
|
|
30080
|
-
|
|
30420
|
+
// ============================================================================
|
|
30421
|
+
// Cleanup
|
|
30422
|
+
// ============================================================================
|
|
30423
|
+
async close() {
|
|
30424
|
+
try {
|
|
30425
|
+
await this.core.close();
|
|
30426
|
+
} catch (error2) {
|
|
30427
|
+
logDebug(`close() failed: ${error2 instanceof Error ? error2.message : String(error2)}`);
|
|
30428
|
+
}
|
|
30081
30429
|
this.consoleEvents = null;
|
|
30082
30430
|
this.networkEvents = null;
|
|
30431
|
+
this.debuggingEvents = null;
|
|
30083
30432
|
this.dom = null;
|
|
30084
30433
|
this.pages = null;
|
|
30085
30434
|
this.snapshot = null;
|
|
30086
30435
|
}
|
|
30087
|
-
// ============================================================================
|
|
30088
|
-
// Cleanup
|
|
30089
|
-
// ============================================================================
|
|
30090
|
-
async close() {
|
|
30091
|
-
await this.core.close();
|
|
30092
|
-
}
|
|
30093
30436
|
};
|
|
30094
30437
|
}
|
|
30095
30438
|
});
|
|
@@ -30371,7 +30714,68 @@ var init_pages2 = __esm({
|
|
|
30371
30714
|
}
|
|
30372
30715
|
});
|
|
30373
30716
|
|
|
30374
|
-
// src/
|
|
30717
|
+
// src/utils/remote-value.ts
|
|
30718
|
+
function remoteValueToNative(rv) {
|
|
30719
|
+
if (!rv || typeof rv !== "object") {
|
|
30720
|
+
return rv;
|
|
30721
|
+
}
|
|
30722
|
+
const { type, value } = rv;
|
|
30723
|
+
switch (type) {
|
|
30724
|
+
case "undefined":
|
|
30725
|
+
return void 0;
|
|
30726
|
+
case "null":
|
|
30727
|
+
return null;
|
|
30728
|
+
case "string":
|
|
30729
|
+
case "boolean":
|
|
30730
|
+
return value;
|
|
30731
|
+
case "number":
|
|
30732
|
+
if (value === "NaN") {
|
|
30733
|
+
return "NaN";
|
|
30734
|
+
}
|
|
30735
|
+
if (value === "Infinity") {
|
|
30736
|
+
return "Infinity";
|
|
30737
|
+
}
|
|
30738
|
+
if (value === "-Infinity") {
|
|
30739
|
+
return "-Infinity";
|
|
30740
|
+
}
|
|
30741
|
+
if (value === "-0") {
|
|
30742
|
+
return "-0";
|
|
30743
|
+
}
|
|
30744
|
+
return value;
|
|
30745
|
+
case "bigint":
|
|
30746
|
+
return `${value}n`;
|
|
30747
|
+
case "array":
|
|
30748
|
+
return value.map(remoteValueToNative);
|
|
30749
|
+
case "object":
|
|
30750
|
+
return Object.fromEntries(
|
|
30751
|
+
value.map(([k, v]) => [k, remoteValueToNative(v)])
|
|
30752
|
+
);
|
|
30753
|
+
case "map":
|
|
30754
|
+
return Object.fromEntries(
|
|
30755
|
+
value.map(([k, v]) => [
|
|
30756
|
+
typeof k === "object" ? JSON.stringify(remoteValueToNative(k)) : String(k),
|
|
30757
|
+
remoteValueToNative(v)
|
|
30758
|
+
])
|
|
30759
|
+
);
|
|
30760
|
+
case "set":
|
|
30761
|
+
return value.map(remoteValueToNative);
|
|
30762
|
+
case "regexp": {
|
|
30763
|
+
const { pattern, flags } = value;
|
|
30764
|
+
return `/${pattern}/${flags ?? ""}`;
|
|
30765
|
+
}
|
|
30766
|
+
case "date":
|
|
30767
|
+
return value;
|
|
30768
|
+
default:
|
|
30769
|
+
return `[${type}]`;
|
|
30770
|
+
}
|
|
30771
|
+
}
|
|
30772
|
+
var init_remote_value = __esm({
|
|
30773
|
+
"src/utils/remote-value.ts"() {
|
|
30774
|
+
"use strict";
|
|
30775
|
+
}
|
|
30776
|
+
});
|
|
30777
|
+
|
|
30778
|
+
// src/utils/js-validation.ts
|
|
30375
30779
|
function validateFunction(fnString) {
|
|
30376
30780
|
if (!fnString || typeof fnString !== "string") {
|
|
30377
30781
|
throw new Error("function parameter is required and must be a string");
|
|
@@ -30395,6 +30799,15 @@ Valid examples:
|
|
|
30395
30799
|
);
|
|
30396
30800
|
}
|
|
30397
30801
|
}
|
|
30802
|
+
var MAX_FUNCTION_SIZE;
|
|
30803
|
+
var init_js_validation = __esm({
|
|
30804
|
+
"src/utils/js-validation.ts"() {
|
|
30805
|
+
"use strict";
|
|
30806
|
+
MAX_FUNCTION_SIZE = 16 * 1024;
|
|
30807
|
+
}
|
|
30808
|
+
});
|
|
30809
|
+
|
|
30810
|
+
// src/tools/script.ts
|
|
30398
30811
|
async function handleEvaluateScript(args2) {
|
|
30399
30812
|
try {
|
|
30400
30813
|
const {
|
|
@@ -30405,17 +30818,13 @@ async function handleEvaluateScript(args2) {
|
|
|
30405
30818
|
validateFunction(fnString);
|
|
30406
30819
|
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
30407
30820
|
const firefox3 = await getFirefox2();
|
|
30408
|
-
const driver = firefox3.getDriver();
|
|
30409
|
-
if (!driver) {
|
|
30410
|
-
throw new Error("WebDriver not available");
|
|
30411
|
-
}
|
|
30412
30821
|
const scriptTimeout = timeout ?? DEFAULT_TIMEOUT;
|
|
30413
30822
|
const resolvedArgs = [];
|
|
30414
30823
|
if (fnArgs && fnArgs.length > 0) {
|
|
30415
30824
|
for (const arg of fnArgs) {
|
|
30416
30825
|
try {
|
|
30417
30826
|
const element = await firefox3.resolveUidToElement(arg.uid);
|
|
30418
|
-
resolvedArgs.push(element);
|
|
30827
|
+
resolvedArgs.push({ sharedId: await element.getId() });
|
|
30419
30828
|
} catch (error2) {
|
|
30420
30829
|
const errorMsg = error2.message;
|
|
30421
30830
|
if (errorMsg.includes("stale") || errorMsg.includes("Snapshot") || errorMsg.includes("UID")) {
|
|
@@ -30430,40 +30839,55 @@ Please call take_snapshot to get fresh UIDs and try again.`
|
|
|
30430
30839
|
}
|
|
30431
30840
|
}
|
|
30432
30841
|
}
|
|
30433
|
-
const
|
|
30434
|
-
|
|
30435
|
-
|
|
30436
|
-
|
|
30437
|
-
|
|
30438
|
-
|
|
30439
|
-
await
|
|
30440
|
-
|
|
30441
|
-
|
|
30442
|
-
|
|
30443
|
-
|
|
30444
|
-
output += "\n```";
|
|
30445
|
-
return successResponse(output);
|
|
30446
|
-
} catch (error2) {
|
|
30447
|
-
const errorMsg = error2.message;
|
|
30448
|
-
if (errorMsg.includes("timeout") || errorMsg.includes("Timeout")) {
|
|
30449
|
-
const timeoutValue = args2?.timeout ?? DEFAULT_TIMEOUT;
|
|
30842
|
+
const callFunctionPromise = firefox3.sendBiDiCommand("script.callFunction", {
|
|
30843
|
+
functionDeclaration: fnString,
|
|
30844
|
+
awaitPromise: true,
|
|
30845
|
+
arguments: resolvedArgs,
|
|
30846
|
+
target: { context: firefox3.getCurrentContextId() }
|
|
30847
|
+
});
|
|
30848
|
+
const result = await Promise.race([
|
|
30849
|
+
new Promise((r) => setTimeout(() => r(TIMEOUT), scriptTimeout)),
|
|
30850
|
+
callFunctionPromise
|
|
30851
|
+
]);
|
|
30852
|
+
if (result === TIMEOUT) {
|
|
30450
30853
|
return errorResponse(
|
|
30451
30854
|
new Error(
|
|
30452
|
-
`Script execution timed out (exceeded ${
|
|
30855
|
+
`Script execution timed out (exceeded ${scriptTimeout}ms).
|
|
30453
30856
|
|
|
30454
30857
|
The function may contain an infinite loop or be waiting for a slow operation.
|
|
30455
30858
|
Try simplifying the script or increasing the timeout parameter.`
|
|
30456
30859
|
)
|
|
30457
30860
|
);
|
|
30861
|
+
} else if (result.type === EvaluateResultType.Success) {
|
|
30862
|
+
let output = "Script ran on page and returned:\n";
|
|
30863
|
+
output += "```json\n";
|
|
30864
|
+
output += JSON.stringify(remoteValueToNative(result.result), null, 2);
|
|
30865
|
+
output += "\n```";
|
|
30866
|
+
return successResponse(output);
|
|
30867
|
+
} else if (result.type === EvaluateResultType.Exception) {
|
|
30868
|
+
const exceptionDetails = result.exceptionDetails;
|
|
30869
|
+
return errorResponse(
|
|
30870
|
+
new Error(
|
|
30871
|
+
`Script execution failed: ${exceptionDetails.text}
|
|
30872
|
+
|
|
30873
|
+
\`\`\`json
|
|
30874
|
+
` + JSON.stringify(remoteValueToNative(exceptionDetails.exception), null, 2) + "\n```"
|
|
30875
|
+
)
|
|
30876
|
+
);
|
|
30877
|
+
} else {
|
|
30878
|
+
return errorResponse(`Unexpected script.callFunction result type: ${result.type}`);
|
|
30458
30879
|
}
|
|
30880
|
+
} catch (error2) {
|
|
30459
30881
|
return errorResponse(error2);
|
|
30460
30882
|
}
|
|
30461
30883
|
}
|
|
30462
|
-
var evaluateScriptTool,
|
|
30884
|
+
var evaluateScriptTool, DEFAULT_TIMEOUT, TIMEOUT, EvaluateResultType;
|
|
30463
30885
|
var init_script = __esm({
|
|
30464
30886
|
"src/tools/script.ts"() {
|
|
30465
30887
|
"use strict";
|
|
30466
30888
|
init_response_helpers();
|
|
30889
|
+
init_remote_value();
|
|
30890
|
+
init_js_validation();
|
|
30467
30891
|
evaluateScriptTool = {
|
|
30468
30892
|
name: "evaluate_script",
|
|
30469
30893
|
description: "Execute JS function in page. Prefer UID tools for interactions.",
|
|
@@ -30496,8 +30920,12 @@ var init_script = __esm({
|
|
|
30496
30920
|
required: ["function"]
|
|
30497
30921
|
}
|
|
30498
30922
|
};
|
|
30499
|
-
MAX_FUNCTION_SIZE = 16 * 1024;
|
|
30500
30923
|
DEFAULT_TIMEOUT = 5e3;
|
|
30924
|
+
TIMEOUT = Symbol("Timeout");
|
|
30925
|
+
EvaluateResultType = {
|
|
30926
|
+
Exception: "exception",
|
|
30927
|
+
Success: "success"
|
|
30928
|
+
};
|
|
30501
30929
|
}
|
|
30502
30930
|
});
|
|
30503
30931
|
|
|
@@ -31710,10 +32138,12 @@ async function handleGetFirefoxInfo(_input) {
|
|
|
31710
32138
|
const firefox3 = await getFirefox();
|
|
31711
32139
|
const options = firefox3.getOptions();
|
|
31712
32140
|
const logFilePath = firefox3.getLogFilePath();
|
|
32141
|
+
const version3 = firefox3.getFirefoxVersion();
|
|
31713
32142
|
const info = [];
|
|
31714
32143
|
info.push("Firefox Instance Configuration");
|
|
31715
32144
|
info.push("");
|
|
31716
32145
|
info.push(`Binary: ${options.firefoxPath ?? "System Firefox (default)"}`);
|
|
32146
|
+
info.push(`Firefox version: ${version3 ?? "(unknown)"}`);
|
|
31717
32147
|
info.push(`Headless: ${options.headless ? "Yes" : "No"}`);
|
|
31718
32148
|
if (options.viewport) {
|
|
31719
32149
|
info.push(`Viewport: ${options.viewport.width}x${options.viewport.height}`);
|
|
@@ -31782,15 +32212,11 @@ async function handleRestartFirefox(input) {
|
|
|
31782
32212
|
profilePath: profilePath ?? currentOptions.profilePath,
|
|
31783
32213
|
env: newEnv !== void 0 ? newEnv : currentOptions.env,
|
|
31784
32214
|
headless: headless !== void 0 ? headless : currentOptions.headless,
|
|
31785
|
-
startUrl: startUrl ?? currentOptions.startUrl ?? "about:
|
|
32215
|
+
startUrl: startUrl ?? currentOptions.startUrl ?? "about:blank",
|
|
31786
32216
|
prefs: mergedPrefs
|
|
31787
32217
|
};
|
|
31788
32218
|
setNextLaunchOptions(newOptions);
|
|
31789
|
-
|
|
31790
|
-
await currentFirefox.close();
|
|
31791
|
-
} catch {
|
|
31792
|
-
}
|
|
31793
|
-
resetFirefox();
|
|
32219
|
+
await resetFirefox();
|
|
31794
32220
|
const changes = [];
|
|
31795
32221
|
if (firefoxPath && firefoxPath !== currentOptions.firefoxPath) {
|
|
31796
32222
|
changes.push(`Binary: ${firefoxPath}`);
|
|
@@ -31821,7 +32247,7 @@ ${changes.join("\n")}`
|
|
|
31821
32247
|
);
|
|
31822
32248
|
} else {
|
|
31823
32249
|
if (currentFirefox) {
|
|
31824
|
-
resetFirefox();
|
|
32250
|
+
await resetFirefox();
|
|
31825
32251
|
}
|
|
31826
32252
|
const resolvedFirefoxPath = firefoxPath ?? args.firefoxPath ?? void 0;
|
|
31827
32253
|
if (!resolvedFirefoxPath) {
|
|
@@ -31836,7 +32262,7 @@ ${changes.join("\n")}`
|
|
|
31836
32262
|
profilePath: profilePath ?? args.profilePath ?? void 0,
|
|
31837
32263
|
env: newEnv,
|
|
31838
32264
|
headless: headless ?? false,
|
|
31839
|
-
startUrl: startUrl ?? "about:
|
|
32265
|
+
startUrl: startUrl ?? "about:blank"
|
|
31840
32266
|
};
|
|
31841
32267
|
setNextLaunchOptions(newOptions);
|
|
31842
32268
|
const config2 = [`Binary: ${resolvedFirefoxPath}`];
|
|
@@ -31927,7 +32353,7 @@ var init_firefox_management = __esm({
|
|
|
31927
32353
|
},
|
|
31928
32354
|
startUrl: {
|
|
31929
32355
|
type: "string",
|
|
31930
|
-
description: "URL to navigate to after restart (optional, uses about:
|
|
32356
|
+
description: "URL to navigate to after restart (optional, uses about:blank if not specified)"
|
|
31931
32357
|
},
|
|
31932
32358
|
prefs: {
|
|
31933
32359
|
type: "object",
|
|
@@ -31943,10 +32369,6 @@ var init_firefox_management = __esm({
|
|
|
31943
32369
|
});
|
|
31944
32370
|
|
|
31945
32371
|
// src/tools/privileged-context.ts
|
|
31946
|
-
function isLikelyStatement(input) {
|
|
31947
|
-
const trimmed = input.trim();
|
|
31948
|
-
return /^(const|let|var)\s/.test(trimmed);
|
|
31949
|
-
}
|
|
31950
32372
|
function formatContextList(contexts) {
|
|
31951
32373
|
if (contexts.length === 0) {
|
|
31952
32374
|
return "No privileged contexts found";
|
|
@@ -32009,41 +32431,46 @@ async function handleSelectPrivilegedContext(args2) {
|
|
|
32009
32431
|
}
|
|
32010
32432
|
async function handleEvaluatePrivilegedScript(args2) {
|
|
32011
32433
|
try {
|
|
32012
|
-
const {
|
|
32013
|
-
|
|
32014
|
-
throw new Error("expression parameter is required and must be a string");
|
|
32015
|
-
}
|
|
32016
|
-
if (isLikelyStatement(expression)) {
|
|
32017
|
-
return errorResponse(
|
|
32018
|
-
new Error(
|
|
32019
|
-
`Cannot evaluate statement: "${expression.substring(0, 50)}${expression.length > 50 ? "..." : ""}". This tool expects an expression, not a statement (const/let/var declarations are statements). To use statements, wrap them in an IIFE: (function() { const x = 1; return x; })()`
|
|
32020
|
-
)
|
|
32021
|
-
);
|
|
32022
|
-
}
|
|
32434
|
+
const { function: fnString } = args2;
|
|
32435
|
+
validateFunction(fnString);
|
|
32023
32436
|
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
32024
32437
|
const firefox3 = await getFirefox2();
|
|
32025
|
-
const
|
|
32026
|
-
|
|
32027
|
-
|
|
32028
|
-
|
|
32029
|
-
|
|
32030
|
-
|
|
32031
|
-
|
|
32438
|
+
const result = await firefox3.sendBiDiCommand("script.callFunction", {
|
|
32439
|
+
functionDeclaration: fnString,
|
|
32440
|
+
awaitPromise: true,
|
|
32441
|
+
arguments: [],
|
|
32442
|
+
target: { context: firefox3.getCurrentContextId() }
|
|
32443
|
+
});
|
|
32444
|
+
if (result.type === EvaluateResultType2.Success) {
|
|
32445
|
+
let output = "Script ran in chrome context and returned:\n";
|
|
32446
|
+
output += "```json\n";
|
|
32447
|
+
output += JSON.stringify(remoteValueToNative(result.result), null, 2);
|
|
32448
|
+
output += "\n```";
|
|
32449
|
+
return successResponse(output);
|
|
32450
|
+
} else if (result.type === EvaluateResultType2.Exception) {
|
|
32451
|
+
const exceptionDetails = result.exceptionDetails;
|
|
32032
32452
|
return errorResponse(
|
|
32033
32453
|
new Error(
|
|
32034
|
-
`Script execution failed: ${
|
|
32454
|
+
`Script execution failed: ${exceptionDetails.text}
|
|
32455
|
+
|
|
32456
|
+
\`\`\`json
|
|
32457
|
+
` + JSON.stringify(remoteValueToNative(exceptionDetails.exception), null, 2) + "\n```"
|
|
32035
32458
|
)
|
|
32036
32459
|
);
|
|
32460
|
+
} else {
|
|
32461
|
+
return errorResponse(`Unexpected script.callFunction result type: ${result.type}`);
|
|
32037
32462
|
}
|
|
32038
32463
|
} catch (error2) {
|
|
32039
32464
|
return errorResponse(error2);
|
|
32040
32465
|
}
|
|
32041
32466
|
}
|
|
32042
|
-
var listPrivilegedContextsTool, selectPrivilegedContextTool, evaluatePrivilegedScriptTool;
|
|
32467
|
+
var listPrivilegedContextsTool, selectPrivilegedContextTool, evaluatePrivilegedScriptTool, EvaluateResultType2;
|
|
32043
32468
|
var init_privileged_context = __esm({
|
|
32044
32469
|
"src/tools/privileged-context.ts"() {
|
|
32045
32470
|
"use strict";
|
|
32046
32471
|
init_response_helpers();
|
|
32472
|
+
init_js_validation();
|
|
32473
|
+
init_remote_value();
|
|
32047
32474
|
listPrivilegedContextsTool = {
|
|
32048
32475
|
name: "list_privileged_contexts",
|
|
32049
32476
|
description: "List privileged (privileged) browsing contexts. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. Use restart_firefox with env parameter to enable.",
|
|
@@ -32068,18 +32495,22 @@ var init_privileged_context = __esm({
|
|
|
32068
32495
|
};
|
|
32069
32496
|
evaluatePrivilegedScriptTool = {
|
|
32070
32497
|
name: "evaluate_privileged_script",
|
|
32071
|
-
description: "
|
|
32498
|
+
description: "Execute JS function in the current privileged context. Requires MOZ_REMOTE_ALLOW_SYSTEM_ACCESS=1 env var. Use select_privileged_context first to target a chrome context.",
|
|
32072
32499
|
inputSchema: {
|
|
32073
32500
|
type: "object",
|
|
32074
32501
|
properties: {
|
|
32075
|
-
|
|
32502
|
+
function: {
|
|
32076
32503
|
type: "string",
|
|
32077
|
-
description:
|
|
32504
|
+
description: 'JS function string, e.g. () => Services.prefs.getBoolPref("foo")'
|
|
32078
32505
|
}
|
|
32079
32506
|
},
|
|
32080
|
-
required: ["
|
|
32507
|
+
required: ["function"]
|
|
32081
32508
|
}
|
|
32082
32509
|
};
|
|
32510
|
+
EvaluateResultType2 = {
|
|
32511
|
+
Exception: "exception",
|
|
32512
|
+
Success: "success"
|
|
32513
|
+
};
|
|
32083
32514
|
}
|
|
32084
32515
|
});
|
|
32085
32516
|
|
|
@@ -32554,6 +32985,354 @@ var init_webextension = __esm({
|
|
|
32554
32985
|
}
|
|
32555
32986
|
});
|
|
32556
32987
|
|
|
32988
|
+
// src/utils/version.ts
|
|
32989
|
+
function getMajorVersion(version3) {
|
|
32990
|
+
const [major2, _rhs] = version3.split(".");
|
|
32991
|
+
if (!major2) {
|
|
32992
|
+
throw new Error(`Unable to parse Firefox version ${version3}`);
|
|
32993
|
+
}
|
|
32994
|
+
return Number.parseInt(major2, 10);
|
|
32995
|
+
}
|
|
32996
|
+
function compareVersions(versionA, versionB) {
|
|
32997
|
+
const majorA = getMajorVersion(versionA);
|
|
32998
|
+
const majorB = getMajorVersion(versionB);
|
|
32999
|
+
if (majorA < majorB) {
|
|
33000
|
+
return -1;
|
|
33001
|
+
}
|
|
33002
|
+
if (majorA > majorB) {
|
|
33003
|
+
return 1;
|
|
33004
|
+
}
|
|
33005
|
+
return 0;
|
|
33006
|
+
}
|
|
33007
|
+
var init_version = __esm({
|
|
33008
|
+
"src/utils/version.ts"() {
|
|
33009
|
+
"use strict";
|
|
33010
|
+
}
|
|
33011
|
+
});
|
|
33012
|
+
|
|
33013
|
+
// src/tools/debugging.ts
|
|
33014
|
+
function requireDebuggingSupport(firefox3) {
|
|
33015
|
+
const version3 = firefox3.getFirefoxVersion();
|
|
33016
|
+
if (version3 !== null && compareVersions(version3, MIN_VERSION) < 0) {
|
|
33017
|
+
throw new Error(
|
|
33018
|
+
`moz:debugging requires Firefox ${MIN_VERSION}+, current version is ${version3}`
|
|
33019
|
+
);
|
|
33020
|
+
}
|
|
33021
|
+
}
|
|
33022
|
+
function requireContext(contextId) {
|
|
33023
|
+
if (!contextId) {
|
|
33024
|
+
throw new Error("No active browsing context");
|
|
33025
|
+
}
|
|
33026
|
+
return contextId;
|
|
33027
|
+
}
|
|
33028
|
+
async function handleEnableDebugger(_args) {
|
|
33029
|
+
try {
|
|
33030
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33031
|
+
const firefox3 = await getFirefox2();
|
|
33032
|
+
requireDebuggingSupport(firefox3);
|
|
33033
|
+
await firefox3.sendBiDiCommand("moz:debugging.setDebuggerEnabled", { enabled: true });
|
|
33034
|
+
return successResponse("Debugger enabled");
|
|
33035
|
+
} catch (error2) {
|
|
33036
|
+
return errorResponse(error2);
|
|
33037
|
+
}
|
|
33038
|
+
}
|
|
33039
|
+
async function handleListScripts(_args) {
|
|
33040
|
+
try {
|
|
33041
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33042
|
+
const firefox3 = await getFirefox2();
|
|
33043
|
+
requireDebuggingSupport(firefox3);
|
|
33044
|
+
const contextId = requireContext(firefox3.getCurrentContextId());
|
|
33045
|
+
const result = await firefox3.sendBiDiCommand("moz:debugging.listScripts", {
|
|
33046
|
+
context: contextId
|
|
33047
|
+
});
|
|
33048
|
+
const scripts = result.scripts;
|
|
33049
|
+
if (scripts.length === 0) {
|
|
33050
|
+
return successResponse("No scripts found");
|
|
33051
|
+
}
|
|
33052
|
+
return successResponse(scripts.join("\n"));
|
|
33053
|
+
} catch (error2) {
|
|
33054
|
+
return errorResponse(error2);
|
|
33055
|
+
}
|
|
33056
|
+
}
|
|
33057
|
+
async function handleGetScriptSource(args2) {
|
|
33058
|
+
try {
|
|
33059
|
+
const { scriptUrl } = args2;
|
|
33060
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33061
|
+
const firefox3 = await getFirefox2();
|
|
33062
|
+
requireDebuggingSupport(firefox3);
|
|
33063
|
+
const contextId = requireContext(firefox3.getCurrentContextId());
|
|
33064
|
+
const result = await firefox3.sendBiDiCommand("moz:debugging.getScriptSource", {
|
|
33065
|
+
context: contextId,
|
|
33066
|
+
scriptUrl
|
|
33067
|
+
});
|
|
33068
|
+
return successResponse(result.source);
|
|
33069
|
+
} catch (error2) {
|
|
33070
|
+
return errorResponse(error2);
|
|
33071
|
+
}
|
|
33072
|
+
}
|
|
33073
|
+
async function handleSetLogpoint(args2) {
|
|
33074
|
+
try {
|
|
33075
|
+
const { url: url2, line, expression } = args2;
|
|
33076
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33077
|
+
const firefox3 = await getFirefox2();
|
|
33078
|
+
requireDebuggingSupport(firefox3);
|
|
33079
|
+
const logpointId = await firefox3.setLogpoint(url2, line, expression);
|
|
33080
|
+
return successResponse(`Logpoint set (id: ${logpointId})`);
|
|
33081
|
+
} catch (error2) {
|
|
33082
|
+
return errorResponse(error2);
|
|
33083
|
+
}
|
|
33084
|
+
}
|
|
33085
|
+
async function handleRemoveLogpoint(args2) {
|
|
33086
|
+
try {
|
|
33087
|
+
const { logpoint } = args2;
|
|
33088
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33089
|
+
const firefox3 = await getFirefox2();
|
|
33090
|
+
requireDebuggingSupport(firefox3);
|
|
33091
|
+
await firefox3.removeLogpoint(logpoint);
|
|
33092
|
+
return successResponse("Logpoint removed");
|
|
33093
|
+
} catch (error2) {
|
|
33094
|
+
return errorResponse(error2);
|
|
33095
|
+
}
|
|
33096
|
+
}
|
|
33097
|
+
async function handleGetLogpointResults(args2) {
|
|
33098
|
+
try {
|
|
33099
|
+
const { logpoint } = args2;
|
|
33100
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33101
|
+
const firefox3 = await getFirefox2();
|
|
33102
|
+
requireDebuggingSupport(firefox3);
|
|
33103
|
+
const results = firefox3.getLogpointResults(logpoint);
|
|
33104
|
+
if (results === null) {
|
|
33105
|
+
return errorResponse(new Error(`Logpoint ${logpoint} not found`));
|
|
33106
|
+
}
|
|
33107
|
+
if (results.length === 0) {
|
|
33108
|
+
return successResponse("No results collected yet");
|
|
33109
|
+
}
|
|
33110
|
+
const lines = results.map((r, i) => {
|
|
33111
|
+
if (r.error) {
|
|
33112
|
+
return `[${i + 1}] Error: ${r.error}`;
|
|
33113
|
+
}
|
|
33114
|
+
return `[${i + 1}] ${JSON.stringify(remoteValueToNative(r.value))}`;
|
|
33115
|
+
});
|
|
33116
|
+
return successResponse(lines.join("\n"));
|
|
33117
|
+
} catch (error2) {
|
|
33118
|
+
return errorResponse(error2);
|
|
33119
|
+
}
|
|
33120
|
+
}
|
|
33121
|
+
var MIN_VERSION, enableDebuggerTool, listScriptsTool, getScriptSourceTool, setLogpointTool, removeLogpointTool, getLogpointResultsTool;
|
|
33122
|
+
var init_debugging2 = __esm({
|
|
33123
|
+
"src/tools/debugging.ts"() {
|
|
33124
|
+
"use strict";
|
|
33125
|
+
init_response_helpers();
|
|
33126
|
+
init_version();
|
|
33127
|
+
init_remote_value();
|
|
33128
|
+
MIN_VERSION = "153";
|
|
33129
|
+
enableDebuggerTool = {
|
|
33130
|
+
name: "enable_debugger",
|
|
33131
|
+
description: "Enable the JS debugger for the current page. Required before set_logpoint works. Requires Firefox 153+.",
|
|
33132
|
+
inputSchema: { type: "object", properties: {} }
|
|
33133
|
+
};
|
|
33134
|
+
listScriptsTool = {
|
|
33135
|
+
name: "list_scripts",
|
|
33136
|
+
description: "List all JavaScript files currently loaded in the page. Requires enable_debugger to have been called.",
|
|
33137
|
+
inputSchema: { type: "object", properties: {} }
|
|
33138
|
+
};
|
|
33139
|
+
getScriptSourceTool = {
|
|
33140
|
+
name: "get_script_source",
|
|
33141
|
+
description: "Get the source code of a JavaScript file loaded in the page. Requires enable_debugger to have been called.",
|
|
33142
|
+
inputSchema: {
|
|
33143
|
+
type: "object",
|
|
33144
|
+
properties: {
|
|
33145
|
+
scriptUrl: { type: "string", description: "URL of the script to retrieve." }
|
|
33146
|
+
},
|
|
33147
|
+
required: ["scriptUrl"]
|
|
33148
|
+
}
|
|
33149
|
+
};
|
|
33150
|
+
setLogpointTool = {
|
|
33151
|
+
name: "set_logpoint",
|
|
33152
|
+
description: "Set a logpoint at a specific location. When execution reaches that line, the expression is evaluated and the result is stored without pausing. Use get_logpoint_results to retrieve collected values. Requires enable_debugger to have been called.",
|
|
33153
|
+
inputSchema: {
|
|
33154
|
+
type: "object",
|
|
33155
|
+
properties: {
|
|
33156
|
+
url: { type: "string", description: "URL of the script." },
|
|
33157
|
+
line: { type: "number", description: "Line number (1-based)." },
|
|
33158
|
+
expression: {
|
|
33159
|
+
type: "string",
|
|
33160
|
+
description: "JavaScript expression to evaluate each time the logpoint is hit."
|
|
33161
|
+
}
|
|
33162
|
+
},
|
|
33163
|
+
required: ["url", "line", "expression"]
|
|
33164
|
+
}
|
|
33165
|
+
};
|
|
33166
|
+
removeLogpointTool = {
|
|
33167
|
+
name: "remove_logpoint",
|
|
33168
|
+
description: "Remove a previously set logpoint.",
|
|
33169
|
+
inputSchema: {
|
|
33170
|
+
type: "object",
|
|
33171
|
+
properties: {
|
|
33172
|
+
logpoint: { type: "string", description: "Logpoint id returned by set_logpoint." }
|
|
33173
|
+
},
|
|
33174
|
+
required: ["logpoint"]
|
|
33175
|
+
}
|
|
33176
|
+
};
|
|
33177
|
+
getLogpointResultsTool = {
|
|
33178
|
+
name: "get_logpoint_results",
|
|
33179
|
+
description: "Get the results collected by a logpoint since it was set.",
|
|
33180
|
+
inputSchema: {
|
|
33181
|
+
type: "object",
|
|
33182
|
+
properties: {
|
|
33183
|
+
logpoint: { type: "string", description: "Logpoint id returned by set_logpoint." }
|
|
33184
|
+
},
|
|
33185
|
+
required: ["logpoint"]
|
|
33186
|
+
}
|
|
33187
|
+
};
|
|
33188
|
+
}
|
|
33189
|
+
});
|
|
33190
|
+
|
|
33191
|
+
// src/tools/profiler.ts
|
|
33192
|
+
function checkProfilerSupported(firefox3) {
|
|
33193
|
+
const version3 = firefox3.getFirefoxVersion();
|
|
33194
|
+
if (version3 !== null && compareVersions(version3, MIN_FIREFOX_VERSION) < 0) {
|
|
33195
|
+
throw new Error(
|
|
33196
|
+
`moz:profiler requires Firefox ${MIN_FIREFOX_VERSION.split(".")[0]} or later (connected: ${version3})`
|
|
33197
|
+
);
|
|
33198
|
+
}
|
|
33199
|
+
}
|
|
33200
|
+
async function handleProfilerIsActive(_args) {
|
|
33201
|
+
try {
|
|
33202
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33203
|
+
const firefox3 = await getFirefox2();
|
|
33204
|
+
checkProfilerSupported(firefox3);
|
|
33205
|
+
const result = await firefox3.sendBiDiCommand("moz:profiler.isActive", {});
|
|
33206
|
+
return successResponse(`Profiler is ${result.active ? "active" : "inactive"}`);
|
|
33207
|
+
} catch (error2) {
|
|
33208
|
+
return errorResponse(error2);
|
|
33209
|
+
}
|
|
33210
|
+
}
|
|
33211
|
+
async function handleProfilerStart(args2) {
|
|
33212
|
+
try {
|
|
33213
|
+
const { preset, entries, interval, features, threads, activeContext } = args2;
|
|
33214
|
+
const params = {};
|
|
33215
|
+
if (preset !== void 0) {
|
|
33216
|
+
params.preset = preset;
|
|
33217
|
+
} else {
|
|
33218
|
+
if (entries === void 0 || interval === void 0 || features === void 0 || threads === void 0) {
|
|
33219
|
+
throw new Error(
|
|
33220
|
+
"When no preset is given, entries, interval, features, and threads are all required."
|
|
33221
|
+
);
|
|
33222
|
+
}
|
|
33223
|
+
params.entries = entries;
|
|
33224
|
+
params.interval = interval;
|
|
33225
|
+
params.features = features;
|
|
33226
|
+
params.threads = threads;
|
|
33227
|
+
}
|
|
33228
|
+
if (activeContext !== void 0) {
|
|
33229
|
+
params.activeContext = activeContext;
|
|
33230
|
+
}
|
|
33231
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33232
|
+
const firefox3 = await getFirefox2();
|
|
33233
|
+
checkProfilerSupported(firefox3);
|
|
33234
|
+
await firefox3.sendBiDiCommand("moz:profiler.start", params);
|
|
33235
|
+
return successResponse("Profiler started");
|
|
33236
|
+
} catch (error2) {
|
|
33237
|
+
return errorResponse(error2);
|
|
33238
|
+
}
|
|
33239
|
+
}
|
|
33240
|
+
async function handleProfilerStop(args2) {
|
|
33241
|
+
try {
|
|
33242
|
+
const { discard } = args2;
|
|
33243
|
+
const params = {};
|
|
33244
|
+
if (discard !== void 0) {
|
|
33245
|
+
params.discard = discard;
|
|
33246
|
+
}
|
|
33247
|
+
const { getFirefox: getFirefox2 } = await Promise.resolve().then(() => (init_src(), src_exports));
|
|
33248
|
+
const firefox3 = await getFirefox2();
|
|
33249
|
+
checkProfilerSupported(firefox3);
|
|
33250
|
+
const result = await firefox3.sendBiDiCommand("moz:profiler.stop", params);
|
|
33251
|
+
if (result.path) {
|
|
33252
|
+
return successResponse(`Profile saved to: ${result.path}`);
|
|
33253
|
+
}
|
|
33254
|
+
return successResponse("Profiler stopped. No profile was saved.");
|
|
33255
|
+
} catch (error2) {
|
|
33256
|
+
return errorResponse(error2);
|
|
33257
|
+
}
|
|
33258
|
+
}
|
|
33259
|
+
var MIN_FIREFOX_VERSION, VALID_PRESETS, profilerIsActiveTool, profilerStartTool, profilerStopTool;
|
|
33260
|
+
var init_profiler = __esm({
|
|
33261
|
+
"src/tools/profiler.ts"() {
|
|
33262
|
+
"use strict";
|
|
33263
|
+
init_response_helpers();
|
|
33264
|
+
init_version();
|
|
33265
|
+
MIN_FIREFOX_VERSION = "154.0";
|
|
33266
|
+
VALID_PRESETS = [
|
|
33267
|
+
"web-developer",
|
|
33268
|
+
"firefox-platform",
|
|
33269
|
+
"graphics",
|
|
33270
|
+
"media",
|
|
33271
|
+
"ml",
|
|
33272
|
+
"networking",
|
|
33273
|
+
"power",
|
|
33274
|
+
"debug"
|
|
33275
|
+
];
|
|
33276
|
+
profilerIsActiveTool = {
|
|
33277
|
+
name: "profiler_is_active",
|
|
33278
|
+
description: "Check whether the Firefox profiler is currently recording.",
|
|
33279
|
+
inputSchema: {
|
|
33280
|
+
type: "object",
|
|
33281
|
+
properties: {}
|
|
33282
|
+
}
|
|
33283
|
+
};
|
|
33284
|
+
profilerStartTool = {
|
|
33285
|
+
name: "profiler_start",
|
|
33286
|
+
description: `Start the Firefox profiler. Provide either a preset name or explicit recording options (entries, interval, features, threads). Cannot combine both. Valid presets: ${VALID_PRESETS.join(", ")}.`,
|
|
33287
|
+
inputSchema: {
|
|
33288
|
+
type: "object",
|
|
33289
|
+
properties: {
|
|
33290
|
+
preset: {
|
|
33291
|
+
type: "string",
|
|
33292
|
+
enum: VALID_PRESETS,
|
|
33293
|
+
description: "Profiler preset name. Cannot be combined with entries, interval, features, or threads."
|
|
33294
|
+
},
|
|
33295
|
+
entries: {
|
|
33296
|
+
type: "integer",
|
|
33297
|
+
description: "Number of entries to keep in the sampling buffer. Required when no preset is given."
|
|
33298
|
+
},
|
|
33299
|
+
interval: {
|
|
33300
|
+
type: "number",
|
|
33301
|
+
description: "Sampling interval in milliseconds. Required when no preset is given."
|
|
33302
|
+
},
|
|
33303
|
+
features: {
|
|
33304
|
+
type: "array",
|
|
33305
|
+
items: { type: "string" },
|
|
33306
|
+
description: "Profiler features to enable. Required when no preset is given."
|
|
33307
|
+
},
|
|
33308
|
+
threads: {
|
|
33309
|
+
type: "array",
|
|
33310
|
+
items: { type: "string" },
|
|
33311
|
+
description: "Thread names to profile. Required when no preset is given."
|
|
33312
|
+
},
|
|
33313
|
+
activeContext: {
|
|
33314
|
+
type: "string",
|
|
33315
|
+
description: "Id of the top-level navigable to mark as the active tab in the profile. Does not restrict profiling to that tab."
|
|
33316
|
+
}
|
|
33317
|
+
}
|
|
33318
|
+
}
|
|
33319
|
+
};
|
|
33320
|
+
profilerStopTool = {
|
|
33321
|
+
name: "profiler_stop",
|
|
33322
|
+
description: "Stop the Firefox profiler and save the recorded profile to a file in the downloads directory. Returns the path to the saved file, or null when nothing was saved.",
|
|
33323
|
+
inputSchema: {
|
|
33324
|
+
type: "object",
|
|
33325
|
+
properties: {
|
|
33326
|
+
discard: {
|
|
33327
|
+
type: "boolean",
|
|
33328
|
+
description: "If true, stop the profiler and discard the recording instead of saving it to disk. Defaults to false."
|
|
33329
|
+
}
|
|
33330
|
+
}
|
|
33331
|
+
}
|
|
33332
|
+
};
|
|
33333
|
+
}
|
|
33334
|
+
});
|
|
33335
|
+
|
|
32557
33336
|
// src/tools/index.ts
|
|
32558
33337
|
var init_tools = __esm({
|
|
32559
33338
|
"src/tools/index.ts"() {
|
|
@@ -32570,6 +33349,8 @@ var init_tools = __esm({
|
|
|
32570
33349
|
init_privileged_context();
|
|
32571
33350
|
init_firefox_prefs();
|
|
32572
33351
|
init_webextension();
|
|
33352
|
+
init_debugging2();
|
|
33353
|
+
init_profiler();
|
|
32573
33354
|
}
|
|
32574
33355
|
});
|
|
32575
33356
|
|
|
@@ -32588,9 +33369,9 @@ import { version as version2 } from "process";
|
|
|
32588
33369
|
import { fileURLToPath as fileURLToPath2 } from "url";
|
|
32589
33370
|
import { resolve as resolve3 } from "path";
|
|
32590
33371
|
import { realpathSync } from "fs";
|
|
32591
|
-
function resetFirefox() {
|
|
33372
|
+
async function resetFirefox() {
|
|
32592
33373
|
if (firefox2) {
|
|
32593
|
-
firefox2.
|
|
33374
|
+
await firefox2.close();
|
|
32594
33375
|
firefox2 = null;
|
|
32595
33376
|
}
|
|
32596
33377
|
pendingWarning = null;
|
|
@@ -32611,7 +33392,7 @@ async function getFirefox() {
|
|
|
32611
33392
|
const isConnected = await firefox2.isConnected();
|
|
32612
33393
|
if (!isConnected) {
|
|
32613
33394
|
log("Firefox connection lost, reconnecting...");
|
|
32614
|
-
resetFirefox();
|
|
33395
|
+
await resetFirefox();
|
|
32615
33396
|
} else {
|
|
32616
33397
|
return firefox2;
|
|
32617
33398
|
}
|
|
@@ -32659,8 +33440,7 @@ async function getFirefox() {
|
|
|
32659
33440
|
pendingWarning = firefox2.getAndClearProfileWarning();
|
|
32660
33441
|
return firefox2;
|
|
32661
33442
|
} catch (error2) {
|
|
32662
|
-
await firefox2.close()
|
|
32663
|
-
});
|
|
33443
|
+
await firefox2.close();
|
|
32664
33444
|
firefox2 = null;
|
|
32665
33445
|
throw error2;
|
|
32666
33446
|
}
|
|
@@ -32680,6 +33460,9 @@ async function run(parseArgsFn, importMetaUrl) {
|
|
|
32680
33460
|
return;
|
|
32681
33461
|
}
|
|
32682
33462
|
args = parseArgsFn(SERVER_VERSION);
|
|
33463
|
+
if (args.logFile) {
|
|
33464
|
+
setupLogFile(args.logFile);
|
|
33465
|
+
}
|
|
32683
33466
|
const toolHandlers = new Map([
|
|
32684
33467
|
// Pages
|
|
32685
33468
|
["list_pages", handleListPages],
|
|
@@ -32719,8 +33502,21 @@ async function run(parseArgsFn, importMetaUrl) {
|
|
|
32719
33502
|
// WebExtensions (install/uninstall use standard BiDi, no privileged context required)
|
|
32720
33503
|
["install_extension", handleInstallExtension],
|
|
32721
33504
|
["uninstall_extension", handleUninstallExtension],
|
|
33505
|
+
// Profiler
|
|
33506
|
+
["profiler_is_active", handleProfilerIsActive],
|
|
33507
|
+
["profiler_start", handleProfilerStart],
|
|
33508
|
+
["profiler_stop", handleProfilerStop],
|
|
32722
33509
|
// Script evaluation — requires --enable-script
|
|
32723
33510
|
...args.enableScript ? [["evaluate_script", handleEvaluateScript]] : [],
|
|
33511
|
+
// Debugging tools — requires --enable-script
|
|
33512
|
+
...args.enableScript ? [
|
|
33513
|
+
["enable_debugger", handleEnableDebugger],
|
|
33514
|
+
["list_scripts", handleListScripts],
|
|
33515
|
+
["get_script_source", handleGetScriptSource],
|
|
33516
|
+
["set_logpoint", handleSetLogpoint],
|
|
33517
|
+
["remove_logpoint", handleRemoveLogpoint],
|
|
33518
|
+
["get_logpoint_results", handleGetLogpointResults]
|
|
33519
|
+
] : [],
|
|
32724
33520
|
// Privileged context tools — requires --enable-privileged-context
|
|
32725
33521
|
...args.enablePrivilegedContext ? [
|
|
32726
33522
|
["list_privileged_contexts", handleListPrivilegedContexts],
|
|
@@ -32761,8 +33557,20 @@ async function run(parseArgsFn, importMetaUrl) {
|
|
|
32761
33557
|
restartFirefoxTool,
|
|
32762
33558
|
installExtensionTool,
|
|
32763
33559
|
uninstallExtensionTool,
|
|
33560
|
+
profilerIsActiveTool,
|
|
33561
|
+
profilerStartTool,
|
|
33562
|
+
profilerStopTool,
|
|
32764
33563
|
// Script evaluation — requires --enable-script
|
|
32765
33564
|
...args.enableScript ? [evaluateScriptTool] : [],
|
|
33565
|
+
// Debugging tools — requires --enable-script
|
|
33566
|
+
...args.enableScript ? [
|
|
33567
|
+
enableDebuggerTool,
|
|
33568
|
+
listScriptsTool,
|
|
33569
|
+
getScriptSourceTool,
|
|
33570
|
+
setLogpointTool,
|
|
33571
|
+
removeLogpointTool,
|
|
33572
|
+
getLogpointResultsTool
|
|
33573
|
+
] : [],
|
|
32766
33574
|
// Privileged context tools — requires --enable-privileged-context
|
|
32767
33575
|
...args.enablePrivilegedContext ? [
|
|
32768
33576
|
listPrivilegedContextsTool,
|
|
@@ -32835,13 +33643,10 @@ async function run(parseArgsFn, importMetaUrl) {
|
|
|
32835
33643
|
log("Firefox DevTools MCP server running on stdio");
|
|
32836
33644
|
log("Ready to accept tool requests");
|
|
32837
33645
|
const cleanup = async () => {
|
|
32838
|
-
|
|
32839
|
-
try {
|
|
32840
|
-
await firefox2.close();
|
|
32841
|
-
} catch {
|
|
32842
|
-
}
|
|
32843
|
-
}
|
|
33646
|
+
await resetFirefox();
|
|
32844
33647
|
await server.close();
|
|
33648
|
+
await flushLogs().catch(() => {
|
|
33649
|
+
});
|
|
32845
33650
|
process.exit(0);
|
|
32846
33651
|
};
|
|
32847
33652
|
const onSignal = () => void cleanup();
|
|
@@ -32947,7 +33752,7 @@ var require_package = __commonJS({
|
|
|
32947
33752
|
var require_main = __commonJS({
|
|
32948
33753
|
"node_modules/dotenv/lib/main.js"(exports, module) {
|
|
32949
33754
|
"use strict";
|
|
32950
|
-
var
|
|
33755
|
+
var fs2 = __require("fs");
|
|
32951
33756
|
var path = __require("path");
|
|
32952
33757
|
var os = __require("os");
|
|
32953
33758
|
var crypto = __require("crypto");
|
|
@@ -33086,7 +33891,7 @@ var require_main = __commonJS({
|
|
|
33086
33891
|
if (options && options.path && options.path.length > 0) {
|
|
33087
33892
|
if (Array.isArray(options.path)) {
|
|
33088
33893
|
for (const filepath of options.path) {
|
|
33089
|
-
if (
|
|
33894
|
+
if (fs2.existsSync(filepath)) {
|
|
33090
33895
|
possibleVaultPath = filepath.endsWith(".vault") ? filepath : `${filepath}.vault`;
|
|
33091
33896
|
}
|
|
33092
33897
|
}
|
|
@@ -33096,7 +33901,7 @@ var require_main = __commonJS({
|
|
|
33096
33901
|
} else {
|
|
33097
33902
|
possibleVaultPath = path.resolve(process.cwd(), ".env.vault");
|
|
33098
33903
|
}
|
|
33099
|
-
if (
|
|
33904
|
+
if (fs2.existsSync(possibleVaultPath)) {
|
|
33100
33905
|
return possibleVaultPath;
|
|
33101
33906
|
}
|
|
33102
33907
|
return null;
|
|
@@ -33149,7 +33954,7 @@ var require_main = __commonJS({
|
|
|
33149
33954
|
const parsedAll = {};
|
|
33150
33955
|
for (const path2 of optionPaths) {
|
|
33151
33956
|
try {
|
|
33152
|
-
const parsed = DotenvModule.parse(
|
|
33957
|
+
const parsed = DotenvModule.parse(fs2.readFileSync(path2, { encoding }));
|
|
33153
33958
|
DotenvModule.populate(parsedAll, parsed, options);
|
|
33154
33959
|
} catch (e) {
|
|
33155
33960
|
if (debug) {
|