65de4e3e2cc5

test: add Firefox sender-context CI coverage (v5.0.27)

time
2026-08-30 12:30 UTC · 1 month ago
machine
koh2
parent
a5aedfd3830e
7 files bun.lock+32 −13manifest.firefox.json+1 −1manifest.json+1 −1package.json+3 −1scripts/ci.ts+7 −0scripts/test-firefox-sender-context.mjs+238 −0tests/fixtures/firefox-sender-probe.js+18 −0
bun.lock+32 −13
@@ -35,6 +35,7 @@3535         "@types/pako": "^2.0.4",3636         "@types/papaparse": "^5.5.2",3737         "chrome-webstore-upload-cli": "^4.0.0",38+        "geckodriver": "6.1.1",3839         "jsdom": "^29.0.2",3940         "typescript": "^6.0.3",4041         "vite": "^8.0.8",@@ -404,8 +405,12 @@404405 405406     "@vitest/utils": ["@vitest/[email protected]", "", { "dependencies": { "@vitest/pretty-format": "4.1.4", "convert-source-map": "^2.0.0", "tinyrainbow": "^3.1.0" } }, "sha512-13QMT+eysM5uVGa1rG4kegGYNp6cnQcsTc67ELFbhNLQO+vgsygtYJx2khvdt4gVQqSSpC/KT5FZZxUpP3Oatw=="],406407 408+    "@wdio/logger": ["@wdio/[email protected]", "", { "dependencies": { "chalk": "^5.1.2", "loglevel": "^1.6.0", "loglevel-plugin-prefix": "^0.8.4", "safe-regex2": "^5.0.0", "strip-ansi": "^7.1.0" } }, "sha512-0ZAEIo6PNyMIJPlOGkIgyOJUjcd0pC8/QHlVAAe1c91/IcjZ1X+k0yidXHaboJdN7dq1XPUacmhRdtua0U5EZg=="],409+407410     "@webcomponents/custom-elements": ["@webcomponents/[email protected]", "", {}, "sha512-CqTpxOlUCPWRNUPZDxT5v2NnHXA4oox612iUGnmTUGQFhZ1Gkj8kirtl/2wcF6MqX7+PqqicZzOCBKKfIn0dww=="],408411 412+    "@zip.js/zip.js": ["@zip.js/[email protected]", "", {}, "sha512-F9RnQJXgvCSdAJfMtSrH5Uh/LcLg8fZlg5ARC4u5N4w9v/DG27cN3C/ngUrY1JdJGX2LRv5DDViXeZJgbcppWA=="],413+409414     "@zxcvbn-ts/core": ["@zxcvbn-ts/[email protected]", "", { "dependencies": { "fastest-levenshtein": "1.0.16" } }, "sha512-aQeiT0F09FuJaAqNrxynlAwZ2mW/1MdXakKWNmGM1Qp/VaY6CnB/GfnMS2T8gB2231Esp1/maCWd8vTG4OuShw=="],410415 411416     "@zxcvbn-ts/language-common": ["@zxcvbn-ts/[email protected]", "", {}, "sha512-viSNNnRYtc7ULXzxrQIVUNwHAPSXRtoIwy/Tq4XQQdIknBzw4vz36lQLF6mvhMlTIlpjoN/Z1GFu/fwiAlUSsw=="],@@ -436,7 +441,7 @@436441 437442     "ansi-align": ["[email protected]", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],438443 439-    "ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],444+    "ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],440445 441446     "ansi-styles": ["[email protected]", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],442447 @@ -664,6 +669,8 @@664669 665670     "fx-runner": ["[email protected]", "", { "dependencies": { "commander": "2.9.0", "shell-quote": "1.7.3", "spawn-sync": "1.0.15", "when": "3.7.7", "which": "1.2.4", "winreg": "0.0.12" }, "bin": "bin/fx-runner" }, "sha512-rci1g6U0rdTg6bAaBboP7XdRu01dzTAaKXxFf+PUqGuCv6Xu7o8NZdY1D5MvKGIjb6EdS1g3VlXOgksir1uGkg=="],666671 672+    "geckodriver": ["[email protected]", "", { "dependencies": { "@wdio/logger": "^9.18.0", "@zip.js/zip.js": "^2.8.11", "decamelize": "^6.0.1", "http-proxy-agent": "^7.0.2", "https-proxy-agent": "^7.0.6", "modern-tar": "^0.7.3" }, "bin": { "geckodriver": "bin/geckodriver.js" } }, "sha512-/AcCyc9o9o6hUbudaSJM2iOtXbxSLqQPOb4GrPvEN40cjraUeaX/j5kH3mSgwiroyMn7qzx2wM61AQ2XY8j3sA=="],673+667674     "gensync": ["[email protected]", "", {}, "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="],668675 669676     "get-caller-file": ["[email protected]", "", {}, "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="],@@ -694,6 +701,8 @@694701 695702     "htmlparser2": ["[email protected]", "", { "dependencies": { "domelementtype": "^2.3.0", "domhandler": "^5.0.3", "domutils": "^3.2.2", "entities": "^7.0.1" } }, "sha512-VTZkM9GWRAtEpveh7MSF6SjjrpNVNNVJfFup7xTY3UpFtm67foy9HDVXneLtFVt4pMz5kZtgNcvCniNFb1hlEQ=="],696703 704+    "http-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.0", "debug": "^4.3.4" } }, "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig=="],705+697706     "https-proxy-agent": ["[email protected]", "", { "dependencies": { "agent-base": "^7.1.2", "debug": "4" } }, "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw=="],698707 699708     "iconv-lite": ["[email protected]", "", { "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" } }, "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw=="],@@ -838,6 +847,10 @@838847 839848     "lodash.once": ["[email protected]", "", {}, "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg=="],840849 850+    "loglevel": ["[email protected]", "", {}, "sha512-HgMmCqIJSAKqo68l0rS2AanEWfkxaZ5wNiEFb5ggm08lDs9Xl2KxBlX3PTcaD2chBM1gXAYf491/M2Rv8Jwayg=="],851+852+    "loglevel-plugin-prefix": ["[email protected]", "", {}, "sha512-WpG9CcFAOjz/FtNht+QJeGpvVl/cdR6P0z6OcXSkr8wFJOsV2GRj2j10JLfjuA4aYkcKCNIEqRGCyTife9R8/g=="],853+841854     "lru-cache": ["[email protected]", "", {}, "sha512-NxVFwLAnrd9i7KUBxC4DrUhmgjzOs+1Qm50D3oF1/oL+r1NpZ4gA7xvG0/zJ8evR7zIKn4vLf7qTNduWFtCrRw=="],842855 843856     "magic-string": ["[email protected]", "", { "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.5" } }, "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ=="],@@ -860,6 +873,8 @@860873 861874     "minimist": ["[email protected]", "", {}, "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA=="],862875 876+    "modern-tar": ["[email protected]", "", {}, "sha512-t9VmxaqrmANnEOBhpSDI6HD192Ge48k8vmWqQQL7hSFEqHEYwZbbsu49+aKLWZeRvFs3j1pMhXOqqF4kPlvjkQ=="],877+863878     "mrmime": ["[email protected]", "", {}, "sha512-Y3wQdFg2Va6etvQ5I82yUhGdsKrcYox6p7FfL1LbK2J4V01F9TGlepTIhnK24t7koZibmg82KGglhA1XK5IsLQ=="],864879 865880     "ms": ["[email protected]", "", {}, "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA=="],@@ -976,6 +991,8 @@976991 977992     "resolve-from": ["[email protected]", "", {}, "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="],978993 994+    "ret": ["[email protected]", "", {}, "sha512-I1XxrZSQ+oErkRR4jYbAyEEu2I0avBvvMM5JN+6EBprOGRCs63ENqZ3vjavq8fBw2+62G5LF5XelKwuJpcvcxw=="],995+979996     "reusify": ["[email protected]", "", {}, "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw=="],980997 981998     "rolldown": ["[email protected]", "", { "dependencies": { "@oxc-project/types": "=0.124.0", "@rolldown/pluginutils": "1.0.0-rc.15" }, "optionalDependencies": { "@rolldown/binding-android-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-arm64": "1.0.0-rc.15", "@rolldown/binding-darwin-x64": "1.0.0-rc.15", "@rolldown/binding-freebsd-x64": "1.0.0-rc.15", "@rolldown/binding-linux-arm-gnueabihf": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-arm64-musl": "1.0.0-rc.15", "@rolldown/binding-linux-ppc64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-s390x-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-gnu": "1.0.0-rc.15", "@rolldown/binding-linux-x64-musl": "1.0.0-rc.15", "@rolldown/binding-openharmony-arm64": "1.0.0-rc.15", "@rolldown/binding-wasm32-wasi": "1.0.0-rc.15", "@rolldown/binding-win32-arm64-msvc": "1.0.0-rc.15", "@rolldown/binding-win32-x64-msvc": "1.0.0-rc.15" }, "bin": "bin/cli.mjs" }, "sha512-Ff31guA5zT6WjnGp0SXw76X6hzGRk/OQq2hE+1lcDe+lJdHSgnSX6nK3erbONHyCbpSj9a9E+uX/OvytZoWp2g=="],@@ -990,6 +1007,8 @@9901007 9911008     "safe-buffer": ["[email protected]", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="],9921009 1010+    "safe-regex2": ["[email protected]", "", { "dependencies": { "ret": "~0.5.0" }, "bin": { "safe-regex2": "bin/safe-regex2.js" } }, "sha512-mOSBvHGDZMuIEZMdOz/aCEYDCv0E7nfcNsIhUF+/P+xC7Hyf3FkvymqgPbg9D1EdSGu+uKbJgy09K/RKKc7kJA=="],1011+9931012     "safe-stable-stringify": ["[email protected]", "", {}, "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA=="],9941013 9951014     "safer-buffer": ["[email protected]", "", {}, "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg=="],@@ -1042,7 +1061,7 @@10421061 10431062     "string_decoder": ["[email protected]", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],10441063 1045-    "strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],1064+    "strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],10461065 10471066     "strip-bom": ["[email protected]", "", {}, "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A=="],10481067 @@ -1230,8 +1249,12 @@12301249 12311250     "cheerio/whatwg-mimetype": ["[email protected]", "", {}, "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg=="],12321251 1252+    "cliui/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],1253+12331254     "cliui/wrap-ansi": ["[email protected]", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],12341255 1256+    "columnify/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],1257+12351258     "config-chain/ini": ["[email protected]", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],12361259 12371260     "cross-spawn/which": ["[email protected]", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],@@ -1280,6 +1303,8 @@12801303 12811304     "rxjs/tslib": ["[email protected]", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="],12821305 1306+    "string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],1307+12831308     "thread-stream/real-require": ["[email protected]", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],12841309 12851310     "update-notifier/semver": ["[email protected]", "", { "bin": "bin/semver.js" }, "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA=="],@@ -1292,8 +1317,6 @@12921317 12931318     "wrap-ansi/string-width": ["[email protected]", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],12941319 1295-    "wrap-ansi/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],1296-12971320     "wsl-utils/is-wsl": ["[email protected]", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],12981321 12991322     "yauzl/buffer-crc32": ["[email protected]", "", {}, "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ=="],@@ -1306,22 +1329,18 @@13061329 13071330     "boxen/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],13081331 1309-    "boxen/string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],1332+    "cliui/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],13101333 1334+    "columnify/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],1335+13111336     "cross-spawn/which/isexe": ["[email protected]", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],13121337 13131338     "eslint/ajv/json-schema-traverse": ["[email protected]", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],13141339 1340+    "string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],1341+13151342     "widest-line/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],13161343 1317-    "widest-line/string-width/strip-ansi": ["[email protected]", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],1318-13191344     "wrap-ansi/string-width/emoji-regex": ["[email protected]", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],1320-1321-    "wrap-ansi/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],1322-1323-    "boxen/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],1324-1325-    "widest-line/string-width/strip-ansi/ansi-regex": ["[email protected]", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="],13261345   }13271346 }

view file

manifest.firefox.json+1 −1
@@ -1,7 +1,7 @@11 {22   "manifest_version": 3,33   "name": "Peach Password Manager",4-  "version": "5.0.25",4+  "version": "5.0.27",55   "description": "Zero-knowledge password manager",66   "browser_specific_settings": {77     "gecko": {

view file

manifest.json+1 −1
@@ -2,7 +2,7 @@22   "manifest_version": 3,33   "minimum_chrome_version": "116",44   "name": "Peach Password Manager",5-  "version": "5.0.25",5+  "version": "5.0.27",66   "description": "Zero-knowledge password manager",77   "action": {88     "default_popup": "src/popup/index.html",

view file

package.json+3 −1
@@ -1,6 +1,6 @@11 {22   "name": "peach",3-  "version": "5.0.25",3+  "version": "5.0.27",44   "description": "Zero-knowledge password manager",55   "license": "GPL-3.0-only",66   "type": "module",@@ -12,6 +12,7 @@1212     "build:chrome": "node scripts/clean-chrome-dist.mjs && BUILD_TARGET=chrome vite build && npm run check:sw",1313     "build:firefox": "node scripts/clean-firefox-artifacts.mjs && BUILD_TARGET=firefox vite build",1414     "build:e2e": "PEACH_E2E_PASSKEYS=1 BUILD_TARGET=chrome vite build",15+    "test:firefox:sender": "node scripts/test-firefox-sender-context.mjs",1516     "package:firefox": "node scripts/clean-firefox-artifacts.mjs && npm run build:firefox && cd dist-firefox && web-ext build --overwrite-dest",1617     "lint": "biome check src/",1718     "lint:fix": "biome check --write src/",@@ -62,6 +63,7 @@6263     "@types/pako": "^2.0.4",6364     "@types/papaparse": "^5.5.2",6465     "chrome-webstore-upload-cli": "^4.0.0",66+    "geckodriver": "6.1.1",6567     "jsdom": "^29.0.2",6668     "typescript": "^6.0.3",6769     "vite": "^8.0.8",

view file

scripts/ci.ts+7 −0
@@ -12,6 +12,7 @@1212 const YELLOW = '\x1b[33m';1313 const BOLD   = '\x1b[1m';1414 const DIM    = '\x1b[2m';15+const RUN_FIREFOX_BROWSER_CHECKS = process.env.CI === 'true' || process.env.PEACH_E2E_FIREFOX === '1';1516 1617 interface Step {1718   name: string;@@ -53,6 +54,12 @@5354     name: 'Firefox build',5455     cmd: ['npm', 'run', 'build:firefox'],5556   },57+  ...(RUN_FIREFOX_BROWSER_CHECKS58+    ? [{59+        name: 'Firefox sender context',60+        cmd: ['npm', 'run', 'test:firefox:sender'],61+      }]62+    : []),5663 ];5764 5865 function run(step: Step): { ok: boolean; output: string; duration: number } {

view file

scripts/test-firefox-sender-context.mjs+238 −0
@@ -0,0 +1,238 @@1+#!/usr/bin/env node2+3+import { execFile } from "node:child_process";4+import { promisify } from "node:util";5+import { cp, mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";6+import { existsSync } from "node:fs";7+import { tmpdir } from "node:os";8+import { join, resolve } from "node:path";9+import net from "node:net";10+import { start } from "geckodriver";11+12+const execFileAsync = promisify(execFile);13+const ROOT = resolve(new URL("..", import.meta.url).pathname);14+const DIST = join(ROOT, "dist-firefox");15+const PROBE = join(ROOT, "tests/fixtures/firefox-sender-probe.js");16+const WEB_EXT = join(ROOT, "node_modules/.bin/web-ext");17+const EXTENSION_ID = "[email protected]";18+19+function findFirefoxBinary() {20+  const candidates = [21+    process.env.PEACH_FIREFOX_BINARY,22+    process.env.FIREFOX_BIN,23+    "/usr/bin/firefox",24+    "/usr/local/bin/firefox",25+    "/Applications/Firefox.app/Contents/MacOS/firefox",26+    "/Applications/Firefox Nightly.app/Contents/MacOS/firefox",27+  ].filter(Boolean);28+  return candidates.find((candidate) => existsSync(candidate));29+}30+31+function findFreePort() {32+  return new Promise((resolvePort, reject) => {33+    const server = net.createServer();34+    server.once("error", reject);35+    server.listen(0, "127.0.0.1", () => {36+      const address = server.address();37+      if (!address || typeof address === "string") {38+        server.close();39+        reject(new Error("Could not allocate a WebDriver port"));40+        return;41+      }42+      server.close(() => resolvePort(address.port));43+    });44+  });45+}46+47+async function waitForDriver(port) {48+  const deadline = Date.now() + 30_000;49+  while (Date.now() < deadline) {50+    try {51+      const response = await fetchWithTimeout(`http://127.0.0.1:${port}/status`, {}, 500);52+      if (response.ok && (await response.json()).value?.ready) return;53+    } catch {54+      // geckodriver is still starting.55+    }56+    await new Promise((resolveDelay) => setTimeout(resolveDelay, 250));57+  }58+  throw new Error("geckodriver did not become ready within 30 seconds");59+}60+61+async function fetchWithTimeout(url, options, timeoutMs) {62+  const controller = new AbortController();63+  let timeoutId;64+  const timeout = new Promise((_, reject) => {65+    timeoutId = setTimeout(() => reject(new Error(`Request timed out after ${timeoutMs}ms`)), timeoutMs);66+  });67+  try {68+    return await Promise.race([69+      fetch(url, { ...options, signal: controller.signal }),70+      timeout,71+    ]);72+  } finally {73+    clearTimeout(timeoutId);74+    controller.abort();75+  }76+}77+78+async function webdriverRequest(port, path, options = {}) {79+  const response = await fetchWithTimeout(`http://127.0.0.1:${port}${path}`, {80+    ...options,81+    headers: { "content-type": "application/json", ...(options.headers ?? {}) },82+  }, 10_000);83+  const body = await response.json();84+  if (!response.ok || body.value?.error) {85+    throw new Error(`WebDriver ${options.method ?? "GET"} ${path} failed: ${JSON.stringify(body)}`);86+  }87+  return body.value;88+}89+90+async function buildTestExtension(tempRoot) {91+  const sourceDir = join(tempRoot, "extension");92+  const artifactsDir = join(tempRoot, "artifacts");93+  await cp(DIST, sourceDir, { recursive: true });94+  await mkdir(artifactsDir, { recursive: true });95+  await cp(PROBE, join(sourceDir, "firefox-sender-probe.js"));96+97+  const manifestPath = join(sourceDir, "manifest.json");98+  const manifest = JSON.parse(await readFile(manifestPath, "utf8"));99+  manifest.content_scripts.push({100+    matches: ["<all_urls>"],101+    js: ["firefox-sender-probe.js"],102+    run_at: "document_start",103+    all_frames: false,104+  });105+  await writeFile(manifestPath, `${JSON.stringify(manifest, null, 2)}\n`);106+107+  await execFileAsync(WEB_EXT, [108+    "build",109+    "--source-dir", sourceDir,110+    "--artifacts-dir", artifactsDir,111+    "--filename", "peach-firefox-sender-context.xpi",112+    "--overwrite-dest",113+    "--no-input",114+  ], { cwd: ROOT });115+  return join(artifactsDir, "peach-firefox-sender-context.xpi");116+}117+118+async function run() {119+  if (!existsSync(DIST)) throw new Error("dist-firefox is missing; run npm run build:firefox first");120+  if (!existsSync(WEB_EXT)) throw new Error("web-ext is missing; run bun install first");121+122+  const tempRoot = await mkdtemp(join(tmpdir(), "peach-firefox-sender-"));123+  let driver;124+  let driverPort;125+  let sessionId;126+  try {127+    const xpiPath = await buildTestExtension(tempRoot);128+    const firefoxBinary = findFirefoxBinary();129+    const port = await findFreePort();130+    driverPort = port;131+    driver = await start({132+      host: "127.0.0.1",133+      port,134+      geckoDriverVersion: "0.37.1",135+      ...(firefoxBinary ? { binary: firefoxBinary } : {}),136+      log: "error",137+    });138+    await waitForDriver(port);139+140+    const capabilities = {141+      browserName: "firefox",142+      acceptInsecureCerts: true,143+      "moz:firefoxOptions": {144+        args: ["-headless"],145+        ...(firefoxBinary ? { binary: firefoxBinary } : {}),146+        prefs: {147+          "browser.shell.checkDefaultBrowser": false,148+          "browser.startup.page": 0,149+          "browser.startup.homepage": "about:blank",150+        },151+      },152+    };153+    const session = await webdriverRequest(port, "/session", {154+      method: "POST",155+      body: JSON.stringify({ capabilities: { alwaysMatch: capabilities } }),156+    });157+    sessionId = session.sessionId;158+159+    const install = await webdriverRequest(port, `/session/${sessionId}/moz/addon/install`, {160+      method: "POST",161+      body: JSON.stringify({162+        addon: (await readFile(xpiPath)).toString("base64"),163+        temporary: true,164+      }),165+    });166+    if (install?.id && install.id !== EXTENSION_ID) {167+      throw new Error(`Unexpected Firefox add-on id: ${install.id}`);168+    }169+170+    await webdriverRequest(port, `/session/${sessionId}/url`, {171+      method: "POST",172+      body: JSON.stringify({ url: "https://example.com/" }),173+    });174+    const result = await webdriverRequest(port, `/session/${sessionId}/execute/async`, {175+      method: "POST",176+      body: JSON.stringify({177+        script: `178+          const done = arguments[arguments.length - 1];179+          const source = "peach-firefox-sender-probe";180+          const send = (message) => new Promise((resolve) => {181+            const id = crypto.randomUUID();182+            const timer = setTimeout(() => {183+              window.removeEventListener("message", onMessage);184+              resolve({ ok: false, error: "probe_timeout" });185+            }, 5000);186+            function onMessage(event) {187+              if (event.source !== window || event.data?.source !== source || event.data.id !== id) return;188+              clearTimeout(timer);189+              window.removeEventListener("message", onMessage);190+              resolve(event.data.response);191+            }192+            window.addEventListener("message", onMessage);193+            window.postMessage({ source, id, message }, window.location.origin);194+          });195+          Promise.all([196+            send({ type: "GET_SETTINGS" }),197+            send({ type: "GET_ENTRIES" }),198+            send({ type: "GET_RECOVERY_WORDS" }),199+          ]).then(([allowed, entries, recovery]) => done({200+            pageOrigin: location.origin,201+            allowed,202+            entries,203+            recovery,204+          }));205+        `,206+        args: [],207+      }),208+    });209+210+    if (result.pageOrigin !== "https://example.com") {211+      throw new Error(`Unexpected page origin: ${result.pageOrigin}`);212+    }213+    if (result.allowed?.error === "invalid_sender") {214+      throw new Error("Content-script-allowed GET_SETTINGS was rejected");215+    }216+    for (const [name, response] of [["GET_ENTRIES", result.entries], ["GET_RECOVERY_WORDS", result.recovery]]) {217+      if (response?.error !== "invalid_sender") {218+        throw new Error(`${name} was not rejected from the real Firefox content-script sender: ${JSON.stringify(response)}`);219+      }220+    }221+    console.log("Firefox sender-context test passed: real content-script GET_ENTRIES and GET_RECOVERY_WORDS were rejected, GET_SETTINGS remained allowed.");222+  } finally {223+    if (driver && sessionId) {224+      try {225+        await webdriverRequest(driverPort, `/session/${sessionId}`, { method: "DELETE" });226+      } catch {227+        // The browser may already have exited after a failed startup.228+      }229+    }230+    driver?.kill("SIGTERM");231+    await rm(tempRoot, { recursive: true, force: true });232+  }233+}234+235+run().catch((error) => {236+  console.error(error instanceof Error ? error.message : error);237+  process.exitCode = 1;238+});

view file

tests/fixtures/firefox-sender-probe.js+18 −0
@@ -0,0 +1,18 @@1+(() => {2+  const SOURCE = "peach-firefox-sender-probe";3+  const ALLOWED_TYPES = new Set(["GET_ENTRIES", "GET_RECOVERY_WORDS", "GET_SETTINGS"]);4+5+  window.addEventListener("message", async (event) => {6+    if (event.source !== window || event.data?.source !== SOURCE) return;7+    const { id, message } = event.data;8+    if (typeof id !== "string" || !message || !ALLOWED_TYPES.has(message.type)) return;9+10+    let response;11+    try {12+      response = await chrome.runtime.sendMessage(message);13+    } catch (error) {14+      response = { ok: false, error: String(error) };15+    }16+    window.postMessage({ source: SOURCE, id, response }, window.location.origin);17+  });18+})();

view file