diff --git a/.codex/config.toml b/.codex/config.toml index 4192b15039..16acc3faef 100644 --- a/.codex/config.toml +++ b/.codex/config.toml @@ -27,3 +27,6 @@ import(pathToFileURL(path.join(root, entry)).href).catch((error) => { env_vars = ["PHOENIX_DESKTOP_PATH", "PHOENIX_MCP_WS_PORT"] # Live-preview connection and execution can take longer than the default 60s. tool_timeout_sec = 120 + +[mcp_servers.phoenix-builder.tools.get_phoenix_status] +approval_mode = "approve" diff --git a/serve-proxy.js b/serve-proxy.js index d7a1819902..2e2dbc684d 100644 --- a/serve-proxy.js +++ b/serve-proxy.js @@ -309,8 +309,26 @@ const server = http.createServer((req, res) => { } } - // Serve static files - let filePath = path.join(config.root, parsedUrl.pathname); + // Serve static files. url.parse leaves the pathname percent encoded, so a + // directory or file whose name contains a space was looked up on disk as + // "sub%20dir" and answered 404 even though it was right there. Decode before + // touching the filesystem - the traversal check below still runs on the + // result, which is what stops an encoded "%2e%2e" from buying anything. + let decodedPathname; + try { + decodedPathname = decodeURIComponent(parsedUrl.pathname); + } catch (e) { + // A malformed escape is a bad request, not a missing file. + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Bad Request'); + return; + } + if (decodedPathname.indexOf('\0') !== -1) { + res.writeHead(400, { 'Content-Type': 'text/plain' }); + res.end('Bad Request'); + return; + } + let filePath = path.join(config.root, decodedPathname); // Security: prevent directory traversal const normalizedPath = path.normalize(filePath); diff --git a/src-node/index.js b/src-node/index.js index 5cde37499f..795cd055b3 100644 --- a/src-node/index.js +++ b/src-node/index.js @@ -169,17 +169,62 @@ rl.on('close', () => { process.exit(1); }); +// Opt-in watchdog for test processes; disabled by default. node-loader.js arms +// it for Phoenix.isTestWindow, and SpecRunnerUtils refreshes the runner and its +// test window. Normal editor sessions never send setIdleExit. +// +// Tauri owns this process and holds stdin open after the spawning page goes away. +// A terminate command sent during beforeunload can be lost, leaving Node and its +// LSP servers running. Test runs accept timeout-based cleanup for these orphans. +// Keep this disabled in normal editor sessions: sleep, page suspension or long +// pauses can stop heartbeats even though the page still exists. Expiry can then +// trigger a Node crash dialog after wake. Earlier heartbeat/socket orphan checks +// were removed for sleep-related crashes in a1e660cb5 and ba5800d93. +let _idleExitMs = 0; +let _idleExitTimer = null; + +function _shutdown(reason) { + lmdb.dumpDBToFileAndCloseDB() + .catch(console.error) + .finally(() => { + console.log(reason); + process.exit(0); + }); +} + +function _refreshIdleExit() { + if (!_idleExitMs) { + return; + } + if (_idleExitTimer) { + clearTimeout(_idleExitTimer); + } + _idleExitTimer = setTimeout(() => { + _shutdown(`No command for ${_idleExitMs}ms, the page that spawned us is gone.`); + }, _idleExitMs); + // never let this timer alone hold the process up + if (_idleExitTimer.unref) { + _idleExitTimer.unref(); + } +} + function processCommand(line) { try{ let jsonCmd = JSON.parse(line); + // any command at all is proof the page is still there + _refreshIdleExit(); switch (jsonCmd.commandCode) { + case "setIdleExit": + _idleExitMs = Number(jsonCmd.commandData) || 0; + if (!_idleExitMs && _idleExitTimer) { + clearTimeout(_idleExitTimer); + _idleExitTimer = null; + } + _refreshIdleExit(); + _sendResponse(_idleExitMs, jsonCmd.commandID); + return; case "terminate": - lmdb.dumpDBToFileAndCloseDB() - .catch(console.error) - .finally(()=>{ - console.log("Node terminated by phcode."); - process.exit(0); - }); + _shutdown("Node terminated by phcode."); return; case "ping": _sendResponse("pong", jsonCmd.commandID); return; case "setDebugMode": diff --git a/src-node/package-lock.json b/src-node/package-lock.json index ccba61da88..484fd57899 100644 --- a/src-node/package-lock.json +++ b/src-node/package-lock.json @@ -86,9 +86,6 @@ "cpu": [ "arm64" ], - "libc": [ - "glibc" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -102,9 +99,6 @@ "cpu": [ "arm64" ], - "libc": [ - "musl" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -118,9 +112,6 @@ "cpu": [ "x64" ], - "libc": [ - "glibc" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -134,9 +125,6 @@ "cpu": [ "x64" ], - "libc": [ - "musl" - ], "license": "SEE LICENSE IN LICENSE.md", "optional": true, "os": [ @@ -195,7 +183,6 @@ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.2.tgz", "integrity": "sha512-JiDShH45zKHWyGe4ZNVRrCjBz8Nh9TMmZG1kh4QTK8hCBTWBi8Da+i7s1fJw7/lYpM4ccepSNfqzZ/QvABBi5g==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.9.0" } @@ -216,7 +203,6 @@ "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.14.tgz", "integrity": "sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.14.1" }, @@ -497,7 +483,6 @@ "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", - "peer": true, "dependencies": { "mime-types": "^3.0.0", "negotiator": "^1.0.0" @@ -511,7 +496,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -521,7 +505,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -538,7 +521,6 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", "integrity": "sha512-Thbli+OlOj+iMPYFBVBfJ3OmCAnaSyNn4M1vz9T6Gka5Jt9ba/HIR56joy65tY6kx/FCF5VXNB819Y7/GUrBGA==", "license": "MIT", - "peer": true, "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -555,7 +537,6 @@ "resolved": "https://registry.npmjs.org/ajv-formats/-/ajv-formats-3.0.1.tgz", "integrity": "sha512-8iUql50EUR+uUcdRQ3HDqa6EVyo3docL8g5WJ3FNcWmu62IbkGUue/pEyLBW8VGKKucTPgqeks4fIU1DA4yowQ==", "license": "MIT", - "peer": true, "dependencies": { "ajv": "^8.0.0" }, @@ -593,7 +574,6 @@ "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", "license": "MIT", - "peer": true, "dependencies": { "bytes": "^3.1.2", "content-type": "^1.0.5", @@ -649,7 +629,6 @@ "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -659,7 +638,6 @@ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -673,7 +651,6 @@ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -716,7 +693,6 @@ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -730,7 +706,6 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -740,7 +715,6 @@ "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -750,7 +724,6 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", "license": "MIT", - "peer": true, "engines": { "node": ">=6.6.0" } @@ -771,7 +744,6 @@ "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "license": "MIT", - "peer": true, "dependencies": { "object-assign": "^4", "vary": "^1" @@ -830,7 +802,6 @@ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", "license": "MIT", - "peer": true, "dependencies": { "ms": "^2.1.3" }, @@ -885,7 +856,6 @@ "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -959,7 +929,6 @@ "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -973,15 +942,13 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/encodeurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -1003,7 +970,6 @@ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -1013,7 +979,6 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -1023,7 +988,6 @@ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -1035,15 +999,13 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -1053,7 +1015,6 @@ "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-3.0.7.tgz", "integrity": "sha512-CRT1WTyuQoD771GW56XEZFQ/ZoSfWid1alKGDYMmkt2yl8UXrVR4pspqWNEcqKvVIzg6PAltWjxcSSPrboA4iA==", "license": "MIT", - "peer": true, "dependencies": { "eventsource-parser": "^3.0.1" }, @@ -1066,7 +1027,6 @@ "resolved": "https://registry.npmjs.org/eventsource-parser/-/eventsource-parser-3.0.8.tgz", "integrity": "sha512-70QWGkr4snxr0OXLRWsFLeRBIRPuQOvt4s8QYjmUlmlkyTZkRqS7EDVRZtzU3TiyDbXSzaOeF0XUKy8PchzukQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18.0.0" } @@ -1076,7 +1036,6 @@ "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", "license": "MIT", - "peer": true, "dependencies": { "accepts": "^2.0.0", "body-parser": "^2.2.1", @@ -1120,7 +1079,6 @@ "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", "license": "MIT", - "peer": true, "dependencies": { "ip-address": "^10.2.0" }, @@ -1139,7 +1097,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -1149,7 +1106,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -1165,8 +1121,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.2", @@ -1182,8 +1137,7 @@ "url": "https://opencollective.com/fastify" } ], - "license": "BSD-3-Clause", - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fill-range": { "version": "7.1.1", @@ -1201,7 +1155,6 @@ "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "encodeurl": "^2.0.0", @@ -1223,7 +1176,6 @@ "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -1233,7 +1185,6 @@ "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -1256,7 +1207,6 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -1266,7 +1216,6 @@ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "license": "MIT", - "peer": true, "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -1291,7 +1240,6 @@ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "license": "MIT", - "peer": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -1316,7 +1264,6 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -1329,7 +1276,6 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -1342,7 +1288,6 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", "license": "MIT", - "peer": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -1374,7 +1319,6 @@ "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", "license": "MIT", - "peer": true, "dependencies": { "depd": "~2.0.0", "inherits": "~2.0.4", @@ -1395,7 +1339,6 @@ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", "license": "MIT", - "peer": true, "dependencies": { "safer-buffer": ">= 2.1.2 < 3.0.0" }, @@ -1419,15 +1362,13 @@ "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ip-address": { "version": "10.2.0", "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", "license": "MIT", - "peer": true, "engines": { "node": ">= 12" } @@ -1437,7 +1378,6 @@ "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.10" } @@ -1515,8 +1455,7 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-wsl": { "version": "3.1.0", @@ -1542,7 +1481,6 @@ "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", "license": "MIT", - "peer": true, "funding": { "url": "https://github.com/sponsors/panva" } @@ -1552,7 +1490,6 @@ "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-3.1.1.tgz", "integrity": "sha512-+DWg8jCJG2TEnpy7kOm/7/AxaYoaRbjVB4LFZLySZlWn8exGs3A4OLJR966cVvU26N7X9TWxl+Jsw7dzAqKT6g==", "license": "MIT", - "peer": true, "dependencies": { "@babel/runtime": "^7.18.3", "ts-algebra": "^2.0.0" @@ -1565,15 +1502,13 @@ "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-schema-typed": { "version": "8.0.2", "resolved": "https://registry.npmjs.org/json-schema-typed/-/json-schema-typed-8.0.2.tgz", "integrity": "sha512-fQhoXdcvc3V28x7C7BMs4P5+kNlgUURe2jmUT1T//oBRMDrqy1QPelJimwZGo7Hg9VPV3EQV5Bnq4hbFy2vetA==", - "license": "BSD-2-Clause", - "peer": true + "license": "BSD-2-Clause" }, "node_modules/jsonc-parser": { "version": "3.3.1", @@ -1625,7 +1560,6 @@ "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" } @@ -1635,7 +1569,6 @@ "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -1645,7 +1578,6 @@ "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -1676,8 +1608,7 @@ "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/msgpackr": { "version": "1.11.8", @@ -1715,7 +1646,6 @@ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -3593,6 +3523,7 @@ "version": "4.0.3", "inBundle": true, "license": "MIT", + "peer": true, "engines": { "node": ">=12" }, @@ -3730,7 +3661,6 @@ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3740,7 +3670,6 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.4" }, @@ -3753,7 +3682,6 @@ "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", "license": "MIT", - "peer": true, "dependencies": { "ee-first": "1.1.1" }, @@ -3766,7 +3694,6 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", "license": "ISC", - "peer": true, "dependencies": { "wrappy": "1" } @@ -3799,7 +3726,6 @@ "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -3817,7 +3743,6 @@ "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "license": "MIT", - "peer": true, "funding": { "type": "opencollective", "url": "https://opencollective.com/express" @@ -3839,7 +3764,6 @@ "resolved": "https://registry.npmjs.org/pkce-challenge/-/pkce-challenge-5.0.1.tgz", "integrity": "sha512-wQ0b/W4Fr01qtpHlqSqspcj3EhBvimsdh0KlHhH8HRZnMsEa0ea2fTULOXOS9ccQr3om+GcGRk4e+isrZWV8qQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=16.20.0" } @@ -3849,7 +3773,6 @@ "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", "license": "MIT", - "peer": true, "dependencies": { "forwarded": "0.2.0", "ipaddr.js": "1.9.1" @@ -3863,7 +3786,6 @@ "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "license": "BSD-3-Clause", - "peer": true, "dependencies": { "side-channel": "^1.1.0" }, @@ -3879,7 +3801,6 @@ "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -3889,7 +3810,6 @@ "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", "license": "MIT", - "peer": true, "dependencies": { "bytes": "~3.1.2", "http-errors": "~2.0.1", @@ -3928,7 +3848,6 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3938,7 +3857,6 @@ "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.0", "depd": "^2.0.0", @@ -3965,8 +3883,7 @@ "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "7.5.2", @@ -3988,7 +3905,6 @@ "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", "license": "MIT", - "peer": true, "dependencies": { "debug": "^4.4.3", "encodeurl": "^2.0.0", @@ -4015,7 +3931,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -4025,7 +3940,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -4042,7 +3956,6 @@ "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", "license": "MIT", - "peer": true, "dependencies": { "encodeurl": "^2.0.0", "escape-html": "^1.0.3", @@ -4061,8 +3974,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/shebang-command": { "version": "2.0.0", @@ -4088,7 +4000,6 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -4108,7 +4019,6 @@ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "license": "MIT", - "peer": true, "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.4" @@ -4125,7 +4035,6 @@ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -4144,7 +4053,6 @@ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "license": "MIT", - "peer": true, "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -4164,7 +4072,6 @@ "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -4185,7 +4092,6 @@ "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", "license": "MIT", - "peer": true, "engines": { "node": ">=0.6" } @@ -4194,15 +4100,13 @@ "version": "2.0.0", "resolved": "https://registry.npmjs.org/ts-algebra/-/ts-algebra-2.0.0.tgz", "integrity": "sha512-FPAhNPFMrkwz76P7cdjdmiShwMynZYN6SgOujD1urY4oNm80Ou9oMdmbR45LotcKOXoy7wSmHkRFE6Mxbrhefw==", - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/type-is": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", "license": "MIT", - "peer": true, "dependencies": { "content-type": "^2.0.0", "media-typer": "^1.1.0", @@ -4221,7 +4125,6 @@ "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", "license": "MIT", - "peer": true, "engines": { "node": ">=18" }, @@ -4235,7 +4138,6 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.6" } @@ -4245,7 +4147,6 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", - "peer": true, "dependencies": { "mime-db": "^1.54.0" }, @@ -4275,7 +4176,6 @@ "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -4285,7 +4185,6 @@ "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", "license": "MIT", - "peer": true, "engines": { "node": ">= 0.8" } @@ -4521,8 +4420,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/ws": { "version": "8.17.1", @@ -4556,6 +4454,7 @@ "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", "license": "MIT", + "peer": true, "funding": { "url": "https://github.com/sponsors/colinhacks" } @@ -4565,7 +4464,6 @@ "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "license": "ISC", - "peer": true, "peerDependencies": { "zod": "^3.25.28 || ^4" } diff --git a/src/JSUtils/ScopeManager.js b/src/JSUtils/ScopeManager.js index ab6944cac1..3b0cd5d9bd 100644 --- a/src/JSUtils/ScopeManager.js +++ b/src/JSUtils/ScopeManager.js @@ -110,20 +110,22 @@ define(function (require, exports, module) { /** * Init preferences from a file in the project root or builtin - * defaults if no file is found; + * defaults if no file is found. Ignore callbacks from a previous project. * @private * @param {string=} projectRootPath - new project root path. Only needed * for unit tests. */ function initPreferences(projectRootPath) { - // Reject the old preferences if they have not completed. - if (deferredPreferences && deferredPreferences.state() === "pending") { - deferredPreferences.reject(); + const previousRequest = deferredPreferences; + const request = $.Deferred(); + deferredPreferences = request; + // Cancelling the old request can release queued editor changes. Those + // changes must already see the new project's preference request. + if (previousRequest && previousRequest.state() === "pending") { + previousRequest.reject(); } - - deferredPreferences = $.Deferred(); - var pr = ProjectManager.getProjectRoot(); + const pr = ProjectManager.getProjectRoot(); // Open preferences relative to the project root // Normally there is a project root, but for unit tests we need to @@ -133,6 +135,7 @@ define(function (require, exports, module) { } else if (!projectRootPath) { console.log("initPreferences: projectRootPath has no value. Using Defaults."); preferences = new Preferences(); + request.resolve(); return; } @@ -140,8 +143,14 @@ define(function (require, exports, module) { preferences = new Preferences(); FileSystem.resolve(path, function (err, file) { + if (deferredPreferences !== request) { + return; + } if (!err) { FileUtils.readAsText(file).done(function (text) { + if (deferredPreferences !== request) { + return; + } var configObj = null; try { configObj = JSON.parse(text); @@ -154,13 +163,16 @@ define(function (require, exports, module) { } } preferences = new Preferences(configObj); - deferredPreferences.resolve(); + request.resolve(); }).fail(function (error) { + if (deferredPreferences !== request) { + return; + } preferences = new Preferences(); - deferredPreferences.resolve(); + request.resolve(); }); } else { - deferredPreferences.resolve(); + request.resolve(); } }); } @@ -366,16 +378,31 @@ define(function (require, exports, module) { } /** - * Get the text of a document, applying any size restrictions - * if necessary + * Prepare a background file read or update for Tern's JavaScript parser. + * HTML and PHP start empty: getFileInfo() supplies their extracted scripts with each hint + * request. Parsing host markup here can throw and strand Tern's pending reads. + * @private + * @param {string} path - full path of the file. + * @param {string} text - unfiltered file contents. + * @return {string} JavaScript source, or an empty placeholder for HTML/PHP. + */ + function filterTernFileText(path, text) { + const languageId = LanguageManager.getLanguageForPath(path).getId(); + if (languageId === "html" || languageId === "php") { + return ""; + } + return filterText(text); + } + + /** + * Get a document's source for a background Tern update, excluding host markup + * and applying the configured size limit. * @private * @param {Document} document - the document to get the text from - * @return {string} the text, or the empty text if the original was too long + * @return {string} JavaScript source, or empty text for HTML/PHP or an oversized file. */ function getTextFromDocument(document) { - var text = document.getText(); - text = filterText(text); - return text; + return filterTernFileText(document.file.fullPath, document.getText()); } /** @@ -594,14 +621,14 @@ define(function (require, exports, module) { * @return {{type: string, name: string, offsetLines: number, text: string}} */ function getFileInfo(session, preventPartialUpdates) { + const languageId = LanguageManager.getLanguageForPath(session.editor.document.file.fullPath).getId(); var start = session.getCursor(), end = start, document = session.editor.document, path = document.file.fullPath, - isHtmlFile = LanguageManager.getLanguageForPath(path).getId() === "html", result; - if (isHtmlFile) { + if (languageId === "html" || languageId === "php") { result = {type: MessageIds.TERN_FILE_INFO_TYPE_FULL, name: path, text: session.getJavascriptText()}; @@ -934,7 +961,7 @@ define(function (require, exports, module) { promise.done(function (docText) { resolvedFiles[name] = filePath; numResolvedFiles++; - replyWith(name, filterText(docText)); + replyWith(name, filterTernFileText(filePath, docText)); }); return promise; } @@ -1063,8 +1090,8 @@ define(function (require, exports, module) { * to tern. * @private * @param {string} dir - the root directory to add. - * @param {function ()} doneCallback - called when all files have been - * added to tern. + * @param {function ()} doneCallback - called when traversal finishes, including + * when the directory can no longer be resolved. */ function addAllFilesAndSubdirectories(dir, doneCallback) { FileSystem.resolve(dir, function (err, directory) { @@ -1081,6 +1108,9 @@ define(function (require, exports, module) { } if (err) { + // The directory can disappear before background indexing starts. + // Release initialization so later editors do not wait on it indefinitely. + doneCallback(); return; } @@ -1228,7 +1258,9 @@ define(function (require, exports, module) { return; } - if (previousDocument && previousDocument.isDirty) { + // A failed initial directory lookup leaves this module without a server. + // There is no cached previous file to update until initialization succeeds. + if (ternPromise && previousDocument && previousDocument.isDirty) { updateTernFile(previousDocument); } @@ -1299,6 +1331,10 @@ define(function (require, exports, module) { }); }); }); + }).fail(function () { + // A project switch cancelled these preferences. Release this + // initialization so the next editor change can initialize Tern. + addFilesDeferred.resolveWith(null); }); } @@ -1310,12 +1346,14 @@ define(function (require, exports, module) { * @param {?Document} previousDocument - the document of the editor is changing from */ function handleEditorChange(session, document, previousDocument) { - if (addFilesPromise === null) { - doEditorChange(session, document, previousDocument); - } else { + if (addFilesPromise && addFilesPromise.state() === "pending") { addFilesPromise.done(function () { - doEditorChange(session, document, previousDocument); + // An earlier queued editor may have started another initialization. + // Recheck the current promise so only one worker-ready handler owns it. + handleEditorChange(session, document, previousDocument); }); + } else { + doEditorChange(session, document, previousDocument); } } diff --git a/src/JSUtils/Session.js b/src/JSUtils/Session.js index 3c91377299..2dc5d9e987 100644 --- a/src/JSUtils/Session.js +++ b/src/JSUtils/Session.js @@ -742,15 +742,16 @@ define(function (require, exports, module) { /** * Get the javascript text of the file open in the editor for this Session. - * For a javascript file, this is just the text of the file. For an HTML file, + * For a javascript file, this is just the text of the file. For an HTML/PHP file, * this will be only the text in the script tags. This is so that we can pass * just the javascript text to tern, and avoid confusing it with HTML tags, since it * only knows how to parse javascript. * @return {String} - the "javascript" text that can be sent to Tern. */ Session.prototype.getJavascriptText = function () { - if (LanguageManager.getLanguageForPath(this.editor.document.file.fullPath).getId() === "html") { - // HTML file - need to send back only the bodies of the + const languageId = LanguageManager.getLanguageForPath(this.editor.document.file.fullPath).getId(); + if (languageId === "html" || languageId === "php") { + // HTML/PHP file - need to send back only the bodies of the // script tags var text = "", editor = this.editor, diff --git a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js index 0538382595..a17b5b3e75 100644 --- a/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js +++ b/src/LiveDevelopment/MultiBrowserImpl/documents/LiveDocument.js @@ -346,7 +346,7 @@ define(function (require, exports, module) { /** * Highlight all nodes affected by a CSS rule. Should be called by subclass implementations of - * `updateHighlight()`. + * `updateHighlight()`. Failed sends can be retried on subsequent cursor activity. * @param {string} name The selector whose matched nodes should be highlighted. */ LiveDocument.prototype.highlightRule = function (name) { @@ -357,7 +357,14 @@ define(function (require, exports, module) { return; } this._lastHighlight = highlight; - this.protocol.evaluate("_LD.highlightRule(" + JSON.stringify(name) + ", " + keepSelection + ")"); + this.protocol.evaluate("_LD.highlightRule(" + JSON.stringify(name) + ", " + keepSelection + ")") + .fail(() => { + // Attaching an editor can highlight before any preview client connects. + // Do not cache a failed send, or clear a newer selector's pending highlight. + if (this._lastHighlight === highlight) { + this._lastHighlight = null; + } + }); }; /** diff --git a/src/editor/MediaViewer.js b/src/editor/MediaViewer.js index 82bdbc8ed1..a252fc1c05 100644 --- a/src/editor/MediaViewer.js +++ b/src/editor/MediaViewer.js @@ -83,7 +83,14 @@ define(function (require, exports, module) { */ function _mediaStreamURL(file) { const platformPath = Phoenix.fs.getTauriPlatformPath(file.fullPath); - return window.PhNodeEngine.mediaURL + + // The file name rides in the path, before the query that actually names the + // file, purely so the URL ends in the right extension. WebKit picks its + // media engine for WebM off the URL extension rather than the Content-Type, + // so without this a .webm plays as "format not supported" on the Mac + // desktop app while the very same bytes play from a blob. mp4 is content + // sniffed and so never showed the problem. Node ignores this segment - it + // matches the route by prefix and reads platformPath from the query. + return window.PhNodeEngine.mediaURL + "/" + encodeURIComponent(file.name) + "?platformPath=" + encodeURIComponent(platformPath); } diff --git a/src/extensions/default/DocCommentHints/integration-tests.js b/src/extensions/default/DocCommentHints/integration-tests.js index f7c836473a..978a020b4b 100644 --- a/src/extensions/default/DocCommentHints/integration-tests.js +++ b/src/extensions/default/DocCommentHints/integration-tests.js @@ -81,7 +81,12 @@ define(function (require, exports, module) { await awaitsForDone(SpecRunnerUtils.openProjectFiles([tc.file]), "open " + tc.file); const editor = EditorManager.getActiveEditor(); editor.setCursorPos(tc.line, tc.ch); - CommandManager.execute(Commands.SHOW_CODE_HINTS); + // Hand the editor to the command. Left to itself it asks for the + // focused editor, and an editor only counts as focused once the + // browser has dispatched focus to it - which it does not do while the + // test window is behind another app. Then no session starts, no popup + // opens, and every case here times out together. + CommandManager.execute(Commands.SHOW_CODE_HINTS, editor); // 1) the code-hints popup appears with our hint await awaitsFor(function () { return $docHint().length > 0; }, diff --git a/src/extensions/default/HTMLCodeHints/integ-tests.js b/src/extensions/default/HTMLCodeHints/integ-tests.js index 3c038c0144..b5f4caa5e2 100644 --- a/src/extensions/default/HTMLCodeHints/integ-tests.js +++ b/src/extensions/default/HTMLCodeHints/integ-tests.js @@ -88,15 +88,23 @@ define(function (require, exports, module) { const selected = ProjectManager.getSelectedItem(); expect(selected.fullPath).toBe(testPath + "/jumpToDef.html"); - let editor = EditorManager.getActiveEditor(); - editor.setCursorPos({ line: 5, ch: 6 }); + const hostEditor = EditorManager.getActiveEditor(); + hostEditor.setCursorPos({ line: 5, ch: 6 }); await awaitsForDone(CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION), "jump to def on div"); - editor = EditorManager.getFocusedInlineEditor(); - expect(editor.document.file.fullPath.endsWith("LiveDevelopment-MultiBrowser-test-files/simpleShared.css")) - .toBeTrue(); + // Ask the host for its inline editors rather than for the focused one: + // an inline editor only holds focus while the window does, so the old + // check came back null whenever the test window was not the OS front + // window, without the inline editor being any less open. + let inlineEditor; + await awaitsFor(function () { + inlineEditor = EditorManager.getInlineEditors(hostEditor)[0]; + return !!inlineEditor; + }, "inline editor to open on the definition"); + expect(inlineEditor.document.file.fullPath + .endsWith("LiveDevelopment-MultiBrowser-test-files/simpleShared.css")).toBeTrue(); await closeSession(); }); @@ -105,14 +113,22 @@ define(function (require, exports, module) { const selected = ProjectManager.getSelectedItem(); expect(selected.fullPath).toBe(testPath + "/jumpToDef.html"); - let editor = EditorManager.getActiveEditor(); - editor.setCursorPos({ line: 6, ch: 23 }); + const hostEditor = EditorManager.getActiveEditor(); + hostEditor.setCursorPos({ line: 6, ch: 23 }); await awaitsForDone(CommandManager.execute(Commands.NAVIGATE_JUMPTO_DEFINITION), "jump to def on div"); - editor = EditorManager.getFocusedInlineEditor(); - expect(editor.document.file.fullPath.endsWith("LiveDevelopment-MultiBrowser-test-files/sub/test.css")) + // Ask the host for its inline editors rather than for the focused one: + // an inline editor only holds focus while the window does, so the old + // check came back null whenever the test window was not the OS front + // window, without the inline editor being any less open. + let inlineEditor; + await awaitsFor(function () { + inlineEditor = EditorManager.getInlineEditors(hostEditor)[0]; + return !!inlineEditor; + }, "inline editor to open on the definition"); + expect(inlineEditor.document.file.fullPath.endsWith("LiveDevelopment-MultiBrowser-test-files/sub/test.css")) .toBeTrue(); await closeSession(); }); @@ -153,14 +169,13 @@ define(function (require, exports, module) { await closeSession(); }); - async function _deleteFile(relativeFileName) { - let deleted = false; - FileSystem.getFileForPath(`${testPath}/${relativeFileName}`).unlink(()=>{ - deleted = true; - }); - await awaitsFor(function () { - return deleted; - }, "extension interface registration notification"); + /** + * Remove a fixture, tolerating absence and reporting other filesystem errors. + * @param {string} relativeFileName Fixture name within the test project. + * @return {Promise} + */ + function _deleteFile(relativeFileName) { + return SpecRunnerUtils.deletePathAsync(`${testPath}/${relativeFileName}`, true, FileSystem); } async function createAndVerifyFileContents(fileName, firstLineOfContent) { @@ -202,9 +217,11 @@ define(function (require, exports, module) { let editor = EditorManager.getActiveEditor(); editor.setCursorPos(cursor); - await awaitsForDone(CommandManager.execute(Commands.SHOW_CODE_HINTS), - "show code hints"); - + // The index has to hold the classes before hints are asked for, not after. + // Showing hints is a one-shot request: made while the index is still + // catching up, the provider has no class hints to offer, no menu opens, + // and nothing asks again once the index is ready - so this passed only + // when the index happened to be warm, and timed out under a full run. await awaitsFor(async function () { for(let hint of expectedSomeHintsArray){ const allSelectors = await CSSUtils.getAllCssSelectorsInProject(); @@ -215,6 +232,11 @@ define(function (require, exports, module) { return true; }, "CSSUtils project selectors to be updated"); + // With the editor passed in, the session does not depend on it holding + // focus, which it does not while the test window is in the background. + await awaitsForDone(CommandManager.execute(Commands.SHOW_CODE_HINTS, editor), + "show code hints"); + await awaitsFor(function () { return $(".codehint-menu").is(":visible"); }, "codehints to be shown"); diff --git a/src/extensions/default/QuickView/unittests.js b/src/extensions/default/QuickView/unittests.js index 440b07d94a..22b31b61b4 100644 --- a/src/extensions/default/QuickView/unittests.js +++ b/src/extensions/default/QuickView/unittests.js @@ -197,7 +197,17 @@ define(function (require, exports, module) { let quickViewSwatch = popoverInfo.content.find("#quick-view-color-swatch"); expect(quickViewSwatch.attr("data-for-test")).toBe(color); quickViewSwatch.click(); - expect(EditorManager.getFocusedInlineWidget()._color).toBe(color); + // Find the color editor among the host's inline widgets rather than as + // the focused one: an inline widget only holds focus while the window + // does, so the old check came back null when the test window was in + // the background, with the color editor open all the same. + let colorWidget; + await awaitsFor(function () { + colorWidget = EditorManager.getActiveEditor().getInlineWidgets() + .find(function (widget) { return widget._color; }); + return !!colorWidget; + }, "inline color editor to open"); + expect(colorWidget._color).toBe(color); }); describe("JavaScript file", function () { diff --git a/src/extensions/default/TypeScriptSupport/unittests.js b/src/extensions/default/TypeScriptSupport/unittests.js index bc8aa1c349..f87b64dba7 100644 --- a/src/extensions/default/TypeScriptSupport/unittests.js +++ b/src/extensions/default/TypeScriptSupport/unittests.js @@ -430,22 +430,28 @@ define(function (require, exports, module) { const jsCodeHints = await new Promise(function (resolve, reject) { ExtensionLoader.getRequireContextForExtension("JavaScriptCodeHints")(["main"], resolve, reject); }); - let hintText = ""; - await awaitsFor(function () { + let hintText = "", hintState = "not requested"; + await awaitsFor(async function () { if (!jsCodeHints.jsHintProvider.hasHints(editor, null)) { + hintState = "provider declined the editor context"; return false; // Tern session/worker may still be starting up } - const response = jsCodeHints.jsHintProvider.getHints(null); - if (!response || typeof response.done !== "function") { - return hintText.indexOf("push") !== -1; // sync response already captured below + // The provider returns a Deferred for fresh hints and an object for + // cached hints. Await either result before checking this request. + let result; + try { + hintState = "waiting for the Tern response"; + result = await jsCodeHints.jsHintProvider.getHints(null); + } catch (err) { + hintState = "request rejected: " + String(err); + return false; // A project/session change can cancel a pending request. } - response.done(function (result) { - hintText = ((result && result.hints) || []).map(function (h) { - return $(h).text(); - }).join("|"); - }); + hintText = ((result && result.hints) || []).map(function (h) { + return $(h).text(); + }).join("|"); + hintState = "returned hints: " + hintText; return hintText.indexOf("push") !== -1; - }, "Tern Array-member completions at arr. inside the