diff --git a/.vscode/launch.json b/.vscode/launch.json index 40155a4e9..a12522625 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -45,7 +45,7 @@ "cwd": "${workspaceFolder}/../vscode-node-debug2", "program": "${workspaceFolder}/../vscode-node-debug2/out/src/nodeDebug.js", "args": [ "--server=4712" ], - "outFiles": ["${workspaceFolder}/out/**/*.js"], + "outFiles": ["${workspaceFolder}/out/**/*.js", "${workspaceFolder}/../vscode-node-debug2/out/**/*.js"], "internalConsoleOptions": "openOnSessionStart", "smartStep": true, "skipFiles": [ @@ -60,9 +60,41 @@ // These paths are only valid for my particular setup! You need to replace them with your own. "cwd": "${workspaceFolder}/../vscode-chrome-debug", - "program": "${workspaceFolder}/../vscode-chrome-debug/out/src/chromeDebug.js", + "program": "${workspaceFolder}/../vscode-chrome-debug/out/chromeDebug.js", "args": [ "--server=4712" ], - "outFiles": ["${workspaceFolder}/out/**/*.js"], + "outFiles": ["${workspaceFolder}/out/**/*.js", "${workspaceFolder}/../vscode-chrome-debug/out/**/*.js"], + "internalConsoleOptions": "openOnSessionStart", + "smartStep": true, + "env": { + "BREAK_WHILE_DEBUGGING": "true" + } + }, + { + "name": "launch chrome-debug (No sourcemaps)", + "type": "node", + "request": "launch", + "protocol": "inspector", + + // These paths are only valid for my particular setup! You need to replace them with your own. + "cwd": "${workspaceFolder}/../vscode-chrome-debug", + "program": "${workspaceFolder}/../vscode-chrome-debug/out/chromeDebug.js", + "args": [ "--server=4712" ], + "outFiles": ["${workspaceFolder}/out/**/*.js", "${workspaceFolder}/../vscode-chrome-debug/out/**/*.js"], + "internalConsoleOptions": "openOnSessionStart", + "smartStep": true, + "sourceMaps": false + }, + { + "name": "launch test-debug", + "type": "node", + "request": "launch", + "protocol": "inspector", + + // These paths are only valid for my particular setup! You need to replace them with your own. + "cwd": "${workspaceFolder}", + "program": "${workspaceFolder}/out/test/testDebug/testDebug.js", + "args": [ "--server=4712" ], + "outFiles": ["${workspaceFolder}/out/**/*.js", "${workspaceFolder}/../vscode-chrome-debug/out/**/*.js"], "internalConsoleOptions": "openOnSessionStart", "smartStep": true }, diff --git a/.vscode/settings.json b/.vscode/settings.json index 31d032942..f4d0f339a 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -4,8 +4,13 @@ "files.exclude": { ".git": true, "bin": true, - "node_modules": false, - "ThirdPartyNotices.txt": true + "node_modules": true, + "out": true, + "lib": true, + "i18n": true, + ".github": true, + ".vsts": true, + "ThirdPartyNotices.txt": true, }, "search.exclude": { ".git": true, diff --git a/build.cmd b/build.cmd new file mode 100644 index 000000000..17de6500a --- /dev/null +++ b/build.cmd @@ -0,0 +1,2 @@ +@gulp _dev-build +@color diff --git a/filesWithIssues.cmd b/filesWithIssues.cmd new file mode 100644 index 000000000..632bc5100 --- /dev/null +++ b/filesWithIssues.cmd @@ -0,0 +1,2 @@ +@echo off +cls && npm run build | sed -e 's/ .*//g' | grep ^src | sed -e 's/(.*//g' | uniq | sed -e 's/src/code -g src/g' diff --git a/package-lock.json b/package-lock.json index 5dd85f4de..6882c2d72 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-chrome-debug-core", - "version": "6.7.35", + "version": "10.0.0", "lockfileVersion": 1, "requires": true, "dependencies": { @@ -10,8 +10,8 @@ "integrity": "sha1-iQrnxdjId/bThIYCFazp1+yUW9o=", "dev": true, "requires": { - "normalize-path": "^2.0.1", - "through2": "^2.0.3" + "normalize-path": "2.1.1", + "through2": "2.0.3" }, "dependencies": { "core-util-is": { @@ -38,7 +38,7 @@ "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { - "remove-trailing-separator": "^1.0.1" + "remove-trailing-separator": "1.1.0" } }, "process-nextick-args": { @@ -53,13 +53,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "remove-trailing-separator": { @@ -80,7 +80,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "through2": { @@ -89,8 +89,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.5", + "xtend": "4.0.1" } }, "util-deprecate": { @@ -113,7 +113,7 @@ "integrity": "sha512-5qqtNia+m2I0/85+pd2YzAXaTyKO8j+svirO5aN+XaQJ5+eZ8nx0jPtEWZLxCi50xwYsX10xUHetFzfb1WEs4Q==", "dev": true, "requires": { - "@types/color-convert": "*" + "@types/color-convert": "1.9.0" } }, "@types/color-convert": { @@ -122,7 +122,7 @@ "integrity": "sha512-OKGEfULrvSL2VRbkl/gnjjgbbF7ycIlpSsX7Nkab4MOWi5XxmgBYvuiQ7lcCFY5cPDz7MUNaKgxte2VRmtr4Fg==", "dev": true, "requires": { - "@types/color-name": "*" + "@types/color-name": "1.1.0" } }, "@types/color-name": { @@ -149,11 +149,26 @@ "integrity": "sha512-wc+VveszMLyMWFvXLkloixT4n0harUIVZjnpzztaZ0nKLuul7Z32iMt2fUFGAaZ4y1XWjFRMtCI5ewvyh4aIeg==", "dev": true, "requires": { - "@types/events": "*", - "@types/minimatch": "*", - "@types/node": "*" + "@types/events": "1.2.0", + "@types/minimatch": "2.0.29", + "@types/node": "8.0.58" } }, + "@types/inversify-inject-decorators": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@types/inversify-inject-decorators/-/inversify-inject-decorators-2.0.0.tgz", + "integrity": "sha1-mlC5tHOluL1RpkV1QNEt293N8l4=", + "dev": true, + "requires": { + "inversify-inject-decorators": "3.1.0" + } + }, + "@types/lodash": { + "version": "4.14.116", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.116.tgz", + "integrity": "sha512-lRnAtKnxMXcYYXqOiotTmJd74uawNWuPnsnPrrO7HiFuE3npE2iQhfABatbYDyxTNqZNuXzcKGhw37R7RjBFLg==", + "dev": true + }, "@types/minimatch": { "version": "2.0.29", "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-2.0.29.tgz", @@ -178,6 +193,12 @@ "integrity": "sha512-V746iUU7eHNdzQipoACuguDlVhC7IHK8CES1jSkuFt352wwA84BCWPXaGekBd7R5XdNK5ReHONDVKxlL9IreAw==", "dev": true }, + "@types/semver": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-5.5.0.tgz", + "integrity": "sha512-41qEJgBH/TWgo5NFSvBCJ1qkoi3Q6ONSF2avrHq1LVEZfYpdHmj0y9SuTK+u9ZhG1sYQKBL1AWXKyLWP4RaUoQ==", + "dev": true + }, "@types/source-map": { "version": "0.1.29", "resolved": "https://registry.npmjs.org/@types/source-map/-/source-map-0.1.29.tgz", @@ -189,8 +210,8 @@ "integrity": "sha512-i/dVaSjmTM92EFFFhmGL6AmHzvJ70XpAXmMLvNKh3JrRTGOiXvejfxe5+OSxcJK0paGOYHDaRLS8nXW6/FxSxg==", "dev": true, "requires": { - "@types/events": "*", - "@types/node": "*" + "@types/events": "1.2.0", + "@types/node": "8.0.58" } }, "acorn": { @@ -211,7 +232,7 @@ "integrity": "sha512-SFKX67auSNoVR38N3L+nvsPjOE0bybKTYbkf5tRvushrAPQ9V75huw0ZxBkKVeRU9kqH3d6HA4xTckbwZ4ixmA==", "dev": true, "requires": { - "ansi-wrap": "^0.1.0" + "ansi-wrap": "0.1.0" } }, "ansi-cyan": { @@ -265,7 +286,7 @@ "integrity": "sha1-2CIM9GYIFSXv6lBhTz3mUU36WPE=", "dev": true, "requires": { - "buffer-equal": "^1.0.0" + "buffer-equal": "1.0.0" } }, "archy": { @@ -280,7 +301,7 @@ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, "requires": { - "sprintf-js": "~1.0.2" + "sprintf-js": "1.0.3" } }, "arr-diff": { @@ -325,7 +346,7 @@ "integrity": "sha1-mjRBDk9OPaI96jdb5b5w8kd47Dk=", "dev": true, "requires": { - "array-uniq": "^1.0.1" + "array-uniq": "1.0.3" } }, "array-uniq": { @@ -369,9 +390,9 @@ "integrity": "sha1-Y/1D99weO7fONZR9uP42mj9Yx0s=", "dev": true, "requires": { - "chalk": "^1.1.3", - "esutils": "^2.0.2", - "js-tokens": "^3.0.2" + "chalk": "1.1.3", + "esutils": "2.0.2", + "js-tokens": "3.0.2" }, "dependencies": { "esutils": { @@ -393,13 +414,13 @@ "integrity": "sha512-5T6P4xPgpp0YDFvSWwEZ4NoE3aM4QBQXDzmVbraCkFj8zHM+mba8SyqB5DbZWyR7mYHo6Y7BdQo3MoA4m0TeQg==", "dev": true, "requires": { - "cache-base": "^1.0.1", - "class-utils": "^0.3.5", - "component-emitter": "^1.2.1", - "define-property": "^1.0.0", - "isobject": "^3.0.1", - "mixin-deep": "^1.2.0", - "pascalcase": "^0.1.1" + "cache-base": "1.0.1", + "class-utils": "0.3.6", + "component-emitter": "1.2.1", + "define-property": "1.0.0", + "isobject": "3.0.1", + "mixin-deep": "1.3.1", + "pascalcase": "0.1.1" }, "dependencies": { "define-property": { @@ -408,7 +429,36 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -420,55 +470,39 @@ "dev": true }, "brace-expansion": { - "version": "1.1.8", - "integrity": "sha1-wHshHHyVLsH479Uad+8NHTmQopI=", + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, "braces": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.1.tgz", - "integrity": "sha512-SO5lYHA3vO6gz66erVvedSCkp7AKWdv6VcQ2N4ysXfPxdAlxAMMAdwegGGcv1Bqwm7naF1hNdk5d6AAIEHV2nQ==", - "dev": true, - "requires": { - "arr-flatten": "^1.1.0", - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "extend-shallow": "^2.0.1", - "fill-range": "^4.0.0", - "isobject": "^3.0.1", - "kind-of": "^6.0.2", - "repeat-element": "^1.1.2", - "snapdragon": "^0.8.1", - "snapdragon-node": "^2.0.1", - "split-string": "^3.0.2", - "to-regex": "^3.0.1" + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-2.3.2.tgz", + "integrity": "sha512-aNdbnj9P8PjdXU4ybaWLK2IF3jc/EoDYbC7AazW6to3TRsfXxscC9UXOB5iDiEQrkyIbWp2SLQda4+QAa7nc3w==", + "dev": true, + "requires": { + "arr-flatten": "1.1.0", + "array-unique": "0.3.2", + "extend-shallow": "2.0.1", + "fill-range": "4.0.0", + "isobject": "3.0.1", + "repeat-element": "1.1.3", + "snapdragon": "0.8.2", + "snapdragon-node": "2.1.1", + "split-string": "3.1.0", + "to-regex": "3.0.2" }, "dependencies": { - "arr-flatten": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/arr-flatten/-/arr-flatten-1.1.0.tgz", - "integrity": "sha512-L3hKV5R/p5o81R7O02IGnwpDmkp6E982XhtbuwSe3O4qOtMMMtodicASA1Cny2U+aCXcNpml+m4dPsvsJ3jatg==", - "dev": true - }, - "define-property": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/define-property/-/define-property-1.0.0.tgz", - "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", - "dev": true, - "requires": { - "is-descriptor": "^1.0.0" - } - }, "extend-shallow": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extend-shallow/-/extend-shallow-2.0.1.tgz", "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -497,15 +531,15 @@ "integrity": "sha512-AKcdTnFSWATd5/GCPRxr2ChwIJ85CeyrEyjRHlKxQ56d4XJMGym0uAiKn0xbLOGOl3+yRpOTi484dVCEc5AUzQ==", "dev": true, "requires": { - "collection-visit": "^1.0.0", - "component-emitter": "^1.2.1", - "get-value": "^2.0.6", - "has-value": "^1.0.0", - "isobject": "^3.0.1", - "set-value": "^2.0.0", - "to-object-path": "^0.3.0", - "union-value": "^1.0.0", - "unset-value": "^1.0.0" + "collection-visit": "1.0.0", + "component-emitter": "1.2.1", + "get-value": "2.0.6", + "has-value": "1.0.0", + "isobject": "3.0.1", + "set-value": "2.0.0", + "to-object-path": "0.3.0", + "union-value": "1.0.0", + "unset-value": "1.0.0" } }, "camelcase": { @@ -520,11 +554,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" }, "dependencies": { "ansi-regex": { @@ -545,7 +579,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } } } @@ -562,10 +596,10 @@ "integrity": "sha512-qOhPa/Fj7s6TY8H8esGu5QNpMMQxz79h+urzrNYN6mn+9BnxlDGf5QZ+XeCDsxSjPqsSR56XOZOJmpeurnLMeg==", "dev": true, "requires": { - "arr-union": "^3.1.0", - "define-property": "^0.2.5", - "isobject": "^3.0.0", - "static-extend": "^0.1.1" + "arr-union": "3.1.0", + "define-property": "0.2.5", + "isobject": "3.0.1", + "static-extend": "0.1.2" }, "dependencies": { "arr-union": { @@ -580,71 +614,8 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true } } }, @@ -654,9 +625,9 @@ "integrity": "sha512-4FG+RSG9DL7uEwRUZXZn3SS34DiDPfzP0VOiEwtUWlE+AR2EIg+hSyvrIgUUfhdgR/UkAeW2QHgeP+hWrXs7jQ==", "dev": true, "requires": { - "string-width": "^2.1.1", - "strip-ansi": "^4.0.0", - "wrap-ansi": "^2.0.0" + "string-width": "2.1.1", + "strip-ansi": "4.0.0", + "wrap-ansi": "2.1.0" } }, "clone": { @@ -683,9 +654,9 @@ "integrity": "sha512-DNNEq6JdqBFPzS29TaoqZFPNLn5Xn3XyPFqLIhyBT8Xou4lHQEWzD6FinXoJUfhIfWX3aE1JkRa3cbWCHFbt1g==", "dev": true, "requires": { - "inherits": "^2.0.1", - "process-nextick-args": "^2.0.0", - "readable-stream": "^2.3.5" + "inherits": "2.0.3", + "process-nextick-args": "2.0.0", + "readable-stream": "2.3.5" }, "dependencies": { "isarray": { @@ -706,13 +677,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -721,7 +692,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -738,17 +709,17 @@ "integrity": "sha1-S8A3PBZLwykbTTaMgpzxqApZ3KA=", "dev": true, "requires": { - "map-visit": "^1.0.0", - "object-visit": "^1.0.0" + "map-visit": "1.0.0", + "object-visit": "1.0.1" } }, "color": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/color/-/color-3.0.0.tgz", - "integrity": "sha512-jCpd5+s0s0t7p3pHQKpnJ0TpQKKdleP71LWcA0aqiljpiuAkOSUFN/dyH8ZwF0hRmFlrIuRhufds1QyEP9EB+w==", + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/color/-/color-3.1.0.tgz", + "integrity": "sha512-CwyopLkuRYO5ei2EpzpIh6LqJMt6Mt+jZhO5VI5f/wJLZriXQE32/SSqzmrh+QB+AZT81Cj8yv+7zwToW8ahZg==", "requires": { - "color-convert": "^1.9.1", - "color-string": "^1.5.2" + "color-convert": "1.9.1", + "color-string": "1.5.3" } }, "color-convert": { @@ -756,7 +727,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.1.tgz", "integrity": "sha512-mjGanIiwQJskCC18rPR6OmrZ6fm2Lc7PeGFYwCmy5J34wC6F1PzdGL6xeMfmgicfYcNLGuVFA3WzXtIDCQSZxQ==", "requires": { - "color-name": "^1.1.1" + "color-name": "1.1.3" } }, "color-name": { @@ -769,8 +740,8 @@ "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.5.3.tgz", "integrity": "sha512-dC2C5qeWoYkxki5UAXapdjqO672AM4vZuPGRQfO8b5HKuKGBbKWpITyDYN7TOFKvRW7kOgAn3746clDBMDJyQw==", "requires": { - "color-name": "^1.0.0", - "simple-swizzle": "^0.2.2" + "color-name": "1.1.3", + "simple-swizzle": "0.2.2" } }, "color-support": { @@ -803,9 +774,13 @@ "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=" }, "convert-source-map": { - "version": "1.5.0", - "integrity": "sha1-ms1whRxtXf3ZPZKC5e35SgP/RrU=", - "dev": true + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", + "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "dev": true, + "requires": { + "safe-buffer": "5.1.1" + } }, "copy-descriptor": { "version": "0.1.1", @@ -825,9 +800,9 @@ "integrity": "sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=", "dev": true, "requires": { - "lru-cache": "^4.0.1", - "shebang-command": "^1.2.0", - "which": "^1.2.9" + "lru-cache": "4.1.3", + "shebang-command": "1.2.0", + "which": "1.3.1" }, "dependencies": { "lru-cache": { @@ -836,8 +811,8 @@ "integrity": "sha512-fFEhvcgzuIoJVUF8fYr5KR0YqxD238zgObTps31YdADwPPAp82a4M8TrckkWyx7ekNlf9aBcVn81cFwwXngrJA==", "dev": true, "requires": { - "pseudomap": "^1.0.2", - "yallist": "^2.1.2" + "pseudomap": "1.0.2", + "yallist": "2.1.2" } } } @@ -848,10 +823,10 @@ "integrity": "sha512-0W171WccAjQGGTKLhw4m2nnl0zPHUlTO/I8td4XzJgIB8Hg3ZZx71qT4G4eX8OVsSiaAKiUMy73E3nsbPlg2DQ==", "dev": true, "requires": { - "inherits": "^2.0.1", - "source-map": "^0.1.38", - "source-map-resolve": "^0.5.1", - "urix": "^0.1.0" + "inherits": "2.0.3", + "source-map": "0.1.43", + "source-map-resolve": "0.5.1", + "urix": "0.1.0" }, "dependencies": { "source-map": { @@ -860,7 +835,7 @@ "integrity": "sha1-wkvBRspRfBRx9drL4lcbK3+eM0Y=", "dev": true, "requires": { - "amdefine": ">=0.0.4" + "amdefine": "1.0.1" } } } @@ -886,8 +861,8 @@ "integrity": "sha1-+gccXYdIRoVCSAdCHKSxawsaB2M=", "dev": true, "requires": { - "debug": "2.X", - "lazy-debug-legacy": "0.0.X", + "debug": "2.6.9", + "lazy-debug-legacy": "0.0.1", "object-assign": "4.1.0" }, "dependencies": { @@ -917,7 +892,7 @@ "integrity": "sha1-xlYFHpgX2f8I7YgUd/P+QBnz730=", "dev": true, "requires": { - "clone": "^1.0.2" + "clone": "1.0.4" } }, "define-properties": { @@ -926,8 +901,8 @@ "integrity": "sha1-g6c/L+pWmJj7c3GTyPhzyvbUXJQ=", "dev": true, "requires": { - "foreach": "^2.0.5", - "object-keys": "^1.0.8" + "foreach": "2.0.5", + "object-keys": "1.0.11" } }, "define-property": { @@ -936,8 +911,39 @@ "integrity": "sha512-jwK2UV4cnPpbcG7+VRARKTZPUWowwXA8bzH5NP6ud0oeAxyYPuGZUAC7hMugpCdz4BeSZl2Dl9k66CHJ/46ZYQ==", "dev": true, "requires": { - "is-descriptor": "^1.0.2", - "isobject": "^3.0.1" + "is-descriptor": "1.0.2", + "isobject": "3.0.1" + }, + "dependencies": { + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" + } + } } }, "del": { @@ -946,13 +952,13 @@ "integrity": "sha1-wSyYHQZ4RshLyvhiz/kw2Qf/0ag=", "dev": true, "requires": { - "globby": "^5.0.0", - "is-path-cwd": "^1.0.0", - "is-path-in-cwd": "^1.0.0", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0", - "rimraf": "^2.2.8" + "globby": "5.0.0", + "is-path-cwd": "1.0.0", + "is-path-in-cwd": "1.0.1", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1", + "rimraf": "2.6.2" } }, "deprecated": { @@ -979,8 +985,9 @@ "integrity": "sha512-DDGCT3YFiamX4jRPqg4galOrQS8DsU0j+xM5iaR0YB5TM1fjCZejQ7MSe9ByIMIq4IsnULY/4Qp3TBWAUjMgXw==" }, "diff": { - "version": "3.2.0", - "integrity": "sha1-yc45Okt8vQsFinJck98pkCeGj/k=", + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-3.5.0.tgz", + "integrity": "sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA==", "dev": true }, "doctrine": { @@ -989,7 +996,7 @@ "integrity": "sha1-fLhgNZujvpDgQLJrcpzkv6ZUxSM=", "dev": true, "requires": { - "esutils": "^1.1.6", + "esutils": "1.1.6", "isarray": "0.0.1" } }, @@ -1005,7 +1012,7 @@ "integrity": "sha1-xhTc9n4vsUmVqRcR5aYX6KYKMds=", "dev": true, "requires": { - "readable-stream": "~1.1.9" + "readable-stream": "1.1.14" } }, "duplexify": { @@ -1014,10 +1021,10 @@ "integrity": "sha512-JzYSLYMhoVVBe8+mbHQ4KgpvHpm0DZpJuL8PY93Vyv1fW7jYJ90LoXa1di/CVbJM+TgMs91rbDapE/RNIfnJsA==", "dev": true, "requires": { - "end-of-stream": "^1.0.0", - "inherits": "^2.0.1", - "readable-stream": "^2.0.0", - "stream-shift": "^1.0.0" + "end-of-stream": "1.4.1", + "inherits": "2.0.3", + "readable-stream": "2.3.5", + "stream-shift": "1.0.0" }, "dependencies": { "end-of-stream": { @@ -1026,7 +1033,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } }, "isarray": { @@ -1047,13 +1054,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -1062,7 +1069,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -1073,7 +1080,7 @@ "integrity": "sha1-jhdyBsPICDfYVjLouTWd/osvbq8=", "dev": true, "requires": { - "once": "~1.3.0" + "once": "1.3.3" }, "dependencies": { "once": { @@ -1082,14 +1089,8 @@ "integrity": "sha1-suJhVXzkwxTsgwTz+oJmPkKXyiA=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true } } }, @@ -1117,13 +1118,13 @@ "integrity": "sha1-SrTJoPWlTbkzi0w02Gv86PSzVXE=", "dev": true, "requires": { - "duplexer": "~0.1.1", - "from": "~0", - "map-stream": "~0.1.0", + "duplexer": "0.1.1", + "from": "0.1.7", + "map-stream": "0.1.0", "pause-stream": "0.0.11", - "split": "0.3", - "stream-combiner": "~0.0.4", - "through": "~2.3.1" + "split": "0.3.3", + "stream-combiner": "0.0.4", + "through": "2.3.8" }, "dependencies": { "through": { @@ -1140,13 +1141,13 @@ "integrity": "sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=", "dev": true, "requires": { - "cross-spawn": "^5.0.1", - "get-stream": "^3.0.0", - "is-stream": "^1.1.0", - "npm-run-path": "^2.0.0", - "p-finally": "^1.0.0", - "signal-exit": "^3.0.0", - "strip-eof": "^1.0.0" + "cross-spawn": "5.1.0", + "get-stream": "3.0.0", + "is-stream": "1.1.0", + "npm-run-path": "2.0.2", + "p-finally": "1.0.0", + "signal-exit": "3.0.2", + "strip-eof": "1.0.0" } }, "expand-brackets": { @@ -1155,13 +1156,13 @@ "integrity": "sha1-t3c14xXOMPa27/D4OwQVGiJEliI=", "dev": true, "requires": { - "debug": "^2.3.3", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "posix-character-classes": "^0.1.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "posix-character-classes": "0.1.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -1170,7 +1171,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -1179,71 +1180,8 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-extendable": "0.1.1" } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true } } }, @@ -1253,7 +1191,7 @@ "integrity": "sha1-l+gBqgUt8CRU3kawK/YhZCzchQI=", "dev": true, "requires": { - "homedir-polyfill": "^1.0.1" + "homedir-polyfill": "1.0.1" } }, "extend": { @@ -1268,7 +1206,7 @@ "integrity": "sha1-Gda/lN/AnXa6cR85uHLSH/TdkHE=", "dev": true, "requires": { - "kind-of": "^1.1.0" + "kind-of": "1.1.0" }, "dependencies": { "kind-of": { @@ -1285,14 +1223,14 @@ "integrity": "sha512-Nmb6QXkELsuBr24CJSkilo6UHHgbekK5UiZgfE6UHD3Eb27YC6oD+bhcT+tJ6cl8dmsgdQxnWlcry8ksBIBLpw==", "dev": true, "requires": { - "array-unique": "^0.3.2", - "define-property": "^1.0.0", - "expand-brackets": "^2.1.4", - "extend-shallow": "^2.0.1", - "fragment-cache": "^0.2.1", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "array-unique": "0.3.2", + "define-property": "1.0.0", + "expand-brackets": "2.1.4", + "extend-shallow": "2.0.1", + "fragment-cache": "0.2.1", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "define-property": { @@ -1301,7 +1239,7 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" } }, "extend-shallow": { @@ -1310,7 +1248,36 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -1321,9 +1288,9 @@ "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", "dev": true, "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "time-stamp": "^1.0.0" + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" }, "dependencies": { "time-stamp": { @@ -1340,10 +1307,10 @@ "integrity": "sha1-1USBHUKPmOsGpj3EAtJAPDKMOPc=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-number": "^3.0.0", - "repeat-string": "^1.6.1", - "to-regex-range": "^2.1.0" + "extend-shallow": "2.0.1", + "is-number": "3.0.0", + "repeat-string": "1.6.1", + "to-regex-range": "2.1.1" }, "dependencies": { "extend-shallow": { @@ -1352,7 +1319,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" + "is-extendable": "0.1.1" } } } @@ -1369,7 +1336,7 @@ "integrity": "sha1-RdG35QbHF93UgndaK3eSCjwMV6c=", "dev": true, "requires": { - "locate-path": "^2.0.0" + "locate-path": "2.0.0" } }, "findup-sync": { @@ -1378,10 +1345,10 @@ "integrity": "sha1-kyaxSIwi0aYIhlCoaQGy2akKLLw=", "dev": true, "requires": { - "detect-file": "^1.0.0", - "is-glob": "^3.1.0", - "micromatch": "^3.0.4", - "resolve-dir": "^1.0.1" + "detect-file": "1.0.0", + "is-glob": "3.1.0", + "micromatch": "3.1.10", + "resolve-dir": "1.0.1" } }, "fined": { @@ -1390,22 +1357,11 @@ "integrity": "sha1-s33IRLdqL15wgeiE98CuNE8VNHY=", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "is-plain-object": "^2.0.3", - "object.defaults": "^1.1.0", - "object.pick": "^1.2.0", - "parse-filepath": "^1.0.1" - }, - "dependencies": { - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - } + "expand-tilde": "2.0.2", + "is-plain-object": "2.0.4", + "object.defaults": "1.1.0", + "object.pick": "1.3.0", + "parse-filepath": "1.0.2" } }, "first-chunk-stream": { @@ -1426,8 +1382,8 @@ "integrity": "sha1-yBuQ2HRnZvGmCaRoCZRsRd2K5Bc=", "dev": true, "requires": { - "inherits": "^2.0.1", - "readable-stream": "^2.0.4" + "inherits": "2.0.3", + "readable-stream": "2.3.5" }, "dependencies": { "isarray": { @@ -1448,13 +1404,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -1463,7 +1419,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -1480,7 +1436,7 @@ "integrity": "sha1-xjMy9BXO3EsE2/5wz4NklMU8tEs=", "dev": true, "requires": { - "for-in": "^1.0.1" + "for-in": "1.0.2" } }, "foreach": { @@ -1495,7 +1451,7 @@ "integrity": "sha1-QpD60n8T6Jvn8zeZxrxaCr//DRk=", "dev": true, "requires": { - "map-cache": "^0.2.2" + "map-cache": "0.2.2" } }, "from": { @@ -1510,8 +1466,8 @@ "integrity": "sha1-C3gV/DIBxqaeFNuYzgmMFpNSWes=", "dev": true, "requires": { - "graceful-fs": "^4.1.11", - "through2": "^2.0.3" + "graceful-fs": "4.1.11", + "through2": "2.0.3" }, "dependencies": { "graceful-fs": { @@ -1539,7 +1495,7 @@ "integrity": "sha1-QLcJU30k0dRXZ9takIaJ3+aaxE8=", "dev": true, "requires": { - "globule": "~0.1.0" + "globule": "0.1.0" } }, "get-caller-file": { @@ -1565,12 +1521,12 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.3.tgz", "integrity": "sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ==", "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "glob-stream": { @@ -1579,88 +1535,45 @@ "integrity": "sha1-kXCl8St5Awb9/lmPMT+PeVT9FDs=", "dev": true, "requires": { - "glob": "^4.3.1", - "glob2base": "^0.0.12", - "minimatch": "^2.0.1", - "ordered-read-streams": "^0.1.0", - "through2": "^0.6.1", - "unique-stream": "^1.0.0" + "glob": "4.5.3", + "glob2base": "0.0.12", + "minimatch": "2.0.10", + "ordered-read-streams": "0.1.0", + "through2": "0.6.5", + "unique-stream": "1.0.0" }, "dependencies": { - "balanced-match": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz", - "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c=", - "dev": true - }, - "brace-expansion": { - "version": "1.1.11", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", - "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", - "dev": true, - "requires": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "concat-map": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", - "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", - "dev": true - }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, "glob": { "version": "4.5.3", "resolved": "https://registry.npmjs.org/glob/-/glob-4.5.3.tgz", "integrity": "sha1-xstz0yJsHv7wTePFbQEvAzd+4V8=", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^2.0.1", - "once": "^1.3.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "2.0.10", + "once": "1.4.0" } }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, "minimatch": { "version": "2.0.10", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-2.0.10.tgz", "integrity": "sha1-jQh8OcazjAAbl/ynzm0OHoCvusc=", "dev": true, "requires": { - "brace-expansion": "^1.0.0" - } - }, - "once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", - "dev": true, - "requires": { - "wrappy": "1" + "brace-expansion": "1.1.11" } }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "through2": { @@ -1669,21 +1582,9 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "readable-stream": "1.0.34", + "xtend": "4.0.1" } - }, - "wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", - "dev": true - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true } } }, @@ -1693,7 +1594,7 @@ "integrity": "sha1-uVtKjfdLOcgymLDAXJeLTZo7cQs=", "dev": true, "requires": { - "gaze": "^0.5.1" + "gaze": "0.5.2" } }, "glob2base": { @@ -1702,7 +1603,7 @@ "integrity": "sha1-nUGbPijxLoOjYhZKJ3BVkiycDVY=", "dev": true, "requires": { - "find-index": "^0.1.1" + "find-index": "0.1.1" } }, "global-modules": { @@ -1711,9 +1612,9 @@ "integrity": "sha512-sKzpEkf11GpOFuw0Zzjzmt4B4UZwjOcG757PPvrfhxcLFbq0wpsgpOqxpxtxFiCG4DtG93M6XRVbF2oGdev7bg==", "dev": true, "requires": { - "global-prefix": "^1.0.1", - "is-windows": "^1.0.1", - "resolve-dir": "^1.0.0" + "global-prefix": "1.0.2", + "is-windows": "1.0.2", + "resolve-dir": "1.0.1" } }, "global-prefix": { @@ -1722,11 +1623,11 @@ "integrity": "sha1-2/dDxsFJklk8ZVVoy2btMsASLr4=", "dev": true, "requires": { - "expand-tilde": "^2.0.2", - "homedir-polyfill": "^1.0.1", - "ini": "^1.3.4", - "is-windows": "^1.0.1", - "which": "^1.2.14" + "expand-tilde": "2.0.2", + "homedir-polyfill": "1.0.1", + "ini": "1.3.5", + "is-windows": "1.0.2", + "which": "1.3.1" } }, "globby": { @@ -1735,12 +1636,12 @@ "integrity": "sha1-69hGZ8oNuzMLmbz8aOrCvFQ3Dg0=", "dev": true, "requires": { - "array-union": "^1.0.1", - "arrify": "^1.0.0", - "glob": "^7.0.3", - "object-assign": "^4.0.1", - "pify": "^2.0.0", - "pinkie-promise": "^2.0.0" + "array-union": "1.0.2", + "arrify": "1.0.1", + "glob": "7.1.3", + "object-assign": "4.1.1", + "pify": "2.3.0", + "pinkie-promise": "2.0.1" } }, "globule": { @@ -1749,9 +1650,9 @@ "integrity": "sha1-2cjt3h2nnRJaFRt5UzuXhnY0auU=", "dev": true, "requires": { - "glob": "~3.1.21", - "lodash": "~1.0.1", - "minimatch": "~0.2.11" + "glob": "3.1.21", + "lodash": "1.0.2", + "minimatch": "0.2.14" }, "dependencies": { "glob": { @@ -1760,9 +1661,9 @@ "integrity": "sha1-0p4KBV3qUTj00H7UDomC6DwgZs0=", "dev": true, "requires": { - "graceful-fs": "~1.2.0", - "inherits": "1", - "minimatch": "~0.2.11" + "graceful-fs": "1.2.3", + "inherits": "1.0.2", + "minimatch": "0.2.14" } }, "graceful-fs": { @@ -1777,14 +1678,20 @@ "integrity": "sha1-ykMJ2t7mtUzAuNJH6NfHoJdb3Js=", "dev": true }, + "lodash": { + "version": "1.0.2", + "resolved": "http://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", + "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", + "dev": true + }, "minimatch": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-0.2.14.tgz", "integrity": "sha1-x054BXT2PG+aCQ6Q775u9TpqdWo=", "dev": true, "requires": { - "lru-cache": "2", - "sigmund": "~1.0.0" + "lru-cache": "2.7.3", + "sigmund": "1.0.1" } } } @@ -1795,7 +1702,7 @@ "integrity": "sha512-ynYqXLoluBKf9XGR1gA59yEJisIL7YHEH4xr3ZziHB5/yl4qWfaK8Js9jGe6gBGCSCKVqiyO30WnRZADvemUNw==", "dev": true, "requires": { - "sparkles": "^1.0.0" + "sparkles": "1.0.0" } }, "graceful-fs": { @@ -1804,7 +1711,7 @@ "integrity": "sha1-dhPHeKGv6mLyXGMKCG1/Osu92Bg=", "dev": true, "requires": { - "natives": "^1.1.0" + "natives": "1.1.5" } }, "growl": { @@ -1819,74 +1726,25 @@ "integrity": "sha1-VxzkWSjdQK9lFPxAEYZgFsE4RbQ=", "dev": true, "requires": { - "archy": "^1.0.0", - "chalk": "^1.0.0", - "deprecated": "^0.0.1", - "gulp-util": "^3.0.0", - "interpret": "^1.0.0", - "liftoff": "^2.1.0", - "minimist": "^1.1.0", - "orchestrator": "^0.3.0", - "pretty-hrtime": "^1.0.0", - "semver": "^4.1.0", - "tildify": "^1.0.0", - "v8flags": "^2.0.2", - "vinyl-fs": "^0.3.0" + "archy": "1.0.0", + "chalk": "1.1.3", + "deprecated": "0.0.1", + "gulp-util": "3.0.8", + "interpret": "1.1.0", + "liftoff": "2.5.0", + "minimist": "1.2.0", + "orchestrator": "0.3.8", + "pretty-hrtime": "1.0.3", + "semver": "4.3.6", + "tildify": "1.2.0", + "v8flags": "2.1.1", + "vinyl-fs": "0.3.14" }, "dependencies": { - "ansi-regex": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", - "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", - "dev": true - }, - "ansi-styles": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", - "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", - "dev": true - }, - "chalk": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", - "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", - "dev": true, - "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" - } - }, - "escape-string-regexp": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", - "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true - }, - "has-ansi": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", - "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "strip-ansi": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", - "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", - "dev": true, - "requires": { - "ansi-regex": "^2.0.0" - } - }, - "supports-color": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", - "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "semver": { + "version": "4.3.6", + "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", + "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", "dev": true } } @@ -1897,13 +1755,13 @@ "integrity": "sha1-L1/l9kvNH0zxicFg4IDIrQZUMJQ=", "dev": true, "requires": { - "chalk": "^1.0.0", - "gulp-util": "^3.0.0", - "object-assign": "^4.0.1", - "plur": "^2.0.0", - "stringify-object": "^2.3.0", - "through2": "^2.0.0", - "tildify": "^1.1.2" + "chalk": "1.1.3", + "gulp-util": "3.0.8", + "object-assign": "4.1.1", + "plur": "2.1.2", + "stringify-object": "2.4.0", + "through2": "2.0.3", + "tildify": "1.2.0" }, "dependencies": { "ansi-regex": { @@ -1924,11 +1782,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "core-util-is": { @@ -1949,7 +1807,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "inherits": { @@ -1976,13 +1834,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "safe-buffer": { @@ -1997,7 +1855,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -2006,7 +1864,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -2021,8 +1879,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.5", + "xtend": "4.0.1" } }, "util-deprecate": { @@ -2045,10 +1903,10 @@ "integrity": "sha512-L/LJftsbKoHbVj6dN5pvMsyJn9jYI0wT0nMg3G6VZhDac4NesezecYTi8/48rHi+yEic3sUpw6jlSc7qNWh32A==", "dev": true, "requires": { - "chalk": "^1.1.3", - "fancy-log": "^1.3.2", - "plugin-error": "^0.1.2", - "through2": "^2.0.3" + "chalk": "1.1.3", + "fancy-log": "1.3.2", + "plugin-error": "0.1.2", + "through2": "2.0.3" }, "dependencies": { "ansi-regex": { @@ -2069,11 +1927,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "core-util-is": { @@ -2094,7 +1952,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "inherits": { @@ -2121,13 +1979,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "safe-buffer": { @@ -2142,7 +2000,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -2151,7 +2009,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -2166,8 +2024,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.5", + "xtend": "4.0.1" } }, "util-deprecate": { @@ -2190,17 +2048,17 @@ "integrity": "sha1-tDfR89mAzyboEYSCNxjOFa5ll7Y=", "dev": true, "requires": { - "@gulp-sourcemaps/map-sources": "1.X", - "acorn": "4.X", - "convert-source-map": "1.X", - "css": "2.X", - "debug-fabulous": "0.0.X", - "detect-newline": "2.X", - "graceful-fs": "4.X", - "source-map": "~0.6.0", - "strip-bom": "2.X", - "through2": "2.X", - "vinyl": "1.X" + "@gulp-sourcemaps/map-sources": "1.0.0", + "acorn": "4.0.13", + "convert-source-map": "1.5.1", + "css": "2.2.3", + "debug-fabulous": "0.0.4", + "detect-newline": "2.1.0", + "graceful-fs": "4.1.11", + "source-map": "0.6.1", + "strip-bom": "2.0.0", + "through2": "2.0.3", + "vinyl": "1.2.0" }, "dependencies": { "convert-source-map": { @@ -2251,13 +2109,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "safe-buffer": { @@ -2272,7 +2130,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-bom": { @@ -2281,7 +2139,7 @@ "integrity": "sha1-YhmoVhZSBJHzV4i9vxRHqZx+aw4=", "dev": true, "requires": { - "is-utf8": "^0.2.0" + "is-utf8": "0.2.1" } }, "through2": { @@ -2290,8 +2148,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.5", + "xtend": "4.0.1" } }, "util-deprecate": { @@ -2306,8 +2164,8 @@ "integrity": "sha1-XIgDbPVl5d8FVYv8kR+GVt8hiIQ=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } }, @@ -2328,9 +2186,9 @@ "@types/fancy-log": "1.3.0", "chalk": "2.3.1", "fancy-log": "1.3.2", - "map-stream": "~0.0.7", + "map-stream": "0.0.7", "plugin-error": "1.0.1", - "through": "~2.3.8" + "through": "2.3.8" }, "dependencies": { "ansi-styles": { @@ -2339,7 +2197,7 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.1" } }, "arr-diff": { @@ -2360,9 +2218,9 @@ "integrity": "sha512-QUU4ofkDoMIVO7hcx1iPTISs88wsO8jA92RQIm4JAwZvFGGAV2hSAA1NX7oVj2Ej2Q6NDTcRDjPTFrMCRZoJ6g==", "dev": true, "requires": { - "ansi-styles": "^3.2.0", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.2.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.3.0" } }, "extend-shallow": { @@ -2371,8 +2229,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" } }, "fancy-log": { @@ -2381,9 +2239,9 @@ "integrity": "sha1-9BEl49hPLn2JpD0G2VjI94vha+E=", "dev": true, "requires": { - "ansi-gray": "^0.1.1", - "color-support": "^1.1.3", - "time-stamp": "^1.0.0" + "ansi-gray": "0.1.1", + "color-support": "1.1.3", + "time-stamp": "1.1.0" } }, "has-flag": { @@ -2398,7 +2256,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" + "is-plain-object": "2.0.4" } }, "map-stream": { @@ -2413,10 +2271,10 @@ "integrity": "sha512-L1zP0dk7vGweZME2i+EeakvUNqSrdiI3F91TwEoYiGrAfUXmVv6fJIq4g82PAXxNsWOp0J7ZqQy/3Szz0ajTxA==", "dev": true, "requires": { - "ansi-colors": "^1.0.1", - "arr-diff": "^4.0.0", - "arr-union": "^3.1.0", - "extend-shallow": "^3.0.2" + "ansi-colors": "1.1.0", + "arr-diff": "4.0.0", + "arr-union": "3.1.0", + "extend-shallow": "3.0.2" } }, "supports-color": { @@ -2425,7 +2283,7 @@ "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -2436,12 +2294,12 @@ "integrity": "sha512-BGdaBC1R4SJosXEkkEieeZ21qCZHnfSV78k7zzDljqAxvzDeGRTUqF4geckVclKEeiS3EYOBwNlxoHjJtn20vg==", "dev": true, "requires": { - "ansi-colors": "^1.0.1", - "plugin-error": "^0.1.2", - "source-map": "^0.6.1", - "through2": "^2.0.3", - "vinyl": "^2.1.0", - "vinyl-fs": "^3.0.0" + "ansi-colors": "1.1.0", + "plugin-error": "0.1.2", + "source-map": "0.6.1", + "through2": "2.0.3", + "vinyl": "2.1.0", + "vinyl-fs": "3.0.2" }, "dependencies": { "clone": { @@ -2462,8 +2320,8 @@ "integrity": "sha1-nmr2KZ2NO9K9QEMIMr0RPfkGxa4=", "dev": true, "requires": { - "is-glob": "^3.1.0", - "path-dirname": "^1.0.0" + "is-glob": "3.1.0", + "path-dirname": "1.0.2" } }, "glob-stream": { @@ -2472,16 +2330,24 @@ "integrity": "sha1-cEXJlBOz65SIjYOrRtC0BMx73eQ=", "dev": true, "requires": { - "extend": "^3.0.0", - "glob": "^7.1.1", - "glob-parent": "^3.1.0", - "is-negated-glob": "^1.0.0", - "ordered-read-streams": "^1.0.0", - "pumpify": "^1.3.5", - "readable-stream": "^2.1.5", - "remove-trailing-separator": "^1.0.1", - "to-absolute-glob": "^2.0.0", - "unique-stream": "^2.0.2" + "extend": "3.0.1", + "glob": "7.1.3", + "glob-parent": "3.1.0", + "is-negated-glob": "1.0.0", + "ordered-read-streams": "1.0.1", + "pumpify": "1.4.0", + "readable-stream": "2.3.5", + "remove-trailing-separator": "1.1.0", + "to-absolute-glob": "2.0.2", + "unique-stream": "2.2.1" + }, + "dependencies": { + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + } } }, "graceful-fs": { @@ -2502,7 +2368,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } }, "isarray": { @@ -2517,7 +2383,7 @@ "integrity": "sha1-d8DLN8QVJdZBZtmQ/61+xqDhNj4=", "dev": true, "requires": { - "readable-stream": "^2.0.1" + "readable-stream": "2.3.5" } }, "process-nextick-args": { @@ -2532,13 +2398,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "replace-ext": { @@ -2553,7 +2419,7 @@ "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "unique-stream": { @@ -2562,8 +2428,8 @@ "integrity": "sha1-WqADz76Uxf+GbE59ZouxxNuts2k=", "dev": true, "requires": { - "json-stable-stringify": "^1.0.0", - "through2-filter": "^2.0.0" + "json-stable-stringify": "1.0.1", + "through2-filter": "2.0.0" } }, "vinyl": { @@ -2572,12 +2438,20 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.1", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" + }, + "dependencies": { + "remove-trailing-separator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", + "dev": true + } } }, "vinyl-fs": { @@ -2586,23 +2460,23 @@ "integrity": "sha512-AUSFda1OukBwuLPBTbyuO4IRWgfXmqC4UTW0f8xrCa8Hkv9oyIU+NSqBlgfOLZRoUt7cHdo75hKQghCywpIyIw==", "dev": true, "requires": { - "fs-mkdirp-stream": "^1.0.0", - "glob-stream": "^6.1.0", - "graceful-fs": "^4.0.0", - "is-valid-glob": "^1.0.0", - "lazystream": "^1.0.0", - "lead": "^1.0.0", - "object.assign": "^4.0.4", - "pumpify": "^1.3.5", - "readable-stream": "^2.3.3", - "remove-bom-buffer": "^3.0.0", - "remove-bom-stream": "^1.2.0", - "resolve-options": "^1.1.0", - "through2": "^2.0.0", - "to-through": "^2.0.0", - "value-or-function": "^3.0.0", - "vinyl": "^2.0.0", - "vinyl-sourcemap": "^1.1.0" + "fs-mkdirp-stream": "1.0.0", + "glob-stream": "6.1.0", + "graceful-fs": "4.1.11", + "is-valid-glob": "1.0.0", + "lazystream": "1.0.0", + "lead": "1.0.0", + "object.assign": "4.1.0", + "pumpify": "1.4.0", + "readable-stream": "2.3.5", + "remove-bom-buffer": "3.0.0", + "remove-bom-stream": "1.2.0", + "resolve-options": "1.1.0", + "through2": "2.0.3", + "to-through": "2.0.0", + "value-or-function": "3.0.0", + "vinyl": "2.1.0", + "vinyl-sourcemap": "1.1.0" } } } @@ -2613,24 +2487,24 @@ "integrity": "sha1-AFTh50RQLifATBh8PsxQXdVLu08=", "dev": true, "requires": { - "array-differ": "^1.0.0", - "array-uniq": "^1.0.2", - "beeper": "^1.0.0", - "chalk": "^1.0.0", - "dateformat": "^2.0.0", - "fancy-log": "^1.1.0", - "gulplog": "^1.0.0", - "has-gulplog": "^0.1.0", - "lodash._reescape": "^3.0.0", - "lodash._reevaluate": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.template": "^3.0.0", - "minimist": "^1.1.0", - "multipipe": "^0.1.2", - "object-assign": "^3.0.0", + "array-differ": "1.0.0", + "array-uniq": "1.0.3", + "beeper": "1.1.1", + "chalk": "1.1.3", + "dateformat": "2.2.0", + "fancy-log": "1.3.2", + "gulplog": "1.0.0", + "has-gulplog": "0.1.0", + "lodash._reescape": "3.0.0", + "lodash._reevaluate": "3.0.0", + "lodash._reinterpolate": "3.0.0", + "lodash.template": "3.6.2", + "minimist": "1.2.0", + "multipipe": "0.1.2", + "object-assign": "3.0.0", "replace-ext": "0.0.1", - "through2": "^2.0.0", - "vinyl": "^0.5.0" + "through2": "2.0.3", + "vinyl": "0.5.3" }, "dependencies": { "ansi-regex": { @@ -2651,11 +2525,11 @@ "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", "dev": true, "requires": { - "ansi-styles": "^2.2.1", - "escape-string-regexp": "^1.0.2", - "has-ansi": "^2.0.0", - "strip-ansi": "^3.0.0", - "supports-color": "^2.0.0" + "ansi-styles": "2.2.1", + "escape-string-regexp": "1.0.5", + "has-ansi": "2.0.0", + "strip-ansi": "3.0.1", + "supports-color": "2.0.0" } }, "core-util-is": { @@ -2676,7 +2550,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "inherits": { @@ -2709,13 +2583,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "safe-buffer": { @@ -2730,7 +2604,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } }, "strip-ansi": { @@ -2739,7 +2613,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } }, "supports-color": { @@ -2754,8 +2628,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.5", + "xtend": "4.0.1" } }, "util-deprecate": { @@ -2778,7 +2652,7 @@ "integrity": "sha1-4oxNRdBey77YGDY86PnFkmIp/+U=", "dev": true, "requires": { - "glogg": "^1.0.0" + "glogg": "1.0.1" } }, "has-ansi": { @@ -2787,7 +2661,7 @@ "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" }, "dependencies": { "ansi-regex": { @@ -2810,7 +2684,7 @@ "integrity": "sha1-ZBTIKRNpfaUVkDl9r7EvIpZ4Ec4=", "dev": true, "requires": { - "sparkles": "^1.0.0" + "sparkles": "1.0.0" } }, "has-symbols": { @@ -2825,9 +2699,9 @@ "integrity": "sha1-GLKB2lhbHFxR3vJMkw7SmgvmsXc=", "dev": true, "requires": { - "get-value": "^2.0.6", - "has-values": "^1.0.0", - "isobject": "^3.0.0" + "get-value": "2.0.6", + "has-values": "1.0.0", + "isobject": "3.0.1" } }, "has-values": { @@ -2836,23 +2710,25 @@ "integrity": "sha1-lbC2P+whRmGab+V/51Yo1aOe/k8=", "dev": true, "requires": { - "is-number": "^3.0.0", - "kind-of": "^4.0.0" + "is-number": "3.0.0", + "kind-of": "4.0.0" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "kind-of": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-4.0.0.tgz", "integrity": "sha1-IIE989cSkosgc3hpGkUGb65y3Vc=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } } } } @@ -2869,7 +2745,7 @@ "integrity": "sha1-TCu8inWJmP7r9e1oWA921GdotLw=", "dev": true, "requires": { - "parse-passwd": "^1.0.0" + "parse-passwd": "1.0.0" } }, "iconv-lite": { @@ -2878,7 +2754,7 @@ "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", "dev": true, "requires": { - "safer-buffer": ">= 2.1.2 < 3" + "safer-buffer": "2.1.2" } }, "inflight": { @@ -2886,8 +2762,8 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", "requires": { - "once": "^1.3.0", - "wrappy": "1" + "once": "1.4.0", + "wrappy": "1.0.2" }, "dependencies": { "once": { @@ -2895,7 +2771,7 @@ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "wrappy": { @@ -2922,6 +2798,16 @@ "integrity": "sha1-ftGxQQxqDg94z5XTuEQMY/eLhhQ=", "dev": true }, + "inversify": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/inversify/-/inversify-5.0.1.tgz", + "integrity": "sha512-Ieh06s48WnEYGcqHepdsJUIJUXpwH5o5vodAX+DK2JA/gjy4EbEcQZxw+uFfzysmKjiLXGYwNG3qDZsKVMcINQ==" + }, + "inversify-inject-decorators": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/inversify-inject-decorators/-/inversify-inject-decorators-3.1.0.tgz", + "integrity": "sha512-/seBlVp5bXrLQS3DpKEmlgeZL6C7Tf/QITd+IMQrbBBGuCbxb7k3hRAWu9XSreNpFzLgSboz3sClLSEmGwHphw==" + }, "invert-kv": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/invert-kv/-/invert-kv-1.0.0.tgz", @@ -2946,17 +2832,36 @@ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "is-relative": "1.0.0", + "is-windows": "1.0.2" } }, "is-accessor-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", - "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", + "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + } } }, "is-arrayish": { @@ -2965,28 +2870,56 @@ "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==" }, "is-buffer": { - "version": "1.1.5", - "integrity": "sha1-Hzsm72E7IUuIy8ojzGwB2Hlh7sw=", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", "dev": true }, "is-data-descriptor": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", - "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", + "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", "dev": true, "requires": { - "kind-of": "^6.0.0" + "kind-of": "3.2.2" + }, + "dependencies": { + "kind-of": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", + "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", + "dev": true, + "requires": { + "is-buffer": "1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } + } + } } }, "is-descriptor": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", - "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "version": "0.1.6", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", + "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", "dev": true, "requires": { - "is-accessor-descriptor": "^1.0.0", - "is-data-descriptor": "^1.0.0", - "kind-of": "^6.0.2" + "is-accessor-descriptor": "0.1.6", + "is-data-descriptor": "0.1.4", + "kind-of": "5.1.0" + }, + "dependencies": { + "kind-of": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", + "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", + "dev": true + } } }, "is-extendable": { @@ -3013,7 +2946,7 @@ "integrity": "sha1-e6WuJCF4BKxwcHuWkiVnSGzD6Eo=", "dev": true, "requires": { - "is-extglob": "^2.1.0" + "is-extglob": "2.1.1" } }, "is-negated-glob": { @@ -3028,43 +2961,28 @@ "integrity": "sha1-JP1iAaR4LPUFYcgQJ2r8fRLXEZU=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } } } } }, - "is-odd": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-odd/-/is-odd-2.0.0.tgz", - "integrity": "sha512-OTiixgpZAT1M4NHgS5IguFp/Vz2VI3U7Goh4/HA1adtwyLtSBrxYlcSYkhpAE07s4fKEcjrFxyvtQBND4vFQyQ==", - "dev": true, - "requires": { - "is-number": "^4.0.0" - }, - "dependencies": { - "is-number": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-4.0.0.tgz", - "integrity": "sha512-rSklcAIlf1OmFdyAqbnWTLVelsQ58uvZ66S/ZyawjWqIviTWCjg2PzVGw8WUA+nNuPTqb4wgA+NszrJ+08LlgQ==", - "dev": true - } - } - }, "is-path-cwd": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-path-cwd/-/is-path-cwd-1.0.0.tgz", @@ -3077,7 +2995,7 @@ "integrity": "sha512-FjV1RTW48E7CWM7eE/J2NJvAEEVektecDBVBE5Hh3nM1Jd0kvhHtX68Pr3xsDf857xt3Y4AkwVULK1Vku62aaQ==", "dev": true, "requires": { - "is-path-inside": "^1.0.0" + "is-path-inside": "1.0.1" } }, "is-path-inside": { @@ -3086,7 +3004,7 @@ "integrity": "sha1-jvW33lBDej/cprToZe96pVy0gDY=", "dev": true, "requires": { - "path-is-inside": "^1.0.1" + "path-is-inside": "1.0.2" } }, "is-plain-obj": { @@ -3101,15 +3019,7 @@ "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", "dev": true, "requires": { - "isobject": "^3.0.1" - }, - "dependencies": { - "isobject": { - "version": "3.0.1", - "resolved": false, - "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", - "dev": true - } + "isobject": "3.0.1" } }, "is-regexp": { @@ -3124,7 +3034,7 @@ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "is-unc-path": "^1.0.0" + "is-unc-path": "1.0.0" } }, "is-stream": { @@ -3139,15 +3049,7 @@ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { - "unc-path-regex": "^0.1.2" - }, - "dependencies": { - "unc-path-regex": { - "version": "0.1.2", - "resolved": "https://registry.npmjs.org/unc-path-regex/-/unc-path-regex-0.1.2.tgz", - "integrity": "sha1-5z3T17DXxe2G+6xrCufYxqadUPo=", - "dev": true - } + "unc-path-regex": "0.1.2" } }, "is-utf8": { @@ -3174,6 +3076,12 @@ "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8=", "dev": true }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, "isobject": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", @@ -3192,8 +3100,8 @@ "integrity": "sha512-saJstZWv7oNeOyBh3+Dx1qWzhW0+e6/8eDzo7p5rDFqxntSztloLtuKu+Ejhtq82jsilwOIZYsCz+lIjthg1Hw==", "dev": true, "requires": { - "argparse": "^1.0.7", - "esprima": "^4.0.0" + "argparse": "1.0.10", + "esprima": "4.0.0" } }, "json-stable-stringify": { @@ -3202,7 +3110,7 @@ "integrity": "sha1-mnWdOcXy/1A/1TAGRu1EX4jE+a8=", "dev": true, "requires": { - "jsonify": "~0.0.0" + "jsonify": "0.0.0" } }, "jsonify": { @@ -3229,7 +3137,7 @@ "integrity": "sha1-9plf4PggOS9hOWvolGJAe7dxaOQ=", "dev": true, "requires": { - "readable-stream": "^2.0.5" + "readable-stream": "2.3.5" }, "dependencies": { "isarray": { @@ -3250,13 +3158,13 @@ "integrity": "sha512-tK0yDhrkygt/knjowCUiWP9YdV7c5R+8cR0r/kt9ZhBU906Fs6RpQJCEilamRJj1Nx2rWI6LkW9gKqjTkshhEw==", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.0.3", + "util-deprecate": "1.0.2" } }, "string_decoder": { @@ -3265,7 +3173,7 @@ "integrity": "sha512-4AH6Z5fzNNBcH+6XDMfA/BTt87skxqJlO0lAh3Dker5zThcAxG6mKz+iGu308UKoPPQ8Dcqx/4JhujzltRa+hQ==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -3276,7 +3184,7 @@ "integrity": "sha1-MIrMr6C8SDo4Z7S28rlQYlHRuDU=", "dev": true, "requires": { - "invert-kv": "^1.0.0" + "invert-kv": "1.0.0" } }, "lead": { @@ -3285,7 +3193,7 @@ "integrity": "sha1-bxT5mje+Op3XhPVJVpDlkDRm7kI=", "dev": true, "requires": { - "flush-write-stream": "^1.0.2" + "flush-write-stream": "1.0.2" } }, "liftoff": { @@ -3294,46 +3202,14 @@ "integrity": "sha1-IAkpG7Mc6oYbvxCnwVooyvdcMew=", "dev": true, "requires": { - "extend": "^3.0.0", - "findup-sync": "^2.0.0", - "fined": "^1.0.1", - "flagged-respawn": "^1.0.0", - "is-plain-object": "^2.0.4", - "object.map": "^1.0.0", - "rechoir": "^0.6.2", - "resolve": "^1.1.7" - }, - "dependencies": { - "extend": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.1.tgz", - "integrity": "sha1-p1Xqe8Gt/MWjHOfnYtuq3F5jZEQ=", - "dev": true - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" - } - }, - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "resolve": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.6.0.tgz", - "integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - } + "extend": "3.0.1", + "findup-sync": "2.0.0", + "fined": "1.1.0", + "flagged-respawn": "1.0.0", + "is-plain-object": "2.0.4", + "object.map": "1.0.1", + "rechoir": "0.6.2", + "resolve": "1.8.1" } }, "locate-path": { @@ -3342,15 +3218,14 @@ "integrity": "sha1-K1aLJl7slExtnA3pw9u7ygNUzY4=", "dev": true, "requires": { - "p-locate": "^2.0.0", - "path-exists": "^3.0.0" + "p-locate": "2.0.0", + "path-exists": "3.0.0" } }, "lodash": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-1.0.2.tgz", - "integrity": "sha1-j1dWDIO1n8JwvT1WG2kAQ0MOJVE=", - "dev": true + "version": "4.17.11", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.11.tgz", + "integrity": "sha512-cQKh8igo5QUhZ7lg38DYWAxMvjSAKG0A8wGSVimP07SIUEK2UO+arSRKbRZWtelMtN5V0Hkwh5ryOto/SshYIg==" }, "lodash._basecopy": { "version": "3.0.1", @@ -3412,7 +3287,7 @@ "integrity": "sha1-mV7g3BjBtIzJLv+ucaEKq1tIdpg=", "dev": true, "requires": { - "lodash._root": "^3.0.0" + "lodash._root": "3.0.1" } }, "lodash.isarguments": { @@ -3433,9 +3308,9 @@ "integrity": "sha1-TbwEcrFWvlCgsoaFXRvQsMZWCYo=", "dev": true, "requires": { - "lodash._getnative": "^3.0.0", - "lodash.isarguments": "^3.0.0", - "lodash.isarray": "^3.0.0" + "lodash._getnative": "3.9.1", + "lodash.isarguments": "3.1.0", + "lodash.isarray": "3.0.4" } }, "lodash.restparam": { @@ -3450,15 +3325,15 @@ "integrity": "sha1-+M3sxhaaJVvpCYrosMU9N4kx0U8=", "dev": true, "requires": { - "lodash._basecopy": "^3.0.0", - "lodash._basetostring": "^3.0.0", - "lodash._basevalues": "^3.0.0", - "lodash._isiterateecall": "^3.0.0", - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0", - "lodash.keys": "^3.0.0", - "lodash.restparam": "^3.0.0", - "lodash.templatesettings": "^3.0.0" + "lodash._basecopy": "3.0.1", + "lodash._basetostring": "3.0.1", + "lodash._basevalues": "3.0.0", + "lodash._isiterateecall": "3.0.9", + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0", + "lodash.keys": "3.1.2", + "lodash.restparam": "3.6.1", + "lodash.templatesettings": "3.1.1" } }, "lodash.templatesettings": { @@ -3467,8 +3342,8 @@ "integrity": "sha1-+zB4RHU7Zrnxr6VOJix0UwfbqOU=", "dev": true, "requires": { - "lodash._reinterpolate": "^3.0.0", - "lodash.escape": "^3.0.0" + "lodash._reinterpolate": "3.0.0", + "lodash.escape": "3.2.0" } }, "lru-cache": { @@ -3478,29 +3353,12 @@ "dev": true }, "make-iterator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.0.tgz", - "integrity": "sha1-V7713IXSOSO6I3ZzJNjo+PPZaUs=", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/make-iterator/-/make-iterator-1.0.1.tgz", + "integrity": "sha512-pxiuXh0iVEq7VM7KMIhs5gxsfxCux2URptUQaXo4iZZJxBAzTPOLE2BumO5dbfVYq/hBJFBR/a1mFDmOx5AGmw==", "dev": true, "requires": { - "kind-of": "^3.1.0" - }, - "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } + "kind-of": "6.0.2" } }, "map-cache": { @@ -3521,7 +3379,7 @@ "integrity": "sha1-7Nyo8TFE5mDxtb1B8S80edmN+48=", "dev": true, "requires": { - "object-visit": "^1.0.0" + "object-visit": "1.0.1" } }, "mem": { @@ -3530,7 +3388,7 @@ "integrity": "sha1-Xt1StIXKHZAP5kiVUFOZoN+kX3Y=", "dev": true, "requires": { - "mimic-fn": "^1.0.0" + "mimic-fn": "1.2.0" } }, "merge2": { @@ -3545,19 +3403,19 @@ "integrity": "sha512-MWikgl9n9M3w+bpsY3He8L+w9eF9338xRl8IAO5viDizwSzziFEyUzo2xrrloB64ADbTf8uA8vRqqttDTOmccg==", "dev": true, "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "braces": "^2.3.1", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "extglob": "^2.0.4", - "fragment-cache": "^0.2.1", - "kind-of": "^6.0.2", - "nanomatch": "^1.2.9", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.2" + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "braces": "2.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "extglob": "2.0.4", + "fragment-cache": "0.2.1", + "kind-of": "6.0.2", + "nanomatch": "1.2.13", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -3566,8 +3424,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" } }, "is-extendable": { @@ -3576,16 +3434,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "is-plain-object": "2.0.4" } } } @@ -3601,7 +3450,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "minimist": { @@ -3616,26 +3465,17 @@ "integrity": "sha512-8ZItLHeEgaqEvd5lYBXfm4EZSFCX29Jb9K+lAHhDKzReKBQKj3R+7NOF6tjqYi9t4oI8VUfaWITJQm86wnXGNQ==", "dev": true, "requires": { - "for-in": "^1.0.2", - "is-extendable": "^1.0.1" + "for-in": "1.0.2", + "is-extendable": "1.0.1" }, "dependencies": { "is-extendable": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz", - "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", - "dev": true, - "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "isobject": "^3.0.1" + "is-plain-object": "2.0.4" } } } @@ -3695,12 +3535,12 @@ "integrity": "sha512-MJTUg1kjuLeQCJ+ccE4Vpa6kKVXkPYJ2mOCQyUuKLcLQsdrMCpBPUi8qVE6+YuaJkozeA9NusTAw3hLr8Xe5EQ==", "dev": true, "requires": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.0.4", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "fs.realpath": "1.0.0", + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } }, "supports-color": { @@ -3709,7 +3549,7 @@ "integrity": "sha512-zjaXglF5nnWpsq470jSv6P9DwPvgLkuapYmfDm3JWOm0vkNTVF2tI4UrN2r6jH1qM/uc/WtxYY1hYoA2dOKj5w==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "has-flag": "3.0.0" } } } @@ -3736,23 +3576,22 @@ } }, "nanomatch": { - "version": "1.2.9", - "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.9.tgz", - "integrity": "sha512-n8R9bS8yQ6eSXaV6jHUpKzD8gLsin02w1HSFiegwrs9E098Ylhw5jdyKPaYqvHknHaSCKTPp7C8dGCQ0q9koXA==", - "dev": true, - "requires": { - "arr-diff": "^4.0.0", - "array-unique": "^0.3.2", - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "fragment-cache": "^0.2.1", - "is-odd": "^2.0.0", - "is-windows": "^1.0.2", - "kind-of": "^6.0.2", - "object.pick": "^1.3.0", - "regex-not": "^1.0.0", - "snapdragon": "^0.8.1", - "to-regex": "^3.0.1" + "version": "1.2.13", + "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", + "integrity": "sha512-fpoe2T0RbHwBTBUOftAfBPaDEi06ufaUai0mE6Yn1kacc3SnTErfb/h+X94VXzI64rKFHYImXSvdwGGCmwOqCA==", + "dev": true, + "requires": { + "arr-diff": "4.0.0", + "array-unique": "0.3.2", + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "fragment-cache": "0.2.1", + "is-windows": "1.0.2", + "kind-of": "6.0.2", + "object.pick": "1.3.0", + "regex-not": "1.0.2", + "snapdragon": "0.8.2", + "to-regex": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -3761,8 +3600,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" } }, "is-extendable": { @@ -3771,24 +3610,15 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "is-plain-object": "2.0.4" } } } }, "natives": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.6.tgz", - "integrity": "sha512-6+TDFewD4yxY14ptjKaS63GVdtKiES1pTPyxn9Jb0rBqPMZ7VcCiooEhPNsr+mqHtMGxa/5c/HhcC4uPEUw/nA==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/natives/-/natives-1.1.5.tgz", + "integrity": "sha512-1pJ+02gl2KJgCPFtpZGtuD4lGSJnIZvvFHCQTOeDRMSXjfu2GmYWuhI8NFMA4W2I5NNFRbfy/YCiVt4CgNpP8A==", "dev": true }, "noice-json-rpc": { @@ -3798,6 +3628,7 @@ }, "normalize-path": { "version": "2.1.1", + "resolved": false, "integrity": "sha1-GrKLVW4Zg2Oowab35vogE3/mrtk=", "dev": true, "requires": { @@ -3810,7 +3641,7 @@ "integrity": "sha1-vGHLtFbXnLMiB85HygUTb/Ln1u4=", "dev": true, "requires": { - "once": "^1.3.2" + "once": "1.4.0" } }, "npm-run-path": { @@ -3819,7 +3650,7 @@ "integrity": "sha1-NakjLfo11wZ7TLLd8jV7GHFTbF8=", "dev": true, "requires": { - "path-key": "^2.0.0" + "path-key": "2.0.1" } }, "number-is-nan": { @@ -3840,9 +3671,9 @@ "integrity": "sha1-fn2Fi3gb18mRpBupde04EnVOmYw=", "dev": true, "requires": { - "copy-descriptor": "^0.1.0", - "define-property": "^0.2.5", - "kind-of": "^3.0.3" + "copy-descriptor": "0.1.1", + "define-property": "0.2.5", + "kind-of": "3.2.2" }, "dependencies": { "define-property": { @@ -3851,50 +3682,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" - }, - "dependencies": { - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - } + "is-descriptor": "0.1.6" } }, "kind-of": { @@ -3903,7 +3691,15 @@ "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } } } } @@ -3920,7 +3716,7 @@ "integrity": "sha1-95xEk68MU3e1n+OdOV5BBC3QRbs=", "dev": true, "requires": { - "isobject": "^3.0.0" + "isobject": "3.0.1" } }, "object.assign": { @@ -3929,10 +3725,10 @@ "integrity": "sha512-exHJeq6kBKj58mqGyTQ9DFvrZC/eR6OwxzoM9YRoGBqrXYonaFyGiFMuc9VZrXf7DarreEwMpurG3dd+CNyW5w==", "dev": true, "requires": { - "define-properties": "^1.1.2", - "function-bind": "^1.1.1", - "has-symbols": "^1.0.0", - "object-keys": "^1.0.11" + "define-properties": "1.1.2", + "function-bind": "1.1.1", + "has-symbols": "1.0.0", + "object-keys": "1.0.11" } }, "object.defaults": { @@ -3941,10 +3737,10 @@ "integrity": "sha1-On+GgzS0B96gbaFtiNXNKeQ1/s8=", "dev": true, "requires": { - "array-each": "^1.0.1", - "array-slice": "^1.0.0", - "for-own": "^1.0.0", - "isobject": "^3.0.0" + "array-each": "1.0.1", + "array-slice": "1.1.0", + "for-own": "1.0.0", + "isobject": "3.0.1" } }, "object.map": { @@ -3953,8 +3749,8 @@ "integrity": "sha1-z4Plncj8wK1fQlDh94s7gb2AHTc=", "dev": true, "requires": { - "for-own": "^1.0.0", - "make-iterator": "^1.0.0" + "for-own": "1.0.0", + "make-iterator": "1.0.1" } }, "object.pick": { @@ -3963,11 +3759,12 @@ "integrity": "sha1-h6EKxMFpS9Lhy/U1kaZhQftd10c=", "dev": true, "requires": { - "isobject": "^3.0.1" + "isobject": "3.0.1" } }, "once": { "version": "1.4.0", + "resolved": false, "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "requires": { "wrappy": "1" @@ -3979,8 +3776,8 @@ "integrity": "sha1-2j6nRob6IaGaERwybpDrFaAZZoY=", "dev": true, "requires": { - "minimist": "~0.0.1", - "wordwrap": "~0.0.2" + "minimist": "0.0.10", + "wordwrap": "0.0.3" }, "dependencies": { "minimist": { @@ -3997,9 +3794,9 @@ "integrity": "sha1-FOfp4nZPcxX7rBhOUGx6pt+UrX4=", "dev": true, "requires": { - "end-of-stream": "~0.1.5", - "sequencify": "~0.0.7", - "stream-consume": "~0.1.0" + "end-of-stream": "0.1.5", + "sequencify": "0.0.7", + "stream-consume": "0.1.1" } }, "ordered-read-streams": { @@ -4020,9 +3817,9 @@ "integrity": "sha512-3sslG3zJbEYcaC4YVAvDorjGxc7tv6KVATnLPZONiljsUncvihe9BQoVCEs0RZ1kmf4Hk9OBqlZfJZWI4GanKA==", "dev": true, "requires": { - "execa": "^0.7.0", - "lcid": "^1.0.0", - "mem": "^1.1.0" + "execa": "0.7.0", + "lcid": "1.0.0", + "mem": "1.1.0" } }, "p-finally": { @@ -4037,7 +3834,7 @@ "integrity": "sha512-vvcXsLAJ9Dr5rQOPk7toZQZJApBl2K4J6dANSsEuh6QI41JYcsS/qhTGa9ErIUUgK3WNQoJYvylxvjqmiqEA9Q==", "dev": true, "requires": { - "p-try": "^1.0.0" + "p-try": "1.0.0" } }, "p-locate": { @@ -4046,7 +3843,7 @@ "integrity": "sha1-IKAQOyIqcMj9OcwuWAaA893l7EM=", "dev": true, "requires": { - "p-limit": "^1.1.0" + "p-limit": "1.3.0" } }, "p-try": { @@ -4061,9 +3858,9 @@ "integrity": "sha1-pjISf1Oq89FYdvWHLz/6x2PWyJE=", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "map-cache": "^0.2.0", - "path-root": "^0.1.1" + "is-absolute": "1.0.0", + "map-cache": "0.2.2", + "path-root": "0.1.1" } }, "parse-passwd": { @@ -4119,7 +3916,7 @@ "integrity": "sha1-mkpoFMrBwM1zNgqV8yCDyOpHRbc=", "dev": true, "requires": { - "path-root-regex": "^0.1.0" + "path-root-regex": "0.1.2" } }, "path-root-regex": { @@ -4134,7 +3931,7 @@ "integrity": "sha1-/lo0sMvOErWqaitAPuLnO2AvFEU=", "dev": true, "requires": { - "through": "~2.3" + "through": "2.3.8" }, "dependencies": { "through": { @@ -4163,7 +3960,7 @@ "integrity": "sha1-ITXW36ejWMBprJsXh3YogihFD/o=", "dev": true, "requires": { - "pinkie": "^2.0.0" + "pinkie": "2.0.4" } }, "plugin-error": { @@ -4172,11 +3969,11 @@ "integrity": "sha1-O5uzM1zPAPQl4HQ34ZJ2ln2kes4=", "dev": true, "requires": { - "ansi-cyan": "^0.1.1", - "ansi-red": "^0.1.1", - "arr-diff": "^1.0.1", - "arr-union": "^2.0.1", - "extend-shallow": "^1.1.2" + "ansi-cyan": "0.1.1", + "ansi-red": "0.1.1", + "arr-diff": "1.1.0", + "arr-union": "2.1.0", + "extend-shallow": "1.1.4" }, "dependencies": { "arr-diff": { @@ -4185,8 +3982,8 @@ "integrity": "sha1-aHwydYFjWI/vfeezb6vklesaOZo=", "dev": true, "requires": { - "arr-flatten": "^1.0.1", - "array-slice": "^0.2.3" + "arr-flatten": "1.1.0", + "array-slice": "0.2.3" } }, "array-slice": { @@ -4203,7 +4000,7 @@ "integrity": "sha1-dIJFLBoPUI4+NE6uwxLJHCncZVo=", "dev": true, "requires": { - "irregular-plurals": "^1.0.0" + "irregular-plurals": "1.4.0" } }, "posix-character-classes": { @@ -4224,11 +4021,6 @@ "integrity": "sha1-t+PqQkNaTJsnWdmeDyAesZWALuE=", "dev": true }, - "process-nextick-args": { - "version": "1.0.7", - "integrity": "sha1-FQ4gt1ZZCtP5EJPyWk8q2L/zC6M=", - "dev": true - }, "pseudomap": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/pseudomap/-/pseudomap-1.0.2.tgz", @@ -4241,8 +4033,8 @@ "integrity": "sha512-ruPMNRkN3MHP1cWJc9OWr+T/xDP0jhXYCLfJcBuX54hhfIBnaQmAUMfDcG4DM5UMWByBbJY69QSphm3jtDKIkA==", "dev": true, "requires": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" + "end-of-stream": "1.4.1", + "once": "1.4.0" }, "dependencies": { "end-of-stream": { @@ -4251,7 +4043,7 @@ "integrity": "sha512-1MkrZNvWTKCaigbn+W15elq2BB/L22nqrSY5DKlo3X6+vclJm8Bb5djXJBmEX6fS3+zCh/F4VBK5Z2KxJt4s2Q==", "dev": true, "requires": { - "once": "^1.4.0" + "once": "1.4.0" } } } @@ -4262,9 +4054,9 @@ "integrity": "sha512-2kmNR9ry+Pf45opRVirpNuIFotsxUGLaYqxIwuR77AYrYRMuFCz9eryHBS52L360O+NcR383CL4QYlMKPq4zYA==", "dev": true, "requires": { - "duplexify": "^3.5.3", - "inherits": "^2.0.3", - "pump": "^2.0.0" + "duplexify": "3.5.4", + "inherits": "2.0.3", + "pump": "2.0.1" } }, "readable-stream": { @@ -4273,10 +4065,10 @@ "integrity": "sha1-fPTFTvZI44EwhMY23SB54WbAgdk=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" }, "dependencies": { "core-util-is": { @@ -4299,34 +4091,22 @@ "integrity": "sha1-hSBLVNuoLVdC4oyWdW70OvUOM4Q=", "dev": true, "requires": { - "resolve": "^1.1.6" - }, - "dependencies": { - "path-parse": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.5.tgz", - "integrity": "sha1-PBrfhx6pzWyUMbbqK9dKD/BVxME=", - "dev": true - }, - "resolve": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.6.0.tgz", - "integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==", - "dev": true, - "requires": { - "path-parse": "^1.0.5" - } - } + "resolve": "1.8.1" } }, + "reflect-metadata": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/reflect-metadata/-/reflect-metadata-0.1.12.tgz", + "integrity": "sha512-n+IyV+nGz3+0q3/Yf1ra12KpCyi001bi4XFxSjbiWWjfqb52iTTtpGXmCCAOWWIAn9KEuFZKGqBERHmrtScZ3A==" + }, "regex-not": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/regex-not/-/regex-not-1.0.2.tgz", "integrity": "sha512-J6SDjUgDxQj5NusnOtdFxDwN/+HWykR8GELwctJ7mdqhcyy1xEc4SRFHUXvxTp661YaVKAjfRLZ9cCqS6tn32A==", "dev": true, "requires": { - "extend-shallow": "^3.0.2", - "safe-regex": "^1.1.0" + "extend-shallow": "3.0.2", + "safe-regex": "1.1.0" }, "dependencies": { "extend-shallow": { @@ -4335,8 +4115,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" } }, "is-extendable": { @@ -4345,16 +4125,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "is-plain-object": "2.0.4" } } } @@ -4365,8 +4136,8 @@ "integrity": "sha512-8v2rWhaakv18qcvNeli2mZ/TMTL2nEyAKRvzo1WtnZBl15SHyEhrCu2/xKlJyUFKHiHgfXIyuY6g2dObJJycXQ==", "dev": true, "requires": { - "is-buffer": "^1.1.5", - "is-utf8": "^0.2.1" + "is-buffer": "1.1.6", + "is-utf8": "0.2.1" } }, "remove-bom-stream": { @@ -4375,20 +4146,21 @@ "integrity": "sha1-BfGlk/FuQuH7kOv1nejlaVJflSM=", "dev": true, "requires": { - "remove-bom-buffer": "^3.0.0", - "safe-buffer": "^5.1.0", - "through2": "^2.0.3" + "remove-bom-buffer": "3.0.0", + "safe-buffer": "5.1.1", + "through2": "2.0.3" } }, "remove-trailing-separator": { - "version": "1.0.2", - "integrity": "sha1-abBi2XhyetFNxrVrpKt3L9jXBRE=", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/remove-trailing-separator/-/remove-trailing-separator-1.1.0.tgz", + "integrity": "sha1-wkvOKig62tW8P1jg1IJJuSN52O8=", "dev": true }, "repeat-element": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.2.tgz", - "integrity": "sha1-7wiaF40Ug7quTZPrmLT55OEdmQo=", + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/repeat-element/-/repeat-element-1.1.3.tgz", + "integrity": "sha512-ahGq0ZnV5m5XtZLMb+vP76kcAM5nkLqk0lpqAuojSKGgQtn4eRi4ZZGm2olo2zKFH+sMsWaqOCW1dqAnOru72g==", "dev": true }, "repeat-string": { @@ -4416,12 +4188,12 @@ "dev": true }, "resolve": { - "version": "1.3.3", - "resolved": false, - "integrity": "sha1-ZVkHw0aahoDcLeOidaj91paR8OU=", + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.8.1.tgz", + "integrity": "sha512-AicPrAC7Qu1JxPCZ9ZgCZlY35QgFnNqc+0LtbRNxnVw4TXvjQ72wnuL9JQcEBgXkI9JM8MsT9kaQoHcpCRJOYA==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "1.0.5" } }, "resolve-dir": { @@ -4430,8 +4202,8 @@ "integrity": "sha1-eaQGRMNivoLybv/nOcm7U4IEb0M=", "dev": true, "requires": { - "expand-tilde": "^2.0.0", - "global-modules": "^1.0.0" + "expand-tilde": "2.0.2", + "global-modules": "1.0.0" } }, "resolve-options": { @@ -4440,7 +4212,7 @@ "integrity": "sha1-MrueOcBtZzONyTeMDW1gdFZq0TE=", "dev": true, "requires": { - "value-or-function": "^3.0.0" + "value-or-function": "3.0.0" } }, "resolve-url": { @@ -4461,7 +4233,7 @@ "integrity": "sha512-lreewLK/BlghmxtfH36YYVg1i8IAce4TI7oao75I1g245+6BctqTVQiBP3YUJ9C6DQOXJmkYR9X9fCLtCOJc5w==", "dev": true, "requires": { - "glob": "^7.0.5" + "glob": "7.1.3" } }, "run-sequence": { @@ -4470,8 +4242,8 @@ "integrity": "sha1-UJWgvr6YczsBQL0I3YDsAw3azes=", "dev": true, "requires": { - "chalk": "*", - "gulp-util": "*" + "chalk": "1.1.3", + "gulp-util": "3.0.8" } }, "safe-buffer": { @@ -4486,7 +4258,7 @@ "integrity": "sha1-QKNmnzsHfR6UPURinhV91IAjvy4=", "dev": true, "requires": { - "ret": "~0.1.10" + "ret": "0.1.15" } }, "safer-buffer": { @@ -4502,10 +4274,9 @@ "dev": true }, "semver": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/semver/-/semver-4.3.6.tgz", - "integrity": "sha1-MAvG4OhjdPe6YQaLWx7NV/xlMto=", - "dev": true + "version": "5.6.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.6.0.tgz", + "integrity": "sha512-RS9R6R35NYgQn++fkDWaOmqGoj4Ek9gGs+DPxNUZKuwE183xjJroKvyo1IzVFeXvUrvmALy6FWD5xrdJT25gMg==" }, "sequencify": { "version": "0.0.7", @@ -4525,10 +4296,10 @@ "integrity": "sha512-hw0yxk9GT/Hr5yJEYnHNKYXkIA8mVJgd9ditYZCe16ZczcaELYYcfvaXesNACk2O8O0nTiPQcQhGUQj8JLzeeg==", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.3", - "split-string": "^3.0.1" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "split-string": "3.1.0" }, "dependencies": { "extend-shallow": { @@ -4537,16 +4308,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "is-extendable": "0.1.1" } } } @@ -4557,7 +4319,7 @@ "integrity": "sha1-RKrGW2lbAzmJaMOfNj/uXer98eo=", "dev": true, "requires": { - "shebang-regex": "^1.0.0" + "shebang-regex": "1.0.0" } }, "shebang-regex": { @@ -4583,7 +4345,7 @@ "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.2.tgz", "integrity": "sha1-pNprY1/8zMoz9w0Xy5JZLeleVXo=", "requires": { - "is-arrayish": "^0.3.1" + "is-arrayish": "0.3.2" } }, "snapdragon": { @@ -4592,14 +4354,14 @@ "integrity": "sha512-FtyOnWN/wCHTVXOMwvSv26d+ko5vWlIDD6zoUJ7LW8vh+ZBC8QdljveRP+crNrtBwioEUWy/4dMtbBjA4ioNlg==", "dev": true, "requires": { - "base": "^0.11.1", - "debug": "^2.2.0", - "define-property": "^0.2.5", - "extend-shallow": "^2.0.1", - "map-cache": "^0.2.2", - "source-map": "^0.5.6", - "source-map-resolve": "^0.5.0", - "use": "^3.1.0" + "base": "0.11.2", + "debug": "2.6.9", + "define-property": "0.2.5", + "extend-shallow": "2.0.1", + "map-cache": "0.2.2", + "source-map": "0.5.7", + "source-map-resolve": "0.5.1", + "use": "3.1.1" }, "dependencies": { "define-property": { @@ -4608,7 +4370,7 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" + "is-descriptor": "0.1.6" } }, "extend-shallow": { @@ -4617,72 +4379,9 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-extendable": "0.1.1" } }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true - }, "source-map": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", @@ -4697,9 +4396,9 @@ "integrity": "sha512-O27l4xaMYt/RSQ5TR3vpWCAB5Kb/czIcqUFOM/C4fYcLnbZUc1PkjTAMjof2pBWaSTwOUd6qUHcFGVGj7aIwnw==", "dev": true, "requires": { - "define-property": "^1.0.0", - "isobject": "^3.0.0", - "snapdragon-util": "^3.0.1" + "define-property": "1.0.0", + "isobject": "3.0.1", + "snapdragon-util": "3.0.1" }, "dependencies": { "define-property": { @@ -4708,7 +4407,36 @@ "integrity": "sha1-dp66rz9KY6rTr56NMEybvnm/sOY=", "dev": true, "requires": { - "is-descriptor": "^1.0.0" + "is-descriptor": "1.0.2" + } + }, + "is-accessor-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-1.0.0.tgz", + "integrity": "sha512-m5hnHTkcVsPfqx3AKlyttIPb7J+XykHvJP2B9bZDjlhLIoEq4XoK64Vg7boZlVWYK6LUY94dYPEE7Lh0ZkZKcQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-data-descriptor": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-1.0.0.tgz", + "integrity": "sha512-jbRXy1FmtAoCjQkVmIVYwuuqDFUbaOeDjmed1tOGPrsMhtJA4rD9tkgA0F1qJ3gRFRXcHYVkdeaP50Q5rE/jLQ==", + "dev": true, + "requires": { + "kind-of": "6.0.2" + } + }, + "is-descriptor": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-1.0.2.tgz", + "integrity": "sha512-2eis5WqQGV7peooDyLmNEPUrps9+SXX5c9pL3xEB+4e9HnGuDa7mB7kHxHw4CbqS9k1T2hOH3miL8n8WtiYVtg==", + "dev": true, + "requires": { + "is-accessor-descriptor": "1.0.0", + "is-data-descriptor": "1.0.0", + "kind-of": "6.0.2" } } } @@ -4719,22 +4447,24 @@ "integrity": "sha512-mbKkMdQKsjX4BAL4bRYTj21edOf8cN7XHdYUJEe+Zn99hVEYcMvKPct1IqNe7+AZPirn8BCDOQBHQZknqmKlZQ==", "dev": true, "requires": { - "kind-of": "^3.2.0" + "kind-of": "3.2.2" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } } } } @@ -4750,11 +4480,11 @@ "integrity": "sha512-0KW2wvzfxm8NCTb30z0LMNyPqWCdDGE2viwzUaucqJdkTRXtZiSY3I+2A6nVAjmdOy0I4gU8DwnVVGsk9jvP2A==", "dev": true, "requires": { - "atob": "^2.0.0", - "decode-uri-component": "^0.2.0", - "resolve-url": "^0.2.1", - "source-map-url": "^0.4.0", - "urix": "^0.1.0" + "atob": "2.1.0", + "decode-uri-component": "0.2.0", + "resolve-url": "0.2.1", + "source-map-url": "0.4.0", + "urix": "0.1.0" } }, "source-map-url": { @@ -4775,7 +4505,7 @@ "integrity": "sha1-zQ7qXmOiEd//frDwkcQTPi0N0o8=", "dev": true, "requires": { - "through": "2" + "through": "2.3.8" }, "dependencies": { "through": { @@ -4792,7 +4522,7 @@ "integrity": "sha512-NzNVhJDYpwceVVii8/Hu6DKfD2G+NrQHlS/V/qgv763EYudVwEcMQNxd2lh+0VrUByXN/oJkl5grOhYWvQUYiw==", "dev": true, "requires": { - "extend-shallow": "^3.0.0" + "extend-shallow": "3.0.2" }, "dependencies": { "extend-shallow": { @@ -4801,8 +4531,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" } }, "is-extendable": { @@ -4811,16 +4541,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "is-plain-object": "2.0.4" } } } @@ -4837,8 +4558,8 @@ "integrity": "sha1-YICcOcv/VTNyJv1eC1IPNB8ftcY=", "dev": true, "requires": { - "define-property": "^0.2.5", - "object-copy": "^0.1.0" + "define-property": "0.2.5", + "object-copy": "0.1.0" }, "dependencies": { "define-property": { @@ -4847,71 +4568,8 @@ "integrity": "sha1-w1se+RjsPJkPmlvFe+BKrOxcgRY=", "dev": true, "requires": { - "is-descriptor": "^0.1.0" - } - }, - "is-accessor-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-accessor-descriptor/-/is-accessor-descriptor-0.1.6.tgz", - "integrity": "sha1-qeEss66Nh2cn7u84Q/igiXtcmNY=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, - "is-data-descriptor": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/is-data-descriptor/-/is-data-descriptor-0.1.4.tgz", - "integrity": "sha1-C17mSDiOLIYCgueT8YVv7D8wG1Y=", - "dev": true, - "requires": { - "kind-of": "^3.0.2" - }, - "dependencies": { - "kind-of": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", - "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", - "dev": true, - "requires": { - "is-buffer": "^1.1.5" - } - } - } - }, - "is-descriptor": { - "version": "0.1.6", - "resolved": "https://registry.npmjs.org/is-descriptor/-/is-descriptor-0.1.6.tgz", - "integrity": "sha512-avDYr0SB3DwO9zsMov0gKCESFYqCnE4hq/4z3TdUlukEy5t9C0YRq7HLrsN52NAcqXKaepeCD0n+B0arnVG3Hg==", - "dev": true, - "requires": { - "is-accessor-descriptor": "^0.1.6", - "is-data-descriptor": "^0.1.4", - "kind-of": "^5.0.0" + "is-descriptor": "0.1.6" } - }, - "kind-of": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-5.1.0.tgz", - "integrity": "sha512-NGEErnH6F2vUuXDh+OlbcKW7/wOcfdRHaZ7VWtqCztfHri/++YKmP51OdWeGPuqCOba6kk2OTe5d02VmTB80Pw==", - "dev": true } } }, @@ -4921,7 +4579,7 @@ "integrity": "sha1-TV5DPBhSYd3mI8o/RMWGvPXErRQ=", "dev": true, "requires": { - "duplexer": "~0.1.1" + "duplexer": "0.1.1" } }, "stream-consume": { @@ -4942,8 +4600,8 @@ "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" + "is-fullwidth-code-point": "2.0.0", + "strip-ansi": "4.0.0" } }, "string_decoder": { @@ -4958,8 +4616,8 @@ "integrity": "sha1-xi0RAj6yH+LZsIe+A5om3zsioJ0=", "dev": true, "requires": { - "is-plain-obj": "^1.0.0", - "is-regexp": "^1.0.0" + "is-plain-obj": "1.1.0", + "is-regexp": "1.0.0" } }, "strip-ansi": { @@ -4968,7 +4626,7 @@ "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", "dev": true, "requires": { - "ansi-regex": "^3.0.0" + "ansi-regex": "3.0.0" } }, "strip-bom": { @@ -4977,16 +4635,8 @@ "integrity": "sha1-hbiGLzhEtabV7IRnqTWYFzo295Q=", "dev": true, "requires": { - "first-chunk-stream": "^1.0.0", - "is-utf8": "^0.2.0" - }, - "dependencies": { - "is-utf8": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-utf8/-/is-utf8-0.2.1.tgz", - "integrity": "sha1-Sw2hRCEE0bM2NA6AeX6GXPOffXI=", - "dev": true - } + "first-chunk-stream": "1.0.0", + "is-utf8": "0.2.1" } }, "strip-eof": { @@ -5003,6 +4653,7 @@ }, "through": { "version": "2.3.8", + "resolved": false, "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, @@ -5012,8 +4663,8 @@ "integrity": "sha1-AARWmzfHx0ujnEPzzteNGtlBQL4=", "dev": true, "requires": { - "readable-stream": "^2.1.5", - "xtend": "~4.0.1" + "readable-stream": "2.3.6", + "xtend": "4.0.1" }, "dependencies": { "isarray": { @@ -5023,25 +4674,35 @@ "dev": true }, "readable-stream": { - "version": "2.3.3", - "integrity": "sha1-No8lEtefnUb9/HE0mueHi7weuVw=", - "dev": true, - "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~1.0.6", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.0.3", - "util-deprecate": "~1.0.1" + "version": "2.3.6", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-2.3.6.tgz", + "integrity": "sha512-tQtKA9WIAhBF3+VLAseyMqZeBjW0AHJoxOtYqSUZNJxauErmLbVm2FW1y+J/YA9dUrAC39ITejlZWhVIwawkKw==", + "dev": true, + "requires": { + "core-util-is": "1.0.2", + "inherits": "2.0.3", + "isarray": "1.0.0", + "process-nextick-args": "2.0.0", + "safe-buffer": "5.1.1", + "string_decoder": "1.1.1", + "util-deprecate": "1.0.2" + }, + "dependencies": { + "process-nextick-args": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.0.tgz", + "integrity": "sha512-MtEC1TqN0EU5nephaJ4rAtThHtC86dNN9qCuEhtshvpVBkAW5ZO7BASN9REnF9eoXGcRub+pFuKEpOHE+HbEMw==", + "dev": true + } } }, "string_decoder": { - "version": "1.0.3", - "integrity": "sha1-D8Z9fBQYJd6UKC3VNr7GubzoYKs=", + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, "requires": { - "safe-buffer": "~5.1.0" + "safe-buffer": "5.1.1" } } } @@ -5052,8 +4713,8 @@ "integrity": "sha1-YLxVoNrLdghdsfna6Zq0P4PWIuw=", "dev": true, "requires": { - "through2": "~2.0.0", - "xtend": "~4.0.0" + "through2": "2.0.3", + "xtend": "4.0.1" }, "dependencies": { "xtend": { @@ -5070,7 +4731,7 @@ "integrity": "sha1-3OwD9V3Km3qj5bBPIYF+tW5jWIo=", "dev": true, "requires": { - "os-homedir": "^1.0.0" + "os-homedir": "1.0.2" } }, "time-stamp": { @@ -5085,8 +4746,8 @@ "integrity": "sha1-GGX0PZ50sIItufFFt4z/fQ98hJs=", "dev": true, "requires": { - "is-absolute": "^1.0.0", - "is-negated-glob": "^1.0.0" + "is-absolute": "1.0.0", + "is-negated-glob": "1.0.0" }, "dependencies": { "is-absolute": { @@ -5095,8 +4756,8 @@ "integrity": "sha512-dOWoqflvcydARa360Gvv18DZ/gRuHKi2NU/wU5X1ZFzdYfH29nkiNZsF3mp4OJ3H4yo9Mx8A/uAGNzpzPN3yBA==", "dev": true, "requires": { - "is-relative": "^1.0.0", - "is-windows": "^1.0.1" + "is-relative": "1.0.0", + "is-windows": "1.0.2" } }, "is-relative": { @@ -5105,7 +4766,7 @@ "integrity": "sha512-Kw/ReK0iqwKeu0MITLFuj0jbPAmEiOsIwyIXvvbfa6QfmN9pkD1M+8pdk7Rl/dTKbH34/XBFMbgD4iMJhLQbGA==", "dev": true, "requires": { - "is-unc-path": "^1.0.0" + "is-unc-path": "1.0.0" } }, "is-unc-path": { @@ -5114,7 +4775,7 @@ "integrity": "sha512-mrGpVd0fs7WWLfVsStvgF6iEJnbjDFZh9/emhRDcGWTduTfNHd9CHeUwH3gYIjdbwo4On6hunkztwOaAw0yllQ==", "dev": true, "requires": { - "unc-path-regex": "^0.1.2" + "unc-path-regex": "0.1.2" } }, "is-windows": { @@ -5131,22 +4792,24 @@ "integrity": "sha1-KXWIt7Dn4KwI4E5nL4XB9JmeF68=", "dev": true, "requires": { - "kind-of": "^3.0.2" + "kind-of": "3.2.2" }, "dependencies": { - "is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "dev": true - }, "kind-of": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-3.2.2.tgz", "integrity": "sha1-MeohpzS6ubuw8yRm2JOupR5KPGQ=", "dev": true, "requires": { - "is-buffer": "^1.1.5" + "is-buffer": "1.1.6" + }, + "dependencies": { + "is-buffer": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", + "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", + "dev": true + } } } } @@ -5157,10 +4820,10 @@ "integrity": "sha512-FWtleNAtZ/Ki2qtqej2CXTOayOH9bHDQF+Q48VpWyDXjbYxA4Yz8iDB31zXOBUlOHHKidDbqGVrTUvQMPmBGBw==", "dev": true, "requires": { - "define-property": "^2.0.2", - "extend-shallow": "^3.0.2", - "regex-not": "^1.0.2", - "safe-regex": "^1.1.0" + "define-property": "2.0.2", + "extend-shallow": "3.0.2", + "regex-not": "1.0.2", + "safe-regex": "1.1.0" }, "dependencies": { "extend-shallow": { @@ -5169,8 +4832,8 @@ "integrity": "sha1-Jqcarwc7OfshJxcnRhMcJwQCjbg=", "dev": true, "requires": { - "assign-symbols": "^1.0.0", - "is-extendable": "^1.0.1" + "assign-symbols": "1.0.0", + "is-extendable": "1.0.1" } }, "is-extendable": { @@ -5179,16 +4842,7 @@ "integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==", "dev": true, "requires": { - "is-plain-object": "^2.0.4" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "is-plain-object": "2.0.4" } } } @@ -5199,8 +4853,8 @@ "integrity": "sha1-fIDBe53+vlmeJzZ+DU3VWQFB2zg=", "dev": true, "requires": { - "is-number": "^3.0.0", - "repeat-string": "^1.6.1" + "is-number": "3.0.0", + "repeat-string": "1.6.1" } }, "to-through": { @@ -5209,7 +4863,7 @@ "integrity": "sha1-/JKtq6ByZHvAtn1rA2ZKoZUJOvY=", "dev": true, "requires": { - "through2": "^2.0.3" + "through2": "2.0.3" } }, "tslib": { @@ -5219,23 +4873,23 @@ "dev": true }, "tslint": { - "version": "5.9.1", - "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.9.1.tgz", - "integrity": "sha1-ElX4ej/1frCw4fDmEKi0dIBGya4=", - "dev": true, - "requires": { - "babel-code-frame": "^6.22.0", - "builtin-modules": "^1.1.1", - "chalk": "^2.3.0", - "commander": "^2.12.1", - "diff": "^3.2.0", - "glob": "^7.1.1", - "js-yaml": "^3.7.0", - "minimatch": "^3.0.4", - "resolve": "^1.3.2", - "semver": "^5.3.0", - "tslib": "^1.8.0", - "tsutils": "^2.12.1" + "version": "5.11.0", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-5.11.0.tgz", + "integrity": "sha1-mPMMAurjzecAYgHkwzywi0hYHu0=", + "dev": true, + "requires": { + "babel-code-frame": "6.26.0", + "builtin-modules": "1.1.1", + "chalk": "2.4.1", + "commander": "2.15.1", + "diff": "3.5.0", + "glob": "7.1.3", + "js-yaml": "3.11.0", + "minimatch": "3.0.4", + "resolve": "1.8.1", + "semver": "5.5.1", + "tslib": "1.9.0", + "tsutils": "2.29.0" }, "dependencies": { "ansi-styles": { @@ -5244,45 +4898,42 @@ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, "requires": { - "color-convert": "^1.9.0" + "color-convert": "1.9.1" } }, "chalk": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.3.2.tgz", - "integrity": "sha512-ZM4j2/ld/YZDc3Ma8PgN7gyAk+kHMMMyzLNryCPGhWrsfAuDVeuid5bpRFTDgMH9JBK2lA4dyyAkkZYF/WcqDQ==", + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.1.tgz", + "integrity": "sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==", "dev": true, "requires": { - "ansi-styles": "^3.2.1", - "escape-string-regexp": "^1.0.5", - "supports-color": "^5.3.0" + "ansi-styles": "3.2.1", + "escape-string-regexp": "1.0.5", + "supports-color": "5.5.0" } }, - "commander": { - "version": "2.15.1", - "resolved": "https://registry.npmjs.org/commander/-/commander-2.15.1.tgz", - "integrity": "sha512-VlfT9F3V0v+jr4yxPc5gg9s62/fIVWsd2Bk2iD435um1NlGMYdVCq+MjcXnhYq2icNOizHr1kK+5TI6H0Hy0ag==", - "dev": true - }, - "has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", - "dev": true - }, "semver": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.0.tgz", - "integrity": "sha512-4SJ3dm0WAwWy/NVeioZh5AntkdJoWKxHxcmyP622fOkgHa4z3R0TdBJICINyaSDE6uNwVc8gZr+ZinwZAH4xIA==", + "version": "5.5.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.5.1.tgz", + "integrity": "sha512-PqpAxfrEhlSUWge8dwIp4tZnQ25DIOthpiaHNIthsjEFQD6EvqUKUDM7L8O2rShkFccYo1VjJR0coWfNkCubRw==", "dev": true }, "supports-color": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.3.0.tgz", - "integrity": "sha512-0aP01LLIskjKs3lq52EC0aGBAJhLq7B2Rd8HC/DR/PtNNpcLilNmHC12O+hu0usQpo7wtHNRqtrhBwtDb0+dNg==", + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "3.0.0" + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", "dev": true, "requires": { - "has-flag": "^3.0.0" + "tslib": "1.9.0" } } } @@ -5293,8 +4944,8 @@ "integrity": "sha1-OekvMZVq0qZsAGHDUfqWwICK4Pg=", "dev": true, "requires": { - "doctrine": "^0.7.2", - "tslint": "^3.15.1" + "doctrine": "0.7.2", + "tslint": "3.15.1" }, "dependencies": { "balanced-match": { @@ -5309,7 +4960,7 @@ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, "requires": { - "balanced-match": "^1.0.0", + "balanced-match": "1.0.0", "concat-map": "0.0.1" } }, @@ -5331,7 +4982,7 @@ "integrity": "sha1-N5MKpdgWt3fANEXhlmzGeQpMCxY=", "dev": true, "requires": { - "glob": "~5.0.0" + "glob": "5.0.15" }, "dependencies": { "glob": { @@ -5340,11 +4991,11 @@ "integrity": "sha1-G8k2ueAvSmA/zCIuz3Yz0wuLk7E=", "dev": true, "requires": { - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "2 || 3", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" + "inflight": "1.0.6", + "inherits": "2.0.3", + "minimatch": "3.0.4", + "once": "1.4.0", + "path-is-absolute": "1.0.1" } } } @@ -5361,7 +5012,7 @@ "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, "requires": { - "brace-expansion": "^1.1.7" + "brace-expansion": "1.1.11" } }, "once": { @@ -5370,7 +5021,7 @@ "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, "requires": { - "wrappy": "1" + "wrappy": "1.0.2" } }, "path-parse": { @@ -5385,7 +5036,7 @@ "integrity": "sha512-mw7JQNu5ExIkcw4LPih0owX/TZXjD/ZUF/ZQ/pDnkw3ZKhDcZZw5klmBlj6gVMwjQ3Pz5Jgu7F3d0jcDVuEWdw==", "dev": true, "requires": { - "path-parse": "^1.0.5" + "path-parse": "1.0.5" } }, "tslint": { @@ -5394,13 +5045,13 @@ "integrity": "sha1-2hZcqT2P3CwIa1EWXuG6y0jJjqU=", "dev": true, "requires": { - "colors": "^1.1.2", - "diff": "^2.2.1", - "findup-sync": "~0.3.0", - "glob": "^7.0.3", - "optimist": "~0.6.0", - "resolve": "^1.1.7", - "underscore.string": "^3.3.4" + "colors": "1.2.1", + "diff": "2.2.3", + "findup-sync": "0.3.0", + "glob": "7.1.3", + "optimist": "0.6.1", + "resolve": "1.6.0", + "underscore.string": "3.3.4" } }, "wrappy": { @@ -5417,7 +5068,7 @@ "integrity": "sha512-5AnfTGlfpUzpRHLmoojPBKFTTmbjnwgdaTHMdllausa4GBPya5u36i9ddrTX4PhetGZvd4JUYIpAmgHqVnsctg==", "dev": true, "requires": { - "tsutils": "^2.12.1" + "tsutils": "2.22.2" } }, "tsutils": { @@ -5426,7 +5077,7 @@ "integrity": "sha512-u06FUSulCJ+Y8a2ftuqZN6kIGqdP2yJjUPEngXqmdPND4UQfb04igcotH+dw+IFr417yP6muCLE8/5/Qlfnx0w==", "dev": true, "requires": { - "tslib": "^1.8.1" + "tslib": "1.9.0" } }, "typemoq": { @@ -5435,9 +5086,9 @@ "integrity": "sha512-DtRNLb7x8yCTv/KHlwes+NI+aGb4Vl1iPC63Hhtcvk1DpxSAZzKWQv0RQFY0jX2Uqj0SDBNl8Na4e6MV6TNDgw==", "dev": true, "requires": { - "circular-json": "^0.3.1", - "lodash": "^4.17.4", - "postinstall-build": "^5.0.1" + "circular-json": "0.3.3", + "lodash": "4.17.10", + "postinstall-build": "5.0.1" }, "dependencies": { "lodash": { @@ -5449,9 +5100,9 @@ } }, "typescript": { - "version": "2.7.2", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.7.2.tgz", - "integrity": "sha512-p5TCYZDAO0m4G344hD+wx/LATebLWZNkkh2asWUFqSsD2OrDNhbAHuSjobrmsUmdzjJjEeZVU9g1h3O6vpstnw==", + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-3.1.6.tgz", + "integrity": "sha512-tDMYfVtvpb96msS1lDX9MEdHrW4yOuZ4Kdc4Him9oU796XldPYF/t2+uKoX0BBa0hXXwDlqYQbXY5Rzjzc5hBA==", "dev": true }, "unc-path-regex": { @@ -5466,8 +5117,8 @@ "integrity": "sha1-LCo/n4PmR2L9xF5s6sZRQoZCE9s=", "dev": true, "requires": { - "sprintf-js": "^1.0.3", - "util-deprecate": "^1.0.2" + "sprintf-js": "1.0.3", + "util-deprecate": "1.0.2" }, "dependencies": { "util-deprecate": { @@ -5484,10 +5135,10 @@ "integrity": "sha1-XHHDTLW61dzr4+oM0IIHulqhrqQ=", "dev": true, "requires": { - "arr-union": "^3.1.0", - "get-value": "^2.0.6", - "is-extendable": "^0.1.1", - "set-value": "^0.4.3" + "arr-union": "3.1.0", + "get-value": "2.0.6", + "is-extendable": "0.1.1", + "set-value": "0.4.3" }, "dependencies": { "arr-union": { @@ -5502,16 +5153,7 @@ "integrity": "sha1-Ua99YUrZqfYQ6huvu5idaxxWiQ8=", "dev": true, "requires": { - "is-extendable": "^0.1.0" - } - }, - "is-plain-object": { - "version": "2.0.4", - "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", - "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", - "dev": true, - "requires": { - "isobject": "^3.0.1" + "is-extendable": "0.1.1" } }, "set-value": { @@ -5520,10 +5162,10 @@ "integrity": "sha1-fbCPnT0i3H945Trzw79GZuzfzPE=", "dev": true, "requires": { - "extend-shallow": "^2.0.1", - "is-extendable": "^0.1.1", - "is-plain-object": "^2.0.1", - "to-object-path": "^0.3.0" + "extend-shallow": "2.0.1", + "is-extendable": "0.1.1", + "is-plain-object": "2.0.4", + "to-object-path": "0.3.0" } } } @@ -5540,8 +5182,8 @@ "integrity": "sha1-g3aHP30jNRef+x5vw6jtDfyKtVk=", "dev": true, "requires": { - "has-value": "^0.3.1", - "isobject": "^3.0.0" + "has-value": "0.3.1", + "isobject": "3.0.1" }, "dependencies": { "has-value": { @@ -5550,9 +5192,9 @@ "integrity": "sha1-ex9YutpiyoJ+wKIHgCVlSEWZXh8=", "dev": true, "requires": { - "get-value": "^2.0.3", - "has-values": "^0.1.4", - "isobject": "^2.0.0" + "get-value": "2.0.6", + "has-values": "0.1.4", + "isobject": "2.1.0" }, "dependencies": { "isobject": { @@ -5587,13 +5229,10 @@ "dev": true }, "use": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.0.tgz", - "integrity": "sha512-6UJEQM/L+mzC3ZJNM56Q4DFGLX/evKGRg15UJHGB9X5j5Z3AFbgZvjUh2yq/UJUY4U5dh7Fal++XbNg1uzpRAw==", - "dev": true, - "requires": { - "kind-of": "^6.0.2" - } + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==", + "dev": true }, "user-home": { "version": "1.1.1", @@ -5613,7 +5252,7 @@ "integrity": "sha1-qrGh+jDUX4jdMhFIh1rALAtV5bQ=", "dev": true, "requires": { - "user-home": "^1.1.1" + "user-home": "1.1.1" } }, "value-or-function": { @@ -5628,8 +5267,8 @@ "integrity": "sha1-sEVbOPxeDPMNQyUTLkYZcMIJHN4=", "dev": true, "requires": { - "clone": "^1.0.0", - "clone-stats": "^0.0.1", + "clone": "1.0.4", + "clone-stats": "0.0.1", "replace-ext": "0.0.1" } }, @@ -5639,14 +5278,14 @@ "integrity": "sha1-mmhRzhysHBzqX+hsCTHWIMLPqeY=", "dev": true, "requires": { - "defaults": "^1.0.0", - "glob-stream": "^3.1.5", - "glob-watcher": "^0.0.6", - "graceful-fs": "^3.0.0", - "mkdirp": "^0.5.0", - "strip-bom": "^1.0.0", - "through2": "^0.6.1", - "vinyl": "^0.4.0" + "defaults": "1.0.3", + "glob-stream": "3.1.18", + "glob-watcher": "0.0.6", + "graceful-fs": "3.0.11", + "mkdirp": "0.5.1", + "strip-bom": "1.0.0", + "through2": "0.6.5", + "vinyl": "0.4.6" }, "dependencies": { "clone": { @@ -5655,28 +5294,16 @@ "integrity": "sha1-xhJqkK1Pctv1rNskPMN3JP6T/B8=", "dev": true }, - "core-util-is": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", - "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", - "dev": true - }, - "inherits": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", - "integrity": "sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=", - "dev": true - }, "readable-stream": { "version": "1.0.34", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", + "resolved": "http://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz", "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=", "dev": true, "requires": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.1", + "core-util-is": "1.0.2", + "inherits": "2.0.3", "isarray": "0.0.1", - "string_decoder": "~0.10.x" + "string_decoder": "0.10.31" } }, "through2": { @@ -5685,8 +5312,8 @@ "integrity": "sha1-QaucZ7KdVyCQcUEOHXp6lozTrUg=", "dev": true, "requires": { - "readable-stream": ">=1.0.33-1 <1.1.0-0", - "xtend": ">=4.0.0 <4.1.0-0" + "readable-stream": "1.0.34", + "xtend": "4.0.1" } }, "vinyl": { @@ -5695,15 +5322,9 @@ "integrity": "sha1-LzVsh6VQolVGHza76ypbqL94SEc=", "dev": true, "requires": { - "clone": "^0.2.0", - "clone-stats": "^0.0.1" + "clone": "0.2.0", + "clone-stats": "0.0.1" } - }, - "xtend": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.1.tgz", - "integrity": "sha1-pcbVMr5lbiPbgg77lDofBJmNY68=", - "dev": true } } }, @@ -5713,13 +5334,13 @@ "integrity": "sha1-kqgAWTo4cDqM2xHYswCtS+Y7PhY=", "dev": true, "requires": { - "append-buffer": "^1.0.2", - "convert-source-map": "^1.5.0", - "graceful-fs": "^4.1.6", - "normalize-path": "^2.1.1", - "now-and-later": "^2.0.0", - "remove-bom-buffer": "^3.0.0", - "vinyl": "^2.0.0" + "append-buffer": "1.0.2", + "convert-source-map": "1.6.0", + "graceful-fs": "4.1.11", + "normalize-path": "2.1.1", + "now-and-later": "2.0.0", + "remove-bom-buffer": "3.0.0", + "vinyl": "2.1.0" }, "dependencies": { "clone": { @@ -5752,29 +5373,29 @@ "integrity": "sha1-Ah+cLPlR1rk5lDyJ617lrdT9kkw=", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.1", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.1", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } } } }, "vscode-debugadapter": { - "version": "1.33.0-pre.2", - "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.33.0-pre.2.tgz", - "integrity": "sha512-H5J1YTjsuiBmdnjl/zvrSTNClfcoz/i4tWDoxvW4FEE3jEfhW2npapiwJkLuzVFnv7tlU0iiJzqhv2iYwBFZoA==", + "version": "1.33.0-pre.3", + "resolved": "https://registry.npmjs.org/vscode-debugadapter/-/vscode-debugadapter-1.33.0-pre.3.tgz", + "integrity": "sha512-/mhE1DUIPLdAbYzRa1jkloGnrmhpdv2gqrwZzGg0XW4Y4+vJI4IqiUY2DJGpV7ztXLx1DC8Z2RkY0ZN9HLVpeg==", "requires": { - "mkdirp": "^0.5.1", - "vscode-debugprotocol": "1.32.0" + "mkdirp": "0.5.1", + "vscode-debugprotocol": "1.33.0-pre.0" }, "dependencies": { "vscode-debugprotocol": { - "version": "1.32.0", - "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.32.0.tgz", - "integrity": "sha512-x3+HV+BkLqfl1ZuDJEILAv1sT5mDceuPThDXD12hwXAEjAdfc6MLQFvaoVPuO6C6gb+lHQTd0R9FNkCflEJHbA==" + "version": "1.33.0-pre.0", + "resolved": "https://registry.npmjs.org/vscode-debugprotocol/-/vscode-debugprotocol-1.33.0-pre.0.tgz", + "integrity": "sha512-LRTpPDLkq7fcjvRr8Ttzo+1tiFXuxOcYQ5xhN7i/dYN6ISwg9hcQv4W74n8JBxz1nXdVpbS52KLKQMiZhBwpgg==" } } }, @@ -5794,17 +5415,17 @@ "integrity": "sha512-6XyESZOyNowLza/fV6Kfmwx0+0iNwa4OkTsBRepwP+eaR7JYnf/ohPaFDX7Egqe4330swtRDCbqr+7i3Q9/TvA==", "dev": true, "requires": { - "clone": "^2.1.1", - "event-stream": "^3.3.4", - "glob": "^7.1.2", - "gulp-util": "^3.0.8", - "iconv-lite": "^0.4.19", - "is": "^3.2.1", - "source-map": "^0.6.1", - "typescript": "^2.6.2", - "vinyl": "^2.1.0", - "xml2js": "^0.4.19", - "yargs": "^10.1.1" + "clone": "2.1.2", + "event-stream": "3.3.4", + "glob": "7.1.3", + "gulp-util": "3.0.8", + "iconv-lite": "0.4.24", + "is": "3.2.1", + "source-map": "0.6.1", + "typescript": "2.9.2", + "vinyl": "2.2.0", + "xml2js": "0.4.19", + "yargs": "10.1.2" }, "dependencies": { "clone": { @@ -5825,37 +5446,35 @@ "integrity": "sha1-3mMSg3P8v3w8z6TeWkgMRaZ5WOs=", "dev": true }, + "typescript": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-2.9.2.tgz", + "integrity": "sha512-Gr4p6nFNaoufRIY4NMdpQRNmgxVIGMs4Fcu/ujdYk3nAZqk7supzBE9idmvfZIlH/Cuj//dvi+019qEue9lV0w==", + "dev": true + }, "vinyl": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/vinyl/-/vinyl-2.2.0.tgz", "integrity": "sha512-MBH+yP0kC/GQ5GwBqrTPTzEfiiLjta7hTtvQtbxBgTeSXsmKQRQecjibMbxIXzVT3Y9KJK+drOz1/k+vsu8Nkg==", "dev": true, "requires": { - "clone": "^2.1.1", - "clone-buffer": "^1.0.0", - "clone-stats": "^1.0.0", - "cloneable-readable": "^1.0.0", - "remove-trailing-separator": "^1.0.1", - "replace-ext": "^1.0.0" + "clone": "2.1.2", + "clone-buffer": "1.0.0", + "clone-stats": "1.0.0", + "cloneable-readable": "1.1.1", + "remove-trailing-separator": "1.1.0", + "replace-ext": "1.0.0" } } } }, "which": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.0.tgz", - "integrity": "sha512-xcJpopdamTuY5duC/KnTTNBraPK54YwpenP4lzxU8H91GudWpFv38u0CKjclE1Wi2EH2EDz5LRcHcKbCIzqGyg==", + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, "requires": { - "isexe": "^2.0.0" - }, - "dependencies": { - "isexe": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", - "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", - "dev": true - } + "isexe": "2.0.0" } }, "which-module": { @@ -5872,12 +5491,12 @@ }, "wrap-ansi": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", + "resolved": "http://registry.npmjs.org/wrap-ansi/-/wrap-ansi-2.1.0.tgz", "integrity": "sha1-2Pw9KE3QV5T+hJc8rs3Rz4JP3YU=", "dev": true, "requires": { - "string-width": "^1.0.1", - "strip-ansi": "^3.0.1" + "string-width": "1.0.2", + "strip-ansi": "3.0.1" }, "dependencies": { "ansi-regex": { @@ -5892,7 +5511,7 @@ "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", "dev": true, "requires": { - "number-is-nan": "^1.0.0" + "number-is-nan": "1.0.1" } }, "string-width": { @@ -5901,9 +5520,9 @@ "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", "dev": true, "requires": { - "code-point-at": "^1.0.0", - "is-fullwidth-code-point": "^1.0.0", - "strip-ansi": "^3.0.0" + "code-point-at": "1.1.0", + "is-fullwidth-code-point": "1.0.0", + "strip-ansi": "3.0.1" } }, "strip-ansi": { @@ -5912,7 +5531,7 @@ "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", "dev": true, "requires": { - "ansi-regex": "^2.0.0" + "ansi-regex": "2.1.1" } } } @@ -5927,7 +5546,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-6.0.0.tgz", "integrity": "sha512-c2UlYcAZp1VS8AORtpq6y4RJIkJ9dQz18W32SpR/qXGfLDZ2jU4y4wKvvZwqbi7U6gxFQTeE+urMbXU/tsDy4w==", "requires": { - "async-limiter": "~1.0.0" + "async-limiter": "1.0.0" } }, "xml2js": { @@ -5936,8 +5555,8 @@ "integrity": "sha512-esZnJZJOiJR9wWKMyuvSE1y6Dq5LCuJanqhxslH2bxM6duahNZ+HMpCLhBQGZkbX6xRf8x1Y2eJlgt2q3qo49Q==", "dev": true, "requires": { - "sax": ">=0.6.0", - "xmlbuilder": "~9.0.1" + "sax": "1.2.4", + "xmlbuilder": "9.0.7" } }, "xmlbuilder": { @@ -5970,18 +5589,18 @@ "integrity": "sha512-ivSoxqBGYOqQVruxD35+EyCFDYNEFL/Uo6FcOnz+9xZdZzK0Zzw4r4KhbrME1Oo2gOggwJod2MnsdamSG7H9ig==", "dev": true, "requires": { - "cliui": "^4.0.0", - "decamelize": "^1.1.1", - "find-up": "^2.1.0", - "get-caller-file": "^1.0.1", - "os-locale": "^2.0.0", - "require-directory": "^2.1.1", - "require-main-filename": "^1.0.1", - "set-blocking": "^2.0.0", - "string-width": "^2.0.0", - "which-module": "^2.0.0", - "y18n": "^3.2.1", - "yargs-parser": "^8.1.0" + "cliui": "4.1.0", + "decamelize": "1.2.0", + "find-up": "2.1.0", + "get-caller-file": "1.0.3", + "os-locale": "2.1.0", + "require-directory": "2.1.1", + "require-main-filename": "1.0.1", + "set-blocking": "2.0.0", + "string-width": "2.1.1", + "which-module": "2.0.0", + "y18n": "3.2.1", + "yargs-parser": "8.1.0" } }, "yargs-parser": { @@ -5990,7 +5609,7 @@ "integrity": "sha512-yP+6QqN8BmrgW2ggLtTbdrOyBNSI7zBa4IykmiV5R1wl1JWNxQvWhMfMdmzIYtKU7oP3OOInY/tl2ov3BDjnJQ==", "dev": true, "requires": { - "camelcase": "^4.1.0" + "camelcase": "4.1.0" } } } diff --git a/package.json b/package.json index 4948cf687..2c9c659bf 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "vscode-chrome-debug-core", "displayName": "vscode-chrome-debug-core", - "version": "6.7.35", + "version": "10.0.0", "description": "A library for building VS Code debug adapters for targets that support the Chrome Remote Debug Protocol", "repository": { "type": "git", @@ -16,9 +16,14 @@ "color": "^3.0.0", "devtools-protocol": "0.0.588169", "glob": "^7.1.3", + "inversify": "^5.0.1", + "inversify-inject-decorators": "^3.1.0", + "lodash": "^4.17.11", "noice-json-rpc": "^1.2.0", + "reflect-metadata": "^0.1.12", + "semver": "^5.6.0", "source-map": "^0.6.1", - "vscode-debugadapter": "^1.33.0-pre.2", + "vscode-debugadapter": "^1.33.0-pre.3", "vscode-debugprotocol": "^1.32.0", "vscode-nls": "^4.0.0", "ws": "^6.0.0" @@ -26,10 +31,13 @@ "devDependencies": { "@types/color": "^3.0.0", "@types/glob": "^5.0.35", + "@types/inversify-inject-decorators": "^2.0.0", + "@types/lodash": "^4.14.116", "@types/minimatch": "^2.0.29", "@types/mocha": "^2.2.32", "@types/mockery": "^1.4.29", "@types/node": "^8.0.58", + "@types/semver": "^5.5.0", "@types/ws": "^6.0.0", "del": "^2.2.2", "event-stream": "^3.3.4", @@ -44,11 +52,11 @@ "mocha": "^5.2.0", "mockery": "^1.7.0", "run-sequence": "^1.2.2", - "tslint": "^5.9.1", + "tslint": "^5.11.0", "tslint-eslint-rules": "^1.5.0", "tslint-microsoft-contrib": "^5.0.3", "typemoq": "^2.1.0", - "typescript": "^2.7.2", + "typescript": "^3.1.6", "vscode-nls-dev": "^3.2.2" }, "scripts": { diff --git a/src/chrome/breakOnLoadHelper.ts b/src/chrome/breakOnLoadHelper.ts index b54dfc7b1..ec2907e5e 100644 --- a/src/chrome/breakOnLoadHelper.ts +++ b/src/chrome/breakOnLoadHelper.ts @@ -5,32 +5,39 @@ import { logger } from 'vscode-debugadapter'; import { ISetBreakpointResult, BreakOnLoadStrategy } from '../debugAdapterInterfaces'; -import { Protocol as Crdp } from 'devtools-protocol'; -import { ChromeDebugAdapter } from './chromeDebugAdapter'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { ChromeDebugLogic } from './chromeDebugAdapter'; import * as ChromeUtils from './chromeUtils'; import * as assert from 'assert'; import { InternalSourceBreakpoint } from './internalSourceBreakpoint'; -import { utils, Version } from '..'; - -export interface UrlRegexAndFileSet { +import { Version, parseResourceIdentifier } from '..'; +import { LocationInScript } from './internal/locations/location'; +import { BreakpointsLogic } from './internal/breakpoints/features/breakpointsLogic'; +import { IResourceIdentifier, newResourceIdentifierMap } from './internal/sources/resourceIdentifier'; +import { PausedEvent } from './cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { IDOMInstrumentationBreakpoints } from './cdtpDebuggee/features/cdtpDOMInstrumentationBreakpoints'; + +export interface IUrlRegexAndFileSet { urlRegex: string; - fileSet: Set; + fileSet: Set; } export class BreakOnLoadHelper { - private _doesDOMInstrumentationRecieveExtraEvent = false; + public _doesDOMInstrumentationRecieveExtraEvent = false; private _instrumentationBreakpointSet = false; // Break on load: Store some mapping between the requested file names, the regex for the file, and the chrome breakpoint id to perform lookup operations efficiently - private _stopOnEntryBreakpointIdToRequestedFileName = new Map(); - private _stopOnEntryRequestedFileNameToBreakpointId = new Map(); + private _stopOnEntryBreakpointIdToRequestedFileName = new Map(); + private _stopOnEntryRequestedFileNameToBreakpointId = newResourceIdentifierMap(); private _stopOnEntryRegexToBreakpointId = new Map(); - private _chromeDebugAdapter: ChromeDebugAdapter; + private _chromeDebugAdapter: ChromeDebugLogic; private _breakOnLoadStrategy: BreakOnLoadStrategy; - public constructor(chromeDebugAdapter: ChromeDebugAdapter, breakOnLoadStrategy: BreakOnLoadStrategy) { + public constructor(chromeDebugAdapter: ChromeDebugLogic, breakOnLoadStrategy: BreakOnLoadStrategy, + public readonly _breakpointsLogic: BreakpointsLogic, + private readonly _domInstrumentationBreakpoints: IDOMInstrumentationBreakpoints) { this.validateStrategy(breakOnLoadStrategy); this._chromeDebugAdapter = chromeDebugAdapter; this._breakOnLoadStrategy = breakOnLoadStrategy; @@ -42,11 +49,11 @@ export class BreakOnLoadHelper { } } - public get stopOnEntryRequestedFileNameToBreakpointId(): Map { + public get stopOnEntryRequestedFileNameToBreakpointId(): Map { return this._stopOnEntryRequestedFileNameToBreakpointId; } - public get stopOnEntryBreakpointIdToRequestedFileName(): Map { + public get stopOnEntryBreakpointIdToRequestedFileName(): Map { return this._stopOnEntryBreakpointIdToRequestedFileName; } @@ -54,14 +61,10 @@ export class BreakOnLoadHelper { return this._instrumentationBreakpointSet; } - private getScriptUrlFromId(scriptId: string): string { - return utils.canonicalizeUrl(this._chromeDebugAdapter.scriptsById.get(scriptId).url); - } - public async setBrowserVersion(version: Version): Promise { // On version 69 Chrome stopped sending an extra event for DOM Instrumentation: See https://bugs.chromium.org/p/chromium/issues/detail?id=882909 // On Chrome 68 we were relying on that event to make Break on load work on breakpoints on the first line of a file. On Chrome 69 we need an alternative way to make it work. - this._doesDOMInstrumentationRecieveExtraEvent = !version.isAtLeastVersion(69, 0); + this._doesDOMInstrumentationRecieveExtraEvent = !version.isAtLeastVersion('69.0.0'); } /** @@ -69,58 +72,60 @@ export class BreakOnLoadHelper { * Checks if the event is caused by a stopOnEntry breakpoint of using the regex approach, or the paused event due to the Chrome's instrument approach * Returns whether we should continue or not on this paused event */ - public async handleOnPaused(notification: Crdp.Debugger.PausedEvent): Promise { - if (notification.hitBreakpoints && notification.hitBreakpoints.length) { - // If breakOnLoadStrategy is set to regex, we may have hit a stopOnEntry breakpoint we put. - // So we need to resolve all the pending breakpoints in this script and then decide to continue or not - if (this._breakOnLoadStrategy === 'regex') { - let shouldContinue = await this.handleStopOnEntryBreakpointAndContinue(notification); - return shouldContinue; - } - } else if (this.isInstrumentationPause(notification)) { - // This is fired when Chrome stops on the first line of a script when using the setInstrumentationBreakpoint API - const pausedScriptId = notification.callFrames[0].location.scriptId; - - // Now we wait for all the pending breakpoints to be resolved and then continue - await this._chromeDebugAdapter.getBreakpointsResolvedDefer(pausedScriptId).promise; - logger.log('BreakOnLoadHelper: Finished waiting for breakpoints to get resolved.'); - let shouldContinue = this._doesDOMInstrumentationRecieveExtraEvent || await this.handleStopOnEntryBreakpointAndContinue(notification); - return shouldContinue; - } + public async handleOnPaused(_notification: PausedEvent): Promise { + // function hasHitBreakpoints(notification: PausedEvent): notification is MakePropertyRequired { + // return !!notification.hitBreakpoints && notification.hitBreakpoints.length > 0; + // } + + // if (hasHitBreakpoints(notification)) { + // // If breakOnLoadStrategy is set to regex, we may have hit a stopOnEntry breakpoint we put. + // // So we need to resolve all the pending breakpoints in this script and then decide to continue or not + // if (this._breakOnLoadStrategy === 'regex') { + // let shouldContinue = await this.handleStopOnEntryBreakpointAndContinue(notification); + // return shouldContinue; + // } + // } else if (this.isInstrumentationPause(notification)) { + // // This is fired when Chrome stops on the first line of a script when using the setInstrumentationBreakpoint API + // const pausedScript = notification.callFrames[0].location.script; + + // // Now we wait for all the pending breakpoints to be resolved and then continue + // await this._breakpointsLogic.getBreakpointsResolvedDefer(pausedScript).promise; + // logger.log('BreakOnLoadHelper: Finished waiting for breakpoints to get resolved.'); + // let shouldContinue = this._doesDOMInstrumentationRecieveExtraEvent || await this.handleStopOnEntryBreakpointAndContinue(notification); + // return shouldContinue; + // } return false; } - private isInstrumentationPause(notification: Crdp.Debugger.PausedEvent): boolean { - return (notification.reason === 'EventListener' && notification.data.eventName === 'instrumentation:scriptFirstStatement') || - (notification.reason === 'ambiguous' && Array.isArray(notification.data.reasons) && - notification.data.reasons.every(r => r.reason === 'EventListener' && r.auxData.eventName === 'instrumentation:scriptFirstStatement')); + isInstrumentationPause(_notification: PausedEvent): any { + throw new Error('Method not implemented.'); } /** * Returns whether we should continue on hitting a stopOnEntry breakpoint * Only used when using regex approach for break on load */ - private async shouldContinueOnStopOnEntryBreakpoint(pausedLocation: Crdp.Debugger.Location): Promise { + private async shouldContinueOnStopOnEntryBreakpoint(_pausedLocation: LocationInScript): Promise { // If the file has no unbound breakpoints or none of the resolved breakpoints are at (1,1), we should continue after hitting the stopOnEntry breakpoint let shouldContinue = true; - // Important: For the logic that verifies if a user breakpoint is set in the paused location, we need to resolve pending breakpoints, and commit them, before - // using committedBreakpointsByUrl for our logic. - await this._chromeDebugAdapter.getBreakpointsResolvedDefer(pausedLocation.scriptId).promise; - - const pausedScriptUrl = this.getScriptUrlFromId(pausedLocation.scriptId); - // Important: We need to get the committed breakpoints only after all the pending breakpoints for this file have been resolved. If not this logic won't work - const committedBps = this._chromeDebugAdapter.committedBreakpointsByUrl.get(pausedScriptUrl) || []; - const anyBreakpointsAtPausedLocation = committedBps.filter(bp => - bp.actualLocation.lineNumber === pausedLocation.lineNumber && bp.actualLocation.columnNumber === pausedLocation.columnNumber).length > 0; - - // If there were any breakpoints at this location (Which generally should be (1,1)) we shouldn't continue - if (anyBreakpointsAtPausedLocation) { - // Here we need to store this information per file, but since we can safely assume that scriptParsed would immediately be followed by onPaused event - // for the breakonload files, this implementation should be fine - shouldContinue = false; - } + // // Important: For the logic that verifies if a user breakpoint is set in the paused location, we need to resolve pending breakpoints, and commit them, before + // // using committedBreakpointsByUrl for our logic. + // await this._breakpointsLogic.getBreakpointsResolvedDefer(pausedLocation.script).promise; + + // const pausedScriptUrl = pausedLocation.script; + // // Important: We need to get the committed breakpoints only after all the pending breakpoints for this file have been resolved. If not this logic won't work + // const committedBps = this._breakpointsLogic.committedBreakpointsByUrl.get(pausedScriptUrl.runtimeSource.identifier) || []; + // const anyBreakpointsAtPausedLocation = committedBps.filter(bp => + // bp.actualLocation.lineNumber === pausedLocation.lineNumber && bp.actualLocation.columnNumber === pausedLocation.columnNumber).length > 0; + + // // If there were any breakpoints at this location (Which generally should be (1,1)) we shouldn't continue + // if (anyBreakpointsAtPausedLocation) { + // // Here we need to store this information per file, but since we can safely assume that scriptParsed would immediately be followed by onPaused event + // // for the breakonload files, this implementation should be fine + // shouldContinue = false; + // } return shouldContinue; } @@ -129,30 +134,29 @@ export class BreakOnLoadHelper { * Handles a script with a stop on entry breakpoint and returns whether we should continue or not on hitting that breakpoint * Only used when using regex approach for break on load */ - private async handleStopOnEntryBreakpointAndContinue(notification: Crdp.Debugger.PausedEvent): Promise { + public async handleStopOnEntryBreakpointAndContinue(notification: PausedEvent): Promise { const hitBreakpoints = notification.hitBreakpoints; let allStopOnEntryBreakpoints = true; - const pausedScriptId = notification.callFrames[0].location.scriptId; - const pausedScriptUrl = this._chromeDebugAdapter.scriptsById.get(pausedScriptId).url; - const mappedUrl = await this._chromeDebugAdapter.pathTransformer.scriptParsed(pausedScriptUrl); + const pausedScriptUrl = notification.callFrames[0].location.script; + const mappedUrl = parseResourceIdentifier((await this._chromeDebugAdapter.pathTransformer.scriptParsed(pausedScriptUrl.runtimeSource.identifier)).textRepresentation); // If there is a breakpoint which is not a stopOnEntry breakpoint, we appear as if we hit that one // This is particularly done for cases when we end up with a user breakpoint and a stopOnEntry breakpoint on the same line for (let bp of hitBreakpoints) { - let regexAndFileNames = this._stopOnEntryBreakpointIdToRequestedFileName.get(bp); + let regexAndFileNames = {} as IUrlRegexAndFileSet; // this._stopOnEntryBreakpointIdToRequestedFileName.get(bp); if (!regexAndFileNames) { - notification.hitBreakpoints = [bp]; + // notification.hitBreakpoints = [bp]; allStopOnEntryBreakpoints = false; } else { - const normalizedMappedUrl = utils.canonicalizeUrl(mappedUrl); + const normalizedMappedUrl = mappedUrl; if (regexAndFileNames.fileSet.has(normalizedMappedUrl)) { regexAndFileNames.fileSet.delete(normalizedMappedUrl); assert(this._stopOnEntryRequestedFileNameToBreakpointId.delete(normalizedMappedUrl), `Expected to delete break-on-load information associated with url: ${normalizedMappedUrl}`); if (regexAndFileNames.fileSet.size === 0) { logger.log(`Stop on entry breakpoint hit for last remaining file. Removing: ${bp} created for: ${normalizedMappedUrl}`); - await this.removeBreakpointById(bp); + // await this.removeBreakpointById(bp); assert(this._stopOnEntryRegexToBreakpointId.delete(regexAndFileNames.urlRegex), `Expected to delete break-on-load information associated with regexp: ${regexAndFileNames.urlRegex}`); } else { logger.log(`Stop on entry breakpoint hit but still has remaining files. Keeping: ${bp} that was hit for: ${normalizedMappedUrl} because it's still needed for: ${Array.from(regexAndFileNames.fileSet.entries()).join(', ')}`); @@ -182,13 +186,13 @@ export class BreakOnLoadHelper { * Adds a stopOnEntry breakpoint for the given script url * Only used when using regex approach for break on load */ - private async addStopOnEntryBreakpoint(url: string): Promise { + private async addStopOnEntryBreakpoint(url: IResourceIdentifier): Promise { let responsePs: ISetBreakpointResult[]; // Check if file already has a stop on entry breakpoint if (!this._stopOnEntryRequestedFileNameToBreakpointId.has(url)) { // Generate regex we need for the file - const normalizedUrl = utils.canonicalizeUrl(url); + const normalizedUrl = url; const urlRegex = ChromeUtils.getUrlRegexForBreakOnLoad(normalizedUrl); // Check if we already have a breakpoint for this regexp since two different files like script.ts and script.js may have the same regexp @@ -223,7 +227,7 @@ export class BreakOnLoadHelper { if (regexAndFileNames !== undefined) { regexAndFileNames.fileSet.add(normalizedUrl); } else { // else create an entry for this breakpoint id - const fileSet = new Set(); + const fileSet = new Set(); fileSet.add(normalizedUrl); this._stopOnEntryBreakpointIdToRequestedFileName.set(breakpointId, { urlRegex, fileSet }); } @@ -237,10 +241,13 @@ export class BreakOnLoadHelper { * Handles the AddBreakpoints request when break on load is active * Takes the action based on the strategy */ - public async handleAddBreakpoints(url: string, breakpoints: InternalSourceBreakpoint[]): Promise { + public async handleAddBreakpoints(url: IResourceIdentifier, breakpoints: InternalSourceBreakpoint[]): Promise<{ + breakpointId?: CDTP.Debugger.BreakpointId; + actualLocation?: LocationInScript; + }[]> { // If the strategy is set to regex, we try to match the file where user put the breakpoint through a regex and tell Chrome to put a stop on entry breakpoint there if (this._breakOnLoadStrategy === 'regex') { - await this.addStopOnEntryBreakpoint(url); + await this.addStopOnEntryBreakpoint(url); } else if (this._breakOnLoadStrategy === 'instrument') { // Else if strategy is to use Chrome's experimental instrumentation API, we stop on all the scripts at the first statement before execution if (!this.instrumentationBreakpointSet) { @@ -249,7 +256,7 @@ export class BreakOnLoadHelper { } // Temporary fix: We return an empty element for each breakpoint that was requested - return breakpoints.map(breakpoint => { return {}; }); + return breakpoints.map(() => { return {}; }); } /** @@ -257,19 +264,16 @@ export class BreakOnLoadHelper { * Only used when using instrument approach for break on load */ private async setInstrumentationBreakpoint(): Promise { - await this._chromeDebugAdapter.chrome.DOMDebugger.setInstrumentationBreakpoint({eventName: 'scriptFirstStatement'}); + await this._domInstrumentationBreakpoints.setInstrumentationBreakpoint({ eventName: 'scriptFirstStatement' }); this._instrumentationBreakpointSet = true; } // Sets a breakpoint on (0,0) for the files matching the given regex - private async setStopOnEntryBreakpoint(urlRegex: string): Promise { - let result = await this._chromeDebugAdapter.chrome.Debugger.setBreakpointByUrl({ urlRegex, lineNumber: 0, columnNumber: 0 }); - return result; - } - - // Removes a breakpoint by it's chrome-crdp-id - private async removeBreakpointById(breakpointId: string): Promise { - return await this._chromeDebugAdapter.chrome.Debugger.removeBreakpoint({breakpointId: breakpointId }); + private async setStopOnEntryBreakpoint(_urlRegex: string): Promise { + // DIEGO TODO: Re-enable this code + // let result = await this._chromeDebugAdapter.chrome.Debugger.setBreakpointByUrl({ urlRegex, lineNumber: 0, columnNumber: 0 }); + // return result; + return Promise.reject(new Error('Not yet implemented')); } /** @@ -278,7 +282,7 @@ export class BreakOnLoadHelper { * set break on load breakpoints. For those files, it is called from onPaused function. * For the default Chrome's API approach, we don't need to call resolvePendingBPs from inside scriptParsed */ - public shouldResolvePendingBPs(mappedUrl: string): boolean { + public shouldResolvePendingBPs(mappedUrl: IResourceIdentifier): boolean { if (this._breakOnLoadStrategy === 'regex' && !this.stopOnEntryRequestedFileNameToBreakpointId.has(mappedUrl)) { return true; } diff --git a/src/chrome/cdtpDebuggee/cdtpPrimitives.ts b/src/chrome/cdtpDebuggee/cdtpPrimitives.ts new file mode 100644 index 000000000..a3078b2b8 --- /dev/null +++ b/src/chrome/cdtpDebuggee/cdtpPrimitives.ts @@ -0,0 +1,19 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IScript } from '../internal/scripts/script'; +import { CDTPScriptUrl } from '../internal/sources/resourceIdentifierSubtypes'; +import { URLRegexp } from '../internal/locations/subtypes'; +import { AlwaysPause, ConditionalPause } from '../internal/breakpoints/bpActionWhenHit'; +import { IResourceIdentifier } from '../internal/sources/resourceIdentifier'; +import { MappableBreakpoint } from '../internal/breakpoints/breakpoint'; +import { IMappedBPRecipie } from '../internal/breakpoints/baseMappedBPRecipie'; + +export type integer = number; +// The IResourceIdentifier is used with the URL that is associated with each Script in CDTP. This should be a URL, but it could also be a string that is not a valid URL +// For that reason we use IResourceIdentifier for this type, instead of IURL +export type CDTPSupportedResources = IScript | IResourceIdentifier | URLRegexp; +export type CDTPSupportedHitActions = AlwaysPause | ConditionalPause; +export type CDTPBPRecipie = IMappedBPRecipie; +export type CDTPBreakpoint = MappableBreakpoint; diff --git a/src/chrome/cdtpDebuggee/eventsProviders/cdtpConsoleEventsProvider.ts b/src/chrome/cdtpDebuggee/eventsProviders/cdtpConsoleEventsProvider.ts new file mode 100644 index 000000000..55310baed --- /dev/null +++ b/src/chrome/cdtpDebuggee/eventsProviders/cdtpConsoleEventsProvider.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { CDTPStackTraceParser } from '../protocolParsers/cdtpStackTraceParser'; +import { inject, injectable } from 'inversify'; +import { CodeFlowStackTrace } from '../../internal/stackTraces/codeFlowStackTrace'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; + +export type ConsoleAPIEventType = 'log' | 'debug' | 'info' | 'error' | 'warning' | 'dir' | 'dirxml' | 'table' | 'trace' | 'clear' | 'startGroup' | 'startGroupCollapsed' | 'endGroup' | 'assert' | 'profile' | 'profileEnd' | 'count' | 'timeEnd'; + +export interface IConsoleAPICalledEvent { + readonly type: ConsoleAPIEventType; + readonly args: CDTP.Runtime.RemoteObject[]; + readonly executionContextId: CDTP.Runtime.ExecutionContextId; + readonly timestamp: CDTP.Runtime.Timestamp; + readonly stackTrace?: CodeFlowStackTrace; + readonly context?: string; +} + +export type onMessageAddedListener = (message: CDTP.Console.MessageAddedEvent) => void; +export type onConsoleAPICalled = (message: IConsoleAPICalledEvent) => void; + +export interface IConsoleEventsProvider { + onMessageAdded(listener: onMessageAddedListener): void; + onConsoleAPICalled(listener: onConsoleAPICalled): void; +} + +class CDTPConsoleEventsFromConsoleProvider extends CDTPEventsEmitterDiagnosticsModule { + protected readonly api = this._protocolApi.Console; + + public readonly onMessageAdded = this.addApiListener('messageAdded', (params: CDTP.Console.MessageAddedEvent) => params); + + constructor( + private readonly _protocolApi: CDTP.ProtocolApi, + domainsEnabler: CDTPDomainsEnabler) { + super(domainsEnabler); + } +} + +class CDTPConsoleEventsFromRuntimeProvider extends CDTPEventsEmitterDiagnosticsModule { + protected readonly api = this._protocolApi.Runtime; + private readonly _stackTraceParser = new CDTPStackTraceParser(this._scriptsRegistry); + + public readonly onConsoleAPICalled = this.addApiListener('consoleAPICalled', async (params: CDTP.Runtime.ConsoleAPICalledEvent) => + ({ + args: params.args, + context: params.context, + executionContextId: params.executionContextId, + timestamp: params.timestamp, + type: params.type, + stackTrace: params.stackTrace && await this._stackTraceParser.toStackTraceCodeFlow(params.stackTrace) + })); + + constructor( + private readonly _protocolApi: CDTP.ProtocolApi, + private _scriptsRegistry: CDTPScriptsRegistry, + domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + } +} + +@injectable() +export class CDTPConsoleEventsProvider implements IConsoleEventsProvider { + private readonly _consoleEventsFromConsoleProvider = new CDTPConsoleEventsFromConsoleProvider(this._protocolApi, this._domainsEnabler); + private readonly _consoleEventsFromRuntimeProvider = new CDTPConsoleEventsFromRuntimeProvider(this._protocolApi, this._scriptsRegistry, this._domainsEnabler); + + public readonly onMessageAdded = (listener: onMessageAddedListener) => this._consoleEventsFromConsoleProvider.onMessageAdded(listener); + public readonly onConsoleAPICalled = (listener: onConsoleAPICalled) => this._consoleEventsFromRuntimeProvider.onConsoleAPICalled(listener); + + constructor( + @inject(TYPES.CDTPClient) private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.CDTPScriptsRegistry) private _scriptsRegistry: CDTPScriptsRegistry, + @inject(TYPES.IDomainsEnabler) private readonly _domainsEnabler: CDTPDomainsEnabler, + ) { + } +} diff --git a/src/chrome/cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider.ts b/src/chrome/cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider.ts new file mode 100644 index 000000000..e274e14cb --- /dev/null +++ b/src/chrome/cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { asyncMap } from '../../collections/async'; +import { CDTPStackTraceParser } from '../protocolParsers/cdtpStackTraceParser'; +import { CDTPBreakpointIdsRegistry } from '../registries/cdtpBreakpointIdsRegistry'; +import { ScriptCallFrame, CodeFlowFrame } from '../../internal/stackTraces/callFrame'; +import { asyncUndefinedOnFailure } from '../../utils/failures'; +import { CDTPLocationParser } from '../protocolParsers/cdtpLocationParser'; +import { Scope } from '../../internal/stackTraces/scopes'; +import { IScript } from '../../internal/scripts/script'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { CodeFlowStackTrace } from '../../internal/stackTraces/codeFlowStackTrace'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { CDTPCallFrameRegistry } from '../registries/cdtpCallFrameRegistry'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; +import { CDTPBPRecipie } from '../cdtpPrimitives'; + +export type PauseEventReason = 'XHR' | 'DOM' | 'EventListener' | 'exception' | 'assert' | 'debugCommand' | 'promiseRejection' | 'OOM' | 'other' | 'ambiguous'; + +export class PausedEvent { + constructor( + public readonly callFrames: ScriptCallFrame[], + public readonly reason: PauseEventReason, + public readonly data: any, + public readonly hitBreakpoints: CDTPBPRecipie[], + public readonly asyncStackTrace: CodeFlowStackTrace | undefined, + public readonly asyncStackTraceId: CDTP.Runtime.StackTraceId | undefined, + public readonly asyncCallStackTraceId: CDTP.Runtime.StackTraceId | undefined) { } +} + +export interface ICDTDebuggeeExecutionEventsProvider { + onPaused(listener: (event: PausedEvent) => void): void; + onResumed(listener: () => void): void; +} + +@injectable() +export class CDTDebuggeeExecutionEventsProvider extends CDTPEventsEmitterDiagnosticsModule implements ICDTDebuggeeExecutionEventsProvider { + protected readonly api = this._protocolApi.Debugger; + + private readonly _cdtpLocationParser = new CDTPLocationParser(this._scriptsRegistry); + private readonly _stackTraceParser = new CDTPStackTraceParser(this._scriptsRegistry); + + public readonly onPaused = this.addApiListener('paused', async (params: CDTP.Debugger.PausedEvent) => { + if (params.callFrames.length === 0) { + throw new Error(`Expected a pause event to have at least a single call frame: ${JSON.stringify(params)}`); + } + + const callFrames = await asyncMap(params.callFrames, (callFrame, index) => this.toCallFrame(index, callFrame)); + + return new PausedEvent(callFrames, params.reason, params.data, await asyncMap(params.hitBreakpoints, hbp => this.getBPFromID(hbp)), + params.asyncStackTrace && await this._stackTraceParser.toStackTraceCodeFlow(params.asyncStackTrace), + params.asyncStackTraceId, params.asyncCallStackTraceId); + }); + + public readonly onResumed = this.addApiListener('resumed', (params: void) => params); + + public readonly onScriptFailedToParse = this.addApiListener('resumed', (params: CDTP.Debugger.ScriptFailedToParseEvent) => params); + + constructor( + @inject(TYPES.CDTPClient) private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.CDTPScriptsRegistry) private _scriptsRegistry: CDTPScriptsRegistry, + @inject(CDTPBreakpointIdsRegistry) private readonly _breakpointIdRegistry: CDTPBreakpointIdsRegistry, + @inject(CDTPCallFrameRegistry) private readonly _callFrameRegistry: CDTPCallFrameRegistry, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + } + + private getBPFromID(hitBreakpoint: CDTP.Debugger.BreakpointId): CDTPBPRecipie { + return this._breakpointIdRegistry.getRecipieByBreakpointId(hitBreakpoint); + } + + private async toCallFrame(index: number, callFrame: CDTP.Debugger.CallFrame): Promise { + const frame = new ScriptCallFrame(await this.toCodeFlowFrame(index, callFrame), + await asyncMap(callFrame.scopeChain, scope => this.toScope(scope)), + callFrame.this, callFrame.returnValue); + + this._callFrameRegistry.registerFrameId(callFrame.callFrameId, frame); + + return frame; + } + + private toCodeFlowFrame(index: number, callFrame: CDTP.Debugger.CallFrame): Promise> { + return this._stackTraceParser.toCodeFlowFrame(index, callFrame, callFrame.location); + } + + private async toScope(scope: CDTP.Debugger.Scope): Promise { + return { + type: scope.type, + object: scope.object, + name: scope.name, + // TODO FILE BUG: Chrome sometimes returns line -1 when the doc says it's 0 based + startLocation: await asyncUndefinedOnFailure(async () => scope.startLocation && await this._cdtpLocationParser.getLocationInScript(scope.startLocation)), + endLocation: await asyncUndefinedOnFailure(async () => scope.endLocation && await this._cdtpLocationParser.getLocationInScript(scope.endLocation)) + }; + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/eventsProviders/cdtpExceptionThrownEventsProvider.ts b/src/chrome/cdtpDebuggee/eventsProviders/cdtpExceptionThrownEventsProvider.ts new file mode 100644 index 000000000..c0107b40e --- /dev/null +++ b/src/chrome/cdtpDebuggee/eventsProviders/cdtpExceptionThrownEventsProvider.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; + +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { CDTPStackTraceParser } from '../protocolParsers/cdtpStackTraceParser'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { integer } from '../cdtpPrimitives'; +import { IScript } from '../../internal/scripts/script'; +import { CodeFlowStackTrace } from '../../internal/stackTraces/codeFlowStackTrace'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; + +export interface IExceptionThrownEvent { + readonly timestamp: CDTP.Runtime.Timestamp; + readonly exceptionDetails: IExceptionDetails; +} + +export interface IExceptionDetails { + readonly exceptionId: integer; + readonly text: string; + readonly lineNumber: integer; + readonly columnNumber: integer; + readonly script?: IScript; + readonly url?: string; + readonly stackTrace?: CodeFlowStackTrace; + readonly exception?: CDTP.Runtime.RemoteObject; + readonly executionContextId?: CDTP.Runtime.ExecutionContextId; +} + +export interface IExceptionThrownEventProvider { + onExceptionThrown(listener: (event: IExceptionThrownEvent) => void): void; +} + +@injectable() +export class CDTPExceptionThrownEventsProvider extends CDTPEventsEmitterDiagnosticsModule implements IExceptionThrownEventProvider { + protected readonly api = this.protocolApi.Runtime; + + private readonly _stackTraceParser = new CDTPStackTraceParser(this._scriptsRegistry); + + public readonly onExceptionThrown = this.addApiListener('exceptionThrown', async (params: CDTP.Runtime.ExceptionThrownEvent) => + ({ + timestamp: params.timestamp, + exceptionDetails: await this.toExceptionDetails(params.exceptionDetails) + })); + + constructor( + @inject(TYPES.CDTPClient) private readonly protocolApi: CDTP.ProtocolApi, + @inject(TYPES.CDTPScriptsRegistry) private _scriptsRegistry: CDTPScriptsRegistry, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + } + + private async toExceptionDetails(exceptionDetails: CDTP.Runtime.ExceptionDetails): Promise { + return { + exceptionId: exceptionDetails.exceptionId, + text: exceptionDetails.text, + lineNumber: exceptionDetails.lineNumber, + columnNumber: exceptionDetails.columnNumber, + script: exceptionDetails.scriptId ? await this._scriptsRegistry.getScriptByCdtpId(exceptionDetails.scriptId) : undefined, + url: exceptionDetails.url, + stackTrace: exceptionDetails.stackTrace && await this._stackTraceParser.toStackTraceCodeFlow(exceptionDetails.stackTrace), + exception: exceptionDetails.exception, + executionContextId: exceptionDetails.executionContextId, + }; + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/eventsProviders/cdtpExecutionContextEventsProvider.ts b/src/chrome/cdtpDebuggee/eventsProviders/cdtpExecutionContextEventsProvider.ts new file mode 100644 index 000000000..f280b76d4 --- /dev/null +++ b/src/chrome/cdtpDebuggee/eventsProviders/cdtpExecutionContextEventsProvider.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { Protocol as CDTP } from 'devtools-protocol'; + +import { inject, injectable } from 'inversify'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; + +@injectable() +export class CDTPExecutionContextEventsProvider extends CDTPEventsEmitterDiagnosticsModule { + protected readonly api = this._protocolApi.Runtime; + + public readonly onExecutionContextsCleared = this.addApiListener('executionContextsCleared', (params: void) => params); + + public readonly onExecutionContextDestroyed = this.addApiListener('executionContextDestroyed', async (params: CDTP.Runtime.ExecutionContextDestroyedEvent) => + this._scriptsRegistry.markExecutionContextAsDestroyed(params.executionContextId)); + + public readonly onExecutionContextCreated = this.addApiListener('executionContextCreated', async (params: CDTP.Runtime.ExecutionContextCreatedEvent) => + this._scriptsRegistry.registerExecutionContext(params.context.id)); + + constructor( + @inject(TYPES.CDTPClient) private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.CDTPScriptsRegistry) private readonly _scriptsRegistry: CDTPScriptsRegistry, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/eventsProviders/cdtpLogEventsProvider.ts b/src/chrome/cdtpDebuggee/eventsProviders/cdtpLogEventsProvider.ts new file mode 100644 index 000000000..fd2053610 --- /dev/null +++ b/src/chrome/cdtpDebuggee/eventsProviders/cdtpLogEventsProvider.ts @@ -0,0 +1,65 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { Protocol as CDTP } from 'devtools-protocol'; + +import { CDTPStackTraceParser } from '../protocolParsers/cdtpStackTraceParser'; +import { integer } from '../cdtpPrimitives'; +import { CodeFlowStackTrace } from '../../internal/stackTraces/codeFlowStackTrace'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { inject } from 'inversify'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; + +export type LogEntrySource = 'xml' | 'javascript' | 'network' | 'storage' | 'appcache' | 'rendering' | 'security' | 'deprecation' | 'worker' | 'violation' | 'intervention' | 'recommendation' | 'other'; +export type LogLevel = 'verbose' | 'info' | 'warning' | 'error'; + +export interface ILogEntry { + readonly source: LogEntrySource; + readonly level: LogLevel; + readonly text: string; + readonly timestamp: CDTP.Runtime.Timestamp; + readonly url?: string; + readonly lineNumber?: integer; + readonly stackTrace?: CodeFlowStackTrace; + readonly networkRequestId?: CDTP.Network.RequestId; + readonly workerId?: string; + readonly args?: CDTP.Runtime.RemoteObject[]; +} + +export interface ILogEventsProvider { + onEntryAdded(listener: (entry: ILogEntry) => void): void; +} + +export class CDTPLogEventsProvider extends CDTPEventsEmitterDiagnosticsModule implements ILogEventsProvider { + protected readonly api = this._protocolApi.Log; + + private readonly _stackTraceParser = new CDTPStackTraceParser(this._scriptsRegistry); + + public readonly onEntryAdded = this.addApiListener('entryAdded', async (params: CDTP.Log.EntryAddedEvent) => await this.toLogEntry(params.entry)); + + constructor( + @inject(TYPES.CDTPClient) private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.CDTPScriptsRegistry) private _scriptsRegistry: CDTPScriptsRegistry, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + } + + private async toLogEntry(entry: CDTP.Log.LogEntry): Promise { + return { + source: entry.source, + level: entry.level, + text: entry.text, + timestamp: entry.timestamp, + url: entry.url, + lineNumber: entry.lineNumber, + networkRequestId: entry.networkRequestId, + workerId: entry.workerId, + args: entry.args, + stackTrace: entry.stackTrace && await this._stackTraceParser.toStackTraceCodeFlow(entry.stackTrace), + }; + } +} diff --git a/src/chrome/cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider.ts b/src/chrome/cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider.ts new file mode 100644 index 000000000..1b367b5a7 --- /dev/null +++ b/src/chrome/cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider.ts @@ -0,0 +1,120 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { CDTP, parseResourceIdentifier, BasePathTransformer, BaseSourceMapTransformer } from '../../..'; +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { IScript, Script } from '../../internal/scripts/script'; +import { createCDTPScriptUrl } from '../../internal/sources/resourceIdentifierSubtypes'; +import { SourcesMapper, NoSourceMapping as NoSourcesMapper } from '../../internal/scripts/sourcesMapper'; +import { ResourceName } from '../../internal/sources/resourceIdentifier'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { CDTPStackTraceParser } from '../protocolParsers/cdtpStackTraceParser'; +import { inject } from 'inversify'; +import { integer } from '../cdtpPrimitives'; +import { CodeFlowStackTrace } from '../../internal/stackTraces/codeFlowStackTrace'; +import { IExecutionContext } from '../../internal/scripts/executionContext'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; + +/** + * A new JavaScript Script has been parsed by the debugee and it's about to be executed + */ +export interface IScriptParsedEvent { + readonly script: IScript; + readonly url: string; + readonly startLine: integer; + readonly startColumn: integer; + readonly endLine: integer; + readonly endColumn: integer; + readonly executionContext: IExecutionContext; + readonly hash: string; + readonly executionContextAuxData?: any; + readonly isLiveEdit?: boolean; + readonly sourceMapURL?: string; + readonly hasSourceURL?: boolean; + readonly isModule?: boolean; + readonly length?: integer; + readonly stackTrace?: CodeFlowStackTrace; +} + +export type ScriptParsedListener = (params: IScriptParsedEvent) => void; + +export interface IScriptParsedProvider { + onScriptParsed(listener: (event: IScriptParsedEvent) => void): void; +} + +export class CDTPOnScriptParsedEventProvider extends CDTPEventsEmitterDiagnosticsModule implements IScriptParsedProvider { + protected readonly api = this._protocolApi.Debugger; + + private readonly _stackTraceParser = new CDTPStackTraceParser(this._scriptsRegistry); + + public onScriptParsed = this.addApiListener('scriptParsed', async (params: CDTP.Debugger.ScriptParsedEvent) => { + await this.createAndRegisterScript(params); + + return await this.toScriptParsedEvent(params); + }); + + constructor( + @inject(TYPES.CDTPClient) private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.BasePathTransformer) private readonly _pathTransformer: BasePathTransformer, + @inject(TYPES.BaseSourceMapTransformer) private readonly _sourceMapTransformer: BaseSourceMapTransformer, + @inject(TYPES.CDTPScriptsRegistry) private readonly _scriptsRegistry: CDTPScriptsRegistry, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + } + + private async createAndRegisterScript(params: CDTP.Debugger.ScriptParsedEvent): Promise { + // The stack trace and hash can be large and the DA doesn't need it. + delete params.stackTrace; + delete params.hash; + + const executionContext = this._scriptsRegistry.getExecutionContextById(params.executionContextId); + + const script = await this._scriptsRegistry.registerScript(params.scriptId, async () => { + if (params.url !== undefined && params.url !== '') { + const runtimeSourceLocation = parseResourceIdentifier(createCDTPScriptUrl(params.url)); + const developmentSourceLocation = await this._pathTransformer.scriptParsed(runtimeSourceLocation); + const sourceMap = await this._sourceMapTransformer.scriptParsed(runtimeSourceLocation.canonicalized, params.sourceMapURL); + const sourceMapper = sourceMap + ? new SourcesMapper(sourceMap) + : new NoSourcesMapper(); + + const runtimeScript = Script.create(executionContext, runtimeSourceLocation, developmentSourceLocation, sourceMapper); + return runtimeScript; + } else { + const sourceMap = await this._sourceMapTransformer.scriptParsed('', params.sourceMapURL); + const sourceMapper = sourceMap + ? new SourcesMapper(sourceMap) + : new NoSourcesMapper(); + const runtimeScript = Script.createEval(executionContext, new ResourceName(createCDTPScriptUrl(params.scriptId)), sourceMapper); + return runtimeScript; + } + }); + + return script; + } + + private async toScriptParsedEvent(params: CDTP.Debugger.ScriptParsedEvent): Promise { + const executionContext = this._scriptsRegistry.getExecutionContextById(params.executionContextId); + + return { + url: params.url, + startLine: params.startLine, + startColumn: params.startColumn, + endLine: params.endLine, + endColumn: params.endColumn, + executionContext: executionContext, + hash: params.hash, + executionContextAuxData: params.executionContextAuxData, + isLiveEdit: params.isLiveEdit, + sourceMapURL: params.sourceMapURL, + hasSourceURL: params.hasSourceURL, + isModule: params.isModule, + length: params.length, + script: await this._scriptsRegistry.getScriptByCdtpId(params.scriptId), + stackTrace: params.stackTrace && await this._stackTraceParser.toStackTraceCodeFlow(params.stackTrace) + }; + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/features/CDTPSchema.ts b/src/chrome/cdtpDebuggee/features/CDTPSchema.ts new file mode 100644 index 000000000..a37a63a24 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/CDTPSchema.ts @@ -0,0 +1,13 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; + +export class CDTPSchema { + constructor(protected api: CDTP.SchemaApi) { } + + public async getDomains(): Promise { + return (await this.api.getDomains()).domains; + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpAsyncDebuggingConfigurer.ts b/src/chrome/cdtpDebuggee/features/cdtpAsyncDebuggingConfigurer.ts new file mode 100644 index 000000000..ccee93feb --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpAsyncDebuggingConfigurer.ts @@ -0,0 +1,25 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; +import { inject, injectable } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; + +export interface IAsyncDebuggingConfigurer { + setAsyncCallStackDepth(maxDepth: CDTP.integer): Promise; +} + +@injectable() +export class CDTPAsyncDebuggingConfigurer implements IAsyncDebuggingConfigurer { + protected readonly api = this._protocolApi.Debugger; + + constructor( + @inject(TYPES.CDTPClient) + private readonly _protocolApi: CDTP.ProtocolApi) { + } + + public setAsyncCallStackDepth(maxDepth: CDTP.integer): Promise { + return this.api.setAsyncCallStackDepth({ maxDepth }); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpBlackboxPatternsConfigurer.ts b/src/chrome/cdtpDebuggee/features/cdtpBlackboxPatternsConfigurer.ts new file mode 100644 index 000000000..2be6cf3be --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpBlackboxPatternsConfigurer.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; +import { IScript } from '../../internal/scripts/script'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { inject, injectable } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { IPositionInScript } from '../../internal/scripts/sourcesMapper'; + +export interface IBlackboxPatternsConfigurer { + setBlackboxPatterns(params: CDTP.Debugger.SetBlackboxPatternsRequest): Promise; + setBlackboxedRanges(script: IScript, positions: IPositionInScript[]): Promise; +} + +@injectable() +export class CDTPBlackboxPatternsConfigurer implements IBlackboxPatternsConfigurer { + protected readonly api = this._protocolApi.Debugger; + + constructor( + @inject(TYPES.CDTPClient) + private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.CDTPScriptsRegistry) + private readonly _scriptsRegistry: CDTPScriptsRegistry) { + } + + public setBlackboxedRanges(script: IScript, positions: IPositionInScript[]): Promise { + const cdtpPositions: CDTP.Debugger.ScriptPosition[] = positions.map(p => ({ + lineNumber: p.line, + columnNumber: p.column + })); + + return this.api.setBlackboxedRanges({ scriptId: this._scriptsRegistry.getCdtpId(script), positions: cdtpPositions }); + } + + public setBlackboxPatterns(params: CDTP.Debugger.SetBlackboxPatternsRequest): Promise { + return this.api.setBlackboxPatterns(params); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpBreakpointFeaturesSupport.ts b/src/chrome/cdtpDebuggee/features/cdtpBreakpointFeaturesSupport.ts new file mode 100644 index 000000000..e3abca386 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpBreakpointFeaturesSupport.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { Protocol as CDTP } from 'devtools-protocol'; +import * as utils from '../../../utils'; + +export interface IBreakpointFeaturesSupport { + supportsColumnBreakpoints: Promise; +} + +@injectable() +export class CDTPBreakpointFeaturesSupport implements IBreakpointFeaturesSupport { + private result = utils.promiseDefer(); + + public supportsColumnBreakpoints = this.result.promise; + + constructor( + @inject(TYPES.CDTPClient) private readonly api: CDTP.ProtocolApi) { + api.Debugger.on('scriptParsed', params => this.onScriptParsed(params)); + } + + private async onScriptParsed(params: CDTP.Debugger.ScriptParsedEvent): Promise { + const scriptId = params.scriptId; + + try { + await this.api.Debugger.getPossibleBreakpoints({ + start: { scriptId, lineNumber: 0, columnNumber: 0 }, + end: { scriptId, lineNumber: 1, columnNumber: 0 }, + restrictToFunction: false + }); + + this.result.resolve(true); + } catch (e) { + this.result.resolve(false); + } + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/features/cdtpBrowserNavigator.ts b/src/chrome/cdtpDebuggee/features/cdtpBrowserNavigator.ts new file mode 100644 index 000000000..4f24a170d --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpBrowserNavigator.ts @@ -0,0 +1,39 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; + +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; + +export interface IBrowserNavigator { + navigate(params: CDTP.Page.NavigateRequest): Promise; + reload(params: CDTP.Page.ReloadRequest): Promise; + onFrameNavigated(listener: (params: CDTP.Page.FrameNavigatedEvent) => void): void; +} + +@injectable() +export class CDTPBrowserNavigator extends CDTPEventsEmitterDiagnosticsModule implements IBrowserNavigator { + protected api = this._protocolApi.Page; + + public readonly onFrameNavigated = this.addApiListener('frameNavigated', (params: CDTP.Page.FrameNavigatedEvent) => params); + + constructor( + @inject(TYPES.CDTPClient) + protected _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + } + + public navigate(params: CDTP.Page.NavigateRequest): Promise { + return this.api.navigate(params); + } + + public reload(params: CDTP.Page.ReloadRequest): Promise { + return this.api.reload(params); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpDOMInstrumentationBreakpoints.ts b/src/chrome/cdtpDebuggee/features/cdtpDOMInstrumentationBreakpoints.ts new file mode 100644 index 000000000..adc5f900b --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpDOMInstrumentationBreakpoints.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; + +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; + +export interface IDOMInstrumentationBreakpoints { + setInstrumentationBreakpoint(params: CDTP.DOMDebugger.SetInstrumentationBreakpointRequest): Promise; + removeInstrumentationBreakpoint(params: CDTP.DOMDebugger.SetInstrumentationBreakpointRequest): Promise; +} + +@injectable() +export class CDTPDOMDebugger implements IDOMInstrumentationBreakpoints { + protected api = this._protocolApi.DOMDebugger; + + constructor( + @inject(TYPES.CDTPClient) + protected _protocolApi: CDTP.ProtocolApi) { } + + public setInstrumentationBreakpoint(params: CDTP.DOMDebugger.SetInstrumentationBreakpointRequest): Promise { + return this.api.setInstrumentationBreakpoint(params); + } + + public removeInstrumentationBreakpoint(params: CDTP.DOMDebugger.SetInstrumentationBreakpointRequest): Promise { + return this.api.removeInstrumentationBreakpoint(params); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpDebugeeExecutionController.ts b/src/chrome/cdtpDebuggee/features/cdtpDebugeeExecutionController.ts new file mode 100644 index 000000000..10ed4eb24 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpDebugeeExecutionController.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; + +export interface IDebugeeExecutionController { + resume(): Promise; + pause(): Promise; +} + +@injectable() +export class CDTPDebugeeExecutionController implements IDebugeeExecutionController { + constructor( + @inject(TYPES.CDTPClient) protected readonly api: CDTP.ProtocolApi) { + } + + public resume(): Promise { + return this.api.Debugger.resume(); + } + + public pause(): Promise { + return this.api.Debugger.pause(); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpDebugeeRuntimeVersionProvider.ts b/src/chrome/cdtpDebuggee/features/cdtpDebugeeRuntimeVersionProvider.ts new file mode 100644 index 000000000..5ba86e53a --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpDebugeeRuntimeVersionProvider.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; + +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { Version } from '../../utils/version'; + +export interface IDebugeeRuntimeVersionProvider { + version(): Promise; +} + +/// TODO: Move this to a browser-shared package +/// TODO: Update this so we automatically try to use ChromeConnection.version first, and then fallback to this if neccesary +@injectable() +export class CDTPDebugeeRuntimeVersionProvider implements IDebugeeRuntimeVersionProvider { + protected api = this._protocolApi.Browser; + + constructor( + @inject(TYPES.CDTPClient) + protected _protocolApi: CDTP.ProtocolApi) { + } + + public async version(): Promise { + // const version = productVersionText.replace(/Chrome\/([0-9]{2})\..*/, '$1'); + return Version.coerce((await this.api.getVersion()).product); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpDebugeeSteppingController.ts b/src/chrome/cdtpDebuggee/features/cdtpDebugeeSteppingController.ts new file mode 100644 index 000000000..99c95b12b --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpDebugeeSteppingController.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; +import { ScriptCallFrame } from '../../internal/stackTraces/callFrame'; +import { CDTPCallFrameRegistry } from '../registries/cdtpCallFrameRegistry'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; + +export interface IDebugeeSteppingController { + stepOver(): Promise; + stepInto(params: { breakOnAsyncCall: boolean }): Promise; + stepOut(): Promise; + restartFrame(callFrame: ScriptCallFrame): Promise; + pauseOnAsyncCall(params: CDTP.Debugger.PauseOnAsyncCallRequest): Promise; +} + +@injectable() +export class CDTPDebugeeSteppingController implements IDebugeeSteppingController { + constructor( + @inject(TYPES.CDTPClient) + protected readonly api: CDTP.ProtocolApi, + private readonly _callFrameRegistry: CDTPCallFrameRegistry) { + } + + public pauseOnAsyncCall(params: CDTP.Debugger.PauseOnAsyncCallRequest): Promise { + return this.api.Debugger.pauseOnAsyncCall(params); + } + + public stepOver(): Promise { + return this.api.Debugger.stepOver(); + } + + public stepInto(params: CDTP.Debugger.StepIntoRequest): Promise { + return this.api.Debugger.stepInto(params); + } + + public stepOut(): Promise { + return this.api.Debugger.stepOut(); + } + + public restartFrame(frame: ScriptCallFrame): Promise { + return this.api.Debugger.restartFrame({ callFrameId: this._callFrameRegistry.getFrameId(frame) }); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpDebuggeeBreakpoints.ts b/src/chrome/cdtpDebuggee/features/cdtpDebuggeeBreakpoints.ts new file mode 100644 index 000000000..2c8920c43 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpDebuggeeBreakpoints.ts @@ -0,0 +1,152 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { BPRecipie, IBPRecipie } from '../../internal/breakpoints/bpRecipie'; +import { RangeInScript } from '../../internal/locations/rangeInScript'; +import { LocationInScript } from '../../internal/locations/location'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { inject, injectable } from 'inversify'; +import { CDTPBreakpointIdsRegistry } from '../registries/cdtpBreakpointIdsRegistry'; +import { asyncMap } from '../../collections/async'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { CDTPLocationParser } from '../protocolParsers/cdtpLocationParser'; +import { CDTPEventsEmitterDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; +import { Position } from '../../internal/locations/location'; +import { singleElementOfArray } from '../../collections/utilities'; +import { CDTPSupportedResources, CDTPSupportedHitActions, CDTPBreakpoint } from '../cdtpPrimitives'; +import { Listeners } from '../../communication/listeners'; +import { IScript } from '../../internal/scripts/script'; +import { IURL, IResourceIdentifier } from '../../internal/sources/resourceIdentifier'; +import { CDTPScriptUrl } from '../../internal/sources/resourceIdentifierSubtypes'; +import { URLRegexp } from '../../internal/locations/subtypes'; +import { MappableBreakpoint, ActualLocation } from '../../internal/breakpoints/breakpoint'; +import { BPRecipieInScript, BPRecipieInUrl, BPRecipieInUrlRegexp, IMappedBPRecipie } from '../../internal/breakpoints/BaseMappedBPRecipie'; +import { ConditionalPause } from '../../internal/breakpoints/bpActionWhenHit'; + +type SetBPInCDTPCall = (resource: TResource, position: Position, cdtpConditionField: string) => Promise; +export type OnBreakpointResolvedListener = (breakpoint: CDTPBreakpoint) => void; + +export interface IDebuggeeBreakpoints { + setBreakpoint(bpRecipie: BPRecipieInScript): Promise>; + setBreakpointByUrl(bpRecipie: BPRecipieInUrl): Promise>[]>; + setBreakpointByUrlRegexp(bpRecipie: BPRecipieInUrlRegexp): Promise[]>; + getPossibleBreakpoints(rangeInScript: RangeInScript): Promise; + removeBreakpoint(bpRecipie: IBPRecipie): Promise; + onBreakpointResolvedAsync(listener: OnBreakpointResolvedListener): void; + onBreakpointResolvedSyncOrAsync(listener: OnBreakpointResolvedListener): void; +} + +@injectable() +export class CDTPDebuggeeBreakpoints extends CDTPEventsEmitterDiagnosticsModule implements IDebuggeeBreakpoints { + protected readonly api = this.protocolApi.Debugger; + + private readonly _cdtpLocationParser = new CDTPLocationParser(this._scriptsRegistry); + + private readonly onBreakpointResolvedSyncOrAsyncListeners = new Listeners(); + + public readonly onBreakpointResolvedAsync = this.addApiListener('breakpointResolved', async (params: CDTP.Debugger.BreakpointResolvedEvent) => { + const bpRecipie = this._breakpointIdRegistry.getRecipieByBreakpointId(params.breakpointId); + const breakpoint = new MappableBreakpoint(bpRecipie, + await this.toLocationInScript(params.location)); + return breakpoint; + }); + + constructor( + @inject(TYPES.CDTPClient) protected readonly protocolApi: CDTP.ProtocolApi, + @inject(CDTPBreakpointIdsRegistry) private readonly _breakpointIdRegistry: CDTPBreakpointIdsRegistry, + @inject(TYPES.CDTPScriptsRegistry) private readonly _scriptsRegistry: CDTPScriptsRegistry, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, + ) { + super(domainsEnabler); + this.onBreakpointResolvedAsync(bp => this.onBreakpointResolvedSyncOrAsyncListeners.call(bp)); + } + + public onBreakpointResolvedSyncOrAsync(listener: (breakpoint: MappableBreakpoint) => void): void { + this.onBreakpointResolvedSyncOrAsyncListeners.add(listener); + } + + public async setBreakpoint(bpRecipie: BPRecipieInScript): Promise> { + const breakpoints = await this.setBreakpointHelper(bpRecipie, async (_resource, _position, cdtpConditionField) => { + const response = await this.api.setBreakpoint({ location: this.toCrdpLocation(bpRecipie.location), condition: cdtpConditionField }); + return { breakpointId: response.breakpointId, locations: [response.actualLocation] }; + }); + + return singleElementOfArray(breakpoints); + } + + public async setBreakpointByUrl(bpRecipie: BPRecipieInUrl): Promise>[]> { + return this.setBreakpointHelper(bpRecipie, (resource, position, cdtpConditionField) => + this.api.setBreakpointByUrl({ + url: resource.textRepresentation, lineNumber: position.lineNumber, + columnNumber: position.columnNumber, condition: cdtpConditionField + })); + } + + public async setBreakpointByUrlRegexp(bpRecipie: BPRecipieInUrlRegexp): Promise[]> { + return this.setBreakpointHelper(bpRecipie, (resource, position, cdtpConditionField) => + this.api.setBreakpointByUrl({ + urlRegex: resource, lineNumber: position.lineNumber, + columnNumber: position.columnNumber, condition: cdtpConditionField + })); + } + + private async setBreakpointHelper | URLRegexp, TBPActionWhenHit extends CDTPSupportedHitActions> + (bpRecipie: IMappedBPRecipie, + setBPInCDTPCall: SetBPInCDTPCall): Promise[]> { + const cdtpConditionField = this.getCDTPConditionField(bpRecipie); + const resource: TResource = bpRecipie.location.resource; // TODO: Figure out why the is needed and remove it + const position = bpRecipie.location.position; + + const response = await setBPInCDTPCall(resource, position, cdtpConditionField); + + /* + * We need to call registerRecipie sync with the response, before any awaits so if we get an event with + * a breakpointId we'll be able to resolve it properly + */ + this._breakpointIdRegistry.registerRecipie(response.breakpointId, bpRecipie); + + const breakpoints = await Promise.all(response.locations.map(cdtpLocation => this.toBreakpoinInResource(bpRecipie, cdtpLocation))); + breakpoints.forEach(bp => this.onBreakpointResolvedSyncOrAsyncListeners.call(bp)); + return breakpoints; + } + + public async getPossibleBreakpoints(rangeInScript: RangeInScript): Promise { + const response = await this.api.getPossibleBreakpoints({ + start: this.toCrdpLocation(rangeInScript.startInScript), + end: this.toCrdpLocation(rangeInScript.endInScript) + }); + + return asyncMap(response.locations, async location => await this.toLocationInScript(location)); + } + + public async removeBreakpoint(bpRecipie: BPRecipie): Promise { + await this.api.removeBreakpoint({ breakpointId: this._breakpointIdRegistry.getBreakpointId(bpRecipie) }); + this._breakpointIdRegistry.unregisterRecipie(bpRecipie); + } + + private getCDTPConditionField(bpRecipie: IBPRecipie): string | undefined { + return bpRecipie.bpActionWhenHit instanceof ConditionalPause + ? bpRecipie.bpActionWhenHit.expressionOfWhenToPause + : undefined; + } + + private async toBreakpoinInResource(bpRecipie: IMappedBPRecipie, actualLocation: CDTP.Debugger.Location): Promise> { + const breakpoint = new MappableBreakpoint(bpRecipie, >await this.toLocationInScript(actualLocation)); + return breakpoint; + } + + private toCrdpLocation(location: LocationInScript): CDTP.Debugger.Location { + return { + scriptId: this._scriptsRegistry.getCdtpId(location.script), + lineNumber: location.position.lineNumber, + columnNumber: location.position.columnNumber + }; + } + + public toLocationInScript(location: CDTP.Debugger.Location): Promise { + return this._cdtpLocationParser.getLocationInScript(location); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpInspectDebugeeState.ts b/src/chrome/cdtpDebuggee/features/cdtpInspectDebugeeState.ts new file mode 100644 index 000000000..82664d9d0 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpInspectDebugeeState.ts @@ -0,0 +1,82 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; + +import { CDTPCallFrameRegistry } from '../registries/cdtpCallFrameRegistry'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { ScriptCallFrame } from '../../internal/stackTraces/callFrame'; + +export interface IEvaluateOnCallFrameRequest { + readonly frame: ScriptCallFrame; + readonly expression: string; + readonly objectGroup?: string; + readonly includeCommandLineAPI?: boolean; + readonly silent?: boolean; + readonly returnByValue?: boolean; + readonly generatePreview?: boolean; + readonly throwOnSideEffect?: boolean; + readonly timeout?: CDTP.Runtime.TimeDelta; +} + +export interface IInspectDebugeeState { + callFunctionOn(params: CDTP.Runtime.CallFunctionOnRequest): Promise; + getProperties(params: CDTP.Runtime.GetPropertiesRequest): Promise; + evaluate(params: CDTP.Runtime.EvaluateRequest): Promise; + evaluateOnCallFrame(params: IEvaluateOnCallFrameRequest): Promise; +} + +export class AddSourceUriToExpession { + private nextEvaluateScriptId = 0; + + constructor(private readonly _prefix: string) { } + + public addURLIfMissing(expression: string): string { + const sourceUrlPrefix = '\n//# sourceURL='; + + if (expression.indexOf(sourceUrlPrefix) < 0) { + expression += `${sourceUrlPrefix}/${this._prefix}/id=${this.nextEvaluateScriptId++}`; + } + + return expression; + } +} + +@injectable() +export class CDTPInspectDebugeeState implements IInspectDebugeeState { + private addSourceUriToEvaluates = new AddSourceUriToExpession('evaluateOnFrame'); + + constructor( + @inject(TYPES.CDTPClient) protected readonly api: CDTP.ProtocolApi, + private readonly _callFrameRegistry: CDTPCallFrameRegistry) { + } + + public callFunctionOn(params: CDTP.Runtime.CallFunctionOnRequest): Promise { + return this.api.Runtime.callFunctionOn(params); + } + + public getProperties(params: CDTP.Runtime.GetPropertiesRequest): Promise { + return this.api.Runtime.getProperties(params); + } + + public evaluate(params: CDTP.Runtime.EvaluateRequest): Promise { + params.expression = this.addSourceUriToEvaluates.addURLIfMissing(params.expression); + return this.api.Runtime.evaluate(params); + } + + public evaluateOnCallFrame(params: IEvaluateOnCallFrameRequest): Promise { + return this.api.Debugger.evaluateOnCallFrame({ + callFrameId: this._callFrameRegistry.getFrameId(params.frame), + expression: this.addSourceUriToEvaluates.addURLIfMissing(params.expression), + objectGroup: params.objectGroup, + includeCommandLineAPI: params.includeCommandLineAPI, + silent: params.silent, + returnByValue: params.returnByValue, + generatePreview: params.generatePreview, + throwOnSideEffect: params.throwOnSideEffect, + timeout: params.timeout, + }); + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/features/cdtpNetworkCacheConfiguration.ts b/src/chrome/cdtpDebuggee/features/cdtpNetworkCacheConfiguration.ts new file mode 100644 index 000000000..e9b4f2499 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpNetworkCacheConfiguration.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; + +export interface INetworkCacheConfiguration { + setCacheDisabled(params: CDTP.Network.SetCacheDisabledRequest): Promise; +} + +export class CDTPNetwork implements INetworkCacheConfiguration { + constructor(protected api: CDTP.NetworkApi) { + } + + public enable(parameters: CDTP.Network.EnableRequest): Promise { + return this.api.enable(parameters); + } + + public disable(): Promise { + return this.api.disable(); + } + + public setCacheDisabled(params: CDTP.Network.SetCacheDisabledRequest): Promise { + return this.api.setCacheDisabled(params); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpPauseOnExceptionsConfigurer.ts b/src/chrome/cdtpDebuggee/features/cdtpPauseOnExceptionsConfigurer.ts new file mode 100644 index 000000000..fbdc45075 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpPauseOnExceptionsConfigurer.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; +import { IPauseOnExceptionsStrategy, PauseOnAllExceptions, PauseOnUnhandledExceptions, DoNotPauseOnAnyExceptions } from '../../internal/exceptions/strategies'; +import { inject, injectable } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; + +export interface IPauseOnExceptionsConfigurer { + setPauseOnExceptions(strategy: IPauseOnExceptionsStrategy): Promise; +} + +export type ExceptionCategories = 'none' | 'uncaught' | 'all'; + +@injectable() +export class CDTPPauseOnExceptionsConfigurer implements IPauseOnExceptionsConfigurer { + protected readonly api = this._protocolApi.Debugger; + + constructor( + @inject(TYPES.CDTPClient) + private readonly _protocolApi: CDTP.ProtocolApi) { + } + + public setPauseOnExceptions(strategy: IPauseOnExceptionsStrategy): Promise { + let state: ExceptionCategories; + + if (strategy instanceof PauseOnAllExceptions) { + state = 'all'; + } else if (strategy instanceof PauseOnUnhandledExceptions) { + state = 'uncaught'; + } else if (strategy instanceof DoNotPauseOnAnyExceptions) { + state = 'none'; + } else { + throw new Error(`Can't pause on exception using an unknown strategy ${strategy}`); + } + + return this.api.setPauseOnExceptions({ state }); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpPausedOverlay.ts b/src/chrome/cdtpDebuggee/features/cdtpPausedOverlay.ts new file mode 100644 index 000000000..6a2beb824 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpPausedOverlay.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { CDTPEnableableDiagnosticsModule } from '../infrastructure/cdtpDiagnosticsModule'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { inject } from 'inversify'; +import { CDTPDomainsEnabler } from '../infrastructure/cdtpDomainsEnabler'; + +export interface IPausedOverlay { + setPausedInDebuggerMessage(params: CDTP.Overlay.SetPausedInDebuggerMessageRequest): Promise; +} + +// TODO: Move this to a browser shared package +export class CDTPOverlay extends CDTPEnableableDiagnosticsModule implements IPausedOverlay { + protected readonly api = this._protocolApi.Overlay; + + constructor( + @inject(TYPES.CDTPClient) private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.IDomainsEnabler) domainsEnabler: CDTPDomainsEnabler, ) { + super(domainsEnabler); + } + + public setPausedInDebuggerMessage(params: CDTP.Overlay.SetPausedInDebuggerMessageRequest): Promise { + return this.api.setPausedInDebuggerMessage(params); + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpRuntime.ts b/src/chrome/cdtpDebuggee/features/cdtpRuntime.ts new file mode 100644 index 000000000..900a38b62 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpRuntime.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; + +export interface IRuntime { + runIfWaitingForDebugger(): Promise; +} + +export class CDTPRuntime implements IRuntime { + constructor( + protected readonly api: CDTP.RuntimeApi) { + } + + public async runIfWaitingForDebugger(): Promise { + // This is a CDTP version difference which will have to be handled more elegantly with others later... + // For now, we need to send both messages and ignore a failing one. + try { + await Promise.all([ + this.api.runIfWaitingForDebugger(), + (this.api as any).run() + ]); + } catch (exception) { + // TODO: Make sure that at least one of the two calls succeeded + // Ignore the failed call + } + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpScriptSourcesRetriever.ts b/src/chrome/cdtpDebuggee/features/cdtpScriptSourcesRetriever.ts new file mode 100644 index 000000000..01d7a1646 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpScriptSourcesRetriever.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; +import { IScript } from '../../internal/scripts/script'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { inject, injectable } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; + +export interface IScriptSourcesRetriever { + getScriptSource(script: IScript): Promise; +} + +@injectable() +export class CDTPScriptSourcesRetriever implements IScriptSourcesRetriever { + protected readonly api = this._protocolApi.Debugger; + + constructor( + @inject(TYPES.CDTPClient) + private readonly _protocolApi: CDTP.ProtocolApi, + @inject(TYPES.CDTPScriptsRegistry) + private readonly _scriptsRegistry: CDTPScriptsRegistry) { + } + + public async getScriptSource(script: IScript): Promise { + return (await this.api.getScriptSource({ scriptId: this._scriptsRegistry.getCdtpId(script) })).scriptSource; + } +} diff --git a/src/chrome/cdtpDebuggee/features/cdtpUpdateDebugeeState.ts b/src/chrome/cdtpDebuggee/features/cdtpUpdateDebugeeState.ts new file mode 100644 index 000000000..f95802242 --- /dev/null +++ b/src/chrome/cdtpDebuggee/features/cdtpUpdateDebugeeState.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; +import { CDTPCallFrameRegistry } from '../registries/cdtpCallFrameRegistry'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { injectable, inject } from 'inversify'; +import { ScriptCallFrame } from '../../internal/stackTraces/callFrame'; +import { integer } from '../cdtpPrimitives'; + +export interface ISetVariableValueRequest { + readonly scopeNumber: integer; + readonly variableName: string; + readonly newValue: CDTP.Runtime.CallArgument; + readonly frame: ScriptCallFrame; +} + +export interface IUpdateDebugeeState { + setVariableValue(params: ISetVariableValueRequest): Promise; +} + +@injectable() +export class CDTPUpdateDebugeeState implements IUpdateDebugeeState { + constructor( + @inject(TYPES.CDTPClient) private readonly api: CDTP.ProtocolApi, + private readonly _callFrameRegistry: CDTPCallFrameRegistry) { + } + + public setVariableValue(params: ISetVariableValueRequest): Promise { + return this.api.Debugger.setVariableValue({ + callFrameId: this._callFrameRegistry.getFrameId(params.frame), + scopeNumber: params.scopeNumber, + variableName: params.variableName, + newValue: params.newValue + }); + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/infrastructure/cdtpDiagnosticsModule.ts b/src/chrome/cdtpDebuggee/infrastructure/cdtpDiagnosticsModule.ts new file mode 100644 index 000000000..dc319c3b9 --- /dev/null +++ b/src/chrome/cdtpDebuggee/infrastructure/cdtpDiagnosticsModule.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { TransformedListenerRegistry } from '../../communication/transformedListenerRegistry'; +import { PromiseOrNot } from '../../utils/promises'; +import { injectable } from 'inversify'; +import { CDTPDomainsEnabler } from './cdtpDomainsEnabler'; + +export interface IEnableableApi { + enable(parameters: EnableParameters): Promise; + on(eventName: string, listener: Function): void; +} + +@injectable() +export abstract class CDTPEnableableDiagnosticsModule, EnableParameters = void, EnableResponse = void> { + protected abstract get api(): T; + + public enable(): EnableParameters extends void ? Promise : never; + public enable(parameters: EnableParameters): Promise; + public async enable(parameters?: EnableParameters): Promise { + return await this._domainsEnabler.registerToEnable(this.api, parameters); + } + + constructor(private readonly _domainsEnabler: CDTPDomainsEnabler) { } +} + +@injectable() +export abstract class CDTPEventsEmitterDiagnosticsModule, EnableParameters = void, EnableResponse = void> + extends CDTPEnableableDiagnosticsModule { + public addApiListener(eventName: string, transformation: (params: O) => PromiseOrNot): (transformedListener: ((params: T) => void)) => void { + + const transformedListenerRegistryPromise = new TransformedListenerRegistry(this.constructor.name, async originalListener => { + this.api.on(eventName, originalListener); + }, transformation).install(); + + this.enable(); // The domain will be enabled eventually (Assuming this happens during the startup/initial configuration phase). We don't block on it. + + return async transformedListener => (await transformedListenerRegistryPromise).registerListener(transformedListener); + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/infrastructure/cdtpDomainsEnabler.ts b/src/chrome/cdtpDebuggee/infrastructure/cdtpDomainsEnabler.ts new file mode 100644 index 000000000..97ed15551 --- /dev/null +++ b/src/chrome/cdtpDebuggee/infrastructure/cdtpDomainsEnabler.ts @@ -0,0 +1,107 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { IEnableableApi } from './cdtpDiagnosticsModule'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { inject, injectable } from 'inversify'; +import * as utils from '../../../utils'; +import { asyncMap } from '../../collections/async'; +import { ValidatedMap } from '../../collections/validatedMap'; +import * as _ from 'lodash'; + +export interface IDomainsEnabler { + registerToEnable, EnableParameters, EnableResponse> + (api: T, parameters: EnableParameters): Promise; + + enableDomains(): Promise; +} + +interface IState { + registerToEnable, EnableParameters, EnableResponse> + (api: T, parameters: EnableParameters): Promise; + enableDomains(): Promise; +} + +class EnableDomainFunctionAndResultPromise { + constructor( + public readonly enableDomain: () => Promise, + public readonly parameters: unknown, + public readonly defer: utils.IPromiseDefer, + ) { } +} + +class GatheringDomainsToEnableDuringStartup implements IState { + private readonly _registeredDomains = new ValidatedMap, EnableDomainFunctionAndResultPromise>(); + + public async enableDomains(): Promise { + const entries = Array.from(this._registeredDomains.entries()); + await asyncMap(entries, async pair => this.executeEnable(pair[0], pair[1])); + return new DomainsAlreadyEnabledAfterStartup(); + } + + public async executeEnable(domain: IEnableableApi, extras: EnableDomainFunctionAndResultPromise): Promise { + await this.verifyPrerequisitesAreMet(domain); + + try { + extras.defer.resolve(extras.enableDomain()); + } catch (exception) { + extras.defer.reject(exception); + } + } + + public async verifyPrerequisitesAreMet(domain: IEnableableApi): Promise { + if (domain !== this.protocolApi.Runtime) { + // TODO: For the time being we assume that all domains require the Runtime domain to be enabled. Figure out if this can be improved + await this._registeredDomains.get(this.protocolApi.Runtime).defer.promise; + } + } + + public async registerToEnable, EnableParameters, EnableResponse> + (api: T, parameters: EnableParameters): Promise { + const enableDomain = () => api.enable(parameters); + + const entry = this._registeredDomains.getOrAdd(api, () => + new EnableDomainFunctionAndResultPromise(enableDomain, parameters, utils.promiseDefer())); + + if (entry.parameters !== parameters) { + throw new Error(`Cannot register enable(${parameters}) for domain ${this.getDomainName(api)} because it was registered previously with enable(${entry.parameters})`); + } + + return await entry.defer.promise; + } + + private getDomainName(api: IEnableableApi): string { + return _.findKey(this.protocolApi, api); + } + + constructor(@inject(TYPES.CDTPClient) protected readonly protocolApi: CDTP.ProtocolApi) { } +} + +class DomainsAlreadyEnabledAfterStartup implements IState { + public registerToEnable, EnableParameters, EnableResponse> + (api: T, parameters: EnableParameters): Promise { + return api.enable(parameters); + } + + public enableDomains(): Promise { + throw new Error('Startup was already finished'); + } +} + +@injectable() +export class CDTPDomainsEnabler implements IDomainsEnabler { + private _state: IState = new GatheringDomainsToEnableDuringStartup(this._protocolApi); + + public registerToEnable, EnableParameters, EnableResponse> + (api: T, parameters: EnableParameters): Promise { + return this._state.registerToEnable(api, parameters); + } + + public async enableDomains(): Promise { + this._state = await this._state.enableDomains(); + } + + constructor(@inject(TYPES.CDTPClient) private readonly _protocolApi: CDTP.ProtocolApi) { } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/protocolParsers/cdtpLocationParser.ts b/src/chrome/cdtpDebuggee/protocolParsers/cdtpLocationParser.ts new file mode 100644 index 000000000..ad73137e8 --- /dev/null +++ b/src/chrome/cdtpDebuggee/protocolParsers/cdtpLocationParser.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Position, LocationInScript } from '../../internal/locations/location'; +import { createColumnNumber, createLineNumber } from '../../internal/locations/subtypes'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { Protocol as CDTP } from 'devtools-protocol'; + +interface IHasCoordinates { + lineNumber: number; + columnNumber?: number; +} + +interface IHasScript { + scriptId: CDTP.Runtime.ScriptId; +} + +export interface IHasScriptLocation extends IHasCoordinates, IHasScript { } + +export class CDTPLocationParser { + public async getLocationInScript(crdpObjectWithScriptLocation: IHasScriptLocation): Promise { + return new LocationInScript(await this._scriptsRegistry.getScriptByCdtpId(crdpObjectWithScriptLocation.scriptId), + this.getCoordinates(crdpObjectWithScriptLocation)); + } + + private getCoordinates(crdpObjectWithCoordinates: IHasCoordinates): Position { + return new Position(createLineNumber(crdpObjectWithCoordinates.lineNumber), createColumnNumber(crdpObjectWithCoordinates.columnNumber)); + } + + constructor(private _scriptsRegistry: CDTPScriptsRegistry) { } +} diff --git a/src/chrome/cdtpDebuggee/protocolParsers/cdtpStackTraceParser.ts b/src/chrome/cdtpDebuggee/protocolParsers/cdtpStackTraceParser.ts new file mode 100644 index 000000000..7e838ad8b --- /dev/null +++ b/src/chrome/cdtpDebuggee/protocolParsers/cdtpStackTraceParser.ts @@ -0,0 +1,35 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; + +import { IScript, } from '../../internal/scripts/script'; +import { CodeFlowStackTrace } from '../../internal/stackTraces/codeFlowStackTrace'; +import { CodeFlowFrame } from '../../internal/stackTraces/callFrame'; +import { CDTPLocationParser, IHasScriptLocation } from './cdtpLocationParser'; +import { CDTPScriptsRegistry } from '../registries/cdtpScriptsRegistry'; +import { asyncMap } from '../../collections/async'; + +export class CDTPStackTraceParser { + private readonly _cdtpLocationParser = new CDTPLocationParser(this._scriptsRegistry); + + public async toStackTraceCodeFlow(stackTrace: CDTP.Runtime.StackTrace): Promise { + return { + codeFlowFrames: await asyncMap(stackTrace.callFrames, (callFrame, index) => this.runtimeCallFrameToCodeFlowFrame(index, callFrame)), + description: stackTrace.description, + parent: stackTrace.parent && await this.toStackTraceCodeFlow(stackTrace.parent) + }; + } + + private runtimeCallFrameToCodeFlowFrame(index: number, callFrame: CDTP.Runtime.CallFrame): Promise> { + return this.toCodeFlowFrame(index, callFrame, callFrame); + } + + public async toCodeFlowFrame(index: number, callFrame: CDTP.Runtime.CallFrame | CDTP.Debugger.CallFrame, location: IHasScriptLocation): Promise> { + const scriptLocation = await this._cdtpLocationParser.getLocationInScript(location); + return new CodeFlowFrame(index, callFrame.functionName, scriptLocation); + } + + constructor(private _scriptsRegistry: CDTPScriptsRegistry) { } +} diff --git a/src/chrome/cdtpDebuggee/registries/cdtpBreakpointIdsRegistry.ts b/src/chrome/cdtpDebuggee/registries/cdtpBreakpointIdsRegistry.ts new file mode 100644 index 000000000..9159b286f --- /dev/null +++ b/src/chrome/cdtpDebuggee/registries/cdtpBreakpointIdsRegistry.ts @@ -0,0 +1,34 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { BidirectionalMap } from '../../collections/bidirectionalMap'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { injectable } from 'inversify'; +import { CDTPBPRecipie } from '../cdtpPrimitives'; + +@injectable() +export class CDTPBreakpointIdsRegistry { + // TODO DIEGO: Figure out how to handle if two breakpoint rules set a breakpoint in the same location so it ends up being the same breakpoint id + private readonly _recipieToBreakpointId = new BidirectionalMap(); + + public registerRecipie(cdtpBreakpointId: CDTP.Debugger.BreakpointId, bpRecipie: CDTPBPRecipie): void { + this._recipieToBreakpointId.set(bpRecipie, cdtpBreakpointId); + } + + public unregisterRecipie(bpRecipie: CDTPBPRecipie): void { + this._recipieToBreakpointId.deleteByLeft(bpRecipie); + } + + public getBreakpointId(bpRecipie: CDTPBPRecipie): CDTP.Debugger.BreakpointId { + return this._recipieToBreakpointId.getByLeft(bpRecipie); + } + + public getRecipieByBreakpointId(cdtpBreakpointId: CDTP.Debugger.BreakpointId): CDTPBPRecipie { + return this._recipieToBreakpointId.getByRight(cdtpBreakpointId); + } + + public toString(): string { + return `Breakpoint IDs: ${this._recipieToBreakpointId}`; + } +} diff --git a/src/chrome/cdtpDebuggee/registries/cdtpCallFrameRegistry.ts b/src/chrome/cdtpDebuggee/registries/cdtpCallFrameRegistry.ts new file mode 100644 index 000000000..2641e188d --- /dev/null +++ b/src/chrome/cdtpDebuggee/registries/cdtpCallFrameRegistry.ts @@ -0,0 +1,21 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; +import { ValidatedMap } from '../../collections/validatedMap'; +import { ScriptCallFrame } from '../../internal/stackTraces/callFrame'; +import { injectable } from 'inversify'; + +@injectable() +export class CDTPCallFrameRegistry { + private readonly _callFrameToId = new ValidatedMap(); + + public registerFrameId(callFrameId: CDTP.Debugger.CallFrameId, frame: ScriptCallFrame): void { + this._callFrameToId.set(frame, callFrameId); + } + + public getFrameId(frame: ScriptCallFrame): CDTP.Debugger.CallFrameId { + return this._callFrameToId.get(frame); + } +} \ No newline at end of file diff --git a/src/chrome/cdtpDebuggee/registries/cdtpScriptsRegistry.ts b/src/chrome/cdtpDebuggee/registries/cdtpScriptsRegistry.ts new file mode 100644 index 000000000..0fe4fe486 --- /dev/null +++ b/src/chrome/cdtpDebuggee/registries/cdtpScriptsRegistry.ts @@ -0,0 +1,104 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { Protocol as CDTP } from 'devtools-protocol'; +import { IScript } from '../../internal/scripts/script'; +import { ValidatedMap } from '../../collections/validatedMap'; +import { ExecutionContext, IExecutionContext } from '../../internal/scripts/executionContext'; +import { injectable } from 'inversify'; +import { IResourceIdentifier, newResourceIdentifierMap } from '../../internal/sources/resourceIdentifier'; + +/** + * TODO: The CDTPScriptsRegistry is still a work in progress. We need to understand exactly how the ExecutionContexts, the Scripts, and the script "generations" work to figure out the best way to implement this + * Is ExecutionContext == Generation? Or is a Generation a set of ExecutionContexts? + */ + +@injectable() +export class CDTPScriptsRegistry { + private readonly _idToExecutionContext = new ValidatedMap(); + private readonly _scripts = new CDTPCurrentGeneration(); + + public registerExecutionContext(executionContextId: CDTP.Runtime.ExecutionContextId): IExecutionContext { + const executionContext = new ExecutionContext(); + this._idToExecutionContext.set(executionContextId, executionContext); + return executionContext; + } + + public markExecutionContextAsDestroyed(executionContextId: CDTP.Runtime.ExecutionContextId): IExecutionContext { + const executionContext = this._idToExecutionContext.get(executionContextId); + executionContext.markAsDestroyed(); + return executionContext; + } + + public getExecutionContextById(executionContextId: CDTP.Runtime.ExecutionContextId): IExecutionContext { + return this._idToExecutionContext.get(executionContextId); + } + + public registerScript(scriptId: CDTP.Runtime.ScriptId, obtainScript: () => Promise): Promise { + return this._scripts.registerNewScript(scriptId, obtainScript); + } + + public getCdtpId(script: IScript): any { + return this._scripts.getCdtpId(script); + } + + public getScriptByCdtpId(runtimeScriptCrdpId: CDTP.Runtime.ScriptId): Promise { + return this._scripts.getScriptByCdtpId(runtimeScriptCrdpId); + } + + public getAllScripts(): IterableIterator> { + return this._scripts.getAllScripts(); + } + + public getScriptsByPath(nameOrLocation: IResourceIdentifier): IScript[] { + return this._scripts.getScriptByPath(nameOrLocation); + } +} + +class CDTPCurrentGeneration { + // We use these two maps instead of a bidirectional map because we need to map an ID to a Promise instead of a script, to avoid having race conditions... + private readonly _cdtpIdByScript = new ValidatedMap>(); + private readonly _scriptByCdtpId = new ValidatedMap(); + private readonly _scriptByPath = newResourceIdentifierMap(); + + public async registerNewScript(scriptId: CDTP.Runtime.ScriptId, obtainScript: () => Promise): Promise { + const scriptWithConfigurationPromise = obtainScript().then(script => { + /** + * We need to configure the script here, so we can guarantee that clients who try to use a script will get + * blocked until the script is created, and all the initial configuration is done, so they can use APIs to get + * the script id, search by URL, etc... + */ + this.createScriptInitialConfiguration(scriptId, script); + return script; + }); + + this._cdtpIdByScript.set(scriptId, scriptWithConfigurationPromise); + + return await scriptWithConfigurationPromise; + } + + private createScriptInitialConfiguration(scriptId: CDTP.Runtime.ScriptId, script: IScript): void { + this._scriptByCdtpId.set(script, scriptId); + + let scriptsWithSamePath = this._scriptByPath.getOrAdd(script.runtimeSource.identifier, () => []); + scriptsWithSamePath.push(script); + } + + public getCdtpId(script: IScript): CDTP.Runtime.ScriptId { + return this._scriptByCdtpId.get(script); + } + + public getScriptByCdtpId(runtimeScriptCrdpId: string): Promise { + return this._cdtpIdByScript.get(runtimeScriptCrdpId); + } + + public getAllScripts(): IterableIterator> { + return this._cdtpIdByScript.values(); + } + + public getScriptByPath(path: IResourceIdentifier): IScript[] { + const runtimeScript = this._scriptByPath.tryGetting(path); + return runtimeScript || []; + } +} diff --git a/src/chrome/chromeConnection.ts b/src/chrome/chromeConnection.ts index 0819baca3..05905b351 100644 --- a/src/chrome/chromeConnection.ts +++ b/src/chrome/chromeConnection.ts @@ -9,14 +9,12 @@ import { StepProgressEventsEmitter, IObservableEvents, IStepStartedEventsEmitter import * as errors from '../errors'; import * as utils from '../utils'; import { logger } from 'vscode-debugadapter'; -import { ChromeTargetDiscovery, TargetVersions, Version } from './chromeTargetDiscoveryStrategy'; +import { ChromeTargetDiscovery, TargetVersions } from './chromeTargetDiscoveryStrategy'; +import { Version } from './utils/version'; -import { Client, LikeSocket } from 'noice-json-rpc'; +import { Client } from 'noice-json-rpc'; -import { Protocol as Crdp } from 'devtools-protocol'; - -import { CRDPMultiplexor } from './crdpMultiplexing/crdpMultiplexor'; -import { WebSocketToLikeSocketProxy } from './crdpMultiplexing/webSocketToLikeSocketProxy'; +import { Protocol as CDTP } from 'devtools-protocol'; export interface ITarget { description: string; @@ -73,7 +71,7 @@ class LoggingSocket extends WebSocket { }); } - public send(data: any, opts?: any, cb?: (err: Error) => void): void { + public send(data: any, _opts?: any, _?: (err: Error) => void): void { const msgStr = JSON.stringify(data); if (this.readyState !== WebSocket.OPEN) { logger.log(`→ Warning: Target not open! Message: ${msgStr}`); @@ -98,9 +96,8 @@ export class ChromeConnection implements IObservableEvents; private _attachedTarget: ITarget; public readonly events: StepProgressEventsEmitter; @@ -113,7 +110,7 @@ export class ChromeConnection implements IObservableEvents { }); } - public attachToWebsocketUrl(wsUrl: string, extraCRDPChannelPort?: number): void { + public attachToWebsocketUrl(wsUrl: string, _extraCRDPChannelPort?: number): void { /* __GDPR__FRAGMENT__ "StepNames" : { "Attach.AttachToTargetDebuggerWebsocket" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } @@ -141,15 +138,9 @@ export class ChromeConnection implements IObservableEventsthis._socket as any); - } + this._client = new Client(this._socket as any); - this._client.on('error', e => logger.error('Error handling message from target: ' + e.message)); + this._client.on('error', (e: any) => logger.error('Error handling message from target: ' + e.message)); } public getAllTargets(address = '127.0.0.1', port = 9222, targetFilter?: ITargetFilter, targetUrl?: string): Promise { @@ -168,16 +159,6 @@ export class ChromeConnection implements IObservableEvents { - // This is a CDP version difference which will have to be handled more elegantly with others later... - // For now, we need to send both messages and ignore a failing one. - return Promise.all([ - this.api.Runtime.runIfWaitingForDebugger(), - (this.api.Runtime).run() - ]) - .then(() => { }, () => { }); - } - public close(): void { this._socket.close(); } diff --git a/src/chrome/chromeDebugAdapter.ts b/src/chrome/chromeDebugAdapter.ts index d3d07a65b..10abdf778 100644 --- a/src/chrome/chromeDebugAdapter.ts +++ b/src/chrome/chromeDebugAdapter.ts @@ -3,41 +3,126 @@ *--------------------------------------------------------*/ import { DebugProtocol } from 'vscode-debugprotocol'; -import { InitializedEvent, TerminatedEvent, Handles, ContinuedEvent, BreakpointEvent, OutputEvent, Logger, logger, LoadedSourceEvent } from 'vscode-debugadapter'; - -import { ICommonRequestArgs, ILaunchRequestArgs, ISetBreakpointsArgs, ISetBreakpointsResponseBody, IStackTraceResponseBody, - IAttachRequestArgs, IScopesResponseBody, IVariablesResponseBody, - ISourceResponseBody, IThreadsResponseBody, IEvaluateResponseBody, ISetVariableResponseBody, IDebugAdapter, - ICompletionsResponseBody, IToggleSkipFileStatusArgs, IInternalStackTraceResponseBody, - IExceptionInfoResponseBody, ISetBreakpointResult, IRestartRequestArgs, IInitializeRequestArgs, ITelemetryPropertyCollector, IGetLoadedSourcesResponseBody, TimeTravelRuntime } from '../debugAdapterInterfaces'; -import { IChromeDebugAdapterOpts, ChromeDebugSession } from './chromeDebugSession'; +import { TerminatedEvent, ContinuedEvent, logger, } from 'vscode-debugadapter'; + +import { + ICommonRequestArgs, ILaunchRequestArgs, IScopesResponseBody, IVariablesResponseBody, + IThreadsResponseBody, IEvaluateResponseBody, ISetVariableResponseBody, + ICompletionsResponseBody, IRestartRequestArgs, ITimeTravelRuntime +} from '../debugAdapterInterfaces'; + import { ChromeConnection } from './chromeConnection'; import * as ChromeUtils from './chromeUtils'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import { PropertyContainer, ScopeContainer, ExceptionContainer, isIndexedPropName, IVariableContainer } from './variables'; import * as variables from './variables'; import { formatConsoleArguments, formatExceptionDetails, clearConsoleCode } from './consoleHelper'; -import { StoppedEvent2, ReasonType } from './stoppedEvent'; -import { InternalSourceBreakpoint, stackTraceWithoutLogpointFrame } from './internalSourceBreakpoint'; +import { ReasonType } from './stoppedEvent'; +import { stackTraceWithoutLogpointFrame } from './internalSourceBreakpoint'; import * as errors from '../errors'; import * as utils from '../utils'; -import { promiseDefer } from '../utils'; -import { telemetry, BatchTelemetryReporter, IExecutionResultTelemetryProperties } from '../telemetry'; +import { telemetry, BatchTelemetryReporter } from '../telemetry'; import { StepProgressEventsEmitter } from '../executionTimingsReporter'; import { LineColTransformer } from '../transformers/lineNumberTransformer'; import { BasePathTransformer } from '../transformers/basePathTransformer'; -import { RemotePathTransformer } from '../transformers/remotePathTransformer'; import { BaseSourceMapTransformer } from '../transformers/baseSourceMapTransformer'; -import { EagerSourceMapTransformer } from '../transformers/eagerSourceMapTransformer'; -import { FallbackToClientPathTransformer } from '../transformers/fallbackToClientPathTransformer'; import { BreakOnLoadHelper } from './breakOnLoadHelper'; import * as sourceMapUtils from '../sourceMaps/sourceMapUtils'; -import * as path from 'path'; - import * as nls from 'vscode-nls'; +import { ISession } from './client/session'; +import { IScript } from './internal/scripts/script'; + +import { LocationInLoadedSource } from './internal/locations/location'; +import { IEvaluateArguments, ICompletionsArguments } from './internal/requests'; +import { EventSender } from './client/eventSender'; +import { parseResourceIdentifier, ConnectedCDAConfiguration } from '..'; +import { LoadedSourceCallFrame } from './internal/stackTraces/callFrame'; +import { CodeFlowStackTrace } from './internal/stackTraces/codeFlowStackTrace'; +import { IResourceIdentifier } from './internal/sources/resourceIdentifier'; +import { FormattedExceptionParser } from './internal/formattedExceptionParser'; +import { DeleteMeScriptsRegistry } from './internal/scripts/scriptsRegistry'; +import { CDTPExceptionThrownEventsProvider, IExceptionThrownEvent } from './cdtpDebuggee/eventsProviders/cdtpExceptionThrownEventsProvider'; +import { CDTPExecutionContextEventsProvider } from './cdtpDebuggee/eventsProviders/cdtpExecutionContextEventsProvider'; +import { IInspectDebugeeState, IEvaluateOnCallFrameRequest } from './cdtpDebuggee/features/cdtpInspectDebugeeState'; +import { IUpdateDebugeeState } from './cdtpDebuggee/features/cdtpUpdateDebugeeState'; +import { injectable, inject } from 'inversify'; +import { TYPES } from './dependencyInjection.ts/types'; +import { ICDTDebuggeeExecutionEventsProvider, PausedEvent } from './cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { ILogEntry, CDTPLogEventsProvider } from './cdtpDebuggee/eventsProviders/cdtpLogEventsProvider'; +import { IConsoleEventsProvider, IConsoleAPICalledEvent } from './cdtpDebuggee/eventsProviders/cdtpConsoleEventsProvider'; +import { IPauseOnExceptionsConfigurer } from './cdtpDebuggee/features/cdtpPauseOnExceptionsConfigurer'; + +// export class ChromeDebugAdapter extends ChromeDebugAdapterClass { +// /** These methods are called by the ChromeDebugAdapter subclass in chrome-debug. We need to redirect them like this +// * until we complete the refactor in chrome-debug and we make these methods work in a proper way +// */ +// protected hookConnectionEvents(): void { +// return this.chromeDebugAdapter.hookConnectionEvents(); +// } +// public commonArgs(args: ICommonRequestArgs): void { +// return this.chromeDebugAdapter.commonArgs(args); +// } +// protected onResumed(): void { +// return this.chromeDebugAdapter.onResumed(); +// } +// protected terminateSession(reason: string, restart?: IRestartRequestArgs): Promise { +// return this.chromeDebugAdapter.terminateSession(reason, restart); +// } +// protected globalEvaluate(args: CDTP.Runtime.EvaluateRequest): Promise { +// return this.chromeDebugAdapter.globalEvaluate(args); +// } +// protected get _launchAttachArgs(): ICommonRequestArgs { +// return this.chromeDebugAdapter._launchAttachArgs; +// } +// protected set _expectingStopReason(value: ReasonType) { +// this.chromeDebugAdapter._expectingStopReason = value; +// } +// protected get _domains(): Map { +// return this.chromeDebugAdapter._domains; +// } +// protected get _hasTerminated(): boolean { +// return this.chromeDebugAdapter._hasTerminated; +// } +// protected get _session(): ISession { +// return this.chromeDebugAdapter._session; +// } + +// /** These methods are called by the NodeDebugAdapter subclass in node-debug2. We need to redirect them like this +// * until we complete the refactor in node-debug2 and we make these methods work in a proper way +// */ +// protected get _attachMode(): boolean { +// return this.chromeDebugAdapter._attachMode; +// } +// protected set _promiseRejectExceptionFilterEnabled(value: boolean) { +// this.chromeDebugAdapter._promiseRejectExceptionFilterEnabled = value; +// } +// protected get _pathTransformer(): BasePathTransformer { +// return this.chromeDebugAdapter._pathTransformer; +// } +// protected get _inShutdown(): boolean { +// return this.chromeDebugAdapter._inShutdown; +// } +// protected get _port(): number { +// return this.chromeDebugAdapter._port; +// } + +// protected get _sourceMapTransformer(): BaseSourceMapTransformer { +// return this.chromeDebugAdapter.sourceMapTransformer; +// } + +// protected static get EVAL_NAME_PREFIX(): string { +// return ChromeDebugLogic.EVAL_NAME_PREFIX; +// } + +// protected onConsoleAPICalled(event: ConsoleAPICalledEvent): void { +// return this.chromeDebugAdapter.onConsoleAPICalled(event); +// } +// // DIEGO START +// } + let localize = nls.loadMessageBundle(); interface IPropCount { @@ -51,165 +136,101 @@ interface IPropCount { */ export interface ISourceContainer { /** The runtime-side scriptId of this script */ - scriptId?: Crdp.Runtime.ScriptId; + scriptId?: IScript; /** The contents of this script, if they are inlined in the sourcemap */ contents?: string; /** The authored path to this script (only set if the contents are inlined) */ mappedPath?: string; } -export interface IPendingBreakpoint { - args: ISetBreakpointsArgs; - ids: number[]; - requestSeq: number; - setWithPath: string; -} - -interface IHitConditionBreakpoint { - numHits: number; - shouldPause: (numHits: number) => boolean; -} - export type VariableContext = 'variables' | 'watch' | 'repl' | 'hover'; -export type CrdpScript = Crdp.Debugger.ScriptParsedEvent; +export type CrdpScript = CDTP.Debugger.ScriptParsedEvent; export type CrdpDomain = string; export type LoadedSourceEventReason = 'new' | 'changed' | 'removed'; -export interface BreakpointSetResult { - isSet: boolean; - breakpoint: DebugProtocol.Breakpoint; -} - -export interface IOnPausedResult { - didPause: boolean; -} - -export abstract class ChromeDebugAdapter implements IDebugAdapter { +@injectable() +export class ChromeDebugLogic { public static EVAL_NAME_PREFIX = ChromeUtils.EVAL_NAME_PREFIX; public static EVAL_ROOT = ''; - private static SCRIPTS_COMMAND = '.scripts'; - private static THREAD_ID = 1; - private static SET_BREAKPOINTS_TIMEOUT = 5000; - private static HITCONDITION_MATCHER = /^(>|>=|=|<|<=|%)?\s*([0-9]+)$/; - private static ASYNC_CALL_STACK_DEPTH = 4; + public static THREAD_ID = 1; - protected _session: ChromeDebugSession; - protected _domains = new Map(); + public _session: ISession; + public _domains = new Map(); private _clientAttached: boolean; - private _currentPauseNotification: Crdp.Debugger.PausedEvent; - - // when working with _committedBreakpointsByUrl, we want to keep the url keys canonicalized for consistency - // use methods getValueFromCommittedBreakpointsByUrl and setValueForCommittedBreakpointsByUrl - private _committedBreakpointsByUrl: Map; - private _exception: Crdp.Runtime.RemoteObject; - private _setBreakpointsRequestQ: Promise; + private _exception: CDTP.Runtime.RemoteObject | undefined; private _expectingResumedEvent: boolean; - protected _expectingStopReason: ReasonType; + public _expectingStopReason: ReasonType | undefined; private _waitAfterStep = Promise.resolve(); - private _frameHandles: Handles; private _variableHandles: variables.VariableHandles; - private _breakpointIdHandles: utils.ReverseHandles; - private _sourceHandles: utils.ReverseHandles; - - private _scriptsById: Map; - private _scriptsByUrl: Map; - private _pendingBreakpointsByUrl: Map; - private _hitConditionBreakpointsById: Map; private _lineColTransformer: LineColTransformer; - protected _chromeConnection: ChromeConnection; - protected _sourceMapTransformer: BaseSourceMapTransformer; - protected _pathTransformer: BasePathTransformer; - - protected _clientRequestedSessionEnd: boolean; - protected _hasTerminated: boolean; - protected _inShutdown: boolean; - protected _attachMode: boolean; - protected _launchAttachArgs: ICommonRequestArgs; - protected _port: number; - private _blackboxedRegexes: RegExp[] = []; - private _skipFileStatuses = new Map(); + protected _chromeConmer: BaseSourceMapTransformer; + public _pathTransformer: BasePathTransformer; + + public _hasTerminated: boolean; + public _inShutdown: boolean; + public _attachMode: boolean; + public readonly _launchAttachArgs: ICommonRequestArgs = this._configuration.args; + public _port: number; private _currentStep = Promise.resolve(); private _currentLogMessage = Promise.resolve(); - private _nextUnboundBreakpointId = 0; - private _pauseOnPromiseRejections = true; - protected _promiseRejectExceptionFilterEnabled = false; - - private _columnBreakpointsEnabled: boolean; - - private _smartStepEnabled: boolean; - private _smartStepCount = 0; - private _earlyScripts: Crdp.Debugger.ScriptParsedEvent[] = []; - - private _initialSourceMapsP = Promise.resolve(); - - private _lastPauseState: { expecting: ReasonType; event: Crdp.Debugger.PausedEvent }; - - protected _breakOnLoadHelper: BreakOnLoadHelper | null; - - // Queue to synchronize new source loaded and source removed events so that 'remove' script events - // won't be send before the corresponding 'new' event has been sent - private _sourceLoadedQueue: Promise = Promise.resolve(null); - - // Promises so ScriptPaused events can wait for ScriptParsed events to finish resolving breakpoints - private _scriptIdToBreakpointsAreResolvedDefer = new Map>(); + privaRejectExceptionFilterEnabled = false; private _batchTelemetryReporter: BatchTelemetryReporter; public readonly events: StepProgressEventsEmitter; - private _loadedSourcesByScriptId = new Map(); - - protected _isVSClient: boolean; + protected _breakOnLoadHelper: BreakOnLoadHelper | null; - public constructor({ chromeConnection, lineColTransformer, sourceMapTransformer, pathTransformer, targetFilter }: IChromeDebugAdapterOpts, - session: ChromeDebugSession) { + private readonly _chromeConnection: ChromeConnection; + private readonly _sourceMapTransformer: BaseSourceMapTransformer; + public _promiseRejectExceptionFilterEnabled = false; + public _pauseOnPromiseRejections = true; + static HITCONDITION_MATCHER: any; + + public constructor( + @inject(TYPES.LineColTransformer) lineColTransformer: LineColTransformer, + @inject(TYPES.BaseSourceMapTransformer) sourceMapTransformer: BaseSourceMapTransformer, + @inject(TYPES.BasePathTransformer) pathTransformer: BasePathTransformer, + @inject(TYPES.ISession) session: ISession, + @inject(TYPES.ChromeConnection) chromeConnection: ChromeConnection, + @inject(TYPES.DeleteMeScriptsRegistry) private readonly _scriptsLogic: DeleteMeScriptsRegistry, + @inject(TYPES.EventSender) private readonly _eventSender: EventSender, + @inject(TYPES.ExceptionThrownEventProvider) private readonly _exceptionThrownEventProvider: CDTPExceptionThrownEventsProvider, + @inject(TYPES.ExecutionContextEventsProvider) private readonly _executionContextEventsProvider: CDTPExecutionContextEventsProvider, + @inject(TYPES.IInspectDebugeeState) private readonly _inspectDebugeeState: IInspectDebugeeState, + @inject(TYPES.IUpdateDebugeeState) private readonly _updateDebugeeState: IUpdateDebugeeState, + @inject(TYPES.ConnectedCDAConfiguration) private readonly _configuration: ConnectedCDAConfiguration, + @inject(TYPES.ICDTPDebuggerEventsProvider) private readonly _debuggerEvents: ICDTDebuggeeExecutionEventsProvider, + @inject(TYPES.IConsoleEventsProvider) private readonly _consoleEventsProvider: IConsoleEventsProvider, + @inject(TYPES.ILogEventsProvider) private readonly _logEventsProvider: CDTPLogEventsProvider, + @inject(TYPES.IPauseOnExceptions) private readonly _pauseOnExceptions: IPauseOnExceptionsConfigurer, + ) { telemetry.setupEventHandler(e => session.sendEvent(e)); this._batchTelemetryReporter = new BatchTelemetryReporter(telemetry); this._session = session; - this._chromeConnection = new (chromeConnection || ChromeConnection)(undefined, targetFilter); + this._chromeConnection = chromeConnection; this.events = new StepProgressEventsEmitter(this._chromeConnection.events ? [this._chromeConnection.events] : []); - this._frameHandles = new Handles(); this._variableHandles = new variables.VariableHandles(); - this._breakpointIdHandles = new utils.ReverseHandles(); - this._sourceHandles = new utils.ReverseHandles(); - this._pendingBreakpointsByUrl = new Map(); - this._hitConditionBreakpointsById = new Map(); - this._lineColTransformer = new (lineColTransformer || LineColTransformer)(this._session); - this._sourceMapTransformer = new (sourceMapTransformer || EagerSourceMapTransformer)(this._sourceHandles); - this._pathTransformer = new (pathTransformer || RemotePathTransformer)(); + this._lineColTransformer = lineColTransformer; + this._sourceMapTransformer = sourceMapTransformer; + this._pathTransformer = pathTransformer; this.clearTargetContext(); } - public get chrome(): Crdp.ProtocolApi { - return this._chromeConnection.api; - } - - public get scriptsById(): Map { - return this._scriptsById; - } - public get pathTransformer(): BasePathTransformer { return this._pathTransformer; } - public get pendingBreakpointsByUrl(): Map { - return this._pendingBreakpointsByUrl; - } - - public get committedBreakpointsByUrl(): Map { - return this._committedBreakpointsByUrl; - } - public get sourceMapTransformer(): BaseSourceMapTransformer { return this._sourceMapTransformer; } @@ -219,86 +240,9 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { */ protected clearTargetContext(): void { this._sourceMapTransformer.clearTargetContext(); - - this._scriptsById = new Map(); - this._scriptsByUrl = new Map(); - - this._committedBreakpointsByUrl = new Map(); - this._setBreakpointsRequestQ = Promise.resolve(); - this._pathTransformer.clearTargetContext(); } - /* __GDPR__ - "ClientRequest/initialize" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public initialize(args: IInitializeRequestArgs): DebugProtocol.Capabilities { - if (args.supportsMapURLToFilePathRequest) { - this._pathTransformer = new FallbackToClientPathTransformer(this._session); - } - - this._isVSClient = args.clientID === 'visualstudio'; - utils.setCaseSensitivePaths(!this._isVSClient); - this._sourceMapTransformer.isVSClient = this._isVSClient; - - if (args.pathFormat !== 'path') { - throw errors.pathFormat(); - } - - if (args.locale) { - localize = nls.config({ locale: args.locale })(); - } - - // because session bypasses dispatchRequest - if (typeof args.linesStartAt1 === 'boolean') { - (this)._clientLinesStartAt1 = args.linesStartAt1; - } - if (typeof args.columnsStartAt1 === 'boolean') { - (this)._clientColumnsStartAt1 = args.columnsStartAt1; - } - - const exceptionBreakpointFilters = [ - { - label: localize('exceptions.all', 'All Exceptions'), - filter: 'all', - default: false - }, - { - label: localize('exceptions.uncaught', 'Uncaught Exceptions'), - filter: 'uncaught', - default: false - } - ]; - if (this._promiseRejectExceptionFilterEnabled) { - exceptionBreakpointFilters.push({ - label: localize('exceptions.promise_rejects', 'Promise Rejects'), - filter: 'promise_reject', - default: false - }); - } - - // This debug adapter supports two exception breakpoint filters - return { - exceptionBreakpointFilters, - supportsConfigurationDoneRequest: true, - supportsSetVariable: true, - supportsConditionalBreakpoints: true, - supportsCompletionsRequest: true, - supportsHitConditionalBreakpoints: true, - supportsRestartFrame: true, - supportsExceptionInfoRequest: true, - supportsDelayedStackTraceLoading: true, - supportsValueFormattingOptions: true, - supportsEvaluateForHovers: true, - supportsLoadedSourcesRequest: true - }; - } - /* __GDPR__ "ClientRequest/configurationDone" : { "${include}": [ @@ -315,112 +259,13 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return !!this._breakOnLoadHelper; } - /* __GDPR__ - "ClientRequest/launch" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async launch(args: ILaunchRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector): Promise { - this.commonArgs(args); - - if (args.pathMapping) { - for (const urlToMap in args.pathMapping) { - args.pathMapping[urlToMap] = utils.canonicalizeUrl(args.pathMapping[urlToMap]); - } - } - - this._sourceMapTransformer.launch(args); - await this._pathTransformer.launch(args); - - if (!args.__restart) { - /* __GDPR__ - "debugStarted" : { - "request" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "args" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "${include}": [ "${DebugCommonProperties}" ] - } - */ - telemetry.reportEvent('debugStarted', { request: 'launch', args: Object.keys(args) }); - } - } - - /* __GDPR__ - "ClientRequest/attach" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async attach(args: IAttachRequestArgs): Promise { - this._attachMode = true; - this.commonArgs(args); - this._sourceMapTransformer.attach(args); - await this._pathTransformer.attach(args); - - if (!args.port) { - args.port = 9229; - } - - /* __GDPR__ - "debugStarted" : { - "request" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "args" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }, - "${include}": [ "${DebugCommonProperties}" ] - } - */ - telemetry.reportEvent('debugStarted', { request: 'attach', args: Object.keys(args) }); - await this.doAttach(args.port, args.url, args.address, args.timeout, args.websocketUrl, args.extraCRDPChannelPort); - } - - protected commonArgs(args: ICommonRequestArgs): void { - let logToFile = false; - let logLevel: Logger.LogLevel; - if (args.trace === 'verbose') { - logLevel = Logger.LogLevel.Verbose; - logToFile = true; - } else if (args.trace) { - logLevel = Logger.LogLevel.Warn; - logToFile = true; - } else { - logLevel = Logger.LogLevel.Warn; - } - - let logTimestamps = args.logTimestamps; - - // The debug configuration provider should have set logFilePath on the launch config. If not, default to 'true' to use the - // "legacy" log file path from the CDA subclass - const logFilePath = args.logFilePath || logToFile; - logger.setup(logLevel, logFilePath, logTimestamps); - - this._launchAttachArgs = args; - - // Enable sourcemaps and async callstacks by default - args.sourceMaps = typeof args.sourceMaps === 'undefined' || args.sourceMaps; - args.showAsyncStacks = typeof args.showAsyncStacks === 'undefined' || args.showAsyncStacks; - - this._smartStepEnabled = this._launchAttachArgs.smartStep; - - if (args.breakOnLoadStrategy && args.breakOnLoadStrategy !== 'off') { - this._breakOnLoadHelper = new BreakOnLoadHelper(this, args.breakOnLoadStrategy); - } - - // Use hasOwnProperty to explicitly permit setting a falsy targetFilter. - if (args.hasOwnProperty('targetFilter')) { - this._chromeConnection.setTargetFilter(args.targetFilter); - } - } - public shutdown(): void { this._batchTelemetryReporter.finalize(); this._inShutdown = true; this._session.shutdown(); } - protected async terminateSession(reason: string, disconnectArgs?: DebugProtocol.DisconnectArguments, restart?: IRestartRequestArgs): Promise { + public async terminateSession(reason: string, restart?: IRestartRequestArgs): Promise { logger.log(`Terminated: ${reason}`); if (!this._hasTerminated) { @@ -450,1472 +295,218 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { /** * Hook up all connection events */ - protected hookConnectionEvents(): void { - this.chrome.Debugger.on('paused', params => { - /* __GDPR__ - "target/notification/onPaused" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - this.runAndMeasureProcessingTime('target/notification/onPaused', async () => { - await this.onPaused(params); - }); - }); - this.chrome.Debugger.on('resumed', () => this.onResumed()); - this.chrome.Debugger.on('scriptParsed', params => { - /* __GDPR__ - "target/notification/onScriptParsed" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - this.runAndMeasureProcessingTime('target/notification/onScriptParsed', () => { - return this.onScriptParsed(params); - }); - }); - this.chrome.Debugger.on('breakpointResolved', params => this.onBreakpointResolved(params)); - this.chrome.Console.on('messageAdded', params => this.onMessageAdded(params)); - this.chrome.Runtime.on('consoleAPICalled', params => this.onConsoleAPICalled(params)); - this.chrome.Runtime.on('exceptionThrown', params => this.onExceptionThrown(params)); - this.chrome.Runtime.on('executionContextsCleared', () => this.onExecutionContextsCleared()); - this.chrome.Log.on('entryAdded', params => this.onLogEntryAdded(params)); + public install(): ChromeDebugLogic { + this._debuggerEvents.onResumed(() => this.onResumed()); + this._debuggerEvents.onPaused(paused => this.onPaused(paused)); + this._consoleEventsProvider.onMessageAdded(params => this.onMessageAdded(params)); + this._consoleEventsProvider.onConsoleAPICalled(params => this.onConsoleAPICalled(params)); + this._exceptionThrownEventProvider.onExceptionThrown(params => this.onExceptionThrown(params)); + this._executionContextEventsProvider.onExecutionContextsCleared(() => this.clearTargetContext()); + this._logEventsProvider.onEntryAdded(entry => this.onLogEntryAdded(entry)); this._chromeConnection.onClose(() => this.terminateSession('websocket closed')); - } - - private async runAndMeasureProcessingTime(notificationName: string, procedure: () => Promise): Promise { - const startTime = Date.now(); - const startTimeMark = process.hrtime(); - let properties: IExecutionResultTelemetryProperties = { - startTime: startTime.toString() - }; - - try { - await procedure(); - properties.successful = 'true'; - } catch (e) { - properties.successful = 'false'; - properties.exceptionType = 'firstChance'; - utils.fillErrorDetails(properties, e); - } - - const elapsedTime = utils.calculateElapsedTime(startTimeMark); - properties.timeTakenInMilliseconds = elapsedTime.toString(); - - // Callers set GDPR annotation - this._batchTelemetryReporter.reportEvent(notificationName, properties); - } - - /** - * Enable clients and run connection - */ - protected runConnection(): Promise[] { - return [ - this.chrome.Console.enable() - .catch(e => { /* Specifically ignore a fail here since it's only for backcompat */ }), - utils.toVoidP(this.chrome.Debugger.enable()), - this.chrome.Runtime.enable(), - this.chrome.Log.enable() - .catch(e => { }), // Not supported by all runtimes - this._chromeConnection.run(), - ]; - } - - protected async doAttach(port: number, targetUrl?: string, address?: string, timeout?: number, websocketUrl?: string, extraCRDPChannelPort?: number): Promise { - /* __GDPR__FRAGMENT__ - "StepNames" : { - "Attach" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.events.emitStepStarted('Attach'); - // Client is attaching - if not attached to the chrome target, create a connection and attach - this._clientAttached = true; - if (!this._chromeConnection.isAttached) { - if (websocketUrl) { - await this._chromeConnection.attachToWebsocketUrl(websocketUrl, extraCRDPChannelPort); - } else { - await this._chromeConnection.attach(address, port, targetUrl, timeout, extraCRDPChannelPort); - } - - /* __GDPR__FRAGMENT__ - "StepNames" : { - "Attach.ConfigureDebuggingSession.Internal" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.events.emitStepStarted('Attach.ConfigureDebuggingSession.Internal'); - - this._port = port; - - this.hookConnectionEvents(); - let patterns: string[] = []; - - if (this._launchAttachArgs.skipFiles) { - const skipFilesArgs = this._launchAttachArgs.skipFiles.filter(glob => { - if (glob.startsWith('!')) { - logger.warn(`Warning: skipFiles entries starting with '!' aren't supported and will be ignored. ("${glob}")`); - return false; - } - - return true; - }); - - patterns = skipFilesArgs.map(glob => utils.pathGlobToBlackboxedRegex(glob)); - } - - if (this._launchAttachArgs.skipFileRegExps) { - patterns = patterns.concat(this._launchAttachArgs.skipFileRegExps); - } - - /* __GDPR__FRAGMENT__ - "StepNames" : { - "Attach.ConfigureDebuggingSession.Target" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } - } - */ - this.events.emitStepStarted('Attach.ConfigureDebuggingSession.Target'); - - // Make sure debugging domain is enabled before calling refreshBlackboxPatterns() below - await Promise.all(this.runConnection()); - - if (patterns.length) { - this._blackboxedRegexes = patterns.map(pattern => new RegExp(pattern, 'i')); - this.refreshBlackboxPatterns(); - } - - await this.initSupportedDomains(); - const maxDepth = this._launchAttachArgs.showAsyncStacks ? ChromeDebugAdapter.ASYNC_CALL_STACK_DEPTH : 0; - try { - await this.chrome.Debugger.setAsyncCallStackDepth({ maxDepth }); - } catch (e) { - // Not supported by older runtimes, ignore it. - } - - if (this._breakOnLoadHelper) { - this._breakOnLoadHelper.setBrowserVersion((await this._chromeConnection.version).browser); - } - } - } - - private async initSupportedDomains(): Promise { - try { - const domainResponse = await this.chrome.Schema.getDomains(); - domainResponse.domains.forEach(domain => this._domains.set(domain.name, domain)); - } catch (e) { - // If getDomains isn't supported for some reason, skip this - } - } - - /** - * This event tells the client to begin sending setBP requests, etc. Some consumers need to override this - * to send it at a later time of their choosing. - */ - protected async sendInitializedEvent(): Promise { - // Wait to finish loading sourcemaps from the initial scriptParsed events - if (this._initialSourceMapsP) { - const initialSourceMapsP = this._initialSourceMapsP; - this._initialSourceMapsP = null; - - await initialSourceMapsP; - - this._session.sendEvent(new InitializedEvent()); - this.events.emitStepCompleted('NotifyInitialized'); - await Promise.all(this._earlyScripts.map(script => this.sendLoadedSourceEvent(script))); - this._earlyScripts = null; - } - } - - public doAfterProcessingSourceEvents(action: () => void): Promise { - return this._sourceLoadedQueue = this._sourceLoadedQueue.then(action); - } - - /** - * e.g. the target navigated - */ - protected onExecutionContextsCleared(): Promise { - const cachedScriptParsedEvents = Array.from(this._scriptsById.values()); - this.clearTargetContext(); - return this.doAfterProcessingSourceEvents(async () => { // This will not execute until all the on-flight 'new' source events have been processed - for (let scriptedParseEvent of cachedScriptParsedEvents) { - this.sendLoadedSourceEvent(scriptedParseEvent, 'removed'); - } - }); - } - - protected async onPaused(notification: Crdp.Debugger.PausedEvent, expectingStopReason = this._expectingStopReason): Promise { - if (notification.asyncCallStackTraceId) { - await this.chrome.Debugger.pauseOnAsyncCall({ parentStackTraceId: notification.asyncCallStackTraceId }); - await this.chrome.Debugger.resume(); - return { didPause: false }; - } - - this._variableHandles.onPaused(); - this._frameHandles.reset(); - this._exception = undefined; - this._lastPauseState = { event: notification, expecting: expectingStopReason }; - this._currentPauseNotification = notification; - - // If break on load is active, we pass the notification object to breakonload helper - // If it returns true, we continue and return - if (this.breakOnLoadActive) { - let shouldContinue = await this._breakOnLoadHelper.handleOnPaused(notification); - if (shouldContinue) { - this.chrome.Debugger.resume() - .catch(e => { - logger.error('Failed to resume due to exception: ' + e.message); - }); - return { didPause: false }; - } - } - - // We can tell when we've broken on an exception. Otherwise if hitBreakpoints is set, assume we hit a - // breakpoint. If not set, assume it was a step. We can't tell the difference between step and 'break on anything'. - let reason: ReasonType; - let shouldSmartStep = false; - if (notification.reason === 'exception') { - reason = 'exception'; - this._exception = notification.data; - } else if (notification.reason === 'promiseRejection') { - reason = 'promise_rejection'; - - // After processing smartStep and so on, check whether we are paused on a promise rejection, and should continue past it - if (this._promiseRejectExceptionFilterEnabled && !this._pauseOnPromiseRejections) { - this.chrome.Debugger.resume() - .catch(e => { /* ignore failures */ }); - return { didPause: false }; - } - - this._exception = notification.data; - } else if (notification.hitBreakpoints && notification.hitBreakpoints.length) { - reason = 'breakpoint'; - - // Did we hit a hit condition breakpoint? - for (let hitBp of notification.hitBreakpoints) { - if (this._hitConditionBreakpointsById.has(hitBp)) { - // Increment the hit count and check whether to pause - const hitConditionBp = this._hitConditionBreakpointsById.get(hitBp); - hitConditionBp.numHits++; - // Only resume if we didn't break for some user action (step, pause button) - if (!expectingStopReason && !hitConditionBp.shouldPause(hitConditionBp.numHits)) { - this.chrome.Debugger.resume() - .catch(e => { /* ignore failures */ }); - return { didPause: false }; - } - } - } - } else if (expectingStopReason) { - // If this was a step, check whether to smart step - reason = expectingStopReason; - shouldSmartStep = await this.shouldSmartStepCallFrame(this._currentPauseNotification.callFrames[0]); - } else { - reason = 'debugger_statement'; - } - this._expectingStopReason = undefined; + return this; + } + + // private async runAndMeasureProcessingTime(notificationName: string, procedure: () => Promise): Promise { + // const startTime = Date.now(); + // const startTimeMark = process.hrtime(); + // let properties: IExecutionResultTelemetryProperties = { + // startTime: startTime.toString() + // }; + + // try { + // return await procedure(); + // properties.successful = 'true'; + // } catch (e) { + // properties.successful = 'false'; + // properties.exceptionType = 'firstChance'; + // utils.fillErrorDetails(properties, e); + // throw e; + // } finally { + // const elapsedTime = utils.calculateElapsedTime(startTimeMark); + // properties.timeTakenInMilliseconds = elapsedTime.toString(); + + // // Callers set GDPR annotation + // this._batchTelemetryReporter.reportEvent(notificationName, properties); + // } + // } + + public onResumed(): void { + if (this._expectingResumedEvent) { + this._expectingResumedEvent = false; - if (shouldSmartStep) { - this._smartStepCount++; - await this.stepIn(false); - return { didPause: false }; + // Need to wait to eval just a little after each step, because of #148 + this._waitAfterStep = utils.promiseTimeout(null, 50); } else { - if (this._smartStepCount > 0) { - logger.log(`SmartStep: Skipped ${this._smartStepCount} steps`); - this._smartStepCount = 0; - } - - // Enforce that the stopped event is not fired until we've sent the response to the step that induced it. - // Also with a timeout just to ensure things keep moving - const sendStoppedEvent = () => { - return this._session.sendEvent(new StoppedEvent2(reason, /*threadId=*/ChromeDebugAdapter.THREAD_ID, this._exception)); - }; - await utils.promiseTimeout(this._currentStep, /*timeoutMs=*/300) - .then(sendStoppedEvent, sendStoppedEvent); - - return { didPause: true }; + let resumedEvent = new ContinuedEvent(ChromeDebugLogic.THREAD_ID); + this._session.sendEvent(resumedEvent); } } - /* __GDPR__ - "ClientRequest/exceptionInfo" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { - if (args.threadId !== ChromeDebugAdapter.THREAD_ID) { - throw errors.invalidThread(args.threadId); + public onConsoleAPICalled(event: IConsoleAPICalledEvent): void { + if (this._launchAttachArgs._suppressConsoleOutput) { + return; } - if (this._exception) { - const isError = this._exception.subtype === 'error'; - const message = isError ? utils.firstLine(this._exception.description) : (this._exception.description || this._exception.value); - const formattedMessage = message && message.replace(/\*/g, '\\*'); - const response: IExceptionInfoResponseBody = { - exceptionId: this._exception.className || this._exception.type || 'Error', - breakMode: 'unhandled', - details: { - stackTrace: this._exception.description && await this.mapFormattedException(this._exception.description), - message, - formattedDescription: formattedMessage, // VS workaround - see https://github.com/Microsoft/vscode/issues/34259 - typeName: this._exception.subtype || this._exception.type - } - }; - - return response; - } else { - throw errors.noStoredException(); + const result = formatConsoleArguments(event.type, event.args, event.stackTrace); + const stack = stackTraceWithoutLogpointFrame(event.stackTrace); + if (result) { + this.logObjects(result.args, result.isError, stack); } } - private async shouldSmartStepCallFrame(frame: Crdp.Debugger.CallFrame): Promise { - if (!this._smartStepEnabled) return Promise.resolve(false); - - const stackFrame = this.callFrameToStackFrame(frame); - return this.shouldSmartStep(stackFrame); - } - - private async shouldSmartStep(stackFrame: DebugProtocol.StackFrame): Promise { - const clientPath = this._pathTransformer.getClientPathFromTargetPath(stackFrame.source.path) || stackFrame.source.path; - const mapping = await this._sourceMapTransformer.mapToAuthored(clientPath, stackFrame.line, stackFrame.column); - if (mapping) { - return false; - } - - if ((await this.sourceMapTransformer.allSources(clientPath)).length) { - return true; + private onLogEntryAdded(entry: ILogEntry): void { + // The Debug Console doesn't give the user a way to filter by level, just ignore 'verbose' logs + if (entry.level === 'verbose') { + return; } - return false; - } - - protected onResumed(): void { - this._currentPauseNotification = null; + const args = entry.args || []; - if (this._expectingResumedEvent) { - this._expectingResumedEvent = false; + let text = entry.text || ''; + if (entry.url && !entry.stackTrace) { + if (text) { + text += ' '; + } - // Need to wait to eval just a little after each step, because of #148 - this._waitAfterStep = utils.promiseTimeout(null, 50); - } else { - let resumedEvent = new ContinuedEvent(ChromeDebugAdapter.THREAD_ID); - this._session.sendEvent(resumedEvent); + text += `[${entry.url}]`; } - } - private async detectColumnBreakpointSupport(scriptId: Crdp.Runtime.ScriptId): Promise { - this._columnBreakpointsEnabled = false; // So it isn't requested multiple times - try { - await this.chrome.Debugger.getPossibleBreakpoints({ - start: { scriptId, lineNumber: 0, columnNumber: 0 }, - end: { scriptId, lineNumber: 1, columnNumber: 0 }, - restrictToFunction: false + if (text) { + args.unshift({ + type: 'string', + value: text }); - this._columnBreakpointsEnabled = true; - } catch (e) { - this._columnBreakpointsEnabled = false; } - this._lineColTransformer.columnBreakpointsEnabled = this._columnBreakpointsEnabled; - } - - public getBreakpointsResolvedDefer(scriptId: string): utils.IPromiseDefer { - const existingValue = this._scriptIdToBreakpointsAreResolvedDefer.get(scriptId); - if (existingValue) { - return existingValue; - } else { - const newValue = promiseDefer(); - this._scriptIdToBreakpointsAreResolvedDefer.set(scriptId, newValue); - return newValue; + const type = entry.level === 'error' ? 'error' : + entry.level === 'warning' ? 'warning' : + 'log'; + const result = formatConsoleArguments(type, args, entry.stackTrace); + const stack = entry.stackTrace; + if (result) { + this.logObjects(result.args, result.isError, stack); } } - protected async onScriptParsed(script: Crdp.Debugger.ScriptParsedEvent): Promise { - // The stack trace and hash can be large and the DA doesn't need it. - delete script.stackTrace; - delete script.hash; - - const breakpointsAreResolvedDefer = this.getBreakpointsResolvedDefer(script.scriptId); - try { - this.doAfterProcessingSourceEvents(async () => { // This will block future 'removed' source events, until this processing has been completed - if (typeof this._columnBreakpointsEnabled === 'undefined') { - await this.detectColumnBreakpointSupport(script.scriptId); - await this.sendInitializedEvent(); + private async logObjects(objs: CDTP.Runtime.RemoteObject[], isError = false, stackTrace?: CodeFlowStackTrace): Promise { + // This is an asynchronous method, so ensure that we handle one at a time so that they are sent out in the same order that they came in. + this._currentLogMessage = this._currentLogMessage + .then(async () => { + const category = isError ? 'stderr' : 'stdout'; + + let location: LocationInLoadedSource = null; + if (stackTrace && stackTrace.codeFlowFrames.length) { + location = stackTrace.codeFlowFrames[0].location.mappedToSource(); } - if (this._earlyScripts) { - this._earlyScripts.push(script); + // Shortcut the common log case to reduce unnecessary back and forth + if (objs.length === 1 && objs[0].type === 'string') { + let msg: string = objs[0].value; + if (isError) { + const stackTrace = await new FormattedExceptionParser(this._scriptsLogic, msg).parse(); + this._eventSender.sendExceptionThrown({ exceptionStackTrace: stackTrace, category, location }); + } else { + if (!msg.endsWith(clearConsoleCode)) { + // If this string will clear the console, don't append a \n + msg += '\n'; + } + this._eventSender.sendOutput({ output: msg, category, location }); + } } else { - await this.sendLoadedSourceEvent(script); + const variablesReference = this._variableHandles.create(new variables.LoggedObjects(objs), 'repl'); + this._eventSender.sendOutput({ output: 'output', category, variablesReference, location }); } - }); - - if (script.url) { - script.url = utils.fixDriveLetter(script.url); - } else { - script.url = ChromeDebugAdapter.EVAL_NAME_PREFIX + script.scriptId; - } - this._scriptsById.set(script.scriptId, script); - this._scriptsByUrl.set(utils.canonicalizeUrl(script.url), script); - - const mappedUrl = await this._pathTransformer.scriptParsed(script.url); - - const resolvePendingBPs = async (source: string) => { - source = source && utils.canonicalizeUrl(source); - const pendingBP = this._pendingBreakpointsByUrl.get(source); - if (pendingBP && (!pendingBP.setWithPath || utils.canonicalizeUrl(pendingBP.setWithPath) === source)) { - logger.log(`OnScriptParsed.resolvePendingBPs: Resolving pending breakpoints: ${JSON.stringify(pendingBP)}`); - await this.resolvePendingBreakpoint(pendingBP); - this._pendingBreakpointsByUrl.delete(source); - } else if (source) { - const sourceFileName = path.basename(source).toLowerCase(); - if (Array.from(this._pendingBreakpointsByUrl.keys()).find(key => key.toLowerCase().indexOf(sourceFileName) > -1)) { - logger.log(`OnScriptParsed.resolvePendingBPs: The following pending breakpoints won't be resolved: ${JSON.stringify(pendingBP)} pendingBreakpointsByUrl = ${JSON.stringify([...this._pendingBreakpointsByUrl])} source = ${source}`); - } - } - }; - - const sourceMapsP = this._sourceMapTransformer.scriptParsed(mappedUrl, script.sourceMapURL).then(async sources => { - if (this._hasTerminated) { - return undefined; - } - - if (sources) { - const filteredSources = sources.filter(source => source !== mappedUrl); // Tools like babel-register will produce sources with the same path as the generated script - for (const filteredSource of filteredSources) { - await resolvePendingBPs(filteredSource); - } - } - - if (script.url === mappedUrl && this._pendingBreakpointsByUrl.has(mappedUrl) && this._pendingBreakpointsByUrl.get(mappedUrl).setWithPath === mappedUrl) { - // If the pathTransformer had no effect, and we attempted to set the BPs with that path earlier, then assume that they are about - // to be resolved in this loaded script, and remove the pendingBP. - this._pendingBreakpointsByUrl.delete(mappedUrl); - } else { - await resolvePendingBPs(mappedUrl); - } - - await this.resolveSkipFiles(script, mappedUrl, sources); - }); - - if (this._initialSourceMapsP) { - this._initialSourceMapsP = >Promise.all([this._initialSourceMapsP, sourceMapsP]); - } - await sourceMapsP; - - breakpointsAreResolvedDefer.resolve(); // By now no matter which code path we choose, resolving pending breakpoints should be finished, so trigger the defer - } catch (exception) { - breakpointsAreResolvedDefer.reject(exception); - } - } - - protected async sendLoadedSourceEvent(script: Crdp.Debugger.ScriptParsedEvent, loadedSourceEventReason: LoadedSourceEventReason = 'new'): Promise { - const source = await this.scriptToSource(script); - - // This is a workaround for an edge bug, see https://github.com/Microsoft/vscode-chrome-debug-core/pull/329 - switch (loadedSourceEventReason) { - case 'new': - case 'changed': - if (this._loadedSourcesByScriptId.get(script.scriptId)) { - if (source.sourceReference) { - // We only need to send changed events for dynamic scripts. The client tracks files on storage on it's own, so this notification is not needed - loadedSourceEventReason = 'changed'; - } else { - return; // VS is strict about the changed notifications, and it will fail if we send a changed notification for a file on storage, so we omit it on purpose - } - } else { - loadedSourceEventReason = 'new'; - } - this._loadedSourcesByScriptId.set(script.scriptId, script); - break; - case 'removed': - if (!this._loadedSourcesByScriptId.delete(script.scriptId)) { - telemetry.reportEvent('LoadedSourceEventError', { issue: 'Tried to remove non-existent script', scriptId: script.scriptId }); - return; - } - break; - default: - telemetry.reportEvent('LoadedSourceEventError', { issue: 'Unknown reason', reason: loadedSourceEventReason }); - } - - const scriptEvent = new LoadedSourceEvent(loadedSourceEventReason, source as any); - - this._session.sendEvent(scriptEvent); - } - - private async resolveSkipFiles(script: CrdpScript, mappedUrl: string, sources: string[], toggling?: boolean): Promise { - if (sources && sources.length) { - const parentIsSkipped = this.shouldSkipSource(script.url); - const libPositions: Crdp.Debugger.ScriptPosition[] = []; - - // Figure out skip/noskip transitions within script - let inLibRange = parentIsSkipped; - for (let s of sources) { - let isSkippedFile = this.shouldSkipSource(s); - if (typeof isSkippedFile !== 'boolean') { - // Inherit the parent's status - isSkippedFile = parentIsSkipped; - } - - this._skipFileStatuses.set(s, isSkippedFile); - - if ((isSkippedFile && !inLibRange) || (!isSkippedFile && inLibRange)) { - const details = await this.sourceMapTransformer.allSourcePathDetails(mappedUrl); - const detail = details.find(d => d.inferredPath === s); - libPositions.push({ - lineNumber: detail.startPosition.line, - columnNumber: detail.startPosition.column - }); - inLibRange = !inLibRange; - } - } - - // If there's any change from the default, set proper blackboxed ranges - if (libPositions.length || toggling) { - if (parentIsSkipped) { - libPositions.splice(0, 0, { lineNumber: 0, columnNumber: 0}); - } - - if (libPositions[0].lineNumber !== 0 || libPositions[0].columnNumber !== 0) { - // The list of blackboxed ranges must start with 0,0 for some reason. - // https://github.com/Microsoft/vscode-chrome-debug/issues/667 - libPositions[0] = { - lineNumber: 0, - columnNumber: 0 - }; - } - - await this.chrome.Debugger.setBlackboxedRanges({ - scriptId: script.scriptId, - positions: [] - }).catch(() => this.warnNoSkipFiles()); - - if (libPositions.length) { - this.chrome.Debugger.setBlackboxedRanges({ - scriptId: script.scriptId, - positions: libPositions - }).catch(() => this.warnNoSkipFiles()); - } - } - } else { - const status = await this.getSkipStatus(mappedUrl); - const skippedByPattern = this.matchesSkipFilesPatterns(mappedUrl); - if (typeof status === 'boolean' && status !== skippedByPattern) { - const positions = status ? [{ lineNumber: 0, columnNumber: 0 }] : []; - this.chrome.Debugger.setBlackboxedRanges({ - scriptId: script.scriptId, - positions - }).catch(() => this.warnNoSkipFiles()); - } - } - } - - private warnNoSkipFiles(): void { - logger.log('Warning: this runtime does not support skipFiles'); - } - - /** - * If the source has a saved skip status, return that, whether true or false. - * If not, check it against the patterns list. - */ - private shouldSkipSource(sourcePath: string): boolean|undefined { - const status = this.getSkipStatus(sourcePath); - if (typeof status === 'boolean') { - return status; - } - - if (this.matchesSkipFilesPatterns(sourcePath)) { - return true; - } - - return undefined; - } - - /** - * Returns true if this path matches one of the static skip patterns - */ - private matchesSkipFilesPatterns(sourcePath: string): boolean { - return this._blackboxedRegexes.some(regex => { - return regex.test(sourcePath); - }); - } - - /** - * Returns the current skip status for this path, which is either an authored or generated script. - */ - private getSkipStatus(sourcePath: string): boolean|undefined { - if (this._skipFileStatuses.has(sourcePath)) { - return this._skipFileStatuses.get(sourcePath); - } - - return undefined; - } - - /* __GDPR__ - "ClientRequest/toggleSmartStep" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async toggleSmartStep(): Promise { - this._smartStepEnabled = !this._smartStepEnabled; - this.onPaused(this._lastPauseState.event, this._lastPauseState.expecting); - } - - /* __GDPR__ - "ClientRequest/toggleSkipFileStatus" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async toggleSkipFileStatus(args: IToggleSkipFileStatusArgs): Promise { - if (args.path) { - args.path = utils.fileUrlToPath(args.path); - } - - if (!await this.isInCurrentStack(args)) { - // Only valid for files that are in the current stack - const logName = args.path || this.displayNameForSourceReference(args.sourceReference); - logger.log(`Can't toggle the skipFile status for ${logName} - it's not in the current stack.`); - return; - } - - // e.g. strip / - if (args.path) { - args.path = this.displayPathToRealPath(args.path); - } - - const aPath = args.path || this.fakeUrlForSourceReference(args.sourceReference); - const generatedPath = await this._sourceMapTransformer.getGeneratedPathFromAuthoredPath(aPath); - if (!generatedPath) { - logger.log(`Can't toggle the skipFile status for: ${aPath} - haven't seen it yet.`); - return; - } - - const sources = await this._sourceMapTransformer.allSources(generatedPath); - if (generatedPath === aPath && sources.length) { - // Ignore toggling skip status for generated scripts with sources - logger.log(`Can't toggle skipFile status for ${aPath} - it's a script with a sourcemap`); - return; - } - - const newStatus = !this.shouldSkipSource(aPath); - logger.log(`Setting the skip file status for: ${aPath} to ${newStatus}`); - this._skipFileStatuses.set(aPath, newStatus); - - const targetPath = this._pathTransformer.getTargetPathFromClientPath(generatedPath) || generatedPath; - const script = this.getScriptByUrl(targetPath); - - await this.resolveSkipFiles(script, generatedPath, sources, /*toggling=*/true); - - if (newStatus) { - this.makeRegexesSkip(script.url); - } else { - this.makeRegexesNotSkip(script.url); - } - - this.onPaused(this._lastPauseState.event, this._lastPauseState.expecting); - } - - private async isInCurrentStack(args: IToggleSkipFileStatusArgs): Promise { - const currentStack = await this.stackTrace({ threadId: undefined }); - - if (args.path) { - return currentStack.stackFrames.some(frame => frame.source && frame.source.path === args.path); - } else { - return currentStack.stackFrames.some(frame => frame.source && frame.source.sourceReference === args.sourceReference); - } - } - - private makeRegexesNotSkip(noSkipPath: string): void { - let somethingChanged = false; - this._blackboxedRegexes = this._blackboxedRegexes.map(regex => { - const result = utils.makeRegexNotMatchPath(regex, noSkipPath); - somethingChanged = somethingChanged || (result !== regex); - return result; - }); - - if (somethingChanged) { - this.refreshBlackboxPatterns(); - } - } - - private makeRegexesSkip(skipPath: string): void { - let somethingChanged = false; - this._blackboxedRegexes = this._blackboxedRegexes.map(regex => { - const result = utils.makeRegexMatchPath(regex, skipPath); - somethingChanged = somethingChanged || (result !== regex); - return result; - }); - - if (!somethingChanged) { - this._blackboxedRegexes.push(new RegExp(utils.pathToRegex(skipPath), 'i')); - } - - this.refreshBlackboxPatterns(); - } - - private refreshBlackboxPatterns(): void { - this.chrome.Debugger.setBlackboxPatterns({ - patterns: this._blackboxedRegexes.map(regex => regex.source) - }).catch(() => this.warnNoSkipFiles()); - } - - /* __GDPR__ - "ClientRequest/loadedSources" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async loadedSources(args: DebugProtocol.LoadedSourcesArguments): Promise { - const sources = await Promise.all(Array.from(this._scriptsByUrl.values()) - .map(script => this.scriptToSource(script))); - - return { sources: sources.sort((a, b) => a.path.localeCompare(b.path)) }; - } - - public resolvePendingBreakpoint(pendingBP: IPendingBreakpoint): Promise { - return this.setBreakpoints(pendingBP.args, null, pendingBP.requestSeq, pendingBP.ids).then(response => { - response.breakpoints.forEach((bp, i) => { - bp.id = pendingBP.ids[i]; - this._session.sendEvent(new BreakpointEvent('changed', bp)); - }); - }); - } - - protected onBreakpointResolved(params: Crdp.Debugger.BreakpointResolvedEvent): void { - const script = this._scriptsById.get(params.location.scriptId); - const breakpointId = this._breakpointIdHandles.lookup(params.breakpointId); - if (!script || !breakpointId) { - // Breakpoint resolved for a script we don't know about or a breakpoint we don't know about - return; - } - - // If the breakpoint resolved is a stopOnEntry breakpoint, we just return since we don't need to send it to client - if (this.breakOnLoadActive && this._breakOnLoadHelper.stopOnEntryBreakpointIdToRequestedFileName.has(params.breakpointId)) { - return; - } - - // committed breakpoints (this._committedBreakpointsByUrl) should always have url keys in canonicalized form - const committedBps = this.getValueFromCommittedBreakpointsByUrl(script.url) || []; - - if (!committedBps.find(committedBp => committedBp.breakpointId === params.breakpointId)) { - committedBps.push({breakpointId: params.breakpointId, actualLocation: params.location}); - } - this.setValueForCommittedBreakpointsByUrl(script.url, committedBps); - - const bp = { - id: breakpointId, - verified: true, - line: params.location.lineNumber, - column: params.location.columnNumber - }; - - // need to canonicalize this path because the following maps use paths canonicalized - const scriptPath = utils.canonicalizeUrl(this._pathTransformer.breakpointResolved(bp, script.url)); - - if (this._pendingBreakpointsByUrl.has(scriptPath)) { - // If we set these BPs before the script was loaded, remove from the pending list - this._pendingBreakpointsByUrl.delete(scriptPath); - } - this._sourceMapTransformer.breakpointResolved(bp, scriptPath); - this._lineColTransformer.breakpointResolved(bp); - this._session.sendEvent(new BreakpointEvent('changed', bp)); - } - - protected onConsoleAPICalled(event: Crdp.Runtime.ConsoleAPICalledEvent): void { - if (this._launchAttachArgs._suppressConsoleOutput) { - return; - } - - const result = formatConsoleArguments(event.type, event.args, event.stackTrace); - const stack = stackTraceWithoutLogpointFrame(event.stackTrace); - if (result) { - this.logObjects(result.args, result.isError, stack); - } - } - - private onLogEntryAdded(event: Crdp.Log.EntryAddedEvent): void { - // The Debug Console doesn't give the user a way to filter by level, just ignore 'verbose' logs - if (event.entry.level === 'verbose') { - return; - } - - const args = event.entry.args || []; - - let text = event.entry.text || ''; - if (event.entry.url && !event.entry.stackTrace) { - if (text) { - text += ' '; - } - - text += `[${event.entry.url}]`; - } - - if (text) { - args.unshift({ - type: 'string', - value: text - }); - } - - const type = event.entry.level === 'error' ? 'error' : - event.entry.level === 'warning' ? 'warning' : - 'log'; - const result = formatConsoleArguments(type, args, event.entry.stackTrace); - const stack = event.entry.stackTrace; - if (result) { - this.logObjects(result.args, result.isError, stack); - } - } - - private async logObjects(objs: Crdp.Runtime.RemoteObject[], isError = false, stackTrace?: Crdp.Runtime.StackTrace): Promise { - // This is an asynchronous method, so ensure that we handle one at a time so that they are sent out in the same order that they came in. - this._currentLogMessage = this._currentLogMessage - .then(async () => { - const category = isError ? 'stderr' : 'stdout'; - - // Shortcut the common log case to reduce unnecessary back and forth - let e: DebugProtocol.OutputEvent; - if (objs.length === 1 && objs[0].type === 'string') { - let msg: string = objs[0].value; - if (isError) { - msg = await this.mapFormattedException(msg); - } - - if (!msg.endsWith(clearConsoleCode)) { - // If this string will clear the console, don't append a \n - msg += '\n'; - } - - e = new OutputEvent(msg, category); - } else { - e = new OutputEvent('output', category); - e.body.variablesReference = this._variableHandles.create(new variables.LoggedObjects(objs), 'repl'); - } - - if (stackTrace && stackTrace.callFrames.length) { - const stackFrame = await this.mapCallFrame(stackTrace.callFrames[0]); - e.body.source = stackFrame.source; - e.body.line = stackFrame.line; - e.body.column = stackFrame.column; - } - - this._session.sendEvent(e); }) .catch(err => logger.error(err.toString())); } - protected async onExceptionThrown(params: Crdp.Runtime.ExceptionThrownEvent): Promise { - if (this._launchAttachArgs._suppressConsoleOutput) { - return; - } - - return this._currentLogMessage = this._currentLogMessage.then(async () => { - const formattedException = formatExceptionDetails(params.exceptionDetails); - const exceptionStr = await this.mapFormattedException(formattedException); - - const e: DebugProtocol.OutputEvent = new OutputEvent(exceptionStr + '\n', 'stderr'); - const stackTrace = params.exceptionDetails.stackTrace; - if (stackTrace && stackTrace.callFrames.length) { - const stackFrame = await this.mapCallFrame(stackTrace.callFrames[0]); - e.body.source = stackFrame.source; - e.body.line = stackFrame.line; - e.body.column = stackFrame.column; - } - - this._session.sendEvent(e); - }) - .catch(err => logger.error(err.toString())); - } - - private async mapCallFrame(frame: Crdp.Runtime.CallFrame): Promise { - const debuggerCF = this.runtimeCFToDebuggerCF(frame); - const stackFrame = this.callFrameToStackFrame(debuggerCF); - await this._pathTransformer.fixSource(stackFrame.source); - await this._sourceMapTransformer.fixSourceLocation(stackFrame); - this._lineColTransformer.convertDebuggerLocationToClient(stackFrame); - return stackFrame; - } - - // We parse stack trace from `formattedException`, source map it and return a new string - protected async mapFormattedException(formattedException: string): Promise { - const exceptionLines = formattedException.split(/\r?\n/); - - for (let i = 0, len = exceptionLines.length; i < len; ++i) { - const line = exceptionLines[i]; - const matches = line.match(/^\s+at (.*?)\s*\(?([^ ]+):(\d+):(\d+)\)?$/); - - if (!matches) continue; - const linePath = matches[2]; - const lineNum = parseInt(matches[3], 10); - const adjustedLineNum = lineNum - 1; - const columnNum = parseInt(matches[4], 10); - const clientPath = this._pathTransformer.getClientPathFromTargetPath(linePath); - const mapped = await this._sourceMapTransformer.mapToAuthored(clientPath || linePath, adjustedLineNum, columnNum); - - if (mapped && mapped.source && utils.isNumber(mapped.line) && utils.isNumber(mapped.column) && utils.existsSync(mapped.source)) { - this._lineColTransformer.mappedExceptionStack(mapped); - exceptionLines[i] = exceptionLines[i].replace( - `${linePath}:${lineNum}:${columnNum}`, - `${mapped.source}:${mapped.line}:${mapped.column}`); - } else if (clientPath && clientPath !== linePath) { - const location = { line: adjustedLineNum, column: columnNum }; - this._lineColTransformer.mappedExceptionStack(location); - exceptionLines[i] = exceptionLines[i].replace( - `${linePath}:${lineNum}:${columnNum}`, - `${clientPath}:${location.line}:${location.column}`); - } - } - - return exceptionLines.join('\n'); - } - - /** - * For backcompat, also listen to Console.messageAdded, only if it looks like the old format. - */ - protected onMessageAdded(params: any): void { - // message.type is undefined when Runtime.consoleAPICalled is being sent - if (params && params.message && params.message.type) { - const onConsoleAPICalledParams: Crdp.Runtime.ConsoleAPICalledEvent = { - type: params.message.type, - timestamp: params.message.timestamp, - args: params.message.parameters || [{ type: 'string', value: params.message.text }], - stackTrace: params.message.stack, - executionContextId: 1 - }; - this.onConsoleAPICalled(onConsoleAPICalledParams); - } - } - - /* __GDPR__ - "ClientRequest/disconnect" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public disconnect(args: DebugProtocol.DisconnectArguments): void { - telemetry.reportEvent('FullSessionStatistics/SourceMaps/Overrides', { aspNetClientAppFallbackCount: sourceMapUtils.getAspNetFallbackCount() }); - this._clientRequestedSessionEnd = true; - this.shutdown(); - this.terminateSession('Got disconnect request', args); - } - - /* __GDPR__ - "ClientRequest/setBreakpoints" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public setBreakpoints(args: ISetBreakpointsArgs, _: ITelemetryPropertyCollector, requestSeq: number, ids?: number[]): Promise { - this.reportBpTelemetry(args); - if (args.source.path) { - args.source.path = this.displayPathToRealPath(args.source.path); - args.source.path = utils.canonicalizeUrl(args.source.path); - } - - return this.validateBreakpointsPath(args) - .then(() => { - // Deep copy the args that we are going to modify, and keep the original values in originalArgs - const originalArgs = args; - args = JSON.parse(JSON.stringify(args)); - args = this._lineColTransformer.setBreakpoints(args); - const sourceMapTransformerResponse = this._sourceMapTransformer.setBreakpoints(args, requestSeq, ids); - if (sourceMapTransformerResponse && sourceMapTransformerResponse.args) { - args = sourceMapTransformerResponse.args; - } - if (sourceMapTransformerResponse && sourceMapTransformerResponse.ids) { - ids = sourceMapTransformerResponse.ids; - } - args = this._pathTransformer.setBreakpoints(args); - - // Get the target url of the script - let targetScriptUrl: string; - if (args.source.sourceReference) { - const handle = this._sourceHandles.get(args.source.sourceReference); - if ((!handle || !handle.scriptId) && args.source.path) { - // A sourcemapped script with inline sources won't have a scriptId here, but the - // source.path has been fixed. - targetScriptUrl = args.source.path; - } else { - const targetScript = this._scriptsById.get(handle.scriptId); - if (targetScript) { - targetScriptUrl = targetScript.url; - } - } - } else if (args.source.path) { - targetScriptUrl = args.source.path; - } - - if (targetScriptUrl) { - // DebugProtocol sends all current breakpoints for the script. Clear all breakpoints for the script then add all of them - const internalBPs = args.breakpoints.map(bp => new InternalSourceBreakpoint(bp)); - const setBreakpointsPFailOnError = this._setBreakpointsRequestQ - .then(() => this.clearAllBreakpoints(targetScriptUrl)) - .then(() => this.addBreakpoints(targetScriptUrl, internalBPs)) - .then(responses => ({ breakpoints: this.targetBreakpointResponsesToBreakpointSetResults(targetScriptUrl, responses, internalBPs, ids) })); - - const setBreakpointsPTimeout = utils.promiseTimeout(setBreakpointsPFailOnError, ChromeDebugAdapter.SET_BREAKPOINTS_TIMEOUT, localize('setBPTimedOut', 'Set breakpoints request timed out')); - - // Do just one setBreakpointsRequest at a time to avoid interleaving breakpoint removed/breakpoint added requests to Crdp, which causes issues. - // Swallow errors in the promise queue chain so it doesn't get blocked, but return the failing promise for error handling. - this._setBreakpointsRequestQ = setBreakpointsPTimeout.catch(e => { - // Log the timeout, but any other error will be logged elsewhere - if (e.message && e.message.indexOf('timed out') >= 0) { - logger.error(e.stack); - } - }); - - // Return the setBP request, no matter how long it takes. It may take awhile in Node 7.5 - 7.7, see https://github.com/nodejs/node/issues/11589 - return setBreakpointsPFailOnError.then(setBpResultBody => { - const body = { breakpoints: setBpResultBody.breakpoints.map(setBpResult => setBpResult.breakpoint) }; - if (body.breakpoints.every(bp => !bp.verified)) { - // If all breakpoints are set, we mark them as set. If not, we mark them as un-set so they'll be set - const areAllSet = setBpResultBody.breakpoints.every(setBpResult => setBpResult.isSet); - // We need to send the original args to avoid adjusting the line and column numbers twice here - return this.unverifiedBpResponseForBreakpoints(originalArgs, requestSeq, targetScriptUrl, body.breakpoints, localize('bp.fail.unbound', 'Breakpoint set but not yet bound'), areAllSet); - } - this._sourceMapTransformer.setBreakpointsResponse(body, requestSeq); - this._lineColTransformer.setBreakpointsResponse(body); - return body; - }); - } else { - return Promise.resolve(this.unverifiedBpResponse(args, requestSeq, undefined, localize('bp.fail.noscript', "Can't find script for breakpoint request"))); - } - }, - e => this.unverifiedBpResponse(args, requestSeq, undefined, e.message)); - } - - private reportBpTelemetry(args: ISetBreakpointsArgs): void { - let fileExt = ''; - if (args.source.path) { - fileExt = path.extname(args.source.path); - } - - /* __GDPR__ - "setBreakpointsRequest" : { - "fileExt" : { "classification": "CustomerContent", "purpose": "FeatureInsight" }, - "${include}": [ "${DebugCommonProperties}" ] - } - */ - telemetry.reportEvent('setBreakpointsRequest', { fileExt }); - } - - protected validateBreakpointsPath(args: ISetBreakpointsArgs): Promise { - if (!args.source.path || args.source.sourceReference) return Promise.resolve(); - - // When break on load is active, we don't need to validate the path, so return - if (this.breakOnLoadActive) { - return Promise.resolve(); - } - - return this._sourceMapTransformer.getGeneratedPathFromAuthoredPath(args.source.path).then(mappedPath => { - - if (!mappedPath) { - return utils.errP(localize('validateBP.sourcemapFail', 'Breakpoint ignored because generated code not found (source map problem?).')); - } - - const targetPath = this._pathTransformer.getTargetPathFromClientPath(mappedPath); - if (!targetPath) { - return utils.errP(localize('validateBP.notFound', 'Breakpoint ignored because target path not found')); - } - - return undefined; - }); - } - - private generateNextUnboundBreakpointId(): string { - const unboundBreakpointUniquePrefix = '__::[vscode_chrome_debug_adapter_unbound_breakpoint]::'; - return `${unboundBreakpointUniquePrefix}${this._nextUnboundBreakpointId++}`; - } - - private unverifiedBpResponse(args: ISetBreakpointsArgs, requestSeq: number, targetScriptUrl: string, message?: string, bpsSet = false): ISetBreakpointsResponseBody { - const breakpoints = args.breakpoints.map(bp => { - return { - verified: false, - line: bp.line, - column: bp.column, - message, - id: this._breakpointIdHandles.create(this.generateNextUnboundBreakpointId()) - }; - }); - - return this.unverifiedBpResponseForBreakpoints(args, requestSeq, targetScriptUrl, breakpoints, message, bpsSet); - } - - private unverifiedBpResponseForBreakpoints(args: ISetBreakpointsArgs, requestSeq: number, targetScriptUrl: string, breakpoints: DebugProtocol.Breakpoint[], defaultMessage?: string, bpsSet = false): ISetBreakpointsResponseBody { - breakpoints.forEach(bp => { - if (!bp.message) { - bp.message = defaultMessage; - } - }); - - if (args.source.path) { - const ids = breakpoints.map(bp => bp.id); - - // setWithPath: record whether we attempted to set the breakpoint, and if so, with which path. - // We can use this to tell when the script is loaded whether we guessed correctly, and predict whether the BP will bind. - this._pendingBreakpointsByUrl.set( - utils.canonicalizeUrl(args.source.path), - { args, ids, requestSeq, setWithPath: this.breakOnLoadActive ? '' : targetScriptUrl }); // Breakpoints need to be re-set when break-on-load is enabled - } - - return { breakpoints }; - } - - private clearAllBreakpoints(url: string): Promise { - // We want to canonicalize this url because this._committedBreakpointsByUrl keeps url keys in canonicalized form - url = utils.canonicalizeUrl(url); - if (!this._committedBreakpointsByUrl.has(url)) { - return Promise.resolve(); - } - - // Remove breakpoints one at a time. Seems like it would be ok to send the removes all at once, - // but there is a chrome bug where when removing 5+ or so breakpoints at once, it gets into a weird - // state where later adds on the same line will fail with 'breakpoint already exists' even though it - // does not break there. - return this._committedBreakpointsByUrl.get(url).reduce((p, bp) => { - return p.then(() => this.chrome.Debugger.removeBreakpoint({ breakpointId: bp.breakpointId })).then(() => { }); - }, Promise.resolve()).then(() => { - this._committedBreakpointsByUrl.delete(url); - }); - } - - /** - * Makes the actual call to either Debugger.setBreakpoint or Debugger.setBreakpointByUrl, and returns the response. - * Responses from setBreakpointByUrl are transformed to look like the response from setBreakpoint, so they can be - * handled the same. - */ - protected async addBreakpoints(url: string, breakpoints: InternalSourceBreakpoint[]): Promise { - let responsePs: Promise[]; - if (ChromeUtils.isEvalScript(url)) { - // eval script with no real url - use debugger_setBreakpoint - const scriptId: Crdp.Runtime.ScriptId = utils.lstrip(url, ChromeDebugAdapter.EVAL_NAME_PREFIX); - responsePs = breakpoints.map(({ line, column = 0, condition }, i) => this.chrome.Debugger.setBreakpoint({ location: { scriptId, lineNumber: line, columnNumber: column }, condition })); - } else { - // script that has a url - use debugger_setBreakpointByUrl so that Chrome will rebind the breakpoint immediately - // after refreshing the page. This is the only way to allow hitting breakpoints in code that runs immediately when - // the page loads. - const script = this.getScriptByUrl(url); - - // If script has been parsed, script object won't be undefined and we would have the mapping file on the disk and we can directly set breakpoint using that - if (!this.breakOnLoadActive || script) { - const urlRegex = utils.pathToRegex(url); - responsePs = breakpoints.map(({ line, column = 0, condition }, i) => { - return this.addOneBreakpointByUrl(script && script.scriptId, urlRegex, line, column, condition); - }); - } else { // Else if script hasn't been parsed and break on load is active, we need to do extra processing - if (this.breakOnLoadActive) { - return await this._breakOnLoadHelper.handleAddBreakpoints(url, breakpoints); - } - } - } - - // Join all setBreakpoint requests to a single promise - return Promise.all(responsePs); - } - - private async addOneBreakpointByUrl(scriptId: Crdp.Runtime.ScriptId | undefined, urlRegex: string, lineNumber: number, columnNumber: number, condition: string): Promise { - let bpLocation = { lineNumber, columnNumber }; - if (this._columnBreakpointsEnabled && scriptId) { // scriptId undefined when script not yet loaded, can't fix up column BP :( - try { - const possibleBpResponse = await this.chrome.Debugger.getPossibleBreakpoints({ - start: { scriptId, lineNumber, columnNumber: 0 }, - end: { scriptId, lineNumber: lineNumber + 1, columnNumber: 0 }, - restrictToFunction: false }); - if (possibleBpResponse.locations.length) { - const selectedLocation = ChromeUtils.selectBreakpointLocation(lineNumber, columnNumber, possibleBpResponse.locations); - bpLocation = { lineNumber: selectedLocation.lineNumber, columnNumber: selectedLocation.columnNumber || 0 }; - } - } catch (e) { - // getPossibleBPs not supported - } - } - - let result; - try { - result = await this.chrome.Debugger.setBreakpointByUrl({ urlRegex, lineNumber: bpLocation.lineNumber, columnNumber: bpLocation.columnNumber, condition }); - } catch (e) { - if (e.message === 'Breakpoint at specified location already exists.') { - return { - actualLocation: { lineNumber: bpLocation.lineNumber, columnNumber: bpLocation.columnNumber, scriptId } - }; - } else { - throw e; - } - } - - // Now convert the response to a SetBreakpointResponse so both response types can be handled the same - const locations = result.locations; - return { - breakpointId: result.breakpointId, - actualLocation: locations[0] && { - lineNumber: locations[0].lineNumber, - columnNumber: locations[0].columnNumber, - scriptId - } - }; - } - - private targetBreakpointResponsesToBreakpointSetResults(url: string, responses: ISetBreakpointResult[], requestBps: InternalSourceBreakpoint[], ids?: number[]): BreakpointSetResult[] { - // Don't cache errored responses - const committedBps = responses - .filter(response => response && response.breakpointId); - - // Cache successfully set breakpoint ids from chrome in committedBreakpoints set - this.setValueForCommittedBreakpointsByUrl(url, committedBps); - - // Map committed breakpoints to DebugProtocol response breakpoints - return responses - .map((response, i) => { - // The output list needs to be the same length as the input list, so map errors to - // unverified breakpoints. - if (!response) { - return { - isSet: false, - breakpoint: { - verified: false - } - }; - } - - // response.breakpointId is undefined when no target BP is backing this BP, e.g. it's at the same location - // as another BP - const responseBpId = response.breakpointId || this.generateNextUnboundBreakpointId(); - - let bpId: number; - if (ids && ids[i]) { - // IDs passed in for previously unverified BPs - bpId = ids[i]; - this._breakpointIdHandles.set(bpId, responseBpId); - } else { - bpId = this._breakpointIdHandles.lookup(responseBpId) || - this._breakpointIdHandles.create(responseBpId); - } - - if (!response.actualLocation) { - // If we don't have an actualLocation nor a breakpointId this is a pseudo-breakpoint because we are using break-on-load - // so we mark the breakpoint as not set, so i'll be set after we load the actual script that has the breakpoint - return { - isSet: response.breakpointId !== undefined, - breakpoint: { - id: bpId, - verified: false - } - }; - } - - const thisBpRequest = requestBps[i]; - if (thisBpRequest.hitCondition) { - if (!this.addHitConditionBreakpoint(thisBpRequest, response)) { - return { - isSet: true, - breakpoint: { - id: bpId, - message: localize('invalidHitCondition', 'Invalid hit condition: {0}', thisBpRequest.hitCondition), - verified: false - } - }; - } - } - - return { - isSet: true, - breakpoint: { - id: bpId, - verified: true, - line: response.actualLocation.lineNumber, - column: response.actualLocation.columnNumber - } - }; - }); - } - - private addHitConditionBreakpoint(requestBp: InternalSourceBreakpoint, response: ISetBreakpointResult): boolean { - const result = ChromeDebugAdapter.HITCONDITION_MATCHER.exec(requestBp.hitCondition.trim()); - if (result && result.length >= 3) { - let op = result[1] || '>='; - if (op === '=') op = '=='; - const value = result[2]; - const expr = op === '%' - ? `return (numHits % ${value}) === 0;` - : `return numHits ${op} ${value};`; - - // eval safe because of the regex, and this is only a string that the current user will type in - /* tslint:disable:no-function-constructor-with-string-args */ - const shouldPause: (numHits: number) => boolean = new Function('numHits', expr); - /* tslint:enable:no-function-constructor-with-string-args */ - this._hitConditionBreakpointsById.set(response.breakpointId, { numHits: 0, shouldPause }); - return true; - } else { - return false; - } - } - - /* __GDPR__ - "ClientRequest/setExceptionBreakpoints" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { - let state: 'all' | 'uncaught' | 'none'; - if (args.filters.indexOf('all') >= 0) { - state = 'all'; - } else if (args.filters.indexOf('uncaught') >= 0) { - state = 'uncaught'; - } else { - state = 'none'; + protected async onExceptionThrown(params: IExceptionThrownEvent): Promise { + if (this._launchAttachArgs._suppressConsoleOutput) { + return; } - if (args.filters.indexOf('promise_reject') >= 0) { - this._pauseOnPromiseRejections = true; - } else { - this._pauseOnPromiseRejections = false; - } + return this._currentLogMessage = this._currentLogMessage.then(async () => { + const formattedException = formatExceptionDetails(params.exceptionDetails); + const exceptionStackTrace = await new FormattedExceptionParser(this._scriptsLogic, formattedException).parse(); - return this.chrome.Debugger.setPauseOnExceptions({ state }) - .then(() => { }); + let location: LocationInLoadedSource = null; + const stackTrace = params.exceptionDetails.stackTrace; + if (stackTrace && stackTrace.codeFlowFrames.length) { + location = stackTrace.codeFlowFrames[0].location.mappedToSource(); + } + + this._eventSender.sendExceptionThrown({ exceptionStackTrace: exceptionStackTrace, category: 'stderr', location }); + }) + .catch(err => logger.error(err.toString())); } - /* __GDPR__ - "ClientRequest/continue" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ /** - * internal -> suppress telemetry + * For backcompat, also listen to Console.messageAdded, only if it looks like the old format. */ - public continue(internal = false): Promise { - /* __GDPR__ - "continueRequest" : { - "${include}": [ "${DebugCommonProperties}" ] - } - */ - if (!internal) telemetry.reportEvent('continueRequest'); - if (!this.chrome) { - return utils.errP(errors.runtimeNotConnectedMsg); + protected onMessageAdded(params: any): void { + // message.type is undefined when Runtime.consoleAPICalled is being sent + if (params && params.message && params.message.type) { + const onConsoleAPICalledParams: IConsoleAPICalledEvent = { + type: params.message.type, + timestamp: params.message.timestamp, + args: params.message.parameters || [{ type: 'string', value: params.message.text }], + stackTrace: params.message.stack, + executionContextId: 1 + }; + this.onConsoleAPICalled(onConsoleAPICalledParams); } - - this._expectingResumedEvent = true; - return this._currentStep = this.chrome.Debugger.resume() - .then(() => { /* make void */ }, - e => { /* ignore failures - client can send the request when the target is no longer paused */ }); } /* __GDPR__ - "ClientRequest/next" : { + "ClientRequest/disconnect" : { "${include}": [ "${IExecutionResultTelemetryProperties}", "${DebugCommonProperties}" ] } */ - public next(): Promise { - if (!this.chrome) { - return utils.errP(errors.runtimeNotConnectedMsg); - } - - /* __GDPR__ - "nextRequest" : { - "${include}": [ "${DebugCommonProperties}" ] - } - */ - telemetry.reportEvent('nextRequest'); - this._expectingStopReason = 'step'; - this._expectingResumedEvent = true; - return this._currentStep = this.chrome.Debugger.stepOver() - .then(() => { /* make void */ }, - e => { /* ignore failures - client can send the request when the target is no longer paused */ }); + public disconnect(): void { + telemetry.reportEvent('FullSessionStatistics/SourceMaps/Overrides', { aspNetClientAppFallbackCount: sourceMapUtils.getAspNetFallbackCount() }); + this.shutdown(); + this.terminateSession('Got disconnect request'); } /* __GDPR__ - "ClientRequest/stepIn" : { + "ClientRequest/setExceptionBreakpoints" : { "${include}": [ "${IExecutionResultTelemetryProperties}", "${DebugCommonProperties}" ] } */ - public stepIn(userInitiated = true): Promise { - if (!this.chrome) { - return utils.errP(errors.runtimeNotConnectedMsg); - } - - if (userInitiated) { - /* __GDPR__ - "stepInRequest" : { - "${include}": [ "${DebugCommonProperties}" ] - } - */ - telemetry.reportEvent('stepInRequest'); + public setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): Promise { + let state: 'all' | 'uncaught' | 'none'; + if (args.filters.indexOf('all') >= 0) { + state = 'all'; + } else if (args.filters.indexOf('uncaught') >= 0) { + state = 'uncaught'; + } else { + state = 'none'; } - this._expectingStopReason = 'step'; - this._expectingResumedEvent = true; - return this._currentStep = this.chrome.Debugger.stepInto({ breakOnAsyncCall: true }) - .then(() => { /* make void */ }, - e => { /* ignore failures - client can send the request when the target is no longer paused */ }); - } - - /* __GDPR__ - "ClientRequest/stepOut" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public stepOut(): Promise { - if (!this.chrome) { - return utils.errP(errors.runtimeNotConnectedMsg); + if (args.filters.indexOf('promise_reject') >= 0) { + this._pauseOnPromiseRejections = true; + } else { + this._pauseOnPromiseRejections = false; } - /* __GDPR__ - "stepOutRequest" : { - "${include}": [ "${DebugCommonProperties}" ] - } - */ - telemetry.reportEvent('stepOutRequest'); - this._expectingStopReason = 'step'; - this._expectingResumedEvent = true; - return this._currentStep = this.chrome.Debugger.stepOut() - .then(() => { /* make void */ }, - e => { /* ignore failures - client can send the request when the target is no longer paused */ }); + return this._pauseOnExceptions.setPauseOnExceptions({ state }) + .then(() => { }); } - /* __GDPR__ - "ClientRequest/stepBack" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ public stepBack(): Promise { - return (this.chrome).TimeTravel.stepBack() + return (this._chromeConnection.api).TimeTravel.stepBack() .then(() => { /* make void */ }, - e => { /* ignore failures - client can send the request when the target is no longer paused */ }); + () => { }); } /* __GDPR__ @@ -1927,215 +518,12 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } */ public reverseContinue(): Promise { - return (this.chrome).TimeTravel.reverse() + return (this._chromeConnection.api).TimeTravel.reverse() .then(() => { /* make void */ }, - e => { /* ignore failures - client can send the request when the target is no longer paused */ }); - } - - /* __GDPR__ - "ClientRequest/pause" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public pause(): Promise { - if (!this.chrome) { - return utils.errP(errors.runtimeNotConnectedMsg); - } - - /* __GDPR__ - "pauseRequest" : { - "${include}": [ "${DebugCommonProperties}" ] - } - */ - telemetry.reportEvent('pauseRequest'); - this._expectingStopReason = 'pause'; - return this._currentStep = this.chrome.Debugger.pause() - .then(() => { }); - } - - /* __GDPR__ - "ClientRequest/stackTrace" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async stackTrace(args: DebugProtocol.StackTraceArguments): Promise { - if (!this._currentPauseNotification) { - return Promise.reject(errors.noCallStackAvailable()); - } - - let stackFrames = this._currentPauseNotification.callFrames.map(frame => this.callFrameToStackFrame(frame)) - .concat(this.asyncFrames(this._currentPauseNotification.asyncStackTrace)); - - const totalFrames = stackFrames.length; - if (typeof args.startFrame === 'number') { - stackFrames = stackFrames.slice(args.startFrame); - } - - if (typeof args.levels === 'number') { - stackFrames = stackFrames.slice(0, args.levels); - } - - const stackTraceResponse: IInternalStackTraceResponseBody = { - stackFrames, - totalFrames - }; - this._pathTransformer.stackTraceResponse(stackTraceResponse); - await this._sourceMapTransformer.stackTraceResponse(stackTraceResponse); - this._lineColTransformer.stackTraceResponse(stackTraceResponse); - - await Promise.all(stackTraceResponse.stackFrames.map(async (frame, i) => { - // Remove isSourceMapped to convert back to DebugProtocol.StackFrame - delete frame.isSourceMapped; - - if (!frame.source) { - return; - } - - // Apply hints to skipped frames - const getSkipReason = reason => localize('skipReason', "(skipped by '{0}')", reason); - if (frame.source.path && this.shouldSkipSource(frame.source.path)) { - frame.source.origin = (frame.source.origin ? frame.source.origin + ' ' : '') + getSkipReason('skipFiles'); - frame.source.presentationHint = 'deemphasize'; - } else if (await this.shouldSmartStep(frame)) { - frame.source.origin = (frame.source.origin ? frame.source.origin + ' ' : '') + getSkipReason('smartStep'); - frame.source.presentationHint = 'deemphasize'; - } - - // Allow consumer to adjust final path - if (frame.source.path && frame.source.sourceReference) { - frame.source.path = this.realPathToDisplayPath(frame.source.path); - } - - // And finally, remove the fake eval path and fix the name, if it was never resolved to a real path - if (frame.source.path && ChromeUtils.isEvalScript(frame.source.path)) { - frame.source.path = undefined; - frame.source.name = this.displayNameForSourceReference(frame.source.sourceReference); - } - - // Format stackframe name - frame.name = this.formatStackFrameName(frame, args.format); - })); - - return stackTraceResponse; - } - - private asyncFrames(stackTrace: Crdp.Runtime.StackTrace): DebugProtocol.StackFrame[] { - if (stackTrace) { - const frames = stackTrace.callFrames - .map(frame => this.runtimeCFToDebuggerCF(frame)) - .map(frame => this.callFrameToStackFrame(frame)); - - frames.unshift({ - id: this._frameHandles.create(null), - name: `[ ${stackTrace.description} ]`, - source: undefined, - line: undefined, - column: undefined, - presentationHint: 'label' - }); - - return frames.concat(this.asyncFrames(stackTrace.parent)); - } else { - return []; - } - } - - private runtimeCFToDebuggerCF(frame: Crdp.Runtime.CallFrame): Crdp.Debugger.CallFrame { - return { - callFrameId: undefined, - scopeChain: undefined, - this: undefined, - location: { - lineNumber: frame.lineNumber, - columnNumber: frame.columnNumber, - scriptId: frame.scriptId - }, - url: frame.url, - functionName: frame.functionName - }; - } - - private async scriptToSource(script: Crdp.Debugger.ScriptParsedEvent): Promise { - const sourceReference = this.getSourceReferenceForScriptId(script.scriptId); - const origin = this.getReadonlyOrigin(script.url); - - const properlyCasedScriptUrl = utils.canonicalizeUrl(script.url); - const displayPath = this.realPathToDisplayPath(properlyCasedScriptUrl); - - const exists = await utils.existsAsync(properlyCasedScriptUrl); // script.url can start with file:/// so we use the canonicalized version - return { - name: path.basename(displayPath), - path: displayPath, - // if the path exists, do not send the sourceReference - sourceReference: exists ? undefined : sourceReference, - origin - }; - } - - private formatStackFrameName(frame: DebugProtocol.StackFrame, formatArgs?: DebugProtocol.StackFrameFormat): string { - let formattedName = frame.name; - if (formatArgs) { - if (formatArgs.module) { - formattedName += ` [${frame.source.name}]`; - } - - if (formatArgs.line) { - formattedName += ` Line ${frame.line}`; - } - } - - return formattedName; - } - - private callFrameToStackFrame(frame: Crdp.Debugger.CallFrame): DebugProtocol.StackFrame { - const { location, functionName } = frame; - const line = location.lineNumber; - const column = location.columnNumber; - const script = this._scriptsById.get(location.scriptId); - - try { - // When the script has a url and isn't one we're ignoring, send the name and path fields. PathTransformer will - // attempt to resolve it to a script in the workspace. Otherwise, send the name and sourceReference fields. - const sourceReference = this.getSourceReferenceForScriptId(script.scriptId); - const origin = this.getReadonlyOrigin(script.url); - const source: DebugProtocol.Source = { - name: path.basename(script.url), - path: script.url, - sourceReference, - origin - }; - - // If the frame doesn't have a function name, it's either an anonymous function - // or eval script. If its source has a name, it's probably an anonymous function. - const frameName = functionName || (script.url ? '(anonymous function)' : '(eval code)'); - return { - id: this._frameHandles.create(frame), - name: frameName, - source, - line, - column - }; - } catch (e) { - // Some targets such as the iOS simulator behave badly and return nonsense callFrames. - // In these cases, return a dummy stack frame - const evalUnknown = `${ChromeDebugAdapter.EVAL_NAME_PREFIX}_Unknown`; - return { - id: this._frameHandles.create({ }), - name: evalUnknown, - source: { name: evalUnknown, path: evalUnknown }, - line, - column - }; - } + () => { }); } - protected getReadonlyOrigin(url: string): string { + public getReadonlyOrigin(): string { // To override return undefined; } @@ -2144,30 +532,14 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { * Called when returning a stack trace, for the path for Sources that have a sourceReference, so consumers can * tweak it, since it's only for display. */ - protected realPathToDisplayPath(realPath: string): string { + protected realPathToDisplayPath(realPath: IResourceIdentifier): IResourceIdentifier { if (ChromeUtils.isEvalScript(realPath)) { - return `${ChromeDebugAdapter.EVAL_ROOT}/${realPath}`; + return parseResourceIdentifier(`${ChromeDebugLogic.EVAL_ROOT}/${realPath}`); } return realPath; } - protected displayPathToRealPath(displayPath: string): string { - if (displayPath.startsWith(ChromeDebugAdapter.EVAL_ROOT)) { - return displayPath.substr(ChromeDebugAdapter.EVAL_ROOT.length + 1); // Trim "/" - } - - return displayPath; - } - - /** - * Get the existing handle for this script, identified by runtime scriptId, or create a new one - */ - private getSourceReferenceForScriptId(scriptId: Crdp.Runtime.ScriptId): number { - return this._sourceHandles.lookupF(container => container.scriptId === scriptId) || - this._sourceHandles.create({ scriptId }); - } - /* __GDPR__ "ClientRequest/scopes" : { "${include}": [ @@ -2176,26 +548,17 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { ] } */ - public scopes(args: DebugProtocol.ScopesArguments): IScopesResponseBody { - const currentFrame = this._frameHandles.get(args.frameId); - if (!currentFrame || !currentFrame.location || !currentFrame.callFrameId) { + public scopes(currentFrame: LoadedSourceCallFrame): IScopesResponseBody { + if (!currentFrame || !currentFrame.location) { throw errors.stackFrameNotValid(); } - if (!currentFrame.callFrameId) { - return { scopes: [] }; - } - - const currentScript = this._scriptsById.get(currentFrame.location.scriptId); - const currentScriptUrl = currentScript && currentScript.url; - const currentScriptPath = (currentScriptUrl && this._pathTransformer.getClientPathFromTargetPath(currentScriptUrl)) || currentScriptUrl; - - const scopes = currentFrame.scopeChain.map((scope: Crdp.Debugger.Scope, i: number) => { + const scopes = currentFrame.scopeChain.map((scope, i) => { // The first scope should include 'this'. Keep the RemoteObject reference for use by the variables request - const thisObj = i === 0 && currentFrame.this; + const thisObj = i === 0 && currentFrame.frameThis; const returnValue = i === 0 && currentFrame.returnValue; const variablesReference = this._variableHandles.create( - new ScopeContainer(currentFrame.callFrameId, i, scope.object.objectId, thisObj, returnValue)); + new ScopeContainer(currentFrame, i, scope.object.objectId, thisObj, returnValue)); const resultScope = { name: scope.type.substr(0, 1).toUpperCase() + scope.type.substr(1), // Take Chrome's scope, uppercase the first letter @@ -2204,16 +567,16 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }; if (scope.startLocation && scope.endLocation) { - resultScope.column = scope.startLocation.columnNumber; - resultScope.line = scope.startLocation.lineNumber; - resultScope.endColumn = scope.endLocation.columnNumber; - resultScope.endLine = scope.endLocation.lineNumber; + resultScope.column = scope.startLocation.position.columnNumber; + resultScope.line = scope.startLocation.position.lineNumber; + resultScope.endColumn = scope.endLocation.position.columnNumber; + resultScope.endLine = scope.endLocation.position.lineNumber; } return resultScope; }); - if (this._exception && this.lookupFrameIndex(args.frameId) === 0) { + if (this._exception && currentFrame.index === 0) { scopes.unshift({ name: localize('scope.exception', 'Exception'), variablesReference: this._variableHandles.create(ExceptionContainer.create(this._exception)) @@ -2221,26 +584,14 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } const scopesResponse = { scopes }; - if (currentScriptPath) { - this._sourceMapTransformer.scopesResponse(currentScriptPath, scopesResponse); + if (currentFrame.source.doesScriptHasUrl()) { + this._sourceMapTransformer.scopesResponse(currentFrame.source.script.url, scopesResponse); this._lineColTransformer.scopeResponse(scopesResponse); } return scopesResponse; } - /** - * Try to lookup the index of the frame with given ID. Returns -1 for async frames and unknown frames. - */ - private lookupFrameIndex(frameId: number): number { - const currentFrame = this._frameHandles.get(frameId); - if (!currentFrame || !currentFrame.callFrameId || !this._currentPauseNotification) { - return -1; - } - - return this._currentPauseNotification.callFrames.findIndex(frame => frame.callFrameId === currentFrame.callFrameId); - } - /* __GDPR__ "ClientRequest/variables" : { "${include}": [ @@ -2250,10 +601,6 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } */ public variables(args: DebugProtocol.VariablesArguments): Promise { - if (!this.chrome) { - return utils.errP(errors.runtimeNotConnectedMsg); - } - const handle = this._variableHandles.get(args.variablesReference); if (!handle) { return Promise.resolve(undefined); @@ -2268,14 +615,14 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }); } - public async propertyDescriptorToVariable(propDesc: Crdp.Runtime.PropertyDescriptor, owningObjectId?: string, parentEvaluateName?: string): Promise { + public async propertyDescriptorToVariable(propDesc: CDTP.Runtime.PropertyDescriptor, owningObjectId?: string, parentEvaluateName?: string): Promise { if (propDesc.get) { // Getter const grabGetterValue = 'function remoteFunction(propName) { return this[propName]; }'; - let response: Crdp.Runtime.CallFunctionOnResponse; + let response: CDTP.Runtime.CallFunctionOnResponse; try { - response = await this.chrome.Runtime.callFunctionOn({ + response = await this._inspectDebugeeState.callFunctionOn({ objectId: owningObjectId, functionDeclaration: grabGetterValue, arguments: [{ value: propDesc.name }] @@ -2315,8 +662,8 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { this.getRuntimeProperties({ objectId, ownProperties: true, accessorPropertiesOnly: false, generatePreview: true }) ]).then(getPropsResponses => { // Sometimes duplicates will be returned - merge all descriptors by name - const propsByName = new Map(); - const internalPropsByName = new Map(); + const propsByName = new Map(); + const internalPropsByName = new Map(); getPropsResponses.forEach(response => { if (response) { response.result.forEach(propDesc => @@ -2351,11 +698,11 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }); } - private getRuntimeProperties(params: Crdp.Runtime.GetPropertiesRequest): Promise { - return this.chrome.Runtime.getProperties(params) + private getRuntimeProperties(params: CDTP.Runtime.GetPropertiesRequest): Promise { + return this._inspectDebugeeState.getProperties(params) .catch(err => { if (err.message.startsWith('Cannot find context with specified id')) { - // Hack to ignore this error until we fix https://github.com/Microsoft/vscode/issues/18001 to not request variables at unexpected times. + // Hack to ignore this error until we fix https://github.com/Microsoft/client/issues/18001 to not request variables at unexpected times. return null; } else { throw err; @@ -2363,7 +710,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }); } - private internalPropertyDescriptorToVariable(propDesc: Crdp.Runtime.InternalPropertyDescriptor, parentEvaluateName: string): Promise { + private internalPropertyDescriptorToVariable(propDesc: CDTP.Runtime.InternalPropertyDescriptor, parentEvaluateName: string): Promise { return this.remoteObjectToVariable(propDesc.name, propDesc.value, parentEvaluateName); } @@ -2389,7 +736,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } private getFilteredVariablesForObjectId(objectId: string, evaluateName: string, getVarsFn: string, filter: string, start: number, count: number): Promise { - return this.chrome.Runtime.callFunctionOn({ + return this._inspectDebugeeState.callFunctionOn({ objectId, functionDeclaration: getVarsFn, arguments: [{ value: start }, { value: count }], @@ -2405,60 +752,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { .then(variables => variables.filter(variable => isIndexedPropName(variable.name))); } }, - error => Promise.reject(errors.errorFromEvaluate(error.message))); - } - - /* __GDPR__ - "ClientRequest/source" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public source(args: DebugProtocol.SourceArguments): Promise { - let scriptId: Crdp.Runtime.ScriptId; - if (args.sourceReference) { - const handle = this._sourceHandles.get(args.sourceReference); - if (!handle) { - return Promise.reject(errors.sourceRequestIllegalHandle()); - } - - // Have inlined content? - if (handle.contents) { - return Promise.resolve({ - content: handle.contents - }); - } - - scriptId = handle.scriptId; - } else if (args.source && args.source.path) { - const realPath = this.displayPathToRealPath(args.source.path); - - // Request url has chars unescaped, but they will be escaped in scriptsByUrl - const script = this.getScriptByUrl( - utils.isURL(realPath) ? - encodeURI(realPath) : - realPath); - - if (!script) { - return Promise.reject(errors.sourceRequestCouldNotRetrieveContent()); - } - - scriptId = script.scriptId; - } - - if (!scriptId) { - return Promise.reject(errors.sourceRequestCouldNotRetrieveContent()); - } - - // If not, should have scriptId - return this.chrome.Debugger.getScriptSource({ scriptId }).then(response => { - return { - content: response.scriptSource, - mimeType: 'text/javascript' - }; - }); + error => Promise.reject(errors.errorFromEvaluate(error.message))); } /* __GDPR__ @@ -2473,7 +767,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return { threads: [ { - id: ChromeDebugAdapter.THREAD_ID, + id: ChromeDebugLogic.THREAD_ID, name: this.threadName() } ] @@ -2481,7 +775,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } protected threadName(): string { - return 'Thread ' + ChromeDebugAdapter.THREAD_ID; + return 'Thread ' + ChromeDebugLogic.THREAD_ID; } /* __GDPR__ @@ -2492,23 +786,15 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { ] } */ - public async evaluate(args: DebugProtocol.EvaluateArguments): Promise { - if (!this.chrome) { - return utils.errP(errors.runtimeNotConnectedMsg); - } - - if (args.expression.startsWith(ChromeDebugAdapter.SCRIPTS_COMMAND)) { - return this.handleScriptsCommand(args); - } - - if (args.expression.startsWith('{') && args.expression.endsWith('}')) { - args.expression = `(${args.expression})`; - } + public async evaluate(args: IEvaluateArguments): Promise { + const expression = args.expression.startsWith('{') && args.expression.endsWith('}') + ? `(${args.expression})` + : args.expression; - const evalResponse = await this.waitThenDoEvaluate(args.expression, args.frameId, { generatePreview: true }); + const evalResponse = await this.waitThenDoEvaluate(expression, args.frame, { generatePreview: true }); // Convert to a Variable object then just copy the relevant fields off - const variable = await this.remoteObjectToVariable(args.expression, evalResponse.result, /*parentEvaluateName=*/undefined, /*stringify=*/undefined, args.context); + const variable = await this.remoteObjectToVariable(expression, evalResponse.result, /*parentEvaluateName=*/undefined, /*stringify=*/undefined, args.context); if (evalResponse.exceptionDetails) { let resultValue = variable.value; if (resultValue && (resultValue.startsWith('ReferenceError: ') || resultValue.startsWith('TypeError: ')) && args.context !== 'repl') { @@ -2527,84 +813,28 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }; } - /** - * Handle the .scripts command, which can be used as `.scripts` to return a list of all script details, - * or `.scripts ` to show the contents of the given script. - */ - private handleScriptsCommand(args: DebugProtocol.EvaluateArguments): Promise { - let outputStringP: Promise; - const scriptsRest = utils.lstrip(args.expression, ChromeDebugAdapter.SCRIPTS_COMMAND).trim(); - if (scriptsRest) { - // `.scripts ` was used, look up the script by url - const requestedScript = this.getScriptByUrl(scriptsRest); - if (requestedScript) { - outputStringP = this.chrome.Debugger.getScriptSource({ scriptId: requestedScript.scriptId }) - .then(result => { - const maxLength = 1e5; - return result.scriptSource.length > maxLength ? - result.scriptSource.substr(0, maxLength) + '[⋯]' : - result.scriptSource; - }); - } else { - outputStringP = Promise.resolve(`No runtime script with url: ${scriptsRest}\n`); - } - } else { - outputStringP = this.getAllScriptsString(); - } - - return outputStringP.then(scriptsStr => { - this._session.sendEvent(new OutputEvent(scriptsStr)); - return { - result: '', - variablesReference: 0 - }; - }); - } - - private getAllScriptsString(): Promise { - const runtimeScripts = Array.from(this._scriptsByUrl.keys()) - .sort(); - return Promise.all(runtimeScripts.map(script => this.getOneScriptString(script))).then(strs => { - return strs.join('\n'); - }); - } - - private getOneScriptString(runtimeScriptPath: string): Promise { - let result = '› ' + runtimeScriptPath; - const clientPath = this._pathTransformer.getClientPathFromTargetPath(runtimeScriptPath); - if (clientPath && clientPath !== runtimeScriptPath) result += ` (${clientPath})`; - - return this._sourceMapTransformer.allSourcePathDetails(clientPath || runtimeScriptPath).then(sourcePathDetails => { - let mappedSourcesStr = sourcePathDetails.map(details => ` - ${details.originalPath} (${details.inferredPath})`).join('\n'); - if (sourcePathDetails.length) mappedSourcesStr = '\n' + mappedSourcesStr; - - return result + mappedSourcesStr; - }); - } - /** * Allow consumers to override just because of https://github.com/nodejs/node/issues/8426 */ - protected globalEvaluate(args: Crdp.Runtime.EvaluateRequest): Promise { - return this.chrome.Runtime.evaluate(args); + public globalEvaluate(args: CDTP.Runtime.EvaluateRequest): Promise { + return this._inspectDebugeeState.evaluate(args); } - private async waitThenDoEvaluate(expression: string, frameId?: number, extraArgs?: Partial): Promise { - const waitThenEval = this._waitAfterStep.then(() => this.doEvaluate(expression, frameId, extraArgs)); + private async waitThenDoEvaluate(expression: string, frame?: LoadedSourceCallFrame, extraArgs?: Partial): Promise { + const waitThenEval = this._waitAfterStep.then(() => this.doEvaluate(expression, frame, extraArgs)); this._waitAfterStep = waitThenEval.then(() => { }, () => { }); // to Promise and handle failed evals return waitThenEval; } - private async doEvaluate(expression: string, frameId?: number, extraArgs?: Partial): Promise { - if (typeof frameId === 'number') { - const frame = this._frameHandles.get(frameId); - if (!frame || !frame.callFrameId) { + private async doEvaluate(expression: string, frame: LoadedSourceCallFrame, extraArgs?: Partial): Promise { + if (frame) { + if (!frame) { return utils.errP(errors.evalNotAvailableMsg); } return this.evaluateOnCallFrame(expression, frame, extraArgs); } else { - let args: Crdp.Runtime.EvaluateRequest = { + let args: CDTP.Runtime.EvaluateRequest = { expression, // silent because of an issue where node will sometimes hang when breaking on exceptions in console messages. Fixed somewhere between 8 and 8.4 silent: true, @@ -2620,10 +850,9 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } } - protected async evaluateOnCallFrame(expression: string, frame: Crdp.Debugger.CallFrame, extraArgs?: Partial): Promise { - const callFrameId = frame.callFrameId; - let args: Crdp.Debugger.EvaluateOnCallFrameRequest = { - callFrameId, + public async evaluateOnCallFrame(expression: string, frame: LoadedSourceCallFrame, extraArgs?: Partial): Promise { + let args: IEvaluateOnCallFrameRequest = { + frame: frame.unmappedCallFrame, expression, // silent because of an issue where node will sometimes hang when breaking on exceptions in console messages. Fixed somewhere between 8 and 8.4 silent: true, @@ -2634,7 +863,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { args = Object.assign(args, extraArgs); } - return this.chrome.Debugger.evaluateOnCallFrame(args); + return this._inspectDebugeeState.evaluateOnCallFrame(args); } /* __GDPR__ @@ -2655,26 +884,26 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { .then(value => ({ value })); } - public setVariableValue(callFrameId: string, scopeNumber: number, variableName: string, value: string): Promise { - let evalResultObject: Crdp.Runtime.RemoteObject; - return this.chrome.Debugger.evaluateOnCallFrame({ callFrameId, expression: value, silent: true }).then(evalResponse => { + public setVariableValue(frame: LoadedSourceCallFrame, scopeNumber: number, variableName: string, value: string): Promise { + let evalResultObject: CDTP.Runtime.RemoteObject; + return this._inspectDebugeeState.evaluateOnCallFrame({ frame: frame.unmappedCallFrame, expression: value, silent: true }).then(evalResponse => { if (evalResponse.exceptionDetails) { const errMsg = ChromeUtils.errorMessageFromExceptionDetails(evalResponse.exceptionDetails); return Promise.reject(errors.errorFromEvaluate(errMsg)); } else { evalResultObject = evalResponse.result; const newValue = ChromeUtils.remoteObjectToCallArgument(evalResultObject); - return this.chrome.Debugger.setVariableValue({ callFrameId, scopeNumber, variableName, newValue }); + return this._updateDebugeeState.setVariableValue({ frame: frame.unmappedCallFrame, scopeNumber, variableName, newValue }); } }, - error => Promise.reject(errors.errorFromEvaluate(error.message))) - // Temporary, Microsoft/vscode#12019 - .then(setVarResponse => ChromeUtils.remoteObjectToValue(evalResultObject).value); + error => Promise.reject(errors.errorFromEvaluate(error.message))) + // Temporary, Microsoft/vscode#12019 + .then(() => ChromeUtils.remoteObjectToValue(evalResultObject).value); } public setPropertyValue(objectId: string, propName: string, value: string): Promise { const setPropertyValueFn = `function() { return this["${propName}"] = ${value} }`; - return this.chrome.Runtime.callFunctionOn({ + return this._inspectDebugeeState.callFunctionOn({ objectId, functionDeclaration: setPropertyValueFn, silent: true }).then(response => { @@ -2686,10 +915,10 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return ChromeUtils.remoteObjectToValue(response.result).value; } }, - error => Promise.reject(errors.errorFromEvaluate(error.message))); + error => Promise.reject(errors.errorFromEvaluate(error.message))); } - public async remoteObjectToVariable(name: string, object: Crdp.Runtime.RemoteObject, parentEvaluateName?: string, stringify = true, context: VariableContext = 'variables'): Promise { + public async remoteObjectToVariable(name: string, object: CDTP.Runtime.RemoteObject, parentEvaluateName?: string, stringify = true, context: VariableContext = 'variables'): Promise { name = name || '""'; if (object) { @@ -2705,7 +934,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } } - public createFunctionVariable(name: string, object: Crdp.Runtime.RemoteObject, context: VariableContext, parentEvaluateName?: string): DebugProtocol.Variable { + public createFunctionVariable(name: string, object: CDTP.Runtime.RemoteObject, context: VariableContext, parentEvaluateName?: string): DebugProtocol.Variable { let value: string; const firstBraceIdx = object.description.indexOf('{'); if (firstBraceIdx >= 0) { @@ -2727,7 +956,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }; } - public createObjectVariable(name: string, object: Crdp.Runtime.RemoteObject, parentEvaluateName: string, context: VariableContext): Promise { + public createObjectVariable(name: string, object: CDTP.Runtime.RemoteObject, parentEvaluateName: string, context: VariableContext): Promise { if ((object.subtype) === 'internal#location') { // Could format this nicely later, see #110 return Promise.resolve(this.createPrimitiveVariableWithValue(name, 'internal#location', parentEvaluateName)); @@ -2755,7 +984,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { propCountP = Promise.resolve({ indexedVariables: undefined, namedVariables: undefined - }); + }); } const evaluateName = ChromeUtils.getEvaluateName(parentEvaluateName, name); @@ -2771,11 +1000,11 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { })); } - protected createPropertyContainer(object: Crdp.Runtime.RemoteObject, evaluateName: string): IVariableContainer { + protected createPropertyContainer(object: CDTP.Runtime.RemoteObject, evaluateName: string): IVariableContainer { return new PropertyContainer(object.objectId, evaluateName); } - public createPrimitiveVariable(name: string, object: Crdp.Runtime.RemoteObject, parentEvaluateName?: string, stringify?: boolean): DebugProtocol.Variable { + public createPrimitiveVariable(name: string, object: CDTP.Runtime.RemoteObject, parentEvaluateName?: string, stringify?: boolean): DebugProtocol.Variable { const value = variables.getRemoteObjectPreview_primitive(object, stringify); const variable = this.createPrimitiveVariableWithValue(name, value, parentEvaluateName); variable.type = object.type; @@ -2792,25 +1021,6 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { }; } - /* __GDPR__ - "ClientRequest/restartFrame" : { - "${include}": [ - "${IExecutionResultTelemetryProperties}", - "${DebugCommonProperties}" - ] - } - */ - public async restartFrame(args: DebugProtocol.RestartFrameArguments): Promise { - const callFrame = this._frameHandles.get(args.frameId); - if (!callFrame || !callFrame.callFrameId) { - return utils.errP(errors.noRestartFrame); - } - - await this.chrome.Debugger.restartFrame({ callFrameId: callFrame.callFrameId }); - this._expectingStopReason = 'frame_entry'; - return this.chrome.Debugger.stepInto({ }); - } - /* __GDPR__ "ClientRequest/completions" : { "${include}": [ @@ -2819,7 +1029,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { ] } */ - public async completions(args: DebugProtocol.CompletionsArguments): Promise { + public async completions(args: ICompletionsArguments): Promise { const text = args.text; const column = args.column; @@ -2832,22 +1042,22 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { expression = prefix.substr(0, dot); } - if (typeof args.frameId === 'number' && !expression) { + if (args.frame && !expression) { logger.verbose(`Completions: Returning global completions`); // If no expression was passed, we must be getting global completions at a breakpoint - if (!this._frameHandles.get(args.frameId)) { + if (!args.frame) { return Promise.reject(errors.stackFrameNotValid()); } - const callFrame = this._frameHandles.get(args.frameId); - if (!callFrame || !callFrame.callFrameId) { + const callFrame = args.frame; + if (!callFrame) { // Async frame or label return { targets: [] }; } const scopeExpandPs = callFrame.scopeChain - .map(scope => new ScopeContainer(callFrame.callFrameId, undefined, scope.object.objectId).expand(this)); + .map(scope => new ScopeContainer(callFrame, undefined, scope.object.objectId).expand(this)); return Promise.all(scopeExpandPs) .then((variableArrs: DebugProtocol.Variable[][]) => { const targets = this.getFlatAndUniqueCompletionItems( @@ -2859,7 +1069,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { logger.verbose(`Completions: Returning for expression '${expression}'`); const getCompletionsFn = `(function(x){var a=[];for(var o=x;o!==null&&typeof o !== 'undefined';o=o.__proto__){a.push(Object.getOwnPropertyNames(o))};return a})(${expression})`; - const response = await this.waitThenDoEvaluate(getCompletionsFn, args.frameId, { returnByValue: true }); + const response = await this.waitThenDoEvaluate(getCompletionsFn, args.frame, { returnByValue: true }); if (response.exceptionDetails) { return { targets: [] }; } else { @@ -2900,7 +1110,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return this.getNumPropsByEval(objectId, getNumPropsFn); } - private getArrayNumPropsByPreview(object: Crdp.Runtime.RemoteObject): IPropCount { + private getArrayNumPropsByPreview(object: CDTP.Runtime.RemoteObject): IPropCount { let indexedVariables = 0; const indexedProps = object.preview.properties .filter(prop => isIndexedPropName(prop.name)); @@ -2918,7 +1128,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return this.getNumPropsByEval(objectId, getNumPropsFn); } - private getCollectionNumPropsByPreview(object: Crdp.Runtime.RemoteObject): IPropCount { + private getCollectionNumPropsByPreview(object: CDTP.Runtime.RemoteObject): IPropCount { let indexedVariables = 0; let namedVariables = object.preview.properties.length + 1; // +1 for [[Entries]]; @@ -2926,7 +1136,7 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { } private getNumPropsByEval(objectId: string, getNumPropsFn: string): Promise { - return this.chrome.Runtime.callFunctionOn({ + return this._inspectDebugeeState.callFunctionOn({ objectId, functionDeclaration: getNumPropsFn, silent: true, @@ -2944,35 +1154,10 @@ export abstract class ChromeDebugAdapter implements IDebugAdapter { return { indexedVariables: resultProps[0], namedVariables: resultProps[1] }; } }, - error => Promise.reject(errors.errorFromEvaluate(error.message))); - } - - private fakeUrlForSourceReference(sourceReference: number): string { - const handle = this._sourceHandles.get(sourceReference); - return `${ChromeDebugAdapter.EVAL_NAME_PREFIX}${handle.scriptId}`; - } - - private displayNameForSourceReference(sourceReference: number): string { - const handle = this._sourceHandles.get(sourceReference); - return (handle && this.displayNameForScriptId(handle.scriptId)) || sourceReference + ''; - } - - private displayNameForScriptId(scriptId: number|string): string { - return `${ChromeDebugAdapter.EVAL_NAME_PREFIX}${scriptId}`; - } - - private getScriptByUrl(url: string): Crdp.Debugger.ScriptParsedEvent { - url = utils.canonicalizeUrl(url); - return this._scriptsByUrl.get(url) || this._scriptsByUrl.get(utils.fixDriveLetter(url)); - } - - private getValueFromCommittedBreakpointsByUrl(url: string): ISetBreakpointResult[] { - let canonicalizedUrl = utils.canonicalizeUrl(url); - return this._committedBreakpointsByUrl.get(canonicalizedUrl); + error => Promise.reject(errors.errorFromEvaluate(error.message))); } - private setValueForCommittedBreakpointsByUrl(url: string, value: ISetBreakpointResult[]): void { - let canonicalizedUrl = utils.canonicalizeUrl(url); - this._committedBreakpointsByUrl.set(canonicalizedUrl, value); + public async onPaused(_notification: PausedEvent): Promise { + this._variableHandles.onPaused(); } } diff --git a/src/chrome/chromeDebugSession.ts b/src/chrome/chromeDebugSession.ts index 93366f1a6..cc2045efe 100644 --- a/src/chrome/chromeDebugSession.ts +++ b/src/chrome/chromeDebugSession.ts @@ -4,9 +4,8 @@ import * as os from 'os'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { LoggingDebugSession, ErrorDestination, Response, logger } from 'vscode-debugadapter'; +import { LoggingDebugSession, ErrorDestination, Response, logger, DebugSession } from 'vscode-debugadapter'; -import { ChromeDebugAdapter } from './chromeDebugAdapter'; import { ITargetFilter, ChromeConnection, IChromeError } from './chromeConnection'; import { BasePathTransformer } from '../transformers/basePathTransformer'; import { BaseSourceMapTransformer } from '../transformers/baseSourceMapTransformer'; @@ -16,15 +15,32 @@ import { IDebugAdapter } from '../debugAdapterInterfaces'; import { telemetry, ExceptionType, IExecutionResultTelemetryProperties, TelemetryPropertyCollector, ITelemetryPropertyCollector } from '../telemetry'; import * as utils from '../utils'; import { ExecutionTimingsReporter, StepProgressEventsEmitter, IObservableEvents, IStepStartedEventsEmitter, IFinishedStartingUpEventsEmitter } from '../executionTimingsReporter'; +import { ChromeDebugAdapter } from './client/chromeDebugAdapter/chromeDebugAdapterV2'; +import { AvailableCommands, CommandText } from './client/requests'; +import { IExtensibilityPoints } from './extensibility/extensibilityPoints'; +import { ConnectedCDAConfiguration } from './client/chromeDebugAdapter/cdaConfiguration'; export interface IChromeDebugAdapterOpts { targetFilter?: ITargetFilter; + + extensibilityPoints: IExtensibilityPoints; + + // Override services + chromeConnection?: typeof ChromeConnection; + pathTransformer?: { new(configuration: ConnectedCDAConfiguration): BasePathTransformer }; + sourceMapTransformer?: { new(configuration: ConnectedCDAConfiguration): BaseSourceMapTransformer }; + lineColTransformer?: { new(configuration: ConnectedCDAConfiguration): LineColTransformer }; +} + +export interface INewChromeDebugAdapterOpts { + targetFilter?: ITargetFilter; logFilePath?: string; // obsolete, vscode log dir should be used + enableSourceMapCaching?: boolean; // Override services chromeConnection?: typeof ChromeConnection; - pathTransformer?: { new(): BasePathTransformer }; - sourceMapTransformer?: { new(sourceHandles: any): BaseSourceMapTransformer }; + pathTransformer?: BasePathTransformer; + sourceMapTransformer: BaseSourceMapTransformer; lineColTransformer?: { new(session: any): LineColTransformer }; } @@ -32,6 +48,8 @@ export interface IChromeDebugSessionOpts extends IChromeDebugAdapterOpts { /** The class of the adapter, which is instantiated for each session */ adapter: typeof ChromeDebugAdapter; extensionName: string; + logFilePath: string; + extensibilityPoints: IExtensibilityPoints; } export const ErrorTelemetryEventName = 'error'; @@ -65,17 +83,17 @@ export class ChromeDebugSession extends LoggingDebugSession implements IObservab * DebugSession.run with the result. Alternatively they could subclass ChromeDebugSession and pass * their options to the super constructor, but I think this is easier to follow. */ - public static getSession(opts: IChromeDebugSessionOpts): typeof ChromeDebugSession { + public static getSession(opts: IChromeDebugSessionOpts): typeof DebugSession { // class expression! return class extends ChromeDebugSession { constructor(debuggerLinesAndColumnsStartAt1?: boolean, isServer?: boolean) { - super(debuggerLinesAndColumnsStartAt1, isServer, opts); + super(!!debuggerLinesAndColumnsStartAt1, !!isServer, opts); } }; } - public constructor(obsolete_debuggerLinesAndColumnsStartAt1?: boolean, obsolete_isServer?: boolean, opts?: IChromeDebugSessionOpts) { - super(opts.logFilePath, obsolete_debuggerLinesAndColumnsStartAt1, obsolete_isServer); + public constructor(obsolete_debuggerLinesAndColumnsStartAt1: boolean, obsolete_isServer: boolean, opts: IChromeDebugSessionOpts) { + super(undefined, obsolete_debuggerLinesAndColumnsStartAt1, obsolete_isServer); logVersionInfo(); this._extensionName = opts.extensionName; @@ -83,7 +101,7 @@ export class ChromeDebugSession extends LoggingDebugSession implements IObservab this.events = new StepProgressEventsEmitter([this._debugAdapter.events]); this.configureExecutionTimingsReporting(); - const safeGetErrDetails = err => { + const safeGetErrDetails = (err: any) => { let errMsg; try { errMsg = (err && (err).stack) ? (err).stack : JSON.stringify(err); @@ -94,7 +112,7 @@ export class ChromeDebugSession extends LoggingDebugSession implements IObservab return errMsg; }; - const reportErrorTelemetry = (err, exceptionType: ExceptionType) => { + const reportErrorTelemetry = (err: any, exceptionType: ExceptionType) => { let properties: IExecutionResultTelemetryProperties = {}; properties.successful = 'false'; properties.exceptionType = exceptionType; @@ -129,28 +147,39 @@ export class ChromeDebugSession extends LoggingDebugSession implements IObservab /** * Overload dispatchRequest to the debug adapters' Promise-based methods instead of DebugSession's callback-based methods */ - protected dispatchRequest(request: DebugProtocol.Request): void { - // We want the request to be non-blocking, so we won't await for reportTelemetry - this.reportTelemetry(`ClientRequest/${request.command}`, async (reportFailure, telemetryPropertyCollector) => { - const response: DebugProtocol.Response = new Response(request); - try { - logger.verbose(`From client: ${request.command}(${JSON.stringify(request.arguments) })`); - - if (!(request.command in this._debugAdapter)) { - reportFailure('The debug adapter doesn\'t recognize this command'); - this.sendUnknownCommandResponse(response, request.command); - } else { - telemetryPropertyCollector.addTelemetryProperty('requestType', request.type); - response.body = await this._debugAdapter[request.command](request.arguments, telemetryPropertyCollector, request.seq); - this.sendResponse(response); - } - } catch (e) { - if (!this.isEvaluateRequest(request.command, e)) { - reportFailure(e); + public dispatchRequest(request: DebugProtocol.Request): Promise { + if (AvailableCommands.has(request.command)) { + const command = request.command as CommandText; + + // We want the request to be non-blocking, so we won't await for reportTelemetry + return this.reportTelemetry(`ClientRequest/${request.command}`, async (reportFailure, telemetryPropertyCollector) => { + const response: DebugProtocol.Response = new Response(request); + try { + logger.verbose(`From client: ${request.command}(${JSON.stringify(request.arguments) })`); + + if (!(request.command in this._debugAdapter)) { + reportFailure('The debug adapter doesn\'t recognize this command'); + this.sendUnknownCommandResponse(response, request.command); + } else { + telemetryPropertyCollector.addTelemetryProperty('requestType', request.type); + const requestHandler = (this._debugAdapter as any) [command] as Function; + if (requestHandler instanceof Function) { + response.body = await requestHandler.call(this._debugAdapter, request.arguments, telemetryPropertyCollector, request.seq); + } else { + throw new Error(`Couldn't find a handler for request ${command}`); + } + this.sendResponse(response); + } + } catch (e) { + if (!this.isEvaluateRequest(request.command, e)) { + reportFailure(e); + } + this.failedRequest(request.command, response, e); } - this.failedRequest(request.command, response, e); - } - }); + }); + } else { + throw new Error(`The client requested ${request.command} which is not a recognized command`); + } } // { command: request.command, type: request.type }; @@ -182,7 +211,7 @@ export class ChromeDebugSession extends LoggingDebugSession implements IObservab telemetry.reportEvent(eventName, properties); }; - const reportFailure = e => { + const reportFailure = (e: any) => { failed = true; properties.successful = 'false'; properties.exceptionType = 'firstChance'; @@ -303,6 +332,25 @@ export class ChromeDebugSession extends LoggingDebugSession implements IObservab } } + public convertClientLineToDebugger(line: number): number { + // LineColTransformer uses this protected method from the session + return super.convertClientLineToDebugger(line); + } + + public convertClientColumnToDebugger(column: number): number { + // LineColTransformer uses this protected method from the session + return super.convertClientColumnToDebugger(column); + } + + public convertDebuggerLineToClient(line: number): number { + // LineColTransformer uses this protected method from the session + return super.convertDebuggerLineToClient(line); + } + + public convertDebuggerColumnToClient(column: number): number { + // LineColTransformer uses this protected method from the session + return super.convertDebuggerColumnToClient(column); + } } function logVersionInfo(): void { diff --git a/src/chrome/chromeTargetDiscoveryStrategy.ts b/src/chrome/chromeTargetDiscoveryStrategy.ts index 12c33ea71..2158c1f7f 100644 --- a/src/chrome/chromeTargetDiscoveryStrategy.ts +++ b/src/chrome/chromeTargetDiscoveryStrategy.ts @@ -12,27 +12,9 @@ import * as chromeUtils from './chromeUtils'; import { ITargetDiscoveryStrategy, ITargetFilter, ITarget } from './chromeConnection'; import * as nls from 'vscode-nls'; +import { Version } from './utils/version'; const localize = nls.loadMessageBundle(); -export class Version { - static parse(versionString: string): Version { - const majorAndMinor = versionString.split('.'); - const major = parseInt(majorAndMinor[0], 10); - const minor = parseInt(majorAndMinor[1], 10); - return new Version(major, minor); - } - - public static unknownVersion(): Version { - return new Version(0, 0); // Using 0.0 will make behave isAtLeastVersion as if this was the oldest possible version - } - - constructor(private _major: number, private _minor: number) {} - - public isAtLeastVersion(major: number, minor: number): boolean { - return this._major > major || (this._major === major && this._minor >= minor); - } -} - export class TargetVersions { constructor(public readonly protocol: Version, public readonly browser: Version) {} } @@ -105,11 +87,11 @@ export class ChromeTargetDiscovery implements ITargetDiscoveryStrategy, IObserva let browserVersion = Version.unknownVersion(); if (browserWithPrefixVersionString.startsWith(chromePrefix)) { const browserVersionString = browserWithPrefixVersionString.substr(chromePrefix.length); - browserVersion = Version.parse(browserVersionString); + browserVersion = Version.coerce(browserVersionString); } this.telemetry.reportEvent('targetDebugProtocolVersion', { debugProtocolVersion: response['Protcol-Version'] }); - return new TargetVersions(Version.parse(protocolVersionString), browserVersion); + return new TargetVersions(Version.coerce(protocolVersionString), browserVersion); } } catch (e) { this.logger.log(`Didn't get a valid response for /json/version call. Error: ${e.message}. Response: ${jsonResponse}`); diff --git a/src/chrome/chromeUtils.ts b/src/chrome/chromeUtils.ts index c425e0f9c..5bb8b019a 100644 --- a/src/chrome/chromeUtils.ts +++ b/src/chrome/chromeUtils.ts @@ -4,13 +4,15 @@ import * as url from 'url'; import * as path from 'path'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import { logger } from 'vscode-debugadapter'; import * as utils from '../utils'; import { ITarget } from './chromeConnection'; import { IPathMapping } from '../debugAdapterInterfaces'; import { pathToRegex } from '../utils'; +import { LocationInScript } from './internal/locations/location'; +import { IResourceIdentifier } from './internal/sources/resourceIdentifier'; /** * Takes the path component of a target url (starting with '/') and applies pathMapping @@ -130,7 +132,7 @@ export function targetUrlToClientPath(aUrl: string, pathMapping: IPathMapping): * Convert a RemoteObject to a value+variableHandleRef for the client. * TODO - Delete after Microsoft/vscode#12019!! */ -export function remoteObjectToValue(object: Crdp.Runtime.RemoteObject, stringify = true): { value: string, variableHandleRef?: string } { +export function remoteObjectToValue(object: CDTP.Runtime.RemoteObject, stringify = true): { value: string, variableHandleRef?: string } { let value = ''; let variableHandleRef: string; @@ -230,7 +232,7 @@ export function compareVariableNames(var1: string, var2: string): number { return var1.localeCompare(var2); } -export function remoteObjectToCallArgument(object: Crdp.Runtime.RemoteObject): Crdp.Runtime.CallArgument { +export function remoteObjectToCallArgument(object: CDTP.Runtime.RemoteObject): CDTP.Runtime.CallArgument { return { objectId: object.objectId, unserializableValue: object.unserializableValue, @@ -243,7 +245,7 @@ export function remoteObjectToCallArgument(object: Crdp.Runtime.RemoteObject): C * protocol differences in the future. * This includes the error message and full stack */ -export function descriptionFromExceptionDetails(exceptionDetails: Crdp.Runtime.ExceptionDetails): string { +export function descriptionFromExceptionDetails(exceptionDetails: CDTP.Runtime.ExceptionDetails): string { let description: string; if (exceptionDetails.exception) { // Take exception object description, or if a value was thrown, the value @@ -259,7 +261,7 @@ export function descriptionFromExceptionDetails(exceptionDetails: Crdp.Runtime.E /** * Get just the error message from the exception details - the first line without the full stack */ -export function errorMessageFromExceptionDetails(exceptionDetails: Crdp.Runtime.ExceptionDetails): string { +export function errorMessageFromExceptionDetails(exceptionDetails: CDTP.Runtime.ExceptionDetails): string { let description = descriptionFromExceptionDetails(exceptionDetails); const newlineIdx = description.indexOf('\n'); if (newlineIdx >= 0) { @@ -284,9 +286,9 @@ export function getEvaluateName(parentEvaluateName: string, name: string): strin return parentEvaluateName + nameAccessor; } -export function selectBreakpointLocation(lineNumber: number, columnNumber: number, locations: Crdp.Debugger.BreakLocation[]): Crdp.Debugger.BreakLocation { +export function selectBreakpointLocation(_lineNumber: number, columnNumber: number, locations: LocationInScript[]): LocationInScript { for (let i = locations.length - 1; i >= 0; i--) { - if (locations[i].columnNumber <= columnNumber) { + if (locations[i].position.columnNumber <= columnNumber) { return locations[i]; } } @@ -296,15 +298,15 @@ export function selectBreakpointLocation(lineNumber: number, columnNumber: numbe export const EVAL_NAME_PREFIX = 'VM'; -export function isEvalScript(scriptPath: string): boolean { - return scriptPath.startsWith(EVAL_NAME_PREFIX); +export function isEvalScript(scriptPath: IResourceIdentifier): boolean { + return scriptPath.canonicalized.startsWith(EVAL_NAME_PREFIX); } /* Constructs the regex for files to enable break on load For example, for a file index.js the regex will match urls containing index.js, index.ts, abc/index.ts, index.bin.js etc It won't match index100.js, indexabc.ts etc */ -export function getUrlRegexForBreakOnLoad(url: string): string { - const fileNameWithoutFullPath = path.parse(url).base; +export function getUrlRegexForBreakOnLoad(url: IResourceIdentifier): string { + const fileNameWithoutFullPath = path.parse(url.canonicalized).base; const fileNameWithoutExtension = path.parse(fileNameWithoutFullPath).name; const escapedFileName = pathToRegex(fileNameWithoutExtension); return '.*[\\\\\\/]' + escapedFileName + '([^A-z^0-9].*)?$'; diff --git a/src/chrome/client/chromeDebugAdapter/cdaConfiguration.ts b/src/chrome/client/chromeDebugAdapter/cdaConfiguration.ts new file mode 100644 index 000000000..20f079881 --- /dev/null +++ b/src/chrome/client/chromeDebugAdapter/cdaConfiguration.ts @@ -0,0 +1,46 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { IExtensibilityPoints } from '../../extensibility/extensibilityPoints'; +import { IClientCapabilities, ILaunchRequestArgs, IAttachRequestArgs } from '../../../debugAdapterInterfaces'; +import { ChromeConnection } from '../../chromeConnection'; +import { ILoggingConfiguration } from '../../internal/services/logging'; +import { ScenarioType } from './unconnectedCDA'; +import { injectable } from 'inversify'; +import { ISession } from '../session'; +import * as utils from '../../../utils'; + +export interface IConnectedCDAConfiguration { + args: ILaunchRequestArgs | IAttachRequestArgs; + isVSClient: boolean; + _extensibilityPoints: IExtensibilityPoints; + loggingConfiguration: ILoggingConfiguration; + _session: ISession; + _clientCapabilities: IClientCapabilities; + _chromeConnectionClass: typeof ChromeConnection; + scenarioType: ScenarioType; +} + +@injectable() +export class ConnectedCDAConfiguration implements IConnectedCDAConfiguration { + public readonly args: ILaunchRequestArgs | IAttachRequestArgs; + + public readonly isVSClient = this._clientCapabilities.clientID === 'visualstudio'; + + constructor(public readonly _extensibilityPoints: IExtensibilityPoints, + public readonly loggingConfiguration: ILoggingConfiguration, + public readonly _session: ISession, + public readonly _clientCapabilities: IClientCapabilities, + public readonly _chromeConnectionClass: typeof ChromeConnection, + public readonly scenarioType: ScenarioType, + private readonly originalArgs: ILaunchRequestArgs | IAttachRequestArgs) { + this.args = this._extensibilityPoints.updateArguments(this.originalArgs); + + if (this.args.pathMapping) { + for (const urlToMap in this.args.pathMapping) { + this.args.pathMapping[urlToMap] = utils.canonicalizeUrl(this.args.pathMapping[urlToMap]); + } + } + } +} diff --git a/src/chrome/client/chromeDebugAdapter/chromeDebugAdapterV2.ts b/src/chrome/client/chromeDebugAdapter/chromeDebugAdapterV2.ts new file mode 100644 index 000000000..3f94cefb6 --- /dev/null +++ b/src/chrome/client/chromeDebugAdapter/chromeDebugAdapterV2.ts @@ -0,0 +1,138 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { + IDebugAdapter, ITelemetryPropertyCollector, PromiseOrNot, ILaunchRequestArgs, IAttachRequestArgs, IThreadsResponseBody, + ISetBreakpointsResponseBody, IStackTraceResponseBody, IScopesResponseBody, IVariablesResponseBody, ISourceResponseBody, + IEvaluateResponseBody, IExceptionInfoResponseBody, IGetLoadedSourcesResponseBody, IDebugAdapterState, IToggleSkipFileStatusArgs +} from '../../..'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { ChromeDebugSession, IChromeDebugSessionOpts } from '../../chromeDebugSession'; +import { ChromeConnection } from '../../chromeConnection'; +import { StepProgressEventsEmitter } from '../../../executionTimingsReporter'; +import { UninitializedCDA } from './uninitializedCDA'; + +export class ChromeDebugAdapter implements IDebugAdapter { + private _state: IDebugAdapterState; + + public events = new StepProgressEventsEmitter(); + + constructor(args: IChromeDebugSessionOpts, originalSession: ChromeDebugSession) { + // Copy the arguments to keep backwards compatibility. TODO DIEGO remove this + args.extensibilityPoints.chromeConnection = args.extensibilityPoints.chromeConnection || args.chromeConnection; + args.extensibilityPoints.pathTransformer = args.extensibilityPoints.pathTransformer || args.pathTransformer; + args.extensibilityPoints.sourceMapTransformer = args.extensibilityPoints.sourceMapTransformer || args.sourceMapTransformer; + args.extensibilityPoints.lineColTransformer = args.extensibilityPoints.lineColTransformer || args.lineColTransformer; + args.extensibilityPoints.targetFilter = args.extensibilityPoints.targetFilter || args.targetFilter; + args.extensibilityPoints.logFilePath = args.logFilePath; + + this._state = new UninitializedCDA(args.extensibilityPoints, originalSession, args.chromeConnection || ChromeConnection); + } + + public shutdown(): void { + return this._state.shutdown(); + } + + public async initialize(args: DebugProtocol.InitializeRequestArguments, _?: ITelemetryPropertyCollector, _2?: number): Promise { + const { capabilities, newState } = await this._state.initialize(args); + this._state = newState; + return capabilities; + } + + public async launch(args: ILaunchRequestArgs, _?: ITelemetryPropertyCollector, _2?: number): Promise { + this._state = await this._state.launch(args); + } + + public async attach(args: IAttachRequestArgs, _?: ITelemetryPropertyCollector, _2?: number): Promise { + this._state = await this._state.attach(args); + } + + public disconnect(args: DebugProtocol.DisconnectArguments): PromiseOrNot { + return this._state.disconnect(args); + } + + public async setBreakpoints(args: DebugProtocol.SetBreakpointsArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector): Promise { + return this._state.setBreakpoints(args, telemetryPropertyCollector); + } + + public async setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments, _?: ITelemetryPropertyCollector, _2?: number): Promise { + return this._state.setExceptionBreakpoints(args); + } + + public configurationDone(): PromiseOrNot { + return this._state.configurationDone(); + } + + public continue(): PromiseOrNot { + return this._state.continue(); + } + + public next(): PromiseOrNot { + return this._state.next(); + } + + public stepIn(): PromiseOrNot { + return this._state.stepIn(); + } + + public stepOut(): PromiseOrNot { + return this._state.stepOut(); + } + + public pause(): PromiseOrNot { + return this._state.pause(); + } + + public async restartFrame(args: DebugProtocol.RestartFrameRequest): Promise { + return this._state.restartFrame(args); + } + + public async stackTrace(args: DebugProtocol.StackTraceArguments, _?: ITelemetryPropertyCollector, _2?: number): Promise { + return this._state.stackTrace(args); + } + + public scopes(args: DebugProtocol.ScopesArguments, _?: ITelemetryPropertyCollector, _2?: number): PromiseOrNot { + return this._state.scopes(args); + } + + public variables(args: DebugProtocol.VariablesArguments, _?: ITelemetryPropertyCollector, _2?: number): PromiseOrNot { + return this._state.variables(args); + } + + public async source(args: DebugProtocol.SourceArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this._state.source(args, _telemetryPropertyCollector); + } + + public threads(): PromiseOrNot { + return this._state.threads(); + } + + public async evaluate(args: DebugProtocol.EvaluateArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this._state.evaluate(args, _telemetryPropertyCollector); + } + + public async loadedSources(args: DebugProtocol.LoadedSourcesArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): Promise { + return this._state.loadedSources(args, telemetryPropertyCollector, requestSeq); + } + + public setFunctionBreakpoints(_args: DebugProtocol.SetFunctionBreakpointsArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): PromiseOrNot { + throw new Error('Method not implemented.'); + } + + public setVariable(_args: DebugProtocol.SetVariableArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): PromiseOrNot { + throw new Error('Method not implemented.'); + } + + public async exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { + return this._state.exceptionInfo(args); + } + + public async toggleSkipFileStatus(args: IToggleSkipFileStatusArgs): Promise { + return this._state.toggleSkipFileStatus(args); + } + + public async toggleSmartStep(): Promise { + return this._state.toggleSmartStep(); + } +} \ No newline at end of file diff --git a/src/chrome/client/chromeDebugAdapter/connectedCDA.ts b/src/chrome/client/chromeDebugAdapter/connectedCDA.ts new file mode 100644 index 000000000..970ac2ef7 --- /dev/null +++ b/src/chrome/client/chromeDebugAdapter/connectedCDA.ts @@ -0,0 +1,228 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import * as errors from '../../../errors'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { ChromeDebugLogic } from '../../chromeDebugAdapter'; +import { ClientToInternal } from '../clientToInternal'; +import { InternalToClient } from '../internalToClient'; +import { IGetLoadedSourcesResponseBody, IDebugAdapterState, PromiseOrNot, ISetBreakpointsResponseBody, IStackTraceResponseBody, IScopesResponseBody, IVariablesResponseBody, ISourceResponseBody, IThreadsResponseBody, IEvaluateResponseBody, IExceptionInfoResponseBody, ILaunchRequestArgs, IAttachRequestArgs } from '../../../debugAdapterInterfaces'; +import { StackTracesLogic } from '../../internal/stackTraces/stackTracesLogic'; +import { SourcesLogic } from '../../internal/sources/sourcesLogic'; +import { BreakpointsLogic } from '../../internal/breakpoints/features/breakpointsLogic'; +import { CDTPScriptsRegistry } from '../../cdtpDebuggee/registries/cdtpScriptsRegistry'; +import { PauseOnExceptionOrRejection } from '../../internal/exceptions/pauseOnException'; +import { Stepping } from '../../internal/stepping/stepping'; +import { DotScriptCommand } from '../../internal/sources/features/dotScriptsCommand'; +import { inject, injectable } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { SkipFilesLogic } from '../../internal/features/skipFiles'; +import { TakeProperActionOnPausedEvent } from '../../internal/features/takeProperActionOnPausedEvent'; +import { SmartStepLogic } from '../../internal/features/smartStep'; +import { NotifyClientOfLoadedSources } from '../../internal/sources/features/notifyClientOfLoadedSources'; +import { CDTPOnScriptParsedEventProvider } from '../../cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider'; +import { Target } from '../../communication/targetChannels'; +import { IDebuggeeRunner } from '../../debugeeStartup/debugeeLauncher'; +import { StepProgressEventsEmitter } from '../../../executionTimingsReporter'; +import { TelemetryPropertyCollector, ITelemetryPropertyCollector } from '../../../telemetry'; +import { ICommunicator, utils, IToggleSkipFileStatusArgs } from '../../..'; +import { CallFramePresentation } from '../../internal/stackTraces/callFramePresentation'; +import { asyncMap } from '../../collections/async'; + +// TODO DIEGO: Remember to call here and only here this._lineColTransformer.convertDebuggerLocationToClient(stackFrame); for all responses +@injectable() +export class ConnectedCDA implements IDebugAdapterState { + private readonly events = new StepProgressEventsEmitter(); + + public static SCRIPTS_COMMAND = '.scripts'; + + constructor( + @inject(TYPES.ChromeDebugLogic) protected readonly _chromeDebugAdapter: ChromeDebugLogic, + @inject(TYPES.SourcesLogic) private readonly _sourcesLogic: SourcesLogic, + @inject(TYPES.CDTPScriptsRegistry) protected _scriptsLogic: CDTPScriptsRegistry, + @inject(TYPES.ClientToInternal) protected readonly _clientToInternal: ClientToInternal, + @inject(TYPES.InternalToClient) private readonly _internalToVsCode: InternalToClient, + @inject(TYPES.StackTracesLogic) private readonly _stackTraceLogic: StackTracesLogic, + @inject(TYPES.BreakpointsLogic) protected readonly _breakpointsLogic: BreakpointsLogic, + @inject(TYPES.PauseOnExceptionOrRejection) public readonly _pauseOnException: PauseOnExceptionOrRejection, + @inject(TYPES.Stepping) private readonly _stepping: Stepping, + @inject(TYPES.DotScriptCommand) public readonly _dotScriptCommand: DotScriptCommand, + @inject(SkipFilesLogic) public readonly _skipFilesLogic: SkipFilesLogic, + @inject(SmartStepLogic) public readonly _smartStepLogic: SmartStepLogic, + @inject(TakeProperActionOnPausedEvent) public readonly _takeProperActionOnPausedEvent: TakeProperActionOnPausedEvent, + @inject(NotifyClientOfLoadedSources) public readonly _notifyClientOfLoadedSources: NotifyClientOfLoadedSources, + @inject(TYPES.IScriptParsedProvider) public readonly _cdtpOnScriptParsedEventProvider: CDTPOnScriptParsedEventProvider, + @inject(TYPES.communicator) public readonly _communicator: ICommunicator, + @inject(TYPES.IDebugeeRunner) public readonly _debugeeRunner: IDebuggeeRunner, + ) { } + + public async install(): Promise { + await this._chromeDebugAdapter.install(); + await this._sourcesLogic.install(); + await this._stackTraceLogic.install(); + await this._breakpointsLogic.install(); + await this._pauseOnException.install(); + await this._stepping.install(); + // await this._dotScriptCommand.install(configuration); + await this._skipFilesLogic.install(); + await this._smartStepLogic.install(); + await this._takeProperActionOnPausedEvent.install(); + await this._notifyClientOfLoadedSources.install(); + + const publishScriptParsed = this._communicator.getPublisher(Target.Debugger.OnScriptParsed); + this._cdtpOnScriptParsedEventProvider.onScriptParsed(publishScriptParsed); + return this; + } + + public shutdown(): void { + return this._chromeDebugAdapter.shutdown(); + } + + public disconnect(_: DebugProtocol.DisconnectArguments): PromiseOrNot { + return this._chromeDebugAdapter.disconnect(); + } + + public async setBreakpoints(args: DebugProtocol.SetBreakpointsArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector): Promise { + if (args.breakpoints) { + const desiredBPRecipies = this._clientToInternal.toBPRecipies(args); + const bpRecipiesStatus = await this._breakpointsLogic.updateBreakpointsForFile(desiredBPRecipies, telemetryPropertyCollector); + return { breakpoints: await asyncMap(bpRecipiesStatus, bprs => this._internalToVsCode.toBPRecipieStatus(bprs)) }; + } else { + throw new Error(`Expected the set breakpoints arguments to have a list of breakpoints yet it was ${args.breakpoints}`); + } + } + + public async setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments, _?: ITelemetryPropertyCollector, _2?: number): Promise { + const exceptionsStrategy = this._clientToInternal.toPauseOnExceptionsStrategy(args.filters); + const promiseRejectionsStrategy = this._clientToInternal.toPauseOnPromiseRejectionsStrategy(args.filters); + await this._pauseOnException.setExceptionsStrategy(exceptionsStrategy); + this._pauseOnException.setPromiseRejectionStrategy(promiseRejectionsStrategy); + } + + public async configurationDone(): Promise { + await this._debugeeRunner.run(new TelemetryPropertyCollector()); + this.events.emitMilestoneReached('RequestedNavigateToUserPage'); // TODO DIEGO: Make sure this is reported + } + + public continue(): PromiseOrNot { + return this._stepping.continue(); + } + + public next(): PromiseOrNot { + return this._stepping.next(); + } + + public stepIn(): PromiseOrNot { + return this._stepping.stepIn(); + } + + public stepOut(): PromiseOrNot { + return this._stepping.stepOut(); + } + + public pause(): PromiseOrNot { + return this._stepping.pause(); + } + + public async restartFrame(args: DebugProtocol.RestartFrameRequest): Promise { + const callFrame = this._clientToInternal.getCallFrameById(args.arguments.frameId); + if (callFrame instanceof CallFramePresentation) { + return this._stepping.restartFrame(callFrame.callFrame.unmappedCallFrame); + } else { + throw new Error(`Cannot restart to a frame that doesn't have state information`); + } + } + + public async stackTrace(args: DebugProtocol.StackTraceArguments, _?: ITelemetryPropertyCollector, _2?: number): Promise { + const stackTracePresentation = await this._stackTraceLogic.stackTrace(args); + const clientStackTracePresentation = { + stackFrames: await this._internalToVsCode.toStackFrames(stackTracePresentation.stackFrames), + totalFrames: stackTracePresentation.totalFrames + }; + return clientStackTracePresentation; + } + + public scopes(args: DebugProtocol.ScopesArguments, _?: ITelemetryPropertyCollector, _2?: number): PromiseOrNot { + const frame = this._clientToInternal.getCallFrameById(args.frameId); + if (frame instanceof CallFramePresentation) { + return this._chromeDebugAdapter.scopes(frame.callFrame); + } else { + throw new Error(`Can't get scopes for the frame because a label frame is only a description of the different sections of the call stack`); + } + } + + public variables(args: DebugProtocol.VariablesArguments, _?: ITelemetryPropertyCollector, _2?: number): PromiseOrNot { + return this._chromeDebugAdapter.variables(args); + } + + public async source(args: DebugProtocol.SourceArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + if (args.source) { + const source = this._clientToInternal.toSource(args.source); + const sourceText = await this._sourcesLogic.getText(source); + return { + content: sourceText, + mimeType: 'text/javascript' + }; + } else { + throw new Error(`Expected the source request to have a source argument yet it was ${args.source}`); + } + } + + public threads(): PromiseOrNot { + return this._chromeDebugAdapter.threads(); + } + + public async evaluate(args: DebugProtocol.EvaluateArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + if (args.expression.startsWith(ConnectedCDA.SCRIPTS_COMMAND)) { + const scriptsRest = utils.lstrip(args.expression, ConnectedCDA.SCRIPTS_COMMAND).trim(); + await this._dotScriptCommand.handleScriptsCommand(scriptsRest); + return { + result: '', + variablesReference: 0 + }; + } else { + return this._chromeDebugAdapter.evaluate(args); + } + } + + public async loadedSources(): Promise { + return { sources: await asyncMap(await this._sourcesLogic.getLoadedSourcesTrees(), st => this._internalToVsCode.toSourceTree(st)) }; + } + + public setFunctionBreakpoints(_args: DebugProtocol.SetFunctionBreakpointsArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): PromiseOrNot { + throw new Error('Method not implemented.'); + } + + public setVariable(_args: DebugProtocol.SetVariableArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): PromiseOrNot { + throw new Error('Method not implemented.'); + } + + public async exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise { + if (args.threadId !== ChromeDebugLogic.THREAD_ID) { + throw errors.invalidThread(args.threadId); + } + + return this._internalToVsCode.toExceptionInfo(await this._pauseOnException.latestExceptionInfo()); + } + + public async toggleSkipFileStatus(args: IToggleSkipFileStatusArgs): Promise { + return this._skipFilesLogic.toggleSkipFileStatus(args); + } + + public async toggleSmartStep(): Promise { + return this._smartStepLogic.toggleSmartStep(); + } + + public launch(_args: ILaunchRequestArgs, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): never { + throw new Error("Can't launch to a new target while connected to a previous target"); + } + + public attach(_args: IAttachRequestArgs, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): never { + throw new Error("Can't attach to a new target while connected to a previous target"); + } + + public initialize(_args: DebugProtocol.InitializeRequestArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): PromiseOrNot<{ capabilities: DebugProtocol.Capabilities; newState: IDebugAdapterState; }> { + throw new Error('The debug adapter is already initialized. Calling initialize again is not supported.'); + } +} diff --git a/src/chrome/client/chromeDebugAdapter/connectedCDAEvents.ts b/src/chrome/client/chromeDebugAdapter/connectedCDAEvents.ts new file mode 100644 index 000000000..688f0838f --- /dev/null +++ b/src/chrome/client/chromeDebugAdapter/connectedCDAEvents.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { IEventsConsumedByStackTrace } from '../../internal/stackTraces/stackTracesLogic'; +import { IEventsConsumedBySkipFilesLogic } from '../../internal/features/skipFiles'; +import { EventsConsumedByBreakpointsLogic } from '../../internal/breakpoints/features/breakpointsLogic'; +import { ICommunicator } from '../../communication/communicator'; +import { Internal } from '../../communication/internalChannels'; +import { Target } from '../../communication/targetChannels'; +import { ILoadedSource } from '../../internal/sources/loadedSource'; +import { asyncMap } from '../../collections/async'; +import { IEventsConsumedByPauseOnException } from '../../internal/exceptions/pauseOnException'; +import { IEventsConsumedByTakeProperActionOnPausedEvent } from '../../internal/features/takeProperActionOnPausedEvent'; +import { IEventsConsumedBySourceResolver } from '../../internal/sources/sourceResolver'; +import { IEventsConsumedBySmartStepLogic } from '../../internal/features/smartStep'; +import { IEventsConsumedByReAddBPsWhenSourceIsLoaded } from '../../internal/breakpoints/features/reAddBPsWhenSourceIsLoaded'; +import { IEventsConsumedByAsyncStepping } from '../../internal/stepping/features/asyncStepping'; +// import { EventsConsumedBySyncStepping } from '../../internal/stepping/features/syncStepping'; + +export interface IEventsConsumedByConnectedCDA extends EventsConsumedByBreakpointsLogic, IEventsConsumedByPauseOnException, + IEventsConsumedByStackTrace, IEventsConsumedByTakeProperActionOnPausedEvent, IEventsConsumedBySkipFilesLogic, + IEventsConsumedBySourceResolver, IEventsConsumedBySmartStepLogic, + IEventsConsumedByReAddBPsWhenSourceIsLoaded, IEventsConsumedByAsyncStepping { } + +export class ConnectedCDAEventsCreator { + constructor(private readonly communicator: ICommunicator) { } + + public create(): IEventsConsumedByConnectedCDA { + const onLoadedSourceIsAvailable = (listener: (source: ILoadedSource) => void) => { + this.communicator.subscribe(Target.Debugger.OnScriptParsed, async scriptParsed => { + await asyncMap(scriptParsed.script.allSources, listener); + }); + }; + + return { + onLoadedSourceIsAvailable: onLoadedSourceIsAvailable, + + notifyNoPendingBPs: this.communicator.getPublisher(Internal.Breakpoints.OnNoPendingBreakpoints), + onNoPendingBreakpoints: this.communicator.getSubscriber(Internal.Breakpoints.OnNoPendingBreakpoints), + + onResumed: this.communicator.getSubscriber(Target.Debugger.OnResumed), + // onPaused: this.communicator.getSubscriber(Target.Debugger.OnPaused), + onAsyncBreakpointResolved: this.communicator.getSubscriber(Target.Debugger.OnAsyncBreakpointResolved), + + onScriptParsed: this.communicator.getSubscriber(Target.Debugger.OnScriptParsed), + + subscriberForAskForInformationAboutPaused: this.communicator.getSubscriber(Internal.Breakpoints.OnPausedOnBreakpoint), + askForInformationAboutPause: this.communicator.getPublisher(Internal.Breakpoints.OnPausedOnBreakpoint), + publishGoingToPauseClient: this.communicator.getPublisher(Internal.Breakpoints.OnGoingToPauseClient) + }; + } +} diff --git a/src/chrome/client/chromeDebugAdapter/unconnectedCDA.ts b/src/chrome/client/chromeDebugAdapter/unconnectedCDA.ts new file mode 100644 index 000000000..0d2137aa2 --- /dev/null +++ b/src/chrome/client/chromeDebugAdapter/unconnectedCDA.ts @@ -0,0 +1,135 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + + import { InitializedEvent, Logger } from 'vscode-debugadapter'; +import { ChromeDebugLogic, ChromeDebugSession, IAttachRequestArgs, IDebugAdapterState, ILaunchRequestArgs, ITelemetryPropertyCollector, LineColTransformer, utils, BaseSourceMapTransformer, BasePathTransformer } from '../../..'; +import { IClientCapabilities } from '../../../debugAdapterInterfaces'; +import * as errors from '../../../errors'; +import { EagerSourceMapTransformer } from '../../../transformers/eagerSourceMapTransformer'; +import { FallbackToClientPathTransformer } from '../../../transformers/fallbackToClientPathTransformer'; +import { RemotePathTransformer } from '../../../transformers/remotePathTransformer'; +import { ChromeConnection } from '../../chromeConnection'; +import { Communicator, LoggingCommunicator } from '../../communication/communicator'; +import { DependencyInjection } from '../../dependencyInjection.ts/di'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { IExtensibilityPoints } from '../../extensibility/extensibilityPoints'; +import { Logging, ILoggingConfiguration } from '../../internal/services/logging'; +import { ExecutionLogger } from '../../logging/executionLogger'; +import { DelayMessagesUntilInitializedSession } from '../delayMessagesUntilInitializedSession'; +import { DoNotPauseWhileSteppingSession } from '../doNotPauseWhileSteppingSession'; +import { ConnectedCDAConfiguration } from './cdaConfiguration'; +import { ConnectedCDA } from './connectedCDA'; +import { ConnectedCDAEventsCreator } from './connectedCDAEvents'; +import { BaseUnconnectedCDA } from './unconnectedCDACommonLogic'; +import { IDebuggeeLauncher } from '../../debugeeStartup/debugeeLauncher'; +import { IDomainsEnabler } from '../../cdtpDebuggee/infrastructure/cdtpDomainsEnabler'; + +export enum ScenarioType { + Launch, + Attach +} + +export class UnconnectedCDA extends BaseUnconnectedCDA implements IDebugAdapterState { + private readonly _session = new DelayMessagesUntilInitializedSession(new DoNotPauseWhileSteppingSession(this._basicSession)); + + private configuration: ConnectedCDAConfiguration; + + public chromeDebugAdapter(): ChromeDebugLogic { + throw new Error('The chrome debug adapter can only be used when the debug adapter is connected'); + } + + public async launch(args: ILaunchRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.createConnection(ScenarioType.Launch, args, telemetryPropertyCollector); + } + + public async attach(args: IAttachRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + const updatedArgs = Object.assign({}, { port: 9229 }, args); + return this.createConnection(ScenarioType.Attach, updatedArgs, telemetryPropertyCollector); + } + + private parseLoggingConfiguration(args: ILaunchRequestArgs | IAttachRequestArgs): ILoggingConfiguration { + const traceMapping: { [key: string]: Logger.LogLevel | undefined } = { true: Logger.LogLevel.Warn, verbose: Logger.LogLevel.Verbose }; + const traceValue = args.trace && traceMapping[args.trace.toString().toLowerCase()]; + return { logLevel: traceValue, logFilePath: args.logFilePath, shouldLogTimestamps: args.logTimestamps }; + } + + private async createConnection(scenarioType: ScenarioType, args: ILaunchRequestArgs | IAttachRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector): Promise { + if (this._clientCapabilities.pathFormat !== 'path') { + throw errors.pathFormat(); + } + + utils.setCaseSensitivePaths(this._clientCapabilities.clientID !== 'visualstudio'); // TODO DIEGO: Find a way to remove this + const di = new DependencyInjection(); + + const pathTransformerClass = this._clientCapabilities.supportsMapURLToFilePathRequest + ? FallbackToClientPathTransformer + : this._extensibilityPoints.pathTransformer || RemotePathTransformer; + const sourceMapTransformerClass = this._extensibilityPoints.sourceMapTransformer || EagerSourceMapTransformer; + const lineColTransformerClass = this._extensibilityPoints.lineColTransformer || LineColTransformer; + const logging = new Logging().install(this._extensibilityPoints, this.parseLoggingConfiguration(args)); + + const chromeConnection = new (this._chromeConnectionClass)(undefined, args.targetFilter || this._extensibilityPoints.targetFilter); + const communicator = new LoggingCommunicator(new Communicator(), new ExecutionLogger(logging)); + + const diContainer = this.getDIContainer(di, lineColTransformerClass, communicator, chromeConnection, pathTransformerClass, sourceMapTransformerClass, args, scenarioType); + + const debugeeLauncher = diContainer.createComponent(TYPES.IDebuggeeLauncher); + + diContainer.unconfigure(TYPES.IDebuggeeLauncher); // TODO DIEGO: Remove this line and do this properly + diContainer.configureValue(TYPES.IDebuggeeLauncher, debugeeLauncher); // TODO DIEGO: Remove this line and do this properly + + const result = await debugeeLauncher.launch(args, telemetryPropertyCollector); + await chromeConnection.attach(result.address, result.port, result.url, args.timeout, args.extraCRDPChannelPort); + + if (chromeConnection.api === undefined) { + throw new Error('Expected the Chrome API object to be properly initialized by now'); + } + + diContainer.configureValue(TYPES.CDTPClient, chromeConnection.api); + + const newState = di.createClassWithDI(ConnectedCDA); + await newState.install(); + + const domainsEnabler = di.createComponent(TYPES.IDomainsEnabler); + await domainsEnabler.enableDomains(); // Enables all the domains that were registered + await chromeConnection.api.Runtime.runIfWaitingForDebugger(); + + this._session.sendEvent(new InitializedEvent()); + + return newState; + } + + constructor( + private readonly _extensibilityPoints: IExtensibilityPoints, + private readonly _basicSession: ChromeDebugSession, + private readonly _clientCapabilities: IClientCapabilities, + private readonly _chromeConnectionClass: typeof ChromeConnection + ) { + super(); + } + + private getDIContainer(di: DependencyInjection, lineColTransformerClass: typeof LineColTransformer, communicator: LoggingCommunicator, chromeConnection: ChromeConnection, pathTransformerClass: (new (configuration: ConnectedCDAConfiguration) => BasePathTransformer), sourceMapTransformerClass: new (configuration: ConnectedCDAConfiguration) => BaseSourceMapTransformer, args: ILaunchRequestArgs | IAttachRequestArgs, scenarioType: ScenarioType): DependencyInjection { + const configuration = this.createConfiguration(args, scenarioType); + return di + .bindAll() + .configureClass(LineColTransformer, lineColTransformerClass) + .configureClass(TYPES.IDebugeeRunner, this._extensibilityPoints.debugeeRunner) + .configureClass(TYPES.IDebuggeeLauncher, this._extensibilityPoints.debugeeLauncher) + // .configureClass(TYPES.IDebugeeLauncher, debugeeLauncher) + .configureValue(TYPES.communicator, communicator) + .configureValue(TYPES.EventsConsumedByConnectedCDA, new ConnectedCDAEventsCreator(communicator).create()) + .configureValue(TYPES.ISession, this._session) + .configureValue(TYPES.BasePathTransformer, new pathTransformerClass(configuration)) + .configureValue(TYPES.BaseSourceMapTransformer, new sourceMapTransformerClass(configuration)) + .configureValue(TYPES.ChromeConnection, chromeConnection) + .configureValue(TYPES.ConnectedCDAConfiguration, configuration); + } + + private createConfiguration(args: ILaunchRequestArgs | IAttachRequestArgs, scenarioType: ScenarioType): ConnectedCDAConfiguration { + if (this.configuration === undefined) { + this.configuration = new ConnectedCDAConfiguration(this._extensibilityPoints, this.parseLoggingConfiguration(args), this._session, this._clientCapabilities, this._chromeConnectionClass, scenarioType, args); + } + return this.configuration; + } +} \ No newline at end of file diff --git a/src/chrome/client/chromeDebugAdapter/unconnectedCDACommonLogic.ts b/src/chrome/client/chromeDebugAdapter/unconnectedCDACommonLogic.ts new file mode 100644 index 000000000..aed1ef1e6 --- /dev/null +++ b/src/chrome/client/chromeDebugAdapter/unconnectedCDACommonLogic.ts @@ -0,0 +1,123 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ITelemetryPropertyCollector, ISetBreakpointsResponseBody, IStackTraceResponseBody, IScopesResponseBody, IVariablesResponseBody, ISourceResponseBody, IEvaluateResponseBody, IGetLoadedSourcesResponseBody } from '../../../debugAdapterInterfaces'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { ChromeDebugLogic, ILaunchRequestArgs, IAttachRequestArgs, IExceptionInfoResponseBody, IDebugAdapterState, IToggleSkipFileStatusArgs } from '../../..'; +import { PromiseOrNot } from '../../utils/promises'; + +export abstract class BaseUnconnectedCDA implements IDebugAdapterState { + public abstract chromeDebugAdapter(): ChromeDebugLogic; + + public initialize(_args: DebugProtocol.InitializeRequestArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise<{capabilities: DebugProtocol.Capabilities, newState: IDebugAdapterState}> { + return this.throwNotConnectedError(); + } + + public launch(_args: ILaunchRequestArgs, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): PromiseOrNot { + return this.throwNotConnectedError(); + } + + public attach(_args: IAttachRequestArgs, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): PromiseOrNot { + return this.throwNotConnectedError(); + } + + public restartFrame(_args: DebugProtocol.RestartFrameRequest): Promise { + return this.throwNotConnectedError(); + } + + public exceptionInfo(_args: DebugProtocol.ExceptionInfoArguments): Promise { + return this.throwNotConnectedError(); + } + + public shutdown(): void { + return this.throwNotConnectedError(); + } + + public disconnect(_args: DebugProtocol.DisconnectArguments): Promise { + return this.throwNotConnectedError(); + } + + public setBreakpoints(_args: DebugProtocol.SetBreakpointsArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public setExceptionBreakpoints(_args: DebugProtocol.SetExceptionBreakpointsArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public configurationDone(): Promise { + return this.throwNotConnectedError(); + } + + public continue(): Promise { + + return this.throwNotConnectedError(); + } + + public next(): Promise { + + return this.throwNotConnectedError(); + } + + public stepIn(): Promise { + + return this.throwNotConnectedError(); + } + + public stepOut(): Promise { + return this.throwNotConnectedError(); + } + + public pause(): Promise { + return this.throwNotConnectedError(); + } + + public stackTrace(_args: DebugProtocol.StackTraceArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public scopes(_args: DebugProtocol.ScopesArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public variables(_args: DebugProtocol.VariablesArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public source(_args: DebugProtocol.SourceArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public threads(): Promise<{ threads: DebugProtocol.Thread[]; }> { + return this.throwNotConnectedError(); + } + + public evaluate(_args: DebugProtocol.EvaluateArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public loadedSources(_args: DebugProtocol.LoadedSourcesArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public setFunctionBreakpoints(_args: DebugProtocol.SetFunctionBreakpointsArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public setVariable(_args: DebugProtocol.SetVariableArguments, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise { + return this.throwNotConnectedError(); + } + + public toggleSmartStep(): Promise { + return this.throwNotConnectedError(); + } + + private throwNotConnectedError(): never { + throw new Error("Can't execute this request when the debug adapter is not connected to the target"); + } + + public async toggleSkipFileStatus(_args: IToggleSkipFileStatusArgs): Promise { + return this.throwNotConnectedError(); + } +} \ No newline at end of file diff --git a/src/chrome/client/chromeDebugAdapter/uninitializedCDA.ts b/src/chrome/client/chromeDebugAdapter/uninitializedCDA.ts new file mode 100644 index 000000000..36659a8e1 --- /dev/null +++ b/src/chrome/client/chromeDebugAdapter/uninitializedCDA.ts @@ -0,0 +1,72 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { BaseUnconnectedCDA } from './unconnectedCDACommonLogic'; +import { ChromeConnection } from '../../chromeConnection'; +import { IDebugAdapterState, ChromeDebugLogic, ITelemetryPropertyCollector, IInitializeRequestArgs, ChromeDebugSession } from '../../..'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { UnconnectedCDA } from './unconnectedCDA'; +import { IExtensibilityPoints } from '../../extensibility/extensibilityPoints'; +import * as nls from 'vscode-nls'; +let localize = nls.loadMessageBundle(); // Initialize to an unlocalized version until we know which locale to use + +export class UninitializedCDA extends BaseUnconnectedCDA implements IDebugAdapterState { + public chromeDebugAdapter(): ChromeDebugLogic { + throw new Error('Method not implemented.'); + } + + public async initialize(args: IInitializeRequestArgs, _telemetryPropertyCollector?: ITelemetryPropertyCollector, _requestSeq?: number): Promise<{ capabilities: DebugProtocol.Capabilities, newState: IDebugAdapterState }> { + if (args.locale) { + localize = nls.config({ locale: args.locale })(); // Replace with the proper locale + } + + const exceptionBreakpointFilters = [ + { + label: localize('exceptions.all', 'All Exceptions'), + filter: 'all', + default: false + }, + { + label: localize('exceptions.uncaught', 'Uncaught Exceptions'), + filter: 'uncaught', + default: false + } + ]; + + if (this._extensibilityPoints.isPromiseRejectExceptionFilterEnabled) { + exceptionBreakpointFilters.push({ + label: localize('exceptions.promise_rejects', 'Promise Rejects'), + filter: 'promise_reject', + default: false + }); + } + + // This debug adapter supports two exception breakpoint filters + const capabilities = { + exceptionBreakpointFilters, + supportsConfigurationDoneRequest: true, + supportsSetVariable: true, + supportsConditionalBreakpoints: true, + supportsCompletionsRequest: true, + supportsHitConditionalBreakpoints: true, + supportsRestartFrame: true, + supportsExceptionInfoRequest: true, + supportsDelayedStackTraceLoading: true, + supportsValueFormattingOptions: true, + supportsEvaluateForHovers: true, + supportsLoadedSourcesRequest: true + }; + + const newState = new UnconnectedCDA(this._extensibilityPoints, this._session, args, this._chromeConnectionClass); + return { capabilities, newState }; + } + + constructor( + private readonly _extensibilityPoints: IExtensibilityPoints, + private readonly _session: ChromeDebugSession, + private readonly _chromeConnectionClass: typeof ChromeConnection + ) { + super(); + } +} \ No newline at end of file diff --git a/src/chrome/client/clientToInternal.ts b/src/chrome/client/clientToInternal.ts new file mode 100644 index 000000000..08408ff8f --- /dev/null +++ b/src/chrome/client/clientToInternal.ts @@ -0,0 +1,107 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ILoadedSource } from '../internal/sources/loadedSource'; +import { BPRecipieInSource } from '../internal/breakpoints/bpRecipieInSource'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { SourcesLogic } from '../internal/sources/sourcesLogic'; +import { Position, LocationInSource } from '../internal/locations/location'; +import { LineColTransformer } from '../../transformers/lineNumberTransformer'; +import { BPRecipiesInSource } from '../internal/breakpoints/bpRecipies'; +import { IBPActionWhenHit, AlwaysPause, ConditionalPause } from '../internal/breakpoints/bpActionWhenHit'; +import { HandlesRegistry } from './handlesRegistry'; +import { createLineNumber, createColumnNumber } from '../internal/locations/subtypes'; +import { parseResourceIdentifier } from '../internal/sources/resourceIdentifier'; +import { IPauseOnExceptionsStrategy, PauseOnAllExceptions, PauseOnUnhandledExceptions, DoNotPauseOnAnyExceptions, PauseOnAllRejections, DoNotPauseOnAnyRejections, IPauseOnPromiseRejectionsStrategy } from '../internal/exceptions/strategies'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../dependencyInjection.ts/types'; +import { ISource, SourceAlreadyResolvedToLoadedSource } from '../internal/sources/source'; +import { IStackTracePresentationRow } from '../internal/stackTraces/stackTracePresentationRow'; + +@injectable() +export class ClientToInternal { + public toPauseOnExceptionsStrategy(exceptionFilters: string[]): IPauseOnExceptionsStrategy { + if (exceptionFilters.indexOf('all') >= 0) { + return new PauseOnAllExceptions(); + } else if (exceptionFilters.indexOf('uncaught') >= 0) { + return new PauseOnUnhandledExceptions(); + } else { + return new DoNotPauseOnAnyExceptions(); + } + } + + public toPauseOnPromiseRejectionsStrategy(exceptionFilters: string[]): IPauseOnPromiseRejectionsStrategy { + if (exceptionFilters.indexOf('promise_reject') >= 0) { + return new PauseOnAllRejections(); + } else { + return new DoNotPauseOnAnyRejections(); + } + } + + // V1 reseted the frames on an onPaused event. Figure out if that is the right thing to do + public getCallFrameById(frameId: number): IStackTracePresentationRow { + return this._handlesRegistry.frames.getObjectById(frameId); + } + + public getSourceFromId(handle: number): ILoadedSource { + return this._handlesRegistry.sources.getObjectById(handle); + } + + public toSource(clientSource: DebugProtocol.Source): ISource { + if (clientSource.path && !clientSource.sourceReference) { + const identifier = parseResourceIdentifier(clientSource.path); + return this._sourcesLogic.createSourceResolver(identifier); + } else if (clientSource.sourceReference) { + const source = this.getSourceFromId(clientSource.sourceReference); + return new SourceAlreadyResolvedToLoadedSource(source); + } else { + throw new Error(`Expected the source to have a path (${clientSource.path}) either-or a source reference (${clientSource.sourceReference})`); + } + } + + public toBPRecipies(args: DebugProtocol.SetBreakpointsArguments): BPRecipiesInSource { + const source = this.toSource(args.source); + const breakpoints = args.breakpoints.map(breakpoint => this.toBPRecipie(source, breakpoint)); + return new BPRecipiesInSource(source, breakpoints); + } + + public toBPRecipie(source: ISource, clientBreakpoint: DebugProtocol.SourceBreakpoint): BPRecipieInSource { + return new BPRecipieInSource( + new LocationInSource(source, this.toLocation(clientBreakpoint)), + this.toBPActionWhenHit(clientBreakpoint)); + } + + public toLocation(location: { line: number; column?: number; }): Position { + const lineNumber = createLineNumber(this._lineColTransformer.convertClientLineToDebugger(location.line)); + const columnNumber = location.column !== undefined ? createColumnNumber(this._lineColTransformer.convertClientColumnToDebugger(location.column)) : undefined; + return new Position(lineNumber, columnNumber); + } + + public toBPActionWhenHit(actionWhenHit: { condition?: string; hitCondition?: string; logMessage?: string; }): IBPActionWhenHit { + let howManyDefined = 0; + howManyDefined += actionWhenHit.condition ? 1 : 0; + howManyDefined += actionWhenHit.hitCondition ? 1 : 0; + howManyDefined += actionWhenHit.logMessage ? 1 : 0; + if (howManyDefined === 0) { + return new AlwaysPause(); + } else if (howManyDefined === 1) { + if (actionWhenHit.condition) { + return new ConditionalPause(actionWhenHit.condition); + } else if (actionWhenHit.hitCondition) { + return new ConditionalPause(actionWhenHit.hitCondition); + } else if (actionWhenHit.logMessage) { + return new ConditionalPause(actionWhenHit.logMessage); + } else { + throw new Error(`Couldn't parse the desired action when hit for the breakpoint: 'condition' (${actionWhenHit.condition}), 'hitCondition' (${actionWhenHit.hitCondition}) or 'logMessage' (${actionWhenHit.logMessage})`); + } + } else { // howManyDefined >= 2 + throw new Error(`Expected a single one of 'condition' (${actionWhenHit.condition}), 'hitCondition' (${actionWhenHit.hitCondition}) and 'logMessage' (${actionWhenHit.logMessage}) to be defined, yet multiple were defined.`); + } + } + + constructor( + @inject(HandlesRegistry) private readonly _handlesRegistry: HandlesRegistry, + @inject(TYPES.LineColTransformer) private readonly _lineColTransformer: LineColTransformer, + private readonly _sourcesLogic: SourcesLogic) { } +} \ No newline at end of file diff --git a/src/chrome/client/delayMessagesUntilInitializedSession.ts b/src/chrome/client/delayMessagesUntilInitializedSession.ts new file mode 100644 index 000000000..e30bf0879 --- /dev/null +++ b/src/chrome/client/delayMessagesUntilInitializedSession.ts @@ -0,0 +1,26 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { DebugProtocol } from 'vscode-debugprotocol'; +import { InitializedEvent } from 'vscode-debugadapter'; +import { BaseWrappedSession } from './session'; + +export class DelayMessagesUntilInitializedSession extends BaseWrappedSession { + private _hasSentInitializedMessage = false; + private _eventsWaitingInitialization: DebugProtocol.Event[] = []; + + public sendEvent(event: DebugProtocol.Event): void { + if (this._hasSentInitializedMessage) { + super.sendEvent(event); + } else if (event instanceof InitializedEvent) { + this._wrappedSession.sendEvent(event); + this._hasSentInitializedMessage = true; + this._eventsWaitingInitialization.forEach(storedEvent => + this._wrappedSession.sendEvent(storedEvent)); + this._eventsWaitingInitialization = []; + } else { + this._eventsWaitingInitialization.push(event); + } + } +} \ No newline at end of file diff --git a/src/chrome/client/doNotPauseWhileSteppingSession.ts b/src/chrome/client/doNotPauseWhileSteppingSession.ts new file mode 100644 index 000000000..a2e36180f --- /dev/null +++ b/src/chrome/client/doNotPauseWhileSteppingSession.ts @@ -0,0 +1,54 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { BaseWrappedSession } from './session'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { utils } from '../..'; + +const steppingRequests = { + continue: true, + next: true, + stepIn: true, + stepOut: true, + pause: true, + restartFrame: true, +}; + +export class DoNotPauseWhileSteppingSession extends BaseWrappedSession { + private readonly _onFlightSteppingRequests = new Set>(); + + public async dispatchRequest(request: DebugProtocol.Request): Promise { + const response = this._wrappedSession.dispatchRequest(request); + if (this.isSteppingRequest(request)) { + // We track the on-flight stepping requests + this._onFlightSteppingRequests.add(response); + const finallyHandler = () => { this._onFlightSteppingRequests.delete(response); }; + return response.then(finallyHandler, finallyHandler); + } else { + return await response; + } + } + + public async sendEvent(event: DebugProtocol.Event): Promise { + if (event.event === 'stopped') { + // If this is a stopped event, we try to wait until there are no stepping requests in flight, or we timeout + await utils.promiseTimeout(this.waitUntilThereAreNoOnFlightSteppingRequests(), /*timeoutMs=*/300); + } + + this._wrappedSession.sendEvent(event); + } + + private isSteppingRequest(request: DebugProtocol.Request): boolean { + return !!(steppingRequests as any)[request.command]; + } + + private async waitUntilThereAreNoOnFlightSteppingRequests(): Promise { + while (this._onFlightSteppingRequests.size > 0) { + const onFlightRequests = Array.from(this._onFlightSteppingRequests.keys()); + const noFailOnFlightRequests = onFlightRequests.map(promise => promise.catch(_ => { })); + await Promise.all(noFailOnFlightRequests); + // After we waited for all the on flight requests, a new request might just have appeared, so we check and wait again if needed + } + } +} \ No newline at end of file diff --git a/src/chrome/client/eventSender.ts b/src/chrome/client/eventSender.ts new file mode 100644 index 000000000..62c7a25db --- /dev/null +++ b/src/chrome/client/eventSender.ts @@ -0,0 +1,100 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ILoadedSource } from '../internal/sources/loadedSource'; +import { ISession } from './session'; +import { LoadedSourceEvent, OutputEvent, BreakpointEvent } from 'vscode-debugadapter'; +import { InternalToClient } from './internalToClient'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { LocationInLoadedSource } from '../internal/locations/location'; +import { IBPRecipieStatus } from '../internal/breakpoints/bpRecipieStatus'; +import { IFormattedExceptionLineDescription } from '../internal/formattedExceptionParser'; +import { StoppedEvent2, ReasonType } from '../stoppedEvent'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../dependencyInjection.ts/types'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { ChromeDebugLogic } from '../chromeDebugAdapter'; + +export interface IOutputParameters { + readonly output: string; + readonly category: string; + readonly variablesReference?: number; + readonly location?: LocationInLoadedSource; +} + +export interface ISourceWasLoadedParameters { + readonly reason: 'new' | 'changed' | 'removed'; + readonly source: ILoadedSource; +} + +export interface IBPStatusChangedParameters { + readonly reason: string; + readonly bpRecipieStatus: IBPRecipieStatus; +} + +export interface IExceptionThrownParameters { + readonly exceptionStackTrace: IFormattedExceptionLineDescription[]; + readonly category: string; + readonly location?: LocationInLoadedSource; +} + +export interface IDebugeeIsStoppedParameters { + reason: ReasonType; + exception?: CDTP.Runtime.RemoteObject; +} + +export interface IEventsToClientReporter { + sendOutput(params: IOutputParameters): void; + sendSourceWasLoaded(params: ISourceWasLoadedParameters): Promise; + sendBPStatusChanged(params: IBPStatusChangedParameters): Promise; + sendExceptionThrown(params: IExceptionThrownParameters): Promise; + sendDebuggeeIsStopped(params: IDebugeeIsStoppedParameters): Promise; +} + +@injectable() +export class EventSender implements IEventsToClientReporter { + public sendOutput(params: IOutputParameters): void { + const event = new OutputEvent(params.output, params.category) as DebugProtocol.OutputEvent; + + if (params.variablesReference) { + event.body.variablesReference = params.variablesReference; + } + + if (params.location) { + this._internalToClient.toLocationInSource(params.location, event.body); + } + + this._session.sendEvent(event); + } + + public async sendSourceWasLoaded(params: ISourceWasLoadedParameters): Promise { + const clientSource = await this._internalToClient.toSource(params.source); + const event = new LoadedSourceEvent(params.reason, clientSource); + + this._session.sendEvent(event); + } + + public async sendBPStatusChanged(params: IBPStatusChangedParameters): Promise { + const breakpointStatus = await this._internalToClient.toBPRecipieStatus(params.bpRecipieStatus); + const event = new BreakpointEvent(params.reason, breakpointStatus); + + this._session.sendEvent(event); + } + + public async sendExceptionThrown(params: IExceptionThrownParameters): Promise { + return this.sendOutput({ + output: this._internalToClient.toExceptionStackTracePrintted(params.exceptionStackTrace), + category: params.category, + location: params.location + }); + } + + public async sendDebuggeeIsStopped(params: IDebugeeIsStoppedParameters): Promise { + return this._session.sendEvent(new StoppedEvent2(params.reason, /*threadId=*/ChromeDebugLogic.THREAD_ID, params.exception)); + } + + constructor( + @inject(TYPES.ISession) private readonly _session: ISession, + private readonly _internalToClient: InternalToClient) { } +} diff --git a/src/chrome/client/handlesRegistry.ts b/src/chrome/client/handlesRegistry.ts new file mode 100644 index 000000000..5fb7da71e --- /dev/null +++ b/src/chrome/client/handlesRegistry.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ILoadedSource } from '../internal/sources/loadedSource'; +import { IBPRecipie } from '../internal/breakpoints/bpRecipie'; +import { BidirectionalMap } from '../collections/bidirectionalMap'; +import { injectable } from 'inversify'; +import { IStackTracePresentationRow } from '../internal/stackTraces/stackTracePresentationRow'; +import { ISource } from '../internal/sources/source'; + +export class BidirectionalHandles { + private readonly _idToObject = new BidirectionalMap(); + + public getObjectById(id: number): T { + return this._idToObject.getByLeft(id); + } + + public getIdByObject(obj: T): number { + const id = this._idToObject.tryGettingByRight(obj); + if (id !== undefined) { + return id; + } else { + const newId = this._nextHandle++; + this._idToObject.set(newId, obj); + return newId; + } + } + + public toString(): string { + return this._idToObject.toString(); + } + + constructor(private _nextHandle: number) { } +} + +const prefixMultiplier = 1000000; + +@injectable() +export class HandlesRegistry { + // TODO DIEGO: V1 reseted the frames on an onPaused event. Figure out if that is the right thing to do + // We use different prefixes so it's easier to identify the IDs in the logs... + public readonly breakpoints = new BidirectionalHandles>(888 * prefixMultiplier); + public readonly frames = new BidirectionalHandles(123 * prefixMultiplier); + public readonly sources = new BidirectionalHandles(555 * prefixMultiplier); + + public toString(): string { + return `Handles {\nBPs:\n${this.breakpoints}\nFrames:\n${this.frames}\nSources:\n${this.sources}\n}`; + } + + constructor() { + } +} diff --git a/src/chrome/client/internalToClient.ts b/src/chrome/client/internalToClient.ts new file mode 100644 index 000000000..441bb62cd --- /dev/null +++ b/src/chrome/client/internalToClient.ts @@ -0,0 +1,139 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { DebugProtocol } from 'vscode-debugprotocol'; +import { utils, LineColTransformer, IExceptionInfoResponseBody } from '../..'; +import * as pathModule from 'path'; +import { ILoadedSource, ILoadedSourceTreeNode } from '../internal/sources/loadedSource'; +import { LocationInLoadedSource } from '../internal/locations/location'; +import { RemoveProperty } from '../../typeUtils'; +import { IBPRecipieStatus, BPRecipieIsBinded } from '../internal/breakpoints/bpRecipieStatus'; +import { IBPRecipie } from '../internal/breakpoints/bpRecipie'; +import { HandlesRegistry } from './handlesRegistry'; +import { IExceptionInformation } from '../internal/exceptions/pauseOnException'; +import { IFormattedExceptionLineDescription } from '../internal/formattedExceptionParser'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../dependencyInjection.ts/types'; +import { Source } from 'vscode-debugadapter'; +import { IStackTracePresentationRow, StackTraceLabel } from '../internal/stackTraces/stackTracePresentationRow'; +import { CallFramePresentation } from '../internal/stackTraces/callFramePresentation'; +import { asyncMap } from '../collections/async'; +import { ISource } from '../internal/sources/source'; + +interface IClientLocationInSource { + source: DebugProtocol.Source; + line: number; + column: number; +} + +@injectable() +export class InternalToClient { + public toStackFrames(rows: IStackTracePresentationRow[]): Promise { + return asyncMap(rows, row => this.toStackFrame(row)); + } + + public getFrameId(stackFrame: IStackTracePresentationRow): number { + return this._handlesRegistry.frames.getIdByObject(stackFrame); + } + + public async toStackFrame(stackFrame: IStackTracePresentationRow): Promise { + if (stackFrame instanceof CallFramePresentation) { + const clientStackFrame: RemoveProperty = { + id: this.getFrameId(stackFrame), + name: stackFrame.description, + presentationHint: stackFrame.presentationHint + }; + + const result = await this.toLocationInSource(stackFrame.location, clientStackFrame); + return result; + } else if (stackFrame instanceof StackTraceLabel) { + return { + id: this.getFrameId(stackFrame), + name: `[${stackFrame.description}]`, + presentationHint: 'label' + } as DebugProtocol.StackFrame; + } else { + throw new Error(`Expected stack frames to be either call frame presentations or label frames, yet it was: ${stackFrame}`); + } + } + + private toSourceLeafs(sources: ILoadedSourceTreeNode[]): Promise { + return Promise.all(sources.map(source => this.toSourceTree(source))); + } + + public async toSourceTree(sourceMetadata: ILoadedSourceTreeNode): Promise { + const source = await this.toSource(sourceMetadata.mainSource); + (source as any).sources = await this.toSourceLeafs(sourceMetadata.relatedSources); + return source; + } + + public async toSource(loadedSource: ILoadedSource): Promise { + const exists = await utils.existsAsync(loadedSource.identifier.canonicalized); + + // if the path exists, do not send the sourceReference + // new Source sends 0 for undefined + const source: Source = { + name: pathModule.basename(loadedSource.identifier.textRepresentation), + path: loadedSource.identifier.textRepresentation, + sourceReference: exists ? undefined : this._handlesRegistry.sources.getIdByObject(loadedSource), + }; + + return source; + } + + public async toLocationInSource(locationInSource: LocationInLoadedSource, objectToUpdate: T): Promise { + const source = await this.toSource(locationInSource.source); + const clientLocationInSource = { source, line: locationInSource.position.lineNumber, column: locationInSource.position.columnNumber }; + this._lineColTransformer.convertDebuggerLocationToClient(clientLocationInSource); + return Object.assign(objectToUpdate, clientLocationInSource); + } + + public async toBPRecipieStatus(bpRecipieStatus: IBPRecipieStatus): Promise { + const clientStatus = { + id: this.toBreakpointId(bpRecipieStatus.recipie), + verified: bpRecipieStatus.isVerified(), + message: bpRecipieStatus.statusDescription + }; + + if (bpRecipieStatus instanceof BPRecipieIsBinded) { + await this.toLocationInSource(bpRecipieStatus.actualLocationInSource, clientStatus); + } + + return clientStatus; + } + + public toBreakpointId(recipie: IBPRecipie): number { + return this._handlesRegistry.breakpoints.getIdByObject(recipie); + } + + public isZeroBased(): boolean { + const objWithLine = { line: 0 }; + this._lineColTransformer.convertDebuggerLocationToClient(objWithLine); + return objWithLine.line === 0; + } + + public toExceptionInfo(info: IExceptionInformation): IExceptionInfoResponseBody { + return { + exceptionId: info.exceptionId, + description: info.description, + breakMode: info.breakMode, + details: { + message: info.details.message, + formattedDescription: info.details.formattedDescription, + stackTrace: this.toExceptionStackTracePrintted(info.details.stackTrace), + typeName: info.details.typeName, + } + }; + } + + public toExceptionStackTracePrintted(formattedExceptionLines: IFormattedExceptionLineDescription[]): string { + const stackTraceLines = formattedExceptionLines.map(line => line.generateDescription(this.isZeroBased())); + const stackTracePrintted = stackTraceLines.join('\n') + '\n'; + return stackTracePrintted; + } + + constructor( + @inject(HandlesRegistry) private readonly _handlesRegistry: HandlesRegistry, + @inject(TYPES.LineColTransformer) private readonly _lineColTransformer: LineColTransformer) { } +} \ No newline at end of file diff --git a/src/chrome/client/requests.ts b/src/chrome/client/requests.ts new file mode 100644 index 000000000..f184a8261 --- /dev/null +++ b/src/chrome/client/requests.ts @@ -0,0 +1,6 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export const AvailableCommands = new Set(['runInTerminal', 'initialize', 'configurationDone', 'launch', 'attach', 'restart', 'disconnect', 'terminate', 'setBreakpoints', 'setFunctionBreakpoints', 'setExceptionBreakpoints', 'continue', 'next', 'stepIn', 'stepOut', 'stepBack', 'reverseContinue', 'restartFrame', 'goto', 'pause', 'stackTrace', 'scopes', 'variables', 'setVariable', 'source', 'threads', 'terminateThreads', 'modules', 'loadedSources', 'evaluate', 'setExpression', 'stepInTargets', 'gotoTargets', 'completions', 'exceptionInfo', 'toggleSkipFileStatus', 'toggleSmartStep']); +export type CommandText = 'runInTerminal' | 'initialize' | 'configurationDone' | 'launch' | 'attach' | 'restart' | 'disconnect' | 'terminate' | 'setBreakpoints' | 'setFunctionBreakpoints' | 'setExceptionBreakpoints' | 'continue' | 'next' | 'stepIn' | 'stepOut' | 'stepBack' | 'reverseContinue' | 'restartFrame' | 'goto' | 'pause' | 'stackTrace' | 'scopes' | 'variables' | 'setVariable' | 'source' | 'threads' | 'terminateThreads' | 'modules' | 'loadedSources' | 'evaluate' | 'setExpression' | 'stepInTargets' | 'gotoTargets' | 'completions' | 'exceptionInfo'; diff --git a/src/chrome/client/session.ts b/src/chrome/client/session.ts new file mode 100644 index 000000000..6225d4ff5 --- /dev/null +++ b/src/chrome/client/session.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { DebugProtocol } from 'vscode-debugprotocol'; + +export interface ISession { + sendEvent(event: DebugProtocol.Event): void; + shutdown(): void; + sendRequest(command: string, args: any, timeout: number, cb: (response: DebugProtocol.Response) => void): void; + convertClientLineToDebugger(line: number): number; + convertDebuggerLineToClient(line: number): number; + convertClientColumnToDebugger(column: number): number; + convertDebuggerColumnToClient(column: number): number; + dispatchRequest(request: DebugProtocol.Request): Promise; +} + +export abstract class BaseWrappedSession implements ISession { + public dispatchRequest(request: DebugProtocol.Request): Promise { + return this._wrappedSession.dispatchRequest(request); + } + + public sendRequest(command: string, args: any, timeout: number, cb: (response: DebugProtocol.Response) => void): void { + this._wrappedSession.sendRequest(command, args, timeout, cb); + } + + public sendEvent(event: DebugProtocol.Event): void { + this._wrappedSession.sendEvent(event); + } + + public shutdown(): void { + this._wrappedSession.shutdown(); + } + + public convertClientLineToDebugger(line: number): number { + // LineColTransformer uses this protected method from the session + return this._wrappedSession.convertClientLineToDebugger(line); + } + + public convertClientColumnToDebugger(column: number): number { + // LineColTransformer uses this protected method from the session + return this._wrappedSession.convertClientColumnToDebugger(column); + } + + public convertDebuggerLineToClient(line: number): number { + // LineColTransformer uses this protected method from the session + return this._wrappedSession.convertDebuggerLineToClient(line); + } + + public convertDebuggerColumnToClient(column: number): number { + // LineColTransformer uses this protected method from the session + return this._wrappedSession.convertDebuggerColumnToClient(column); + } + + constructor(protected readonly _wrappedSession: ISession) { } +} \ No newline at end of file diff --git a/src/chrome/collections/printting.ts b/src/chrome/collections/printting.ts new file mode 100644 index 000000000..fc98b2c5f --- /dev/null +++ b/src/chrome/collections/printting.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +/** Methods to print the contents of a collection for logging and debugging purposes (This is not intended for the end-user to see) */ +export function printMap(typeDescription: string, map: { entries(): IterableIterator<[K, V]> }): string { + const elementsPrintted = Array.from(map.entries()).map(entry => `${entry[0]}: ${entry[1]}`).join('; '); + return `${typeDescription} { ${elementsPrintted} }`; +} + +export function printSet(typeDescription: string, set: Set): string { + const elementsPrintted = printElements(Array.from(set), '; '); + return `${typeDescription} { ${elementsPrintted} }`; +} + +export function printArray(typeDescription: string, elements: T[]): string { + const elementsPrintted = printElements(elements, ', '); + return typeDescription ? `${typeDescription} [ ${elementsPrintted} ]` : `[ ${elementsPrintted} ]`; +} + +export function printIterable(typeDescription: string, iterable: IterableIterator): string { + const elementsPrintted = printElements(Array.from(iterable), '; '); + return `${typeDescription} { ${elementsPrintted} }`; +} + +function printElements(elements: T[], separator = '; '): string { + return elements.map(element => `${element}`).join(separator); +} \ No newline at end of file diff --git a/src/chrome/collections/utilities.ts b/src/chrome/collections/utilities.ts index 91f262a28..e2d6252db 100644 --- a/src/chrome/collections/utilities.ts +++ b/src/chrome/collections/utilities.ts @@ -26,4 +26,4 @@ export function singleElementOfArray(array: ReadonlyArray): T { } else { throw new Error(`Expected array ${array} to have exactly a single element yet it had ${array.length}`); } -} \ No newline at end of file +} diff --git a/src/chrome/communication/channel.ts b/src/chrome/communication/channel.ts new file mode 100644 index 000000000..505932e65 --- /dev/null +++ b/src/chrome/communication/channel.ts @@ -0,0 +1,29 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { RequestChannelIdentifier } from './requestsCommunicator'; +import { NamespaceReverseLookupCreator, NamespaceTree } from '../utils/namespaceReverseLookupCreator'; +import { NotificationChannelIdentifier } from './notificationsCommunicator'; +import { IChannelIdentifier } from './channelIdentifier'; + +type ChannelIdentifierNamespace = NamespaceTree; + +const registeredChannels: ChannelIdentifierNamespace = {}; +export function registerChannels(channel: ChannelIdentifierNamespace, name: string): void { + registeredChannels[name] = channel; +} + +let channelToNameMapping: Map | null = null; + +function isChannelIdentifier(obj: any): obj is IChannelIdentifier { + return obj instanceof NotificationChannelIdentifier || obj instanceof RequestChannelIdentifier; +} + +export function getChannelName(channel: IChannelIdentifier): string { + if (channelToNameMapping === null) { + channelToNameMapping = new NamespaceReverseLookupCreator(registeredChannels, isChannelIdentifier, '').create(); + } + + return channelToNameMapping.get(channel); +} diff --git a/src/chrome/communication/channelIdentifier.ts b/src/chrome/communication/channelIdentifier.ts new file mode 100644 index 000000000..14dd7cf86 --- /dev/null +++ b/src/chrome/communication/channelIdentifier.ts @@ -0,0 +1,7 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export interface IChannelIdentifier { + +} \ No newline at end of file diff --git a/src/chrome/communication/collaborativeDecision.ts b/src/chrome/communication/collaborativeDecision.ts new file mode 100644 index 000000000..5a356ab5f --- /dev/null +++ b/src/chrome/communication/collaborativeDecision.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ValidatedMultiMap } from '../collections/validatedMultiMap'; +import { groupByKey } from '../collections/utilities'; +import { PromiseOrNot } from '../utils/promises'; + +export enum VoteRelevance { + OverrideOtherVotes, + NormalVote, + FallbackVote, + Abstained, +} + +export interface IVote { + relevance: VoteRelevance; + isRelevant(): boolean; + + execute(remainingRelevantVotes: IVote[]): Promise; +} + +export abstract class VoteCommonLogic implements IVote { + public abstract execute(): Promise; + public abstract get relevance(): VoteRelevance; + + public isRelevant(): boolean { + return this.relevance !== VoteRelevance.Abstained; + } +} + +export class ReturnValue extends VoteCommonLogic { + public readonly relevance = VoteRelevance.NormalVote; + + public async execute(): Promise { + return this._value; + } + + constructor(private readonly _value: T) { + super(); + } +} + +export class Abstained extends VoteCommonLogic { + public readonly relevance = VoteRelevance.Abstained; + + public async execute(): Promise { + throw new Error(`An abstained vote cannot be executed`); + } + + constructor(public readonly voter: unknown /* Used for debugging purposes only */) { + super(); + } +} + +export class VoteOverride extends VoteCommonLogic { + public readonly relevance = VoteRelevance.OverrideOtherVotes; + + constructor (private readonly _callback: () => Promise) { + super(); + } + + public async execute(): Promise { + return this._callback(); + } +} + +export class ExecuteDecisionBasedOnVotes { + private readonly _votesByRelevance: ValidatedMultiMap>; + + public async execute(): Promise { + const overrideVotes = this.getVotesWithCertainRelevance(VoteRelevance.OverrideOtherVotes); + const normalVotes = this.getVotesWithCertainRelevance(VoteRelevance.NormalVote); + const fallbackVotes = this.getVotesWithCertainRelevance(VoteRelevance.FallbackVote); + + // If we have override or normal votes use those, if not use the fallback ones + let allRelevatVotes = overrideVotes.concat(normalVotes); + let allVotes = allRelevatVotes.concat(fallbackVotes); + + if (allVotes.length > 0) { + const winningVote = allVotes[0]; // We'd normally expect to have a single piece in this array + return winningVote.execute(allRelevatVotes); + } else { + return await this._actionIfNoOneVoted(); + } + } + + public getCountOfVotesWithCertainRelevance(relevance: VoteRelevance): number { + return this.getVotesWithCertainRelevance(relevance).length; + } + + private getVotesWithCertainRelevance(relevance: VoteRelevance): IVote[] { + return Array.from(this._votesByRelevance.tryGetting(relevance) || []); + } + + constructor(private readonly _actionIfNoOneVoted: () => PromiseOrNot, votes: IVote[]) { + this._votesByRelevance = groupByKey(votes, vote => vote.relevance); + } +} \ No newline at end of file diff --git a/src/chrome/communication/communicator.ts b/src/chrome/communication/communicator.ts new file mode 100644 index 000000000..41304c43a --- /dev/null +++ b/src/chrome/communication/communicator.ts @@ -0,0 +1,71 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { NotificationsCommunicator, NotificationChannelIdentifier, PublisherFunction, SubscriberFunction, PublisherWithParamsFunction } from './notificationsCommunicator'; +import { RequestsCommunicator, RequestChannelIdentifier, RequestHandlerCallback } from './requestsCommunicator'; +import { IExecutionLogger } from '../logging/executionLogger'; +import { PromiseOrNot } from '../utils/promises'; + +export interface ICommunicator { + getPublisher(notificationChannelIdentifier: NotificationChannelIdentifier): PublisherFunction; + getSubscriber(notificationChannelIdentifier: NotificationChannelIdentifier): SubscriberFunction; + subscribe(notificationChannelIdentifier: NotificationChannelIdentifier, listener: (notification: Notification) => void): void; + registerHandler(requestChannelIdentifier: RequestChannelIdentifier, handler: (request: Request) => PromiseOrNot): void; + getRequester(requestChannelIdentifier: RequestChannelIdentifier): RequestHandlerCallback; +} + +export class Communicator implements ICommunicator { + private readonly _notificationsCommunicator = new NotificationsCommunicator(); + private readonly _requestsCommunicator = new RequestsCommunicator(); + + public getPublisher(notificationChannelIdentifier: NotificationChannelIdentifier): PublisherFunction { + return this._notificationsCommunicator.getPublisher(notificationChannelIdentifier); + } + + public getSubscriber(notificationChannelIdentifier: NotificationChannelIdentifier): SubscriberFunction { + return this._notificationsCommunicator.getSubscriber(notificationChannelIdentifier); + } + + public subscribe(notificationChannelIdentifier: NotificationChannelIdentifier, listener: (notification: Notification) => void): void { + return this._notificationsCommunicator.subscribe(notificationChannelIdentifier, listener); + } + + public registerHandler(requestChannelIdentifier: RequestChannelIdentifier, handler: (request: Request) => PromiseOrNot): void { + this._requestsCommunicator.registerHandler(requestChannelIdentifier, handler); + } + + public getRequester(requestChannelIdentifier: RequestChannelIdentifier): RequestHandlerCallback { + return this._requestsCommunicator.getRequester(requestChannelIdentifier); + } +} + +export class LoggingCommunicator implements ICommunicator { + public getPublisher(notificationChannelIdentifier: NotificationChannelIdentifier): PublisherFunction { + const publisher = this._wrappedCommunicator.getPublisher(notificationChannelIdentifier) as PublisherWithParamsFunction; + return (notification => { + return this._logger.logAsyncFunctionCall(`Communicator\\Publish: ${notificationChannelIdentifier}`, publisher, notification); + }) as PublisherFunction; + } + + public getRequester(requestChannelIdentifier: RequestChannelIdentifier): RequestHandlerCallback { + const requester = this._wrappedCommunicator.getRequester(requestChannelIdentifier); + return ((request) => { + return this._logger.logAsyncFunctionCall(`Communicator\\Request: ${requestChannelIdentifier}`, requester, request); + }) as RequestHandlerCallback; + } + + public getSubscriber(notificationChannelIdentifier: NotificationChannelIdentifier): SubscriberFunction { + return this._wrappedCommunicator.getSubscriber(notificationChannelIdentifier); + } + + public subscribe(notificationChannelIdentifier: NotificationChannelIdentifier, listener: (notification: Notification) => void): void { + this._wrappedCommunicator.subscribe(notificationChannelIdentifier, listener); + } + + public registerHandler(requestChannelIdentifier: RequestChannelIdentifier, handler: (request: Request) => Response): void { + this._wrappedCommunicator.registerHandler(requestChannelIdentifier, handler); + } + + constructor(private readonly _wrappedCommunicator: ICommunicator, private readonly _logger: IExecutionLogger) { } +} diff --git a/src/chrome/communication/internalChannels.ts b/src/chrome/communication/internalChannels.ts new file mode 100644 index 000000000..aa68d573c --- /dev/null +++ b/src/chrome/communication/internalChannels.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { NotificationChannelIdentifier } from './notificationsCommunicator'; +import { BPRecipie } from '../internal/breakpoints/bpRecipie'; +import { ScriptOrSourceOrURLOrURLRegexp } from '../internal/locations/location'; +import { registerChannels } from './channel'; +import { IVote } from './collaborativeDecision'; +import { PausedEvent } from '../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; + +const _breakpoints = { + // Notifications + OnUnbounBPRecipieIsNowBound: new NotificationChannelIdentifier>(), + OnPausedOnBreakpoint: new NotificationChannelIdentifier>(), + OnNoPendingBreakpoints: new NotificationChannelIdentifier(), + OnGoingToPauseClient: new NotificationChannelIdentifier(), +}; + +const Breakpoints: Readonly = _breakpoints; + +const _Internal = { + Breakpoints, +}; + +export const Internal: Readonly = _Internal; + +registerChannels(Internal, 'Internal'); diff --git a/src/chrome/communication/listeners.ts b/src/chrome/communication/listeners.ts index 3310fef02..330eb3bed 100644 --- a/src/chrome/communication/listeners.ts +++ b/src/chrome/communication/listeners.ts @@ -21,4 +21,4 @@ export class Listeners { public hasListeners(): boolean { return this._listeners.length > 0; } -} \ No newline at end of file +} diff --git a/src/chrome/communication/notificationsCommunicator.ts b/src/chrome/communication/notificationsCommunicator.ts new file mode 100644 index 000000000..10bbbb249 --- /dev/null +++ b/src/chrome/communication/notificationsCommunicator.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ValidatedMap } from '../collections/validatedMap'; +import { IChannelIdentifier } from './channelIdentifier'; +import { getChannelName } from './channel'; +import { Listeners } from './listeners'; +import { PromiseOrNot } from '../utils/promises'; + +type ResponsesArray = T extends void + ? void + : T[]; + +export type NotificationListener = (notification: Notification) => PromiseOrNot; +export type PublisherWithParamsFunction = (notification: Notification) => PromiseOrNot>; +export type PublisherFunction = Notification extends void + ? () => PromiseOrNot> + : PublisherWithParamsFunction; +export type SubscriberFunction = (listener: NotificationListener) => void; + +// We need the template parameter to force the Communicator to be "strongly typed" from the client perspective +export class NotificationChannelIdentifier<_Notification, _Response = void> implements IChannelIdentifier { + [Symbol.toStringTag]: 'NotificationChannelIdentifier' = 'NotificationChannelIdentifier'; + + constructor(public readonly identifierSymbol: Symbol = Symbol()) { } + + public toString(): string { + return getChannelName(this); + } +} + +class NotificationChannel { + public readonly listeners = new Listeners>(); + public readonly publisher: Publisher = new Publisher(this); + + public toString(): string { + return `${this.identifier}`; + } + + constructor(public readonly identifier: NotificationChannelIdentifier) { } +} + +export class Publisher { + public async publish(notification: Notification): Promise { + if (this.notificationChannel.listeners.hasListeners()) { + return await Promise.all(this.notificationChannel.listeners.call(notification)); + } else { + throw new Error(`Can't publish ${this.notificationChannel.identifier} because no listeners are registered`); + } + } + + constructor(private readonly notificationChannel: NotificationChannel) { } + + public toString(): string { + return `${this.notificationChannel} publisher`; + } +} + +export class NotificationsCommunicator { + private readonly _identifierToChannel = new ValidatedMap, NotificationChannel>(); + + public getPublisher(notificationChannelIdentifier: NotificationChannelIdentifier): PublisherFunction { + const publisher = this.getChannel(notificationChannelIdentifier).publisher; + return (notification => publisher.publish(notification)) as PublisherFunction; + } + + public getSubscriber(notificationChannelIdentifier: NotificationChannelIdentifier): SubscriberFunction { + const channelListeners = this.getChannel(notificationChannelIdentifier).listeners; + return listener => channelListeners.add(listener); + } + + public subscribe(notificationChannelIdentifier: NotificationChannelIdentifier, listener: (notification: Notification) => Response): void { + this.getChannel(notificationChannelIdentifier).listeners.add(listener); + } + + private getChannel(notificationChannelIdentifier: NotificationChannelIdentifier): NotificationChannel { + return this._identifierToChannel.getOrAdd(notificationChannelIdentifier, () => new NotificationChannel(notificationChannelIdentifier)); + } +} diff --git a/src/chrome/communication/requestsCommunicator.ts b/src/chrome/communication/requestsCommunicator.ts new file mode 100644 index 000000000..9475cd01b --- /dev/null +++ b/src/chrome/communication/requestsCommunicator.ts @@ -0,0 +1,97 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ValidatedMap } from '../collections/validatedMap'; +import { IChannelIdentifier } from './channelIdentifier'; +import { getChannelName } from './channel'; +import { PromiseOrNot } from '../utils/promises'; + +export type RequestHandlerCallback = + Request extends void + ? () => Promise : + NonVoidRequestHandler; + +export type NonVoidRequestHandler = (request: Request) => Promise; + +// We need the template parameter to force the Communicator to be "strongly typed" from the client perspective +export class RequestChannelIdentifier<_Request, _Response> implements IChannelIdentifier { + [Symbol.toStringTag]: 'RequestChannelIdentifier' = 'RequestChannelIdentifier'; + + constructor(public readonly identifierSymbol: Symbol = Symbol()) { } + + public toString(): string { + return getChannelName(this); + } +} + +interface IRequestHandler { + isRegistered(): boolean; + call(request: Request): Promise; +} + +class NoRegisteredRequestHandler implements IRequestHandler { + public isRegistered(): boolean { + return false; + } + + public call(request: Request): Promise { + throw new Error(`Can't execute request <${request}> because no handler has yet registered to handle requests for channel <${this._channel}>`); + } + + constructor(private readonly _channel: RequestChannel) { } +} + +class RegisteredRequestHandler implements IRequestHandler { + public isRegistered(): boolean { + return true; + } + + public call(request: Request): Promise { + return (this._callback as NonVoidRequestHandler)(request); + } + + constructor(private readonly _callback: RequestHandlerCallback) { } +} + +class RequestChannel { + public readonly requester: Requester = new Requester(this); + public handler: IRequestHandler = new NoRegisteredRequestHandler(this); + + public toString(): string { + return `#${this._identifier}`; + } + + constructor(private readonly _identifier: RequestChannelIdentifier) { } +} + +export class Requester { + constructor(private readonly _requestChannel: RequestChannel) { } + + public request(request: Request): Promise { + return this._requestChannel.handler.call(request); + } +} + +export class RequestsCommunicator { + private readonly _identifierToChannel = new ValidatedMap, RequestChannel>(); + + public registerHandler(requestChannelIdentifier: RequestChannelIdentifier, + handler: (request: Request) => PromiseOrNot): void { + const existingHandler = this.getChannel(requestChannelIdentifier).handler; + if (!existingHandler.isRegistered()) { + this.getChannel(requestChannelIdentifier).handler = new RegisteredRequestHandler(handler as RequestHandlerCallback); + } else { + throw new Error(`Can't register a handler for ${requestChannelIdentifier} because a handler has already been registered (${existingHandler})`); + } + } + + public getRequester(requestChannelIdentifier: RequestChannelIdentifier): RequestHandlerCallback { + const requester = this.getChannel(requestChannelIdentifier).requester; + return ((request: Request) => requester.request(request)) as RequestHandlerCallback; + } + + private getChannel(requestChannelIdentifier: RequestChannelIdentifier): RequestChannel { + return this._identifierToChannel.getOrAdd(requestChannelIdentifier, () => new RequestChannel(requestChannelIdentifier)); + } +} diff --git a/src/chrome/communication/targetChannels.ts b/src/chrome/communication/targetChannels.ts new file mode 100644 index 000000000..e6c31bdcb --- /dev/null +++ b/src/chrome/communication/targetChannels.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { NotificationChannelIdentifier } from './notificationsCommunicator'; +import { MappableBreakpoint } from '../internal/breakpoints/breakpoint'; +import { registerChannels } from './channel'; +import { IScriptParsedEvent } from '../cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider'; +import { PausedEvent } from '../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { IScript } from '../internal/scripts/script'; + +const _debugger = { + // Notifications + OnAsyncBreakpointResolved: new NotificationChannelIdentifier>(), + OnScriptParsed: new NotificationChannelIdentifier(), + OnPaused: new NotificationChannelIdentifier(), + OnResumed: new NotificationChannelIdentifier(), +}; + +const Debugger: Readonly = _debugger; + +const _Target = { + Debugger, +}; + +export const Target: Readonly = _Target; + +registerChannels(Target, 'Target'); diff --git a/src/chrome/communication/transformedListenerRegistry.ts b/src/chrome/communication/transformedListenerRegistry.ts new file mode 100644 index 000000000..85962342a --- /dev/null +++ b/src/chrome/communication/transformedListenerRegistry.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Listeners } from './listeners'; +import { PromiseOrNot } from '../utils/promises'; + +export class TransformedListenerRegistry { + private readonly _transformedListeners = new Listeners(); + + public registerListener(transformedListener: (params: T) => void) { + this._transformedListeners.add(transformedListener); + } + + public async install(): Promise { + await this._registerOriginalListener(async originalParameters => { + const transformedParameters = await this._transformation(originalParameters); + return this._transformedListeners.call(transformedParameters); + }); + return this; + } + + constructor( + public readonly _description: string, // This description is only used for debugging purposes + private readonly _registerOriginalListener: (originalListener: (originalParameters: O) => void) => PromiseOrNot, + private readonly _transformation: (originalParameters: O) => PromiseOrNot) { + } +} diff --git a/src/chrome/consoleHelper.ts b/src/chrome/consoleHelper.ts index fa0ecde2f..2624461bf 100644 --- a/src/chrome/consoleHelper.ts +++ b/src/chrome/consoleHelper.ts @@ -2,11 +2,14 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import * as Color from 'color'; import * as variables from './variables'; +import { CodeFlowStackTrace } from './internal/stackTraces/codeFlowStackTrace'; +import { IExceptionDetails } from './cdtpDebuggee/eventsProviders/cdtpExceptionThrownEventsProvider'; +import { functionDescription } from './internal/stackTraces/callFramePresentation'; -export function formatExceptionDetails(e: Crdp.Runtime.ExceptionDetails): string { +export function formatExceptionDetails(e: IExceptionDetails): string { if (!e.exception) { return `${e.text || 'Uncaught Error'}\n${stackTraceToString(e.stackTrace)}`; } @@ -17,7 +20,7 @@ export function formatExceptionDetails(e: Crdp.Runtime.ExceptionDetails): string export const clearConsoleCode = '\u001b[2J'; -export function formatConsoleArguments(type: Crdp.Runtime.ConsoleAPICalledEvent['type'], args: Crdp.Runtime.RemoteObject[], stackTrace?: Crdp.Runtime.StackTrace): { args: Crdp.Runtime.RemoteObject[], isError: boolean } { +export function formatConsoleArguments(type: CDTP.Runtime.ConsoleAPICalledEvent['type'], args: CDTP.Runtime.RemoteObject[], stackTrace?: CodeFlowStackTrace): { args: CDTP.Runtime.RemoteObject[], isError: boolean } { switch (type) { case 'log': case 'debug': @@ -50,7 +53,7 @@ export function formatConsoleArguments(type: Crdp.Runtime.ConsoleAPICalledEvent[ startMsg += ': ' + formattedGroupParams.shift().value; } - args = [{ type: 'string', value: startMsg}, ...formattedGroupParams]; + args = [{ type: 'string', value: startMsg }, ...formattedGroupParams]; break; case 'endGroup': args = [{ type: 'string', value: '‹End group›' }]; @@ -73,7 +76,7 @@ export function formatConsoleArguments(type: Crdp.Runtime.ConsoleAPICalledEvent[ /** * Collapse non-object arguments, and apply format specifiers (%s, %d, etc). Return a reduced a formatted list of RemoteObjects. */ -function resolveParams(args: Crdp.Runtime.RemoteObject[], skipFormatSpecifiers?: boolean): Crdp.Runtime.RemoteObject[] { +function resolveParams(args: CDTP.Runtime.RemoteObject[], skipFormatSpecifiers?: boolean): CDTP.Runtime.RemoteObject[] { if (!args.length || args[0].objectId) { // If the first arg is not text, nothing is going to happen here return args; @@ -92,7 +95,7 @@ function resolveParams(args: Crdp.Runtime.RemoteObject[], skipFormatSpecifiers?: formatSpecifiers = []; } - const processedArgs: Crdp.Runtime.RemoteObject[] = []; + const processedArgs: CDTP.Runtime.RemoteObject[] = []; const pushStringArg = (strArg: string) => { if (typeof strArg === 'string') { processedArgs.push({ type: 'string', value: strArg }); @@ -137,7 +140,7 @@ function resolveParams(args: Crdp.Runtime.RemoteObject[], skipFormatSpecifiers?: return processedArgs; } -function formatArg(formatSpec: string, arg: Crdp.Runtime.RemoteObject): string | Crdp.Runtime.RemoteObject { +function formatArg(formatSpec: string, arg: CDTP.Runtime.RemoteObject): string | CDTP.Runtime.RemoteObject { const paramValue = String(typeof arg.value !== 'undefined' ? arg.value : arg.description); if (formatSpec === 's') { @@ -206,15 +209,15 @@ function formatArg(formatSpec: string, arg: Crdp.Runtime.RemoteObject): string | } } -function stackTraceToString(stackTrace: Crdp.Runtime.StackTrace): string { +function stackTraceToString(stackTrace: CodeFlowStackTrace): string { if (!stackTrace) { return ''; } - return stackTrace.callFrames + return stackTrace.codeFlowFrames .map(frame => { - const fnName = frame.functionName || (frame.url ? '(anonymous)' : '(eval)'); - const fileName = frame.url ? frame.url : 'eval'; + const fnName = functionDescription(frame.functionName, frame.script); + const fileName = frame.script.developmentSource.identifier.textRepresentation; return ` at ${fnName} (${fileName}:${frame.lineNumber + 1}:${frame.columnNumber})`; }) .join('\n'); diff --git a/src/chrome/crdpMultiplexing/crdpMultiplexor.ts b/src/chrome/crdpMultiplexing/crdpMultiplexor.ts deleted file mode 100644 index c417686ad..000000000 --- a/src/chrome/crdpMultiplexing/crdpMultiplexor.ts +++ /dev/null @@ -1,218 +0,0 @@ -/*--------------------------------------------------------- - * Copyright (C) Microsoft Corporation. All rights reserved. - *--------------------------------------------------------*/ - -import { LikeSocket } from 'noice-json-rpc'; -import { logger } from 'vscode-debugadapter'; - -/* Rational: The debug adapter only exposes the debugging capabilities of the Chrome Debugging Protocol. - We want to be able to connect other components to expose other of the capabilities to the users. - We are trying to do this by creating a multiplexor that will take one connection to a Chrome Debugging Protocol websocket, - and simulate from that that we have actually many independent connections to several Chrome Debugging Protocol websockets. - Given that implementing truly independent connections is not trivial, we are choosing to implement only the features that we - seem to need so far to support a component for an external Console, and an external DOM Explorer. - - The way we are going to make that work is that we'll pass the actual websocket to chrome/node to the Multiplexor, and then we are going - to requests two channels from it. One will be the debugger channel, that will be used by the traditional debug adapter for everything it does. - Another channel will be the extraCRDPEndpoint channel that will be offered thorugh the client using a websocket port (so it'll simulate that - this is an actual connection to chrome or node), so we can connect any utilities that we want there. - - In the future, if we need to, it's easy to modify this multiplexor to support more than 2 channels. -*/ - -/* Assumptions made to implement this multiplexor: - 1. The message IDs of CRDP don't need to be sent in order - 2. The Domain.enable messages don't have any side-effects (We might send them multiple times) - 3. The clients are ready to recieve domain messages when they send the Domain.enable message (A better design would be to assume that they are ready after we've sent the response for that message, but this approach seems to be working so far) - 4. The clients never disable any Domain - 5. The clients enable all domains in the first 60 seconds after they've connected - */ - -function extractDomain(method: string): string { - const methodParts = method.split('.'); - if (methodParts.length === 2) { - return methodParts[0]; - } else { - throwCriticalError(`The method ${method} didn't have exactly two parts`); - return 'Unknown'; - } -} - -function encodifyChannel(channelId: number, id: number): number { - return id * 10 + channelId; -} - -function decodifyChannelId(encodifiedId: number): number { - return encodifiedId % 10; -} -function decodifyId(encodifiedId: number): number { - return Math.floor(encodifiedId / 10); -} - -function throwCriticalError(message: string): void { - logger.error('CRDP Multiplexor - CRITICAL-ERROR: ' + message); - throw new Error(message); -} - -export class CRDPMultiplexor { - private _channels: CRDPChannel[] = []; - - private onMessage(data: string): void { - const message = JSON.parse(data); - if (message.id !== undefined) { - this.onResponseMessage(message, data); - } else if (message.method) { - this.onDomainNotification(message, data); - } else { - throwCriticalError(`Message didn't have id nor method: ${data}`); - } - } - - private onResponseMessage(message: {id: number}, data: string): void { - // The message is a response, so it should only go to the channel that requested this - const channel = this._channels[decodifyChannelId(message.id)]; - if (channel) { - message.id = decodifyId(message.id); - data = JSON.stringify(message); - channel.callMessageCallbacks(data); - } else { - throwCriticalError(`Didn't find channel for message with id: ${message.id} and data: <${data}>`); - } - } - - private onDomainNotification(message: {method: string}, data: string): void { - // The message is a notification, so it should go to all channels. The channels itself will filter based on the enabled domains - const domain = extractDomain(message.method); - for (const channel of this._channels) { - channel.callDomainMessageCallbacks(domain, data); - } - } - - constructor(private _wrappedLikeSocket: LikeSocket) { - this._wrappedLikeSocket.on('message', data => this.onMessage(data)); - } - - public addChannel(channelName: string): CRDPChannel { - if (this._channels.length >= 10) { - throw new Error(`Only 10 channels are supported`); - } - - const channel = new CRDPChannel(channelName, this._channels.length, this); - this._channels.push(channel); - return channel; - } - - public send(channel: CRDPChannel, data: string): void { - const message = JSON.parse(data); - if (message.id !== undefined) { - message.id = encodifyChannel(channel.id, message.id); - data = JSON.stringify(message); - } else { - throwCriticalError(`Channel [${channel.name}] sent a message without an id: ${data}`); - } - this._wrappedLikeSocket.send(data); - } - - public addListenerOfNonMultiplexedEvent(event: string, cb: Function): void { - this._wrappedLikeSocket.on(event, cb); - } - - public removeListenerOfNonMultiplexedEvent(event: string, cb: Function): void { - this._wrappedLikeSocket.removeListener(event, cb); - } -} - -export class CRDPChannel implements LikeSocket { - private static timeToPreserveMessagesInMillis = 60 * 1000; - - private _messageCallbacks: Function[] = []; - private _enabledDomains: { [domain: string]: boolean } = {}; - private _pendingMessagesForDomain: { [domain: string]: string[] } = {}; - - public callMessageCallbacks(messageData: string): void { - this._messageCallbacks.forEach(callback => callback(messageData)); - } - - public callDomainMessageCallbacks(domain: string, messageData: string): void { - if (this._enabledDomains[domain]) { - this.callMessageCallbacks(messageData); - } else if (this._pendingMessagesForDomain !== null) { - // We give clients 60 seconds after they connect to the channel to enable domains and receive all messages - this.storeMessageForLater(domain, messageData); - } - } - - private storeMessageForLater(domain: string, messageData: string): void { - let messagesForDomain = this._pendingMessagesForDomain[domain]; - if (messagesForDomain === undefined) { - this._pendingMessagesForDomain[domain] = []; - messagesForDomain = this._pendingMessagesForDomain[domain]; - } - - // Usually this is too much logging, but we might use it while debugging - // logger.log(`CRDP Multiplexor - Storing message to channel ${this.name} for ${domain} for later: ${messageData}`); - messagesForDomain.push(messageData); - } - - constructor(public name: string, public id: number, private _multiplexor: CRDPMultiplexor) { } - - public send(messageData: string): void { - const message = JSON.parse(messageData); - const method = message.method; - const isEnableMethod = method && method.endsWith('.enable'); - let domain; - - if (isEnableMethod) { - domain = extractDomain(method); - this._enabledDomains[domain] = true; - } - - this._multiplexor.send(this, messageData); - - if (isEnableMethod) { - this.sendUnsentPendingMessages(domain); - } - } - - private sendUnsentPendingMessages(domain: string): void { - if (this._pendingMessagesForDomain !== null) { - const pendingMessagesData = this._pendingMessagesForDomain[domain]; - if (pendingMessagesData !== undefined && this._messageCallbacks.length) { - logger.log(`CRDP Multiplexor - Sending pending messages of domain ${domain}(Count = ${pendingMessagesData.length})`); - delete this._pendingMessagesForDomain[domain]; - pendingMessagesData.forEach(pendingMessageData => { - this.callDomainMessageCallbacks(domain, pendingMessageData); - }); - } - } - } - - private discardUnsentPendingMessages(): void { - logger.log(`CRDP Multiplexor - Discarding unsent pending messages for domains: ${Object.keys(this._pendingMessagesForDomain).join(', ')}`); - this._pendingMessagesForDomain = null; - } - - public on(event: string, cb: Function): void; - public on(event: 'open', cb: (ws: LikeSocket) => void): void; - public on(event: 'message', cb: (data: string) => void): void; - public on(event: string, cb: Function): void { - if (event === 'message') { - if (this._messageCallbacks.length === 0) { - setTimeout(() => this.discardUnsentPendingMessages(), CRDPChannel.timeToPreserveMessagesInMillis); - } - - this._messageCallbacks.push(cb); - } else { - this._multiplexor.addListenerOfNonMultiplexedEvent(event, cb); - } - } - - public removeListener(event: string, cb: Function): void { - if (event === 'message') { - const index = this._messageCallbacks.indexOf(cb); - this._messageCallbacks.splice(index, 1); - } else { - this._multiplexor.removeListenerOfNonMultiplexedEvent(event, cb); - } - } -} diff --git a/src/chrome/crdpMultiplexing/webSocketToLikeSocketProxy.ts b/src/chrome/crdpMultiplexing/webSocketToLikeSocketProxy.ts index 72c280ffe..9f8c026a6 100644 --- a/src/chrome/crdpMultiplexing/webSocketToLikeSocketProxy.ts +++ b/src/chrome/crdpMultiplexing/webSocketToLikeSocketProxy.ts @@ -8,7 +8,7 @@ import { LikeSocket } from 'noice-json-rpc'; export class WebSocketToLikeSocketProxy { private _server: WebSocket.Server; - private _currentlyOpenedWebSocket: WebSocket = null; + private _currentlyOpenedWebSocket: WebSocket | null = null; constructor(private _port: number, private _socket: LikeSocket) { } @@ -48,7 +48,7 @@ export class WebSocketToLikeSocketProxy { this._currentlyOpenedWebSocket = null; }); - this._socket.on('message', data => { + this._socket.on('message', (data: string) => { logger.log(`CRDP Proxy - Target to Client: ${data}`); openedWebSocket.send(data); }); diff --git a/src/chrome/debugeeStartup/debugeeLauncher.ts b/src/chrome/debugeeStartup/debugeeLauncher.ts new file mode 100644 index 000000000..3243130a0 --- /dev/null +++ b/src/chrome/debugeeStartup/debugeeLauncher.ts @@ -0,0 +1,20 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ITelemetryPropertyCollector } from '../../telemetry'; +import { ILaunchRequestArgs } from '../../debugAdapterInterfaces'; + +export interface ILaunchResult { + address?: string; + port?: number; + url?: string; +} + +export interface IDebuggeeLauncher { + launch(args: ILaunchRequestArgs, telemetryPropertyCollector: ITelemetryPropertyCollector): Promise; +} + +export interface IDebuggeeRunner { + run(telemetryPropertyCollector: ITelemetryPropertyCollector): Promise; +} \ No newline at end of file diff --git a/src/chrome/dependencyInjection.ts/bind.ts b/src/chrome/dependencyInjection.ts/bind.ts new file mode 100644 index 000000000..40bcbfdd0 --- /dev/null +++ b/src/chrome/dependencyInjection.ts/bind.ts @@ -0,0 +1,102 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Container, interfaces } from 'inversify'; +import { TYPES } from './types'; +import { EventSender } from '../client/eventSender'; +import { CDTPBreakpointFeaturesSupport } from '../cdtpDebuggee/features/cdtpBreakpointFeaturesSupport'; +import { IStackTracePresentationLogicProvider, StackTracesLogic } from '../internal/stackTraces/stackTracesLogic'; +import { SourcesLogic } from '../internal/sources/sourcesLogic'; +import { CDTPScriptsRegistry } from '../cdtpDebuggee/registries/cdtpScriptsRegistry'; +import { ClientToInternal } from '../client/clientToInternal'; +import { InternalToClient } from '../client/internalToClient'; +import { BreakpointsLogic } from '../internal/breakpoints/features/breakpointsLogic'; +import { PauseOnExceptionOrRejection } from '../internal/exceptions/pauseOnException'; +import { Stepping } from '../internal/stepping/stepping'; +import { DotScriptCommand } from '../internal/sources/features/dotScriptsCommand'; +import { BreakpointsRegistry } from '../internal/breakpoints/registries/breakpointsRegistry'; +import { ReAddBPsWhenSourceIsLoaded } from '../internal/breakpoints/features/reAddBPsWhenSourceIsLoaded'; +import { PauseScriptLoadsToSetBPs } from '../internal/breakpoints/features/pauseScriptLoadsToSetBPs'; +import { BPRecipieAtLoadedSourceLogic } from '../internal/breakpoints/features/bpRecipieAtLoadedSourceLogic'; +import { DeleteMeScriptsRegistry } from '../internal/scripts/scriptsRegistry'; +import { SyncStepping } from '../internal/stepping/features/syncStepping'; +import { AsyncStepping } from '../internal/stepping/features/asyncStepping'; +import { CDTPExceptionThrownEventsProvider } from '../cdtpDebuggee/eventsProviders/cdtpExceptionThrownEventsProvider'; +import { CDTPExecutionContextEventsProvider } from '../cdtpDebuggee/eventsProviders/cdtpExecutionContextEventsProvider'; +import { CDTPInspectDebugeeState } from '../cdtpDebuggee/features/cdtpInspectDebugeeState'; +import { CDTPUpdateDebugeeState } from '../cdtpDebuggee/features/cdtpUpdateDebugeeState'; +import { SmartStepLogic } from '../internal/features/smartStep'; +import { LineColTransformer } from '../../transformers/lineNumberTransformer'; +import { ChromeDebugLogic } from '../chromeDebugAdapter'; +import { CDTPOnScriptParsedEventProvider } from '../cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider'; +import { CDTDebuggeeExecutionEventsProvider } from '../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { CDTPDebuggeeBreakpoints } from '../cdtpDebuggee/features/cdtpDebuggeeBreakpoints'; +import { IDOMInstrumentationBreakpoints, CDTPDOMDebugger } from '../cdtpDebuggee/features/cdtpDOMInstrumentationBreakpoints'; +import { CDTPBrowserNavigator } from '../cdtpDebuggee/features/cdtpBrowserNavigator'; +import { CDTPLogEventsProvider } from '../cdtpDebuggee/eventsProviders/cdtpLogEventsProvider'; +import { CDTPConsoleEventsProvider } from '../cdtpDebuggee/eventsProviders/cdtpConsoleEventsProvider'; +import { IAsyncDebuggingConfigurer, CDTPAsyncDebuggingConfigurer } from '../cdtpDebuggee/features/cdtpAsyncDebuggingConfigurer'; +import { IScriptSourcesRetriever, CDTPScriptSourcesRetriever } from '../cdtpDebuggee/features/cdtpScriptSourcesRetriever'; +import { CDTPDebugeeExecutionController } from '../cdtpDebuggee/features/cdtpDebugeeExecutionController'; +import { CDTPPauseOnExceptionsConfigurer } from '../cdtpDebuggee/features/cdtpPauseOnExceptionsConfigurer'; +import { CDTPDebugeeSteppingController } from '../cdtpDebuggee/features/cdtpDebugeeSteppingController'; +import { CDTPDebugeeRuntimeVersionProvider } from '../cdtpDebuggee/features/cdtpDebugeeRuntimeVersionProvider'; +import { CDTPBlackboxPatternsConfigurer } from '../cdtpDebuggee/features/cdtpBlackboxPatternsConfigurer'; +import { CDTPDomainsEnabler } from '../cdtpDebuggee/infrastructure/cdtpDomainsEnabler'; + +export function bindAll(di: Container) { + bind(di, TYPES.IDOMInstrumentationBreakpoints, CDTPDOMDebugger); + bind(di, TYPES.IAsyncDebuggingConfiguration, CDTPAsyncDebuggingConfigurer); + bind(di, TYPES.IScriptSources, CDTPScriptSourcesRetriever); + bind(di, TYPES.IStackTracePresentationLogicProvider, SmartStepLogic); + // bind(di, TYPES.IStackTracePresentationLogicProvider, SkipFilesLogic); + bind(di, TYPES.IEventsToClientReporter, EventSender); + bind(di, TYPES.ChromeDebugLogic, ChromeDebugLogic); + bind(di, TYPES.SourcesLogic, SourcesLogic); + bind(di, TYPES.CDTPScriptsRegistry, CDTPScriptsRegistry); + bind(di, TYPES.ClientToInternal, ClientToInternal); + bind(di, TYPES.InternalToClient, InternalToClient); + bind(di, TYPES.StackTracesLogic, StackTracesLogic); + bind(di, TYPES.BreakpointsLogic, BreakpointsLogic); + bind(di, TYPES.PauseOnExceptionOrRejection, PauseOnExceptionOrRejection); + bind(di, TYPES.Stepping, Stepping); + bind(di, TYPES.DotScriptCommand, DotScriptCommand); + bind(di, TYPES.BreakpointsRegistry, BreakpointsRegistry); + bind(di, TYPES.ReAddBPsWhenSourceIsLoaded, ReAddBPsWhenSourceIsLoaded); + bind(di, TYPES.PauseScriptLoadsToSetBPs, PauseScriptLoadsToSetBPs); + bind(di, TYPES.EventSender, EventSender); + bind(di, TYPES.DeleteMeScriptsRegistry, DeleteMeScriptsRegistry); + // bind(di, TYPES.BaseSourceMapTransformer, BaseSourceMapTransformer); + // bind(di, TYPES.BasePathTransformer, BasePathTransformer); + // bind(di, TYPES.IStackTracePresentationLogicProvider, SkipFilesLogic); + bind(di, TYPES.IDebugeeExecutionControl, CDTPDebugeeExecutionController); + bind(di, TYPES.IPauseOnExceptions, CDTPPauseOnExceptionsConfigurer); + bind(di, TYPES.IBreakpointFeaturesSupport, CDTPBreakpointFeaturesSupport); + bind(di, TYPES.IInspectDebugeeState, CDTPInspectDebugeeState); + bind(di, TYPES.IUpdateDebugeeState, CDTPUpdateDebugeeState); + bind(di, TYPES.BPRecipieInLoadedSourceLogic, BPRecipieAtLoadedSourceLogic); + bind(di, TYPES.SyncStepping, SyncStepping); + bind(di, TYPES.AsyncStepping, AsyncStepping); + // bind(di, cdtpBreakpointIdsRegistry, cdtpBreakpointIdsRegistry); + bind(di, TYPES.ExceptionThrownEventProvider, CDTPExceptionThrownEventsProvider); + bind(di, TYPES.ExecutionContextEventsProvider, CDTPExecutionContextEventsProvider); + bind(di, TYPES.LineColTransformer, LineColTransformer); + bind(di, TYPES.IBrowserNavigation, CDTPBrowserNavigator); + bind(di, TYPES.IScriptParsedProvider, CDTPOnScriptParsedEventProvider); + bind(di, TYPES.ICDTPDebuggerEventsProvider, CDTDebuggeeExecutionEventsProvider); + bind(di, TYPES.IDebugeeVersionProvider, CDTPDebugeeRuntimeVersionProvider); + bind(di, TYPES.ITargetBreakpoints, CDTPDebuggeeBreakpoints); + bind(di, TYPES.IConsoleEventsProvider, CDTPConsoleEventsProvider); + bind(di, TYPES.ILogEventsProvider, CDTPLogEventsProvider); + bind(di, TYPES.IDebugeeSteppingController, CDTPDebugeeSteppingController); + bind(di, TYPES.IBlackboxPatternsConfigurer, CDTPBlackboxPatternsConfigurer); + bind(di, TYPES.IDomainsEnabler, CDTPDomainsEnabler); +} + +function bind(container: Container, serviceIdentifier: interfaces.ServiceIdentifier, newable: interfaces.Newable): void { + container.bind(serviceIdentifier).to(newable).inSingletonScope().onActivation((_context, object) => { + return object; + /// return new MethodsCalledLogger(object, serviceIdentifier.toString()).wrapped(); + }); +} diff --git a/src/chrome/dependencyInjection.ts/di.ts b/src/chrome/dependencyInjection.ts/di.ts new file mode 100644 index 000000000..4c79a1e45 --- /dev/null +++ b/src/chrome/dependencyInjection.ts/di.ts @@ -0,0 +1,42 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Container, interfaces } from 'inversify'; +import { bindAll } from './bind'; + +// Hides the current DI framework from the rest of our implementation +export class DependencyInjection { + private readonly _container = new Container({ autoBindInjectable: true, defaultScope: 'Singleton' }); + + constructor() { + } + + public configureClass(interfaceClass: interfaces.Newable | symbol, value: interfaces.Newable): this { + this._container.bind(interfaceClass).to(value).inSingletonScope(); + return this; + } + + public unconfigure(interfaceClass: interfaces.Newable | symbol): this { + this._container.unbind(interfaceClass); + return this; + } + + public configureValue(valueClass: interfaces.Newable | symbol, value: T): this { + this._container.bind(valueClass).toConstantValue(value); + return this; + } + + public createClassWithDI(classToCreate: interfaces.Newable): T { + return this._container.get(classToCreate); + } + + public createComponent(componentIdentifier: symbol): T { + return this._container.get(componentIdentifier); + } + + public bindAll(): this { + bindAll(this._container); + return this; + } +} diff --git a/src/chrome/dependencyInjection.ts/types.ts b/src/chrome/dependencyInjection.ts/types.ts index dc992f8f1..ba055d4c8 100644 --- a/src/chrome/dependencyInjection.ts/types.ts +++ b/src/chrome/dependencyInjection.ts/types.ts @@ -1,9 +1,66 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + import 'reflect-metadata'; // TODO: Add all necesary types so we can use inversifyjs to create our components - const TYPES = { + ISession: Symbol.for('ISession'), + communicator: Symbol.for('communicator'), + CDTPClient: Symbol.for('chromeConnection.api'), + IDOMInstrumentationBreakpoints: Symbol.for('IDOMInstrumentationBreakpoints'), + IEventsToClientReporter: Symbol.for('IEventsToClientReporter'), + IDebugeeExecutionControl: Symbol.for('IDebugeeExecutionControl'), + IPauseOnExceptions: Symbol.for('IPauseOnExceptions'), + IBreakpointFeaturesSupport: Symbol.for('IBreakpointFeaturesSupport'), + IAsyncDebuggingConfiguration: Symbol.for('IAsyncDebuggingConfiguration'), + IStackTracePresentationLogicProvider: Symbol.for('IStackTracePresentationLogicProvider'), + IScriptSources: Symbol.for('IScriptSources'), EventsConsumedByConnectedCDA: Symbol.for('EventsConsumedByConnectedCDA'), + ICDTPDebuggerEventsProvider: Symbol.for('ICDTPDebuggerEventsProvider'), + IDebugeeSteppingController: Symbol.for('IDebugeeSteppingController'), + IDebuggeeLauncher: Symbol.for('IDebuggeeLauncher'), + ChromeDebugLogic: Symbol.for('ChromeDebugLogic'), + SourcesLogic: Symbol.for('SourcesLogic'), + CDTPScriptsRegistry: Symbol.for('CDTPScriptsRegistry'), + ClientToInternal: Symbol.for('ClientToInternal'), + InternalToClient: Symbol.for('InternalToClient'), + StackTracesLogic: Symbol.for('StackTracesLogic'), + BreakpointsLogic: Symbol.for('BreakpointsLogic'), + PauseOnExceptionOrRejection: Symbol.for('PauseOnExceptionOrRejection'), + Stepping: Symbol.for('Stepping'), + DotScriptCommand: Symbol.for('DotScriptCommand'), + BreakpointsRegistry: Symbol.for('BreakpointsRegistry'), + ReAddBPsWhenSourceIsLoaded: Symbol.for('ReAddBPsWhenSourceIsLoaded'), + PauseScriptLoadsToSetBPs: Symbol.for('PauseScriptLoadsToSetBPs'), + BPRecipieInLoadedSourceLogic: Symbol.for('BPRecipieInLoadedSourceLogic'), + EventSender: Symbol.for('EventSender'), + DeleteMeScriptsRegistry: Symbol.for('DeleteMeScriptsRegistry'), + BaseSourceMapTransformer: Symbol.for('BaseSourceMapTransformer'), + BasePathTransformer: Symbol.for('BasePathTransformer'), + SyncStepping: Symbol.for('SyncStepping'), + AsyncStepping: Symbol.for('AsyncStepping'), + ConnectedCDAConfiguration: Symbol.for('ConnectedCDAConfiguration'), + ExceptionThrownEventProvider: Symbol.for('ExceptionThrownEventProvider'), + ExecutionContextEventsProvider: Symbol.for('ExecutionContextEventsProvider'), + IInspectDebugeeState: Symbol.for('IInspectDebugeeState'), + IUpdateDebugeeState: Symbol.for('IUpdateDebugeeState'), + LineColTransformer: Symbol.for('LineColTransformer'), + ChromeConnection: Symbol.for('ChromeConnection'), + IDebugeeVersionProvider: Symbol.for('IDebugeeVersionProvider'), + IBrowserNavigation: Symbol.for('IBrowserNavigation'), + IPausedOverlay: Symbol.for('IPausedOverlay'), + INetworkCacheConfiguration: Symbol.for('INetworkCacheConfiguration'), + ISupportedDomains: Symbol.for('ISupportedDomains'), + IDebugeeRunner: Symbol.for('IDebugeeRunner'), + ICDTPRuntime: Symbol.for('ICDTPRuntime'), + IScriptParsedProvider: Symbol.for('IScriptParsedProvider'), + ITargetBreakpoints: Symbol.for('ITargetBreakpoints'), + IConsoleEventsProvider: Symbol.for('IConsoleEventsProvider'), + ILogEventsProvider: Symbol.for('ILogEventsProvider'), + IBlackboxPatternsConfigurer: Symbol.for('IBlackboxPatternsConfigurer'), + IDomainsEnabler: Symbol.for('IDomainsEnabler'), }; export { TYPES }; diff --git a/src/chrome/extensibility/extensibilityPoints.ts b/src/chrome/extensibility/extensibilityPoints.ts new file mode 100644 index 000000000..0586f8cea --- /dev/null +++ b/src/chrome/extensibility/extensibilityPoints.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ChromeConnection, ITargetFilter } from '../chromeConnection'; +import { BasePathTransformer } from '../../transformers/basePathTransformer'; +import { BaseSourceMapTransformer } from '../../transformers/baseSourceMapTransformer'; +import { LineColTransformer } from '../../transformers/lineNumberTransformer'; +import { ILaunchRequestArgs, IAttachRequestArgs } from '../../debugAdapterInterfaces'; +import { interfaces } from 'inversify'; +import { IDebuggeeLauncher, IDebuggeeRunner } from '../debugeeStartup/debugeeLauncher'; +import { ConnectedCDAConfiguration } from '../client/chromeDebugAdapter/cdaConfiguration'; + +export interface IExtensibilityPoints { + isPromiseRejectExceptionFilterEnabled: boolean; + debugeeLauncher: interfaces.Newable; + debugeeRunner: interfaces.Newable; + + targetFilter?: ITargetFilter; + logFilePath: string; + + chromeConnection?: typeof ChromeConnection; + pathTransformer?: { new(configuration: ConnectedCDAConfiguration): BasePathTransformer }; + sourceMapTransformer?: { new(configuration: ConnectedCDAConfiguration): BaseSourceMapTransformer }; + lineColTransformer?: { new(configuration: ConnectedCDAConfiguration): LineColTransformer }; + + updateArguments(argumentsFromClient: T): T; +} + +export class OnlyProvideCustomLauncherExtensibilityPoints implements IExtensibilityPoints { + public readonly isPromiseRejectExceptionFilterEnabled = false; + + targetFilter?: ITargetFilter; + chromeConnection?: typeof ChromeConnection; + pathTransformer?: new () => BasePathTransformer; + sourceMapTransformer?: new (configuration: ConnectedCDAConfiguration) => BaseSourceMapTransformer; + lineColTransformer?: new (configuration: ConnectedCDAConfiguration) => LineColTransformer; + + public updateArguments(argumentsFromClient: T): T { + return argumentsFromClient; + } + + constructor( + public readonly debugeeLauncher: interfaces.Newable, + public readonly debugeeRunner: interfaces.Newable, + public readonly logFilePath: string) { + } +} \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/baseMappedBPRecipie.ts b/src/chrome/internal/breakpoints/baseMappedBPRecipie.ts new file mode 100644 index 000000000..eb5fdc198 --- /dev/null +++ b/src/chrome/internal/breakpoints/baseMappedBPRecipie.ts @@ -0,0 +1,76 @@ +import { Location, ScriptOrSourceOrURLOrURLRegexp, LocationInScript, LocationInUrlRegexp, LocationInUrl } from '../locations/location'; +import { IBPActionWhenHit } from './bpActionWhenHit'; +import { BaseBPRecipie, IBPRecipie } from './bpRecipie'; +import { BPRecipieInSource } from './bpRecipieInSource'; +import { ILoadedSource } from '../sources/loadedSource'; +import { IScript } from '../scripts/script'; +import { CDTPSupportedHitActions } from '../../cdtpDebuggee/cdtpPrimitives'; +import { createURLRegexp, URLRegexp } from '../locations/subtypes'; +import { utils } from '../../..'; +import { IURL } from '../sources/resourceIdentifier'; +import { CDTPScriptUrl } from '../sources/resourceIdentifierSubtypes'; + +export interface IMappedBPRecipie + extends IBPRecipie { + unmappedBPRecipie: BPRecipieInSource; +} + +/** + * This is the base class for classes representing BP Recipies that were mapped in some way (The unmapped BP recipie is the exact + * recipie specified by the client using the ISource interface) + */ +abstract class BaseMappedBPRecipie + extends BaseBPRecipie { + + constructor(public readonly unmappedBPRecipie: BPRecipieInSource, public readonly location: Location) { + super(); + } + + public get bpActionWhenHit(): TBPActionWhenHit { + return this.unmappedBPRecipie.bpActionWhenHit; + } + + public isEquivalentTo(right: IBPRecipie): boolean { + return this.location.isEquivalentTo(right.location) + && (right instanceof BaseMappedBPRecipie) + && right.unmappedBPRecipie.isEquivalentTo(this.unmappedBPRecipie); + } + + public toString(): string { + return `BP @ ${this.location} do: ${this.bpActionWhenHit}`; + } +} + +export class BPRecipieInLoadedSource extends BaseMappedBPRecipie { + public mappedToScript(): BPRecipieInScript { + return new BPRecipieInScript(this.unmappedBPRecipie, this.location.mappedToScript()); + } +} + +export class BPRecipieInScript extends BaseMappedBPRecipie { + private static nextBPGuid = 89000000; + + /** + * We use CDTP.getPossibleBreakpoints to find the best position to set a breakpoint. We use withLocationReplaced to get a new + * BPRecipieInScript instance that is located on the place suggested by CDTP.getPossibleBreakpoints + */ + public withLocationReplaced(newLocation: LocationInScript): BPRecipieInScript { + return new BPRecipieInScript(this.unmappedBPRecipie, newLocation); + } + + /** + * We use mappedToUrlRegexp to transform this BP Recipie into a similar recipie specified in an URL Regexp instead. + */ + public mappedToUrlRegexp(): BPRecipieInUrlRegexp { + const urlRegexp = createURLRegexp(utils.pathToRegex(this.location.script.url, `${BPRecipieInScript.nextBPGuid++}`)); + return new BPRecipieInUrlRegexp(this.unmappedBPRecipie, new LocationInUrlRegexp(urlRegexp, this.location.position)); + } + + public mappedToUrl(): BPRecipieInUrl { + const url = this.location.script.runtimeSource.identifier; + return new BPRecipieInUrl(this.unmappedBPRecipie, new LocationInUrl(url, this.location.position)); + } +} + +export class BPRecipieInUrl extends BaseMappedBPRecipie, CDTPSupportedHitActions> { } +export class BPRecipieInUrlRegexp extends BaseMappedBPRecipie { } diff --git a/src/chrome/internal/breakpoints/bpActionWhenHit.ts b/src/chrome/internal/breakpoints/bpActionWhenHit.ts new file mode 100644 index 000000000..908f5e55a --- /dev/null +++ b/src/chrome/internal/breakpoints/bpActionWhenHit.ts @@ -0,0 +1,109 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IEquivalenceComparable } from '../../utils/equivalence'; + +/** + * These classes represents the different actions that a breakpoint can take when hit + * Breakpoint: AlwaysPause + * Conditional Breakpoint: ConditionalPause + * Logpoint: LogMessage + * Hit Count Breakpoint: PauseOnHitCount + */ +export interface IBPActionWhenHit extends IEquivalenceComparable { + isEquivalentTo(bpActionWhenHit: IBPActionWhenHit): boolean; + accept(visitor: IBPActionWhenHitVisitor): T; +} + +export interface IBPActionWhenHitVisitor { + alwaysPause(alwaysPause: AlwaysPause): T; + conditionalPause(conditionalPause: ConditionalPause): T; + pauseOnHitCount(pauseOnHitCount: PauseOnHitCount): T; + logMessage(logMessage: LogMessage): T; +} + +abstract class BaseBPActionWhenHit { + public abstract accept(visitor: IBPActionWhenHitVisitor): T; + + public isEquivalentTo(bpActionWhenHit: IBPActionWhenHit): boolean { + return bpActionWhenHit.accept(new BPActionWhenHitIsEquivalentVisitor(this)); + } +} + +class BPActionWhenHitIsEquivalentVisitor implements IBPActionWhenHitVisitor { + public alwaysPause(alwaysPause: AlwaysPause): boolean { + return this.areSameClass(this._left, alwaysPause); + } + + public conditionalPause(conditionalPause: ConditionalPause): boolean { + return this.areSameClass(this._left, conditionalPause) + && this._left.expressionOfWhenToPause === conditionalPause.expressionOfWhenToPause; + } + public pauseOnHitCount(pauseOnHitCount: PauseOnHitCount): boolean { + return this.areSameClass(this._left, pauseOnHitCount) + && this._left.pauseOnHitCondition === pauseOnHitCount.pauseOnHitCondition; + } + public logMessage(logMessage: LogMessage): boolean { + return this.areSameClass(this._left, logMessage) + && this._left.expressionToLog === logMessage.expressionToLog; + } + + private areSameClass(left: IBPActionWhenHit, right: T): left is T { + return left.constructor === right.constructor; + } + + constructor(private readonly _left: IBPActionWhenHit) { } +} + +export class AlwaysPause extends BaseBPActionWhenHit { + public accept(visitor: IBPActionWhenHitVisitor): T { + return visitor.alwaysPause(this); + } + + public toString(): string { + return 'always pause'; + } +} + +export class ConditionalPause extends BaseBPActionWhenHit { + public accept(visitor: IBPActionWhenHitVisitor): T { + return visitor.conditionalPause(this); + } + + public toString(): string { + return `pause if: ${this.expressionOfWhenToPause}`; + } + + constructor(public readonly expressionOfWhenToPause: string) { + super(); + } +} + +export class PauseOnHitCount extends BaseBPActionWhenHit { + public accept(visitor: IBPActionWhenHitVisitor): T { + return visitor.pauseOnHitCount(this); + } + + public toString(): string { + return `pause when hits: ${this.pauseOnHitCondition}`; + } + + constructor(public readonly pauseOnHitCondition: string) { + super(); + } +} + +export class LogMessage extends BaseBPActionWhenHit { + public accept(visitor: IBPActionWhenHitVisitor): T { + return visitor.logMessage(this); + } + + public toString(): string { + return `log: ${this.expressionToLog}`; + } + + constructor(public readonly expressionToLog: string) { + super(); + } +} diff --git a/src/chrome/internal/breakpoints/bpRecipie.ts b/src/chrome/internal/breakpoints/bpRecipie.ts new file mode 100644 index 000000000..87f0f2f86 --- /dev/null +++ b/src/chrome/internal/breakpoints/bpRecipie.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ISource } from '../sources/source'; +import { Location, ScriptOrSourceOrURLOrURLRegexp } from '../locations/location'; +import { ILoadedSource } from '../sources/loadedSource'; +import { IScript } from '../scripts/script'; +import { IBPActionWhenHit, AlwaysPause, ConditionalPause } from './bpActionWhenHit'; +import { IResourceIdentifier } from '../sources/resourceIdentifier'; +import { URLRegexp } from '../locations/subtypes'; +import { IEquivalenceComparable } from '../../utils/equivalence'; +import { BPRecipieInLoadedSource, BPRecipieInScript, BPRecipieInUrl, BPRecipieInUrlRegexp } from './baseMappedBPRecipie'; +import { BPRecipieInSource } from './bpRecipieInSource'; + +/** + * IBPRecipie represents the instruction/recipie to set a breakpoint with some particular properties. Assuming that IBPRecipie ends up creating an actual + * breakpoint in the debuggee, an instance of Breakpoint will be created to represent that actual breakpoint. + */ +export interface IBPRecipie + extends IEquivalenceComparable { + readonly location: Location; + readonly bpActionWhenHit: TBPActionWhenHit; +} + +export abstract class BaseBPRecipie implements IBPRecipie { + public abstract get bpActionWhenHit(): TBPActionWhenHit; + public abstract get location(): Location; + public abstract isEquivalentTo(right: this): boolean; + + public toString(): string { + return `BP @ ${this.location} do: ${this.bpActionWhenHit}`; + } +} + +export type BPRecipie + = IBPRecipie & ( + TResource extends ISource ? BPRecipieInSource : + TResource extends ILoadedSource ? BPRecipieInLoadedSource : + TBPActionWhenHit extends (AlwaysPause | ConditionalPause) ? (TResource extends IScript ? BPRecipieInScript : + TResource extends IResourceIdentifier ? BPRecipieInUrl : + TResource extends URLRegexp ? BPRecipieInUrlRegexp : + never) + : never + ); diff --git a/src/chrome/internal/breakpoints/bpRecipieInSource.ts b/src/chrome/internal/breakpoints/bpRecipieInSource.ts new file mode 100644 index 000000000..7f00086b4 --- /dev/null +++ b/src/chrome/internal/breakpoints/bpRecipieInSource.ts @@ -0,0 +1,43 @@ +import { ISource } from '../sources/source'; +import { Location } from '../locations/location'; +import { ILoadedSource } from '../sources/loadedSource'; +import { IBPActionWhenHit, AlwaysPause } from './bpActionWhenHit'; +import { BPRecipieInLoadedSource } from './baseMappedBPRecipie'; +import { BaseBPRecipie, IBPRecipie } from './bpRecipie'; + +export class BPRecipieInSource extends BaseBPRecipie { + constructor(public readonly location: Location, public readonly bpActionWhenHit: TBPActionWhenHit) { + super(); + } + + public isEquivalentTo(right: IBPRecipie): boolean { + return this.location.isEquivalentTo(right.location) && + this.bpActionWhenHit.isEquivalentTo(right.bpActionWhenHit); + } + + /** + * Hit breakpoints are implemented by setting an always break breakpoint, and then auto-resuming until the hit condition is true. + * We use this method to create the always break breakpoint for a hit count breakpoint + */ + public withAlwaysBreakAction(): BPRecipieInSource { + return new BPRecipieInSource(this.location, new AlwaysPause()); + } + + public tryResolvingSource(succesfulAction: (breakpointInLoadedSource: BPRecipieInLoadedSource) => R, + failedAction: (breakpointInUnbindedSource: BPRecipieInSource) => R): R { + + return this.location.tryResolvingSource( + locationInLoadedSource => succesfulAction(new BPRecipieInLoadedSource(this, locationInLoadedSource)), + () => failedAction(this)); + } + + public resolvedToLoadedSource(): BPRecipieInLoadedSource { + return this.tryResolvingSource( + breakpointInLoadedSource => breakpointInLoadedSource, + () => { throw new Error(`Failed to convert ${this} into a breakpoint in a loaded source`); }); + } + + public resolvedWithLoadedSource(source: ILoadedSource): BPRecipieInLoadedSource { + return new BPRecipieInLoadedSource(this, this.location.resolvedWith(source)); + } +} diff --git a/src/chrome/internal/breakpoints/bpRecipieStatus.ts b/src/chrome/internal/breakpoints/bpRecipieStatus.ts new file mode 100644 index 000000000..9031d9e52 --- /dev/null +++ b/src/chrome/internal/breakpoints/bpRecipieStatus.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IBPRecipie } from './bpRecipie'; +import { LocationInLoadedSource } from '../locations/location'; +import { IBreakpoint } from './breakpoint'; +import { printArray } from '../../collections/printting'; +import { ISource } from '../sources/source'; + +/** These interface and classes represent the status of a BP Recipie (Is it binded or not?) */ +export interface IBPRecipieStatus { + readonly recipie: IBPRecipie; + readonly statusDescription: string; + + isVerified(): boolean; +} + +export class BPRecipieIsUnbinded implements IBPRecipieStatus { + public isVerified(): boolean { + return false; + } + + public toString(): string { + return `${this.recipie} is unbinded because ${this.statusDescription}`; + } + + constructor( + public readonly recipie: IBPRecipie, + public readonly statusDescription: string) { + } +} + +export class BPRecipieIsBinded implements IBPRecipieStatus { + public get actualLocationInSource(): LocationInLoadedSource { + // TODO: Figure out what is the right way to decide the actual location when we have multiple breakpoints + return this.breakpoints[0].actualLocation; + } + + public isVerified(): boolean { + return true; + } + + public toString(): string { + return `${this.recipie} is binded with all ${printArray('', this.breakpoints)} because ${this.statusDescription}`; + } + + constructor( + public readonly recipie: IBPRecipie, + public readonly breakpoints: IBreakpoint[], + public readonly statusDescription: string) { + if (this.breakpoints.length === 0) { + throw new Error(`A breakpoint recipie that is binded needs to have at least one breakpoint that was binded for the recipie yet ${this} had none`); + } + } +} diff --git a/src/chrome/internal/breakpoints/bpRecipies.ts b/src/chrome/internal/breakpoints/bpRecipies.ts new file mode 100644 index 000000000..a639944e8 --- /dev/null +++ b/src/chrome/internal/breakpoints/bpRecipies.ts @@ -0,0 +1,44 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ILoadedSource } from '../sources/loadedSource'; +import { ISource } from '../sources/source'; +import { BPRecipie } from './bpRecipie'; +import { printArray } from '../../collections/printting'; +import { IResourceIdentifier } from '../sources/resourceIdentifier'; + +/** + * These classes are used to handle all the set of breakpoints for a single file as a unit, and be able to resolve them all together + */ +export class BaseBPRecipies { + constructor(public readonly source: TResource, public readonly breakpoints: BPRecipie[]) { + this.breakpoints.forEach(breakpoint => { + const bpResource: TResource = breakpoint.location.resource; + if (!(bpResource).isEquivalentTo(this.source)) { // TODO: Figure out a way to remove this any + throw new Error(`Expected all the breakpoints to have source ${source} yet the breakpoint ${breakpoint} had ${bpResource} as it's source`); + } + }); + } + + public toString(): string { + return printArray(`BPs @ ${this.source}`, this.breakpoints); + } +} + +export class BPRecipiesInSource extends BaseBPRecipies { + public tryResolving(ifSuccesfulDo: (bpsInLoadedSource: BPRecipiesInLoadedSource) => R, ifFaileDo: () => R): R { + return this.source.tryResolving( + loadedSource => { + const loadedSourceBPs = this.breakpoints.map(breakpoint => breakpoint.resolvedWithLoadedSource(loadedSource)); + return ifSuccesfulDo(new BPRecipiesInLoadedSource(loadedSource, loadedSourceBPs)); + }, + ifFaileDo); + } + + public get requestedSourcePath(): IResourceIdentifier { + return this.source.sourceIdentifier; + } +} + +export class BPRecipiesInLoadedSource extends BaseBPRecipies { } \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/breakpoint.ts b/src/chrome/internal/breakpoints/breakpoint.ts new file mode 100644 index 000000000..03ed753e0 --- /dev/null +++ b/src/chrome/internal/breakpoints/breakpoint.ts @@ -0,0 +1,53 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { LocationInScript, LocationInLoadedSource } from '../locations/location'; +import { IScript } from '../scripts/script'; +import { URLRegexp } from '../locations/subtypes'; +import { IResourceIdentifier } from '../sources/resourceIdentifier'; +import { CDTPScriptUrl } from '../sources/resourceIdentifierSubtypes'; +import { CDTPSupportedResources, CDTPSupportedHitActions } from '../../cdtpDebuggee/cdtpPrimitives'; +import { ISource } from '../sources/source'; +import { IMappedBPRecipie } from './baseMappedBPRecipie'; +import { BPRecipieInSource } from './bpRecipieInSource'; +import { IBPRecipie } from './bpRecipie'; + +export type BPPossibleResources = IScript | ISource | URLRegexp | IResourceIdentifier; +export type ActualLocation = + TResource extends IScript ? LocationInScript : + TResource extends URLRegexp ? LocationInScript : + TResource extends IResourceIdentifier ? LocationInScript : + TResource extends ISource ? LocationInLoadedSource : + LocationInScript; + +/// We use the breakpoint class when the debugger actually configures a file to stop (or do something) at a certain place under certain conditions +export interface IBreakpoint { + readonly recipie: IBPRecipie; + readonly actualLocation: ActualLocation; +} + +abstract class BaseBreakpoint implements IBreakpoint { + public abstract get recipie(): IBPRecipie; + public abstract get actualLocation(): ActualLocation; + + public toString(): string { + return `${this.recipie} actual location is ${this.actualLocation}`; + } +} + +export class MappableBreakpoint extends BaseBreakpoint { + public mappedToSource(): BreakpointInSource { + return new BreakpointInSource(this.recipie.unmappedBPRecipie, this.actualLocation.mappedToSource()); + } + + constructor(public readonly recipie: IMappedBPRecipie, public readonly actualLocation: ActualLocation) { + super(); + } +} + +export class BreakpointInSource extends BaseBreakpoint { + constructor(public readonly recipie: BPRecipieInSource, public readonly actualLocation: ActualLocation) { + super(); + } +} diff --git a/src/chrome/internal/breakpoints/features/bpRecipieAtLoadedSourceLogic.ts b/src/chrome/internal/breakpoints/features/bpRecipieAtLoadedSourceLogic.ts new file mode 100644 index 000000000..75e0eabe8 --- /dev/null +++ b/src/chrome/internal/breakpoints/features/bpRecipieAtLoadedSourceLogic.ts @@ -0,0 +1,129 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { BPRecipie } from '../bpRecipie'; +import { ConditionalPause, AlwaysPause } from '../bpActionWhenHit'; +import { LocationInScript, Position } from '../../locations/location'; +import { ISource } from '../../sources/source'; +import { chromeUtils, logger } from '../../../..'; +import { createColumnNumber, createLineNumber } from '../../locations/subtypes'; +import { RangeInScript } from '../../locations/rangeInScript'; +import { BreakpointsRegistry } from '../registries/breakpointsRegistry'; +import { PausedEvent } from '../../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { VoteRelevance, IVote, Abstained } from '../../../communication/collaborativeDecision'; +import { inject, injectable } from 'inversify'; +import { IDebuggeeBreakpoints } from '../../../cdtpDebuggee/features/cdtpDebuggeeBreakpoints'; +import { IBreakpointFeaturesSupport } from '../../../cdtpDebuggee/features/cdtpBreakpointFeaturesSupport'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { InformationAboutPausedProvider, NotifyStoppedCommonLogic } from '../../features/takeProperActionOnPausedEvent'; +import { IEventsToClientReporter } from '../../../client/eventSender'; +import { ReasonType } from '../../../stoppedEvent'; +import { CDTPBreakpoint } from '../../../cdtpDebuggee/cdtpPrimitives'; +import { CDTPBPRecipiesRegistry } from '../registries/bpRecipieRegistry'; +import { BPRecipieInLoadedSource } from '../BaseMappedBPRecipie'; + +export type Dummy = VoteRelevance; // If we don't do this the .d.ts doesn't include VoteRelevance and the compilation fails. Remove this when the issue disappears... + +export class HitBreakpoint extends NotifyStoppedCommonLogic { + public readonly relevance = VoteRelevance.NormalVote; + protected reason: ReasonType = 'breakpoint'; + + constructor(protected readonly _eventsToClientReporter: IEventsToClientReporter, + protected readonly _publishGoingToPauseClient: () => void) { + super(); + } +} + +export interface IBreakpointsInLoadedSource { + addBreakpointAtLoadedSource(bpRecipie: BPRecipieInLoadedSource): Promise; +} + +export interface IBPRecipieAtLoadedSourceLogicDependencies { + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; + publishGoingToPauseClient(): void; +} + +@injectable() +export class BPRecipieAtLoadedSourceLogic implements IBreakpointsInLoadedSource { + private readonly doesTargetSupportColumnBreakpointsCached: Promise; + + public async askForInformationAboutPaused(paused: PausedEvent): Promise> { + if (paused.hitBreakpoints && paused.hitBreakpoints.length > 0) { + // TODO DIEGO: Improve this to consider breakpoints where we shouldn't pause + return new HitBreakpoint(this._eventsToClientReporter, + // () => this._dependencies.publishGoingToPauseClient() TODO Figure out if we need this for the Chrome Overlay + () => { }); + } else { + return new Abstained(this); + } + } + + public async addBreakpointAtLoadedSource(bpRecipie: BPRecipieInLoadedSource): Promise { + const bpInScriptRecipie = bpRecipie.mappedToScript(); + const bestLocation = await this.considerColumnAndSelectBestBPLocation(bpInScriptRecipie.location); + const bpRecipieInBestLocation = bpInScriptRecipie.withLocationReplaced(bestLocation); + + const runtimeSource = bpInScriptRecipie.location.script.runtimeSource; + this._breakpointRegistry.registerBPRecipie(bpRecipie.unmappedBPRecipie); + + let breakpoints: CDTPBreakpoint[]; + if (!runtimeSource.doesScriptHasUrl()) { + breakpoints = [await this._targetBreakpoints.setBreakpoint(bpRecipieInBestLocation)]; + } else if (runtimeSource.identifier.isLocalFilePath()) { + breakpoints = await this._targetBreakpoints.setBreakpointByUrlRegexp(bpRecipieInBestLocation.mappedToUrlRegexp()); + } else { + /** + * The script has a URL and it's not a local file path, so we could leave it as-is. + * We transform it into a regexp to add a GUID to it, so CDTP will let us add the same breakpoint/recipie two times (using different guids). + * That way we can always add the new breakpoints for a file, before removing the old ones (except if the script doesn't have an URL) + */ + breakpoints = await this._targetBreakpoints.setBreakpointByUrlRegexp(bpRecipieInBestLocation.mappedToUrlRegexp()); + } + + breakpoints.forEach(breakpoint => { + this._breakpointRegistry.registerBreakpointAsBinded(breakpoint); + this._bpRecipiesRegistry.register(bpRecipie.unmappedBPRecipie, breakpoint.recipie); + }); + + return breakpoints; + } + + public async removeBreakpoint(clientBPRecipie: BPRecipie): Promise { + const debuggeeBPRecipie = this._bpRecipiesRegistry.getDebuggeeBPRecipie(clientBPRecipie); + this._targetBreakpoints.removeBreakpoint(debuggeeBPRecipie); + } + + private async considerColumnAndSelectBestBPLocation(location: LocationInScript): Promise { + if (await this.doesTargetSupportColumnBreakpointsCached) { + const thisLineStart = new Position(location.position.lineNumber, createColumnNumber(0)); + const nextLineStart = new Position(createLineNumber(location.position.lineNumber + 1), createColumnNumber(0)); + const thisLineRange = new RangeInScript(location.script, thisLineStart, nextLineStart); + + const possibleLocations = await this._targetBreakpoints.getPossibleBreakpoints(thisLineRange); + + if (possibleLocations.length > 0) { + const bestLocation = chromeUtils.selectBreakpointLocation(location.position.lineNumber, location.position.columnNumber, possibleLocations); + logger.verbose(`PossibleBreakpoints: Best location for ${location} is ${bestLocation}`); + return bestLocation; + } + } + + return location; + } + + public install(): this { + this._dependencies.subscriberForAskForInformationAboutPaused(params => this.askForInformationAboutPaused(params)); + return this; + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IBPRecipieAtLoadedSourceLogicDependencies, + @inject(TYPES.IBreakpointFeaturesSupport) private readonly _breakpointFeaturesSupport: IBreakpointFeaturesSupport, + private readonly _breakpointRegistry: BreakpointsRegistry, + private readonly _bpRecipiesRegistry: CDTPBPRecipiesRegistry, + @inject(TYPES.ITargetBreakpoints) private readonly _targetBreakpoints: IDebuggeeBreakpoints, + @inject(TYPES.IEventsToClientReporter) private readonly _eventsToClientReporter: IEventsToClientReporter) { + this.doesTargetSupportColumnBreakpointsCached = this._breakpointFeaturesSupport.supportsColumnBreakpoints; + } +} \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/features/bpsDeltaCalculator.ts b/src/chrome/internal/breakpoints/features/bpsDeltaCalculator.ts new file mode 100644 index 000000000..8d90968d7 --- /dev/null +++ b/src/chrome/internal/breakpoints/features/bpsDeltaCalculator.ts @@ -0,0 +1,110 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { BPRecipie } from '../bpRecipie'; +import { BPRecipieInSource } from '../bpRecipieInSource'; +import { BPRecipiesInSource } from '../bpRecipies'; +import { ISource } from '../../sources/source'; +import { ILoadedSource } from '../../sources/loadedSource'; +import { IBPActionWhenHit } from '../bpActionWhenHit'; +import { SetUsingProjection } from '../../../collections/setUsingProjection'; +import assert = require('assert'); + +function canonicalizeBPLocation(breakpoint: BPRecipieInSource): string { + return `${breakpoint.location.position.lineNumber}:${breakpoint.location.position.columnNumber}[${breakpoint.bpActionWhenHit}]`; +} + +export class BPRsDeltaCalculator { + private readonly _currentBPRecipies: SetUsingProjection; + + constructor( + public readonly requestedSourceIdentifier: ISource, + private readonly _requestedBPRecipies: BPRecipiesInSource, + currentBPRecipies: BPRecipieInSource[]) { + this._currentBPRecipies = new SetUsingProjection(canonicalizeBPLocation, currentBPRecipies); + } + + public calculate(): BPRsDeltaInRequestedSource { + const match = { + matchesForRequested: [] as BPRecipieInSource[], // Every iteration we'll add either the existing BP match, or the new BP as it's own match here + requestedToAdd: [] as BPRecipieInSource[], // Every time we don't find an existing match BP, we'll add the desired BP here + existingToLeaveAsIs: [] as BPRecipieInSource[], // Every time we do find an existing match BP, we'll add the existing BP here + existingToRemove: [] as BPRecipieInSource[] // Calculated at the end of the algorithm by doing (existingBreakpoints - existingToLeaveAsIs) + }; + + this._requestedBPRecipies.breakpoints.forEach(requestedBP => { + const existingMatch = this._currentBPRecipies.tryGetting(requestedBP); + + let matchingBreakpoint; + if (existingMatch !== undefined) { + assert(requestedBP.isEquivalentTo(existingMatch), `The existing match ${existingMatch} is expected to be equivalent to the requested BP ${requestedBP}`); + match.existingToLeaveAsIs.push(existingMatch); + matchingBreakpoint = existingMatch; + } else { + match.requestedToAdd.push(requestedBP); + matchingBreakpoint = requestedBP; + } + match.matchesForRequested.push(matchingBreakpoint); + }); + + const setOfExistingToLeaveAsIs = new Set(match.existingToLeaveAsIs); + + match.existingToRemove = Array.from(this._currentBPRecipies).filter(bp => !setOfExistingToLeaveAsIs.has(bp)); + + // Do some minor validations of the result just in case + const delta = new BPRsDeltaInRequestedSource(this.requestedSourceIdentifier, match.matchesForRequested, + match.requestedToAdd, match.existingToRemove, match.existingToLeaveAsIs); + this.validateResult(delta); + return delta; + } + + private validateResult(match: BPRsDeltaInRequestedSource): void { + let errorMessage = ''; + if (match.matchesForRequested.length !== this._requestedBPRecipies.breakpoints.length) { + errorMessage += 'Expected the matches for desired breakpoints list to have the same length as the desired breakpoints list\n'; + } + + if (match.requestedToAdd.length + match.existingToLeaveAsIs.length !== this._requestedBPRecipies.breakpoints.length) { + errorMessage += 'Expected the desired breakpoints to add plus the existing breakpoints to leave as-is to have the same quantity as the total desired breakpoints\n'; + } + + if (match.existingToLeaveAsIs.length + match.existingToRemove.length !== this._currentBPRecipies.size) { + errorMessage += 'Expected the existing breakpoints to leave as-is plus the existing breakpoints to remove to have the same quantity as the total existing breakpoints\n'; + } + + if (errorMessage !== '') { + const matchJson = { + matchesForRequested: this.printLocations(match.matchesForRequested), + requestedToAdd: this.printLocations(match.requestedToAdd), + existingToRemove: this.printLocations(match.existingToRemove), + existingToLeaveAsIs: this.printLocations(match.existingToLeaveAsIs) + }; + + const additionalDetails = `\nDesired breakpoints = ${JSON.stringify(this._requestedBPRecipies.breakpoints.map(canonicalizeBPLocation))}` + + `\Existing breakpoints = ${JSON.stringify(Array.from(this._currentBPRecipies).map(canonicalizeBPLocation))}` + + `\nMatch = ${JSON.stringify(matchJson)}`; + throw new Error(errorMessage + `\nmatch: ${additionalDetails}`); + } + } + + private printLocations(bpRecipies: BPRecipieInSource[]): string[] { + return bpRecipies.map(bpRecipie => `${bpRecipie.location.position}`); + } + + public toString(): string { + return `BPs Delta Calculator {\n\tRequested BPs: ${this._requestedBPRecipies}\n\tExisting BPs: ${this._currentBPRecipies}\n}`; + } +} + +export abstract class BPRsDeltaCommonLogic { + constructor(public readonly resource: TResource, + public readonly matchesForRequested: BPRecipie[], + public readonly requestedToAdd: BPRecipie[], + public readonly existingToRemove: BPRecipie[], + public readonly existingToLeaveAsIs: BPRecipie[]) { } +} + +export class BPRsDeltaInRequestedSource extends BPRsDeltaCommonLogic { } + +export class BPRsDeltaInLoadedSource extends BPRsDeltaCommonLogic { } diff --git a/src/chrome/internal/breakpoints/features/breakpointsLogic.ts b/src/chrome/internal/breakpoints/features/breakpointsLogic.ts new file mode 100644 index 000000000..4c4214a37 --- /dev/null +++ b/src/chrome/internal/breakpoints/features/breakpointsLogic.ts @@ -0,0 +1,118 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IBPRecipie } from '../bpRecipie'; +import { ITelemetryPropertyCollector, IComponent, ConnectedCDAConfiguration } from '../../../..'; +import { BPRecipiesInSource, BPRecipiesInLoadedSource } from '../bpRecipies'; +import { ReAddBPsWhenSourceIsLoaded, IEventsConsumedByReAddBPsWhenSourceIsLoaded } from './reAddBPsWhenSourceIsLoaded'; +import { asyncMap } from '../../../collections/async'; +import { IBPRecipieStatus } from '../bpRecipieStatus'; +import { ClientCurrentBPRecipiesRegistry } from '../registries/clientCurrentBPRecipiesRegistry'; +import { BreakpointsRegistry } from '../registries/breakpointsRegistry'; +import { BPRecipieAtLoadedSourceLogic } from './bpRecipieAtLoadedSourceLogic'; +import { RemoveProperty } from '../../../../typeUtils'; +import { IEventsToClientReporter } from '../../../client/eventSender'; +import { PauseScriptLoadsToSetBPs, IPauseScriptLoadsToSetBPsDependencies } from './pauseScriptLoadsToSetBPs'; +import { inject, injectable } from 'inversify'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { IDebuggeeBreakpoints } from '../../../cdtpDebuggee/features/cdtpDebuggeeBreakpoints'; +import { BPRsDeltaInRequestedSource } from './bpsDeltaCalculator'; +import { CDTPBreakpoint } from '../../../cdtpDebuggee/cdtpPrimitives'; +import { ISource } from '../../sources/source'; + +export interface InternalDependencies extends + IEventsConsumedByReAddBPsWhenSourceIsLoaded, + IPauseScriptLoadsToSetBPsDependencies { + + onAsyncBreakpointResolved(listener: (params: CDTPBreakpoint) => void): void; +} + +export type EventsConsumedByBreakpointsLogic = RemoveProperty & { onNoPendingBreakpoints(listener: () => void): void }; + +@injectable() +export class BreakpointsLogic implements IComponent { + private _isBpsWhileLoadingEnable: boolean; + + private readonly _clientBreakpointsRegistry = new ClientCurrentBPRecipiesRegistry(); + + protected onBreakpointResolved(breakpoint: CDTPBreakpoint): void { + this._breakpointRegistry.registerBreakpointAsBinded(breakpoint); + this.onUnbounBPRecipieIsNowBound(breakpoint.recipie.unmappedBPRecipie); + } + + private onUnbounBPRecipieIsNowBound(bpRecipie: IBPRecipie): void { + const bpRecipieStatus = this._breakpointRegistry.getStatusOfBPRecipie(bpRecipie); + this._eventsToClientReporter.sendBPStatusChanged({ reason: 'changed', bpRecipieStatus }); + } + + public async updateBreakpointsForFile(requestedBPs: BPRecipiesInSource, _?: ITelemetryPropertyCollector): Promise { + const bpsDelta = this._clientBreakpointsRegistry.updateBPRecipiesAndCalculateDelta(requestedBPs); + const requestedBPsToAdd = new BPRecipiesInSource(bpsDelta.resource, bpsDelta.requestedToAdd); + bpsDelta.requestedToAdd.forEach(requestedBP => this._breakpointRegistry.registerBPRecipie(requestedBP)); + + await requestedBPsToAdd.tryResolving( + async requestedBPsToAddInLoadedSources => { + // Match desired breakpoints to existing breakpoints + if (requestedBPsToAddInLoadedSources.source.doesScriptHasUrl()) { + await this.addNewBreakpointsForFile(requestedBPsToAddInLoadedSources); + await this.removeDeletedBreakpointsFromFile(bpsDelta); + } else { + // TODO: We need to pause-update-resume the debugger here to avoid a race condition + await this.removeDeletedBreakpointsFromFile(bpsDelta); + await this.addNewBreakpointsForFile(requestedBPsToAddInLoadedSources); + } + }, + () => { + const existingUnbindedBPs = bpsDelta.existingToLeaveAsIs.filter(bp => !this._breakpointRegistry.getStatusOfBPRecipie(bp).isVerified()); + const requestedBPsPendingToAdd = new BPRecipiesInSource(bpsDelta.resource, bpsDelta.requestedToAdd.concat(existingUnbindedBPs)); + if (this._isBpsWhileLoadingEnable) { + this._bpsWhileLoadingLogic.enableIfNeccesary(); + } + this._unbindedBreakpointsLogic.replaceBPsForSourceWith(requestedBPsPendingToAdd); + }); + + return bpsDelta.matchesForRequested.map(bpRecipie => this._breakpointRegistry.getStatusOfBPRecipie(bpRecipie)); + } + + private async removeDeletedBreakpointsFromFile(bpsDelta: BPRsDeltaInRequestedSource) { + await asyncMap(bpsDelta.existingToRemove, async (existingBPToRemove) => { + await this._bprInLoadedSourceLogic.removeBreakpoint(existingBPToRemove); + }); + } + + private async addNewBreakpointsForFile(requestedBPsToAddInLoadedSources: BPRecipiesInLoadedSource) { + await asyncMap(requestedBPsToAddInLoadedSources.breakpoints, async (requestedBP) => { + // DIEGO TODO: Do we need to do one breakpoint at a time to avoid issues on CDTP, or can we do them in parallel now that we use a different algorithm? + await this._bprInLoadedSourceLogic.addBreakpointAtLoadedSource(requestedBP); + }); + } + + public install(): this { + this._unbindedBreakpointsLogic.install(); + this._bpsWhileLoadingLogic.install(); + this._dependencies.onNoPendingBreakpoints(() => this._bpsWhileLoadingLogic.disableIfNeccesary()); + this._debuggeeBreakpoints.onBreakpointResolvedSyncOrAsync(breakpoint => this.onBreakpointResolved(breakpoint)); + this._bprInLoadedSourceLogic.install(); + return this.configure(); + } + + public configure(): this { + this._isBpsWhileLoadingEnable = this._configuration.args.breakOnLoadStrategy !== 'off'; + return this; + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: EventsConsumedByBreakpointsLogic, + @inject(TYPES.BreakpointsRegistry) private readonly _breakpointRegistry: BreakpointsRegistry, + @inject(TYPES.ReAddBPsWhenSourceIsLoaded) private readonly _unbindedBreakpointsLogic: ReAddBPsWhenSourceIsLoaded, + @inject(TYPES.PauseScriptLoadsToSetBPs) private readonly _bpsWhileLoadingLogic: PauseScriptLoadsToSetBPs, + @inject(TYPES.BPRecipieInLoadedSourceLogic) private readonly _bprInLoadedSourceLogic: BPRecipieAtLoadedSourceLogic, + @inject(TYPES.EventSender) private readonly _eventsToClientReporter: IEventsToClientReporter, + @inject(TYPES.ITargetBreakpoints) private readonly _debuggeeBreakpoints: IDebuggeeBreakpoints, + @inject(TYPES.ConnectedCDAConfiguration) private readonly _configuration: ConnectedCDAConfiguration) { + } +} \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/features/hitCountBreakpoints.ts b/src/chrome/internal/breakpoints/features/hitCountBreakpoints.ts new file mode 100644 index 000000000..493ef8b63 --- /dev/null +++ b/src/chrome/internal/breakpoints/features/hitCountBreakpoints.ts @@ -0,0 +1,86 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IComponent } from '../../features/feature'; +import { IBPRecipie } from '../bpRecipie'; +import { BPRecipieInSource } from '../bpRecipieInSource'; +import { PauseOnHitCount } from '../bpActionWhenHit'; +import { ValidatedMap } from '../../../collections/validatedMap'; +import { HitCountConditionParser, HitCountConditionFunction } from './hitCountConditionParser'; +import { NotifyStoppedCommonLogic, InformationAboutPausedProvider } from '../../features/takeProperActionOnPausedEvent'; +import { ReasonType } from '../../../stoppedEvent'; +import { IVote, Abstained, VoteRelevance } from '../../../communication/collaborativeDecision'; +import { injectable, inject } from 'inversify'; +import { IEventsToClientReporter } from '../../../client/eventSender'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { PausedEvent } from '../../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { ScriptOrSourceOrURLOrURLRegexp } from '../../locations/location'; + +export interface IHitCountBreakpointsDependencies { + registerAddBPRecipieHandler(handlerRequirements: (bpRecipie: BPRecipieInSource) => boolean, + handler: (bpRecipie: BPRecipieInSource) => Promise): void; + + addBPRecipie(bpRecipie: BPRecipieInSource): Promise; + notifyBPWasHit(bpRecipie: BPRecipieInSource): Promise; + + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; + publishGoingToPauseClient(): void; +} + +class HitCountBPData { + private _hitCount = 0; + + public notifyBPHit(): VoteRelevance { + return this._shouldPauseCondition(this._hitCount++) + ? VoteRelevance.NormalVote + : VoteRelevance.Abstained; + } + + constructor( + public readonly hitBPRecipie: BPRecipieInSource, + private readonly _shouldPauseCondition: HitCountConditionFunction) { } +} + +export class HitAndSatisfiedCountBPCondition extends NotifyStoppedCommonLogic { + public readonly relevance = VoteRelevance.NormalVote; + protected reason: ReasonType = 'breakpoint'; + + constructor(protected readonly _eventsToClientReporter: IEventsToClientReporter, + protected readonly _publishGoingToPauseClient: () => void) { + super(); + } +} + +// TODO DIEGO: Install and use this feature +@injectable() +export class HitCountBreakpoints implements IComponent { + private readonly underlyingToBPRecipie = new ValidatedMap, HitCountBPData>(); + + public install(): void { + this._dependencies.registerAddBPRecipieHandler( + bpRecipie => bpRecipie.bpActionWhenHit instanceof PauseOnHitCount, + bpRecipie => this.addBPRecipie(bpRecipie as BPRecipieInSource)); + this._dependencies.subscriberForAskForInformationAboutPaused(paused => this.askForInformationAboutPaused(paused)); + } + + private async addBPRecipie(bpRecipie: BPRecipieInSource): Promise { + const underlyingBPRecipie = bpRecipie.withAlwaysBreakAction(); + const shouldPauseCondition = new HitCountConditionParser(bpRecipie.bpActionWhenHit.pauseOnHitCondition).parse(); + this._dependencies.addBPRecipie(underlyingBPRecipie); + this.underlyingToBPRecipie.set(underlyingBPRecipie, new HitCountBPData(bpRecipie, shouldPauseCondition)); + } + + public async askForInformationAboutPaused(paused: PausedEvent): Promise> { + const hitCountBPData = paused.hitBreakpoints.map(hitBPRecipie => + this.underlyingToBPRecipie.tryGetting(hitBPRecipie.unmappedBPRecipie)).filter(bpRecipie => bpRecipie !== undefined); + + const individualDecisions = hitCountBPData.map(data => data.notifyBPHit()); + return individualDecisions.indexOf(VoteRelevance.NormalVote) >= 0 + ? new HitAndSatisfiedCountBPCondition(this._eventsToClientReporter, this._dependencies.publishGoingToPauseClient) + : new Abstained(this); + } + + constructor(private readonly _dependencies: IHitCountBreakpointsDependencies, + @inject(TYPES.IEventsToClientReporter) private readonly _eventsToClientReporter: IEventsToClientReporter) { } +} \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/features/hitCountConditionParser.ts b/src/chrome/internal/breakpoints/features/hitCountConditionParser.ts new file mode 100644 index 000000000..efff4cb74 --- /dev/null +++ b/src/chrome/internal/breakpoints/features/hitCountConditionParser.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export type HitCountConditionFunction = (numHits: number) => boolean; + +export class HitCountConditionParser { + private readonly HIT_COUNT_CONDITION_PATTERN = /^(>|>=|=|<|<=|%)?\s*([0-9]+)$/; + private patternMatches: RegExpExecArray | undefined; + + public parse(): HitCountConditionFunction { + this.patternMatches = this.HIT_COUNT_CONDITION_PATTERN.exec(this._hitCountCondition.trim()); + if (this.patternMatches && this.patternMatches.length >= 3) { + // eval safe because of the regex, and this is only a string that the current user will type in + /* tslint:disable:no-function-constructor-with-string-args */ + const shouldPause: HitCountConditionFunction = new Function('numHits', this.javaScriptCodeToEvaluateCondition()); + /* tslint:enable:no-function-constructor-with-string-args */ + return shouldPause; + } else { + throw new Error(`Didn't recognize <${this._hitCountCondition}> as a valid hit count condition`); + } + } + + constructor(private readonly _hitCountCondition: string) { } + + private javaScriptCodeToEvaluateCondition() { + const operator = this.parseOperator(); + const value = this.parseValue(); + const javaScriptCode = operator === '%' + ? `return (numHits % ${value}) === 0;` + : `return numHits ${operator} ${value};`; + return javaScriptCode; + } + + private parseValue(): string { + return this.patternMatches[2]; + } + + private parseOperator(): string { + let op = this.patternMatches[1] || '>='; + if (op === '=') + op = '=='; + return op; + } +} \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/features/pauseScriptLoadsToSetBPs.ts b/src/chrome/internal/breakpoints/features/pauseScriptLoadsToSetBPs.ts new file mode 100644 index 000000000..cd77547ea --- /dev/null +++ b/src/chrome/internal/breakpoints/features/pauseScriptLoadsToSetBPs.ts @@ -0,0 +1,133 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { asyncMap } from '../../../collections/async'; +import { ILoadedSource } from '../../sources/loadedSource'; +import { IComponent } from '../../features/feature'; +import { LocationInScript } from '../../locations/location'; +import { IBreakpoint } from '../breakpoint'; +import { NotifyStoppedCommonLogic, ResumeCommonLogic, InformationAboutPausedProvider } from '../../features/takeProperActionOnPausedEvent'; +import { ReasonType } from '../../../stoppedEvent'; +import { VoteRelevance, IVote, Abstained } from '../../../communication/collaborativeDecision'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { IEventsToClientReporter } from '../../../client/eventSender'; +import { IDebugeeExecutionController } from '../../../cdtpDebuggee/features/cdtpDebugeeExecutionController'; +import { ReAddBPsWhenSourceIsLoaded } from './reAddBPsWhenSourceIsLoaded'; +import { BreakpointsRegistry } from '../registries/breakpointsRegistry'; +import { IDOMInstrumentationBreakpoints } from '../../../cdtpDebuggee/features/cdtpDOMInstrumentationBreakpoints'; +import { IDebugeeRuntimeVersionProvider } from '../../../cdtpDebuggee/features/cdtpDebugeeRuntimeVersionProvider'; +import { PausedEvent } from '../../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { IScript } from '../../scripts/script'; +export type Dummy = VoteRelevance; // If we don't do this the .d.ts doesn't include VoteRelevance and the compilation fails. Remove this when the issue disappears... + +export interface IPauseScriptLoadsToSetBPsDependencies { + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; + waitUntilUnbindedBPsAreSet(loadedSource: ILoadedSource): Promise; + + tryGettingBreakpointAtLocation(locationInScript: LocationInScript): IBreakpoint[]; + publishGoingToPauseClient(): void; +} + +export class HitStillPendingBreakpoint extends NotifyStoppedCommonLogic { + public readonly relevance = VoteRelevance.NormalVote; + protected reason: ReasonType = 'breakpoint'; + + constructor(protected readonly _eventsToClientReporter: IEventsToClientReporter, + protected readonly _publishGoingToPauseClient: () => void) { + super(); + } +} + +export class PausedWhileLoadingScriptToResolveBreakpoints extends ResumeCommonLogic { + public readonly relevance = VoteRelevance.FallbackVote; + + constructor(protected readonly _debugeeExecutionControl: IDebugeeExecutionController) { + super(); + } +} + +/// TODO: Move this to a browser-shared package +@injectable() +export class PauseScriptLoadsToSetBPs implements IComponent { + private readonly stopsWhileScriptsLoadInstrumentationName = 'scriptFirstStatement'; + private _isInstrumentationEnabled = false; + private _scriptFirstStatementStopsBeforeFile: boolean; + + public async enableIfNeccesary(): Promise { + if (this._isInstrumentationEnabled === false) { + await this.startPausingOnScriptFirstStatement(); + } + } + + public async disableIfNeccesary(): Promise { + if (this._isInstrumentationEnabled === true) { + await this.stopPausingOnScriptFirstStatement(); + } + } + + private async askForInformationAboutPaused(paused: PausedEvent): Promise> { + if (this.isInstrumentationPause(paused)) { + await asyncMap(paused.callFrames[0].location.script.allSources, async source => { + await this._reAddBPsWhenSourceIsLoaded.waitUntilBPsAreSet(source); + }); + + // If we pause before starting the script, we can just resume, and we'll a breakpoint if it's on 0,0 + if (!this._scriptFirstStatementStopsBeforeFile) { + // On Chrome 69 we pause inside the script, so we need to check if there is a breakpoint at 0,0 that we need to use + const breakpoints = this._breakpointsRegistry.tryGettingBreakpointAtLocation(paused.callFrames[0].location); + if (breakpoints.length > 0) { + return new HitStillPendingBreakpoint(this._eventsToClientReporter, this._dependencies.publishGoingToPauseClient); + } + } + + return new PausedWhileLoadingScriptToResolveBreakpoints(this._debugeeExecutionControl); + } else { + return new Abstained(this); + } + } + + private async startPausingOnScriptFirstStatement(): Promise { + try { + this._isInstrumentationEnabled = true; + await this._domInstrumentationBreakpoints.setInstrumentationBreakpoint({ eventName: this.stopsWhileScriptsLoadInstrumentationName }); + } catch (exception) { + this._isInstrumentationEnabled = false; + throw exception; + } + } + + private async stopPausingOnScriptFirstStatement(): Promise { + await this._domInstrumentationBreakpoints.removeInstrumentationBreakpoint({ eventName: this.stopsWhileScriptsLoadInstrumentationName }); + this._isInstrumentationEnabled = false; + } + + private isInstrumentationPause(notification: PausedEvent): boolean { + return (notification.reason === 'EventListener' && notification.data.eventName.startsWith('instrumentation:')) || + (notification.reason === 'ambiguous' && Array.isArray(notification.data.reasons) && + notification.data.reasons.every((r: any) => r.reason === 'EventListener' && r.auxData.eventName.startsWith('instrumentation:'))); + } + + public async install(): Promise { + this._dependencies.subscriberForAskForInformationAboutPaused(params => this.askForInformationAboutPaused(params)); + // TODO DIEGO: Figure out exactly when we want to block on the browser version + // On version 69 Chrome stopped sending an extra event for DOM Instrumentation: See https://bugs.chromium.org/p/chromium/issues/detail?id=882909 + // On Chrome 68 we were relying on that event to make Break on load work on breakpoints on the first line of a file. On Chrome 69 we need an alternative way to make it work. + // TODO: Reenable the code that uses Versions.Target.Version when this fails + const runtimeVersion = await this._debugeeVersionProvider.version(); + this._scriptFirstStatementStopsBeforeFile = !runtimeVersion.isAtLeastVersion('69.0.0'); + return this; + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IPauseScriptLoadsToSetBPsDependencies, + @inject(TYPES.IDOMInstrumentationBreakpoints) private readonly _domInstrumentationBreakpoints: IDOMInstrumentationBreakpoints, + @inject(TYPES.IDebugeeExecutionControl) private readonly _debugeeExecutionControl: IDebugeeExecutionController, + @inject(TYPES.IEventsToClientReporter) protected readonly _eventsToClientReporter: IEventsToClientReporter, + @inject(TYPES.IDebugeeVersionProvider) protected readonly _debugeeVersionProvider: IDebugeeRuntimeVersionProvider, + @inject(TYPES.ReAddBPsWhenSourceIsLoaded) protected readonly _reAddBPsWhenSourceIsLoaded: ReAddBPsWhenSourceIsLoaded, + @inject(TYPES.BreakpointsRegistry) protected readonly _breakpointsRegistry: BreakpointsRegistry, + ) { + } +} \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/features/reAddBPsWhenSourceIsLoaded.ts b/src/chrome/internal/breakpoints/features/reAddBPsWhenSourceIsLoaded.ts new file mode 100644 index 000000000..7e18e8e5d --- /dev/null +++ b/src/chrome/internal/breakpoints/features/reAddBPsWhenSourceIsLoaded.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { BPRecipiesInSource } from '../bpRecipies'; +import { ILoadedSource } from '../../sources/loadedSource'; +import { asyncMap } from '../../../collections/async'; +import { BPRecipieIsUnbinded, BPRecipieIsBinded } from '../bpRecipieStatus'; +import { newResourceIdentifierMap, IResourceIdentifier } from '../../sources/resourceIdentifier'; +import { IEventsToClientReporter } from '../../../client/eventSender'; +import { IPromiseDefer, promiseDefer } from '../../../../utils'; +import { IComponent } from '../../features/feature'; +import { injectable, inject } from 'inversify'; +import { IBreakpointsInLoadedSource } from './bpRecipieAtLoadedSourceLogic'; +import { TYPES } from '../../../dependencyInjection.ts/types'; + +export interface IEventsConsumedByReAddBPsWhenSourceIsLoaded { + onLoadedSourceIsAvailable(listener: (source: ILoadedSource) => Promise): void; + notifyNoPendingBPs(): void; +} + +@injectable() +export class ReAddBPsWhenSourceIsLoaded implements IComponent { + private readonly _sourcePathToBPRecipies = newResourceIdentifierMap(); + private readonly _sourcePathToBPsAreSetDefer = newResourceIdentifierMap>(); + + public install(): void { + this._dependencies.onLoadedSourceIsAvailable(source => this.onLoadedSourceIsAvailable(source)); + } + + public replaceBPsForSourceWith(requestedBPs: BPRecipiesInSource): void { + this._sourcePathToBPRecipies.set(requestedBPs.requestedSourcePath, requestedBPs); + } + + public waitUntilBPsAreSet(loadedSource: ILoadedSource): Promise { + const bpRecipies = this._sourcePathToBPRecipies.tryGetting(loadedSource.identifier); + if (bpRecipies !== undefined) { + return this.getBPsAreSetDefer(loadedSource.identifier).promise; + } else { + const defer = this._sourcePathToBPsAreSetDefer.tryGetting(loadedSource.identifier); + return Promise.resolve(defer && defer.promise); + } + } + + private getBPsAreSetDefer(identifier: IResourceIdentifier): IPromiseDefer { + return this._sourcePathToBPsAreSetDefer.getOrAdd(identifier, () => promiseDefer()); + } + + private async onLoadedSourceIsAvailable(source: ILoadedSource): Promise { + const unbindBPRecipies = this._sourcePathToBPRecipies.tryGetting(source.identifier); + + if (unbindBPRecipies !== undefined) { + // We remove it first in sync just to avoid race conditions (If we get multiple refreshes fast, we could get events for the same source path severla times) + const defer = this.getBPsAreSetDefer(source.identifier); + this._sourcePathToBPRecipies.delete(source.identifier); + const remainingBPRecipies = new Set(unbindBPRecipies.breakpoints); + await asyncMap(unbindBPRecipies.breakpoints, async bpRecipie => { + try { + const bpRecepieResolved = bpRecipie.resolvedWithLoadedSource(source); + const bpStatus = await this._breakpointsInLoadedSource.addBreakpointAtLoadedSource(bpRecepieResolved); + const mappedBreakpoints = bpStatus.map(breakpoint => breakpoint.mappedToSource()); + this._eventsToClientReporter.sendBPStatusChanged({ + bpRecipieStatus: new BPRecipieIsBinded(bpRecipie, mappedBreakpoints, 'TODO DIEGO'), + reason: 'changed' + }); + remainingBPRecipies.delete(bpRecipie); + } catch (exception) { + this._eventsToClientReporter.sendBPStatusChanged({ + bpRecipieStatus: new BPRecipieIsUnbinded(bpRecipie, `An unexpected error happen while trying to set the breakpoint: ${exception})`), + reason: 'changed' + }); + } + }); + + // Notify others that we are finished setting the BPs + defer.resolve(); + this._sourcePathToBPsAreSetDefer.delete(source.identifier); + + if (remainingBPRecipies.size > 0) { + // TODO DIEGO: Add telemetry given that we don't expect this to happen + // If we still have BPs recipies that we couldn't add, we put them back in + this._sourcePathToBPRecipies.set(source.identifier, new BPRecipiesInSource(unbindBPRecipies.source, Array.from(remainingBPRecipies))); + } + + if (this._sourcePathToBPRecipies.size === 0) { + this._dependencies.notifyNoPendingBPs(); + } + } + } + + public toString(): string { + return `{ BPs to re-add when source is laoded: ${this._sourcePathToBPRecipies}}`; + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IEventsConsumedByReAddBPsWhenSourceIsLoaded, + @inject(TYPES.EventSender) private readonly _eventsToClientReporter: IEventsToClientReporter, + @inject(TYPES.BPRecipieInLoadedSourceLogic) private readonly _breakpointsInLoadedSource: IBreakpointsInLoadedSource) { } +} \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/features/targetDuplicatedBPsLogic.ts b/src/chrome/internal/breakpoints/features/targetDuplicatedBPsLogic.ts new file mode 100644 index 000000000..483f5eb0c --- /dev/null +++ b/src/chrome/internal/breakpoints/features/targetDuplicatedBPsLogic.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +// import { BPRecipieInScript, BPRecipieInUrl, BPRecipieInUrlRegexp, IBPRecipie, BPRecipie } from './bpRecipie'; +// import { AlwaysBreak, ConditionalBreak } from './bpBehavior'; +// import { BreakpointInScript, BreakpointInUrl, BreakpointInUrlRegexp, IBreakpoint } from './breakpoint'; +// import { SetUsingProjection } from '../../collections/setUsingProjection'; +// import { Script } from '../scripts/script'; +// import { ScriptOrSourceOrIdentifierOrUrlRegexp } from '../locations/locationInResource'; + +// export interface ITargetDuplicatedBPsLogicDependencies { +// setBreakpoint(bpRecipie: BPRecipieInScript): Promise; +// setBreakpointByUrl(bpRecipie: BPRecipieInUrl): Promise; +// setBreakpointByUrlRegexp(bpRecipie: BPRecipieInUrlRegexp): Promise; +// } + +// class DuplicatedBPsLogic> { +// private readonly _canonicalizedBPRecipies = new SetUsingProjection>(); + +// public setBreakpoint(bpRecipie: TBreakpoint): Promise> { +// const existingRecipie = this._canonicalizedBPRecipies.tryGetting(bpRecipie); +// return new BreakpointInScript(); +// } +// } + +// export class TargetDuplicatedBPsLogic { +// public async setBreakpoint(bpRecipie: BPRecipieInScript): Promise { +// return this._dependencies.setBreakpoint(bpRecipie); +// } + +// public async setBreakpointByUrl(bpRecipie: BPRecipieInUrl): Promise { +// return this._dependencies.setBreakpointByUrl(bpRecipie); +// } + +// public async setBreakpointByUrlRegexp(bpRecipie: BPRecipieInUrlRegexp): Promise { +// return this._dependencies.setBreakpointByUrlRegexp(bpRecipie); +// } + +// constructor(private readonly _dependencies: ITargetDuplicatedBPsLogicDependencies) { } +// } \ No newline at end of file diff --git a/src/chrome/internal/breakpoints/registries/bpRecipieRegistry.ts b/src/chrome/internal/breakpoints/registries/bpRecipieRegistry.ts new file mode 100644 index 000000000..3afe4eac7 --- /dev/null +++ b/src/chrome/internal/breakpoints/registries/bpRecipieRegistry.ts @@ -0,0 +1,33 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { injectable } from 'inversify'; +import { ISource } from '../../sources/source'; +import { IBPRecipie } from '../bpRecipie'; +import { CDTPBPRecipie } from '../../../cdtpDebuggee/cdtpPrimitives'; +import { BidirectionalMap } from '../../../collections/bidirectionalMap'; + +type ClientBPRecipie = IBPRecipie; +type DebuggeeBPRecipie = CDTPBPRecipie; + +@injectable() +export class CDTPBPRecipiesRegistry { + private readonly _clientRecipieToDebuggeeRecipie = new BidirectionalMap(); + + public register(clientBPRecipie: ClientBPRecipie, debuggeeBPRecipie: DebuggeeBPRecipie): void { + this._clientRecipieToDebuggeeRecipie.set(clientBPRecipie, debuggeeBPRecipie); + } + + public unregister(clientBPRecipie: ClientBPRecipie): void { + this._clientRecipieToDebuggeeRecipie.deleteByLeft(clientBPRecipie); + } + + public getDebuggeeBPRecipie(clientBPRecipie: ClientBPRecipie): DebuggeeBPRecipie { + return this._clientRecipieToDebuggeeRecipie.getByLeft(clientBPRecipie); + } + + public toString(): string { + return `Client to Debuggee BP Recipies: ${this._clientRecipieToDebuggeeRecipie}`; + } +} diff --git a/src/chrome/internal/breakpoints/registries/breakpointsRegistry.ts b/src/chrome/internal/breakpoints/registries/breakpointsRegistry.ts new file mode 100644 index 000000000..d6e936991 --- /dev/null +++ b/src/chrome/internal/breakpoints/registries/breakpointsRegistry.ts @@ -0,0 +1,52 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IBPRecipieStatus, BPRecipieIsBinded, BPRecipieIsUnbinded } from '../bpRecipieStatus'; +import { ValidatedMultiMap } from '../../../collections/validatedMultiMap'; +import { IBPRecipie } from '../bpRecipie'; +import { LocationInScript } from '../../locations/location'; +import { injectable } from 'inversify'; +import { CDTPBreakpoint } from '../../../cdtpDebuggee/cdtpPrimitives'; +import { ISource } from '../../sources/source'; + +@injectable() +export class BreakpointsRegistry { + private readonly _unmappedRecipieToBreakpoints = new ValidatedMultiMap, CDTPBreakpoint>(); + + public registerBPRecipie(bpRecipie: IBPRecipie): void { + this._unmappedRecipieToBreakpoints.addKeyIfNotExistant(bpRecipie); + } + + public registerBreakpointAsBinded(bp: CDTPBreakpoint): void { + this._unmappedRecipieToBreakpoints.add(bp.recipie.unmappedBPRecipie, bp); + } + + public getStatusOfBPRecipie(bpRecipie: IBPRecipie): IBPRecipieStatus { + const breakpoints = Array.from(this._unmappedRecipieToBreakpoints.get(bpRecipie)); + if (breakpoints.length > 0) { + const mappedBreakpoints = breakpoints.map(breakpoint => breakpoint.mappedToSource()); + return new BPRecipieIsBinded(bpRecipie, mappedBreakpoints, 'TODO DIEGO'); + } else { + return new BPRecipieIsUnbinded(bpRecipie, 'TODO DIEGO'); + } + } + + public tryGettingBreakpointAtLocation(locationInScript: LocationInScript): CDTPBreakpoint[] { + // TODO DIEGO: Figure out if we need a faster algorithm for this + const matchinbBps = []; + for (const bps of this._unmappedRecipieToBreakpoints.values()) { + for (const bp of bps) { + if (bp.actualLocation.isSameAs(locationInScript)) { + matchinbBps.push(bp); + } + } + } + + return matchinbBps; + } + + public toString(): string { + return `Breakpoints recipie status Registry:\nRecipie to breakpoints: ${this._unmappedRecipieToBreakpoints}`; + } +} diff --git a/src/chrome/internal/breakpoints/registries/clientCurrentBPRecipiesRegistry.ts b/src/chrome/internal/breakpoints/registries/clientCurrentBPRecipiesRegistry.ts new file mode 100644 index 000000000..8e4962820 --- /dev/null +++ b/src/chrome/internal/breakpoints/registries/clientCurrentBPRecipiesRegistry.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { BPRecipiesInSource } from '../bpRecipies'; + +import { BPRsDeltaCalculator, BPRsDeltaInRequestedSource } from '../features/bpsDeltaCalculator'; +import { BPRecipieInSource } from '../bpRecipieInSource'; +import { newResourceIdentifierMap, IResourceIdentifier } from '../../sources/resourceIdentifier'; + +export class ClientCurrentBPRecipiesRegistry { + private readonly _requestedSourcePathToCurrentBPRecipies = newResourceIdentifierMap(); + + public updateBPRecipiesAndCalculateDelta(requestedBPRecipies: BPRecipiesInSource): BPRsDeltaInRequestedSource { + const bpsDelta = this.calculateBPSDeltaFromExistingBPs(requestedBPRecipies); + this.registerCurrentBPRecipies(requestedBPRecipies.source.sourceIdentifier, bpsDelta.matchesForRequested); + return bpsDelta; + } + + private registerCurrentBPRecipies(requestedSourceIdentifier: IResourceIdentifier, bpRecipies: BPRecipieInSource[]): void { + this._requestedSourcePathToCurrentBPRecipies.setAndReplaceIfExist(requestedSourceIdentifier, Array.from(bpRecipies)); + } + + private calculateBPSDeltaFromExistingBPs(requestedBPRecipies: BPRecipiesInSource): BPRsDeltaInRequestedSource { + const bpRecipiesInSource = this._requestedSourcePathToCurrentBPRecipies.getOrAdd(requestedBPRecipies.requestedSourcePath, () => []); + return new BPRsDeltaCalculator(requestedBPRecipies.source, requestedBPRecipies, bpRecipiesInSource).calculate(); + } + + public toString(): string { + return `Client BP Recipies Registry {${this._requestedSourcePathToCurrentBPRecipies}}`; + } +} diff --git a/src/chrome/internal/domains/supportedDomains.ts b/src/chrome/internal/domains/supportedDomains.ts new file mode 100644 index 000000000..3c8e2a0e0 --- /dev/null +++ b/src/chrome/internal/domains/supportedDomains.ts @@ -0,0 +1,41 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IComponent } from '../features/feature'; +import { Protocol as CDTP } from 'devtools-protocol'; + +import { injectable } from 'inversify'; + +export interface ISupportedDomainsDependencies { + getTargetDebuggerDomainsSchemas(): Promise; +} + +export interface ISupportedDomains { + isSupported(domainName: string): boolean; +} + +@injectable() +export class SupportedDomains implements IComponent, ISupportedDomains { + private readonly _domains = new Map(); + + public isSupported(domainName: string): boolean { + return this._domains.has(domainName); + } + + public async install(): Promise { + await this.initSupportedDomains(); + return this; + } + + private async initSupportedDomains(): Promise { + try { + const domains = await this._dependencies.getTargetDebuggerDomainsSchemas(); + domains.forEach(domain => this._domains.set(domain.name, domain)); + } catch (e) { + // If getDomains isn't supported for some reason, skip this + } + } + + constructor(private readonly _dependencies: ISupportedDomainsDependencies) { } +} \ No newline at end of file diff --git a/src/chrome/internal/exceptions/pauseOnException.ts b/src/chrome/internal/exceptions/pauseOnException.ts new file mode 100644 index 000000000..84cd549d6 --- /dev/null +++ b/src/chrome/internal/exceptions/pauseOnException.ts @@ -0,0 +1,122 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { InformationAboutPausedProvider, NotifyStoppedCommonLogic } from '../features/takeProperActionOnPausedEvent'; +import { IComponent } from '../features/feature'; +import * as errors from '../../../errors'; +import { utils } from '../../..'; +import { FormattedExceptionParser, IFormattedExceptionLineDescription } from '../formattedExceptionParser'; +import { IPauseOnPromiseRejectionsStrategy, IPauseOnExceptionsStrategy } from './strategies'; +import { VoteRelevance, IVote, Abstained } from '../../communication/collaborativeDecision'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { IEventsToClientReporter } from '../../client/eventSender'; +import { DeleteMeScriptsRegistry } from '../scripts/scriptsRegistry'; +import { PausedEvent } from '../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { IPauseOnExceptionsConfigurer } from '../../cdtpDebuggee/features/cdtpPauseOnExceptionsConfigurer'; + +type ExceptionBreakMode = 'never' | 'always' | 'unhandled' | 'userUnhandled'; + +export type Dummy = VoteRelevance; // If we don't do this the .d.ts doesn't include VoteRelevance and the compilation fails. Remove this when the issue disappears... + +export interface IExceptionInformationDetails { + readonly stackTrace: IFormattedExceptionLineDescription[]; + readonly message: string; + readonly formattedDescription: string; + readonly typeName: string; +} + +export interface IExceptionInformation { + readonly exceptionId: string; + readonly description?: string; + readonly breakMode: ExceptionBreakMode; + readonly details?: IExceptionInformationDetails; +} + +export interface IEventsConsumedByPauseOnException { + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; + publishGoingToPauseClient(): void; +} + +export class ExceptionWasThrown extends NotifyStoppedCommonLogic { + public readonly relevance = VoteRelevance.NormalVote; + public readonly reason = 'exception'; // There is an issue of how the .d.ts is generated for this file, so we need to type that explicitly + + constructor(protected readonly _eventsToClientReporter: IEventsToClientReporter, + protected readonly _publishGoingToPauseClient: () => void) { + super(); + } +} + +export class PromiseWasRejected extends NotifyStoppedCommonLogic { + public readonly relevance = VoteRelevance.NormalVote; + public readonly reason: 'promise_rejection' = 'promise_rejection'; // There is an issue of how the .d.ts is generated for this file, so we need to type that explicitly + + constructor(protected readonly _eventsToClientReporter: IEventsToClientReporter, + protected readonly _publishGoingToPauseClient: () => void) { + super(); + } +} + +@injectable() +export class PauseOnExceptionOrRejection implements IComponent { + private _promiseRejectionsStrategy: IPauseOnPromiseRejectionsStrategy; + + private _lastException: any; + + public setExceptionsStrategy(strategy: IPauseOnExceptionsStrategy): Promise { + return this._pauseOnExceptions.setPauseOnExceptions(strategy); + } + + public setPromiseRejectionStrategy(promiseRejectionsStrategy: IPauseOnPromiseRejectionsStrategy): void { + this._promiseRejectionsStrategy = promiseRejectionsStrategy; + } + + public async askForInformationAboutPaused(paused: PausedEvent): Promise> { + if (paused.reason === 'exception') { + // If we are here is because we either configured the debugee to pauser on unhandled or handled exceptions + this._lastException = paused.data; + return new ExceptionWasThrown(this._eventsToClientReporter, this._dependencies.publishGoingToPauseClient); + } else if (paused.reason === 'promiseRejection' && this._promiseRejectionsStrategy.shouldPauseOnRejections()) { + // TODO: Figure out if it makes sense to move this into it's own class + this._lastException = paused.data; + return new PromiseWasRejected(this._eventsToClientReporter, this._dependencies.publishGoingToPauseClient); + } else { + this._lastException = null; + return new Abstained(this); + } + } + + public async latestExceptionInfo(): Promise { + if (this._lastException) { + const isError = this._lastException.subtype === 'error'; + const message = isError ? utils.firstLine(this._lastException.description) : (this._lastException.description || this._lastException.value); + const formattedMessage = message && message.replace(/\*/g, '\\*'); + const response: IExceptionInformation = { + exceptionId: this._lastException.className || this._lastException.type || 'Error', + breakMode: 'unhandled', + details: { + stackTrace: this._lastException.description && await new FormattedExceptionParser(this._scriptsLogic, this._lastException.description).parse(), + message, + formattedDescription: formattedMessage, // VS workaround - see https://github.com/Microsoft/client/issues/34259 + typeName: this._lastException.subtype || this._lastException.type + } + }; + + return response; + } else { + throw errors.noStoredException(); + } + } + + public install(): this { + this._dependencies.subscriberForAskForInformationAboutPaused(paused => this.askForInformationAboutPaused(paused)); + return this; + } + + constructor(@inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IEventsConsumedByPauseOnException, + @inject(TYPES.DeleteMeScriptsRegistry) private readonly _scriptsLogic: DeleteMeScriptsRegistry, + @inject(TYPES.IPauseOnExceptions) private readonly _pauseOnExceptions: IPauseOnExceptionsConfigurer, + @inject(TYPES.IEventsToClientReporter) private readonly _eventsToClientReporter: IEventsToClientReporter) { } +} \ No newline at end of file diff --git a/src/chrome/internal/exceptions/strategies.ts b/src/chrome/internal/exceptions/strategies.ts new file mode 100644 index 000000000..a66eea670 --- /dev/null +++ b/src/chrome/internal/exceptions/strategies.ts @@ -0,0 +1,27 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export interface IPauseOnExceptionsStrategy { + +} + +export class PauseOnUnhandledExceptions implements IPauseOnExceptionsStrategy { } +export class PauseOnAllExceptions implements IPauseOnExceptionsStrategy { } +export class DoNotPauseOnAnyExceptions implements IPauseOnExceptionsStrategy { } + +export interface IPauseOnPromiseRejectionsStrategy { + shouldPauseOnRejections(): boolean; +} + +export class PauseOnAllRejections implements IPauseOnPromiseRejectionsStrategy { + public shouldPauseOnRejections(): boolean { + return true; + } +} + +export class DoNotPauseOnAnyRejections implements IPauseOnPromiseRejectionsStrategy { + public shouldPauseOnRejections(): boolean { + return false; + } +} diff --git a/src/chrome/internal/features/feature.ts b/src/chrome/internal/features/feature.ts index 3be839558..1bc7ad8a2 100644 --- a/src/chrome/internal/features/feature.ts +++ b/src/chrome/internal/features/feature.ts @@ -1,3 +1,30 @@ -export interface IComponent { - // TODO: Implement this +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ConnectedCDAConfiguration } from '../../client/chromeDebugAdapter/cdaConfiguration'; +import { PromiseOrNot } from '../../utils/promises'; + +export interface IConfigurableFeature { + install(configuration: Configuration): PromiseOrNot; +} + +export interface IConfigurationlessFeature { + install(): PromiseOrNot; +} + +export type ComponentConfiguration = ConnectedCDAConfiguration; + +export type IComponent = + Configuration extends void + ? IConfigurationlessFeature + : IConfigurableFeature; + +export interface ICommandHandlerDeclaration { + readonly commandName: string; + readonly commandHandler: (args: any) => PromiseOrNot; +} + +export interface ICommandHandlerDeclarer { + getCommandHandlerDeclarations(): PromiseOrNot; } diff --git a/src/chrome/internal/features/skipFiles.ts b/src/chrome/internal/features/skipFiles.ts new file mode 100644 index 000000000..1259f8857 --- /dev/null +++ b/src/chrome/internal/features/skipFiles.ts @@ -0,0 +1,293 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { inject, injectable, LazyServiceIdentifer } from 'inversify'; +import { logger } from 'vscode-debugadapter/lib/logger'; +import * as nls from 'vscode-nls'; +import { IToggleSkipFileStatusArgs } from '../../../debugAdapterInterfaces'; +import * as utils from '../../../utils'; +import { IScriptParsedEvent } from '../../cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider'; +import { IBlackboxPatternsConfigurer } from '../../cdtpDebuggee/features/cdtpBlackboxPatternsConfigurer'; +import { ConnectedCDAConfiguration } from '../../client/chromeDebugAdapter/cdaConfiguration'; +import { ClientToInternal } from '../../client/clientToInternal'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { LocationInLoadedSource } from '../locations/location'; +import { createColumnNumber, createLineNumber } from '../locations/subtypes'; +import { IScript } from '../scripts/script'; +import { IPositionInScript } from '../scripts/sourcesMapper'; +import { IResourceIdentifier, newResourceIdentifierMap } from '../sources/resourceIdentifier'; +import { CallFramePresentation, ICallFramePresentationDetails } from '../stackTraces/callFramePresentation'; +import { IStackTracePresentationLogicProvider, StackTracesLogic } from '../stackTraces/stackTracesLogic'; +import { IComponent } from './feature'; +const localize = nls.loadMessageBundle(); + +export interface IEventsConsumedBySkipFilesLogic { + onScriptParsed(listener: (scriptEvent: IScriptParsedEvent) => Promise): void; +} + +export interface ISkipFilesConfiguration { + skipFiles?: string[]; // an array of file names or glob patterns + skipFileRegExps?: string[]; // a supplemental array of library code regex patterns +} + +@injectable() +export class SkipFilesLogic implements IComponent, IStackTracePresentationLogicProvider { + private _blackboxedRegexes: RegExp[] = []; + private _skipFileStatuses = newResourceIdentifierMap(); + public reprocessPausedEvent: () => void; // TODO DIEGO: Do this in a better way + + /** + * If the source has a saved skip status, return that, whether true or false. + * If not, check it against the patterns list. + */ + public shouldSkipSource(sourcePath: IResourceIdentifier): boolean | undefined { + const status = this.getSkipStatus(sourcePath); + if (typeof status === 'boolean') { + return status; + } + + if (this.matchesSkipFilesPatterns(sourcePath)) { + return true; + } + + return undefined; + } + + public getCallFrameAdditionalDetails(locationInLoadedSource: LocationInLoadedSource): ICallFramePresentationDetails[] { + return this.shouldSkipSource(locationInLoadedSource.source.identifier) + ? [{ + additionalSourceOrigins: [localize('skipFilesFeatureName', 'skipFiles')], + sourcePresentationHint: 'deemphasize' + }] + : []; + } + + /** + * Returns true if this path matches one of the static skip patterns + */ + private matchesSkipFilesPatterns(sourcePath: IResourceIdentifier): boolean { + return this._blackboxedRegexes.some(regex => { + return regex.test(sourcePath.canonicalized); + }); + } + + /** + * Returns the current skip status for this path, which is either an authored or generated script. + */ + private getSkipStatus(sourcePath: IResourceIdentifier): boolean | undefined { + if (this._skipFileStatuses.has(sourcePath)) { + return this._skipFileStatuses.get(sourcePath); + } + + return undefined; + } + + /* __GDPR__ + 'ClientRequest/toggleSkipFileStatus' : { + '${include}': [ + '${IExecutionResultTelemetryProperties}', + '${DebugCommonProperties}' + ] + } + */ + public async toggleSkipFileStatus(clientArgs: IToggleSkipFileStatusArgs): Promise { + const args = this._clientToInternal.toSource(clientArgs); + await args.tryResolving(async resolvedSource => { + if (!await this.isInCurrentStack(clientArgs)) { + // Only valid for files that are in the current stack + const logName = resolvedSource; + logger.log(`Can't toggle the skipFile status for ${logName} - it's not in the current stack.`); + return; + } + + if (resolvedSource === resolvedSource.script.developmentSource && resolvedSource.script.mappedSources.length < 0) { + // Ignore toggling skip status for generated scripts with sources + logger.log(`Can't toggle skipFile status for ${resolvedSource} - it's a script with a sourcemap`); + return; + } + + const newStatus = !this.shouldSkipSource(resolvedSource.identifier); + logger.log(`Setting the skip file status for: ${resolvedSource} to ${newStatus}`); + this._skipFileStatuses.set(resolvedSource.identifier, newStatus); + + await this.resolveSkipFiles(resolvedSource.script, resolvedSource.script.developmentSource.identifier, + resolvedSource.script.mappedSources.map(s => s.identifier), /*toggling=*/true); + + if (newStatus) { + // TODO: Verify that using targetPath works here. We need targetPath to be this.getScriptByUrl(targetPath).url + this.makeRegexesSkip(resolvedSource.script.runtimeSource.identifier.textRepresentation); + } else { + this.makeRegexesNotSkip(resolvedSource.script.runtimeSource.identifier.textRepresentation); + } + + this.reprocessPausedEvent(); + }, async sourceIdentifier => { + logger.log(`Can't toggle the skipFile status for: ${sourceIdentifier} - haven't seen it yet.`); + }); + } + + private makeRegexesSkip(skipPath: string): void { + let somethingChanged = false; + this._blackboxedRegexes = this._blackboxedRegexes.map(regex => { + const result = utils.makeRegexMatchPath(regex, skipPath); + somethingChanged = somethingChanged || (result !== regex); + return result; + }); + + if (!somethingChanged) { + this._blackboxedRegexes.push(new RegExp(utils.pathToRegex(skipPath), 'i')); + } + + this.refreshBlackboxPatterns(); + } + + private refreshBlackboxPatterns(): void { + // Make sure debugging domain is enabled before calling refreshBlackboxPatterns() + this._blackboxPatternsConfigurer.setBlackboxPatterns({ + patterns: this._blackboxedRegexes.map(regex => regex.source) + }).catch(() => this.warnNoSkipFiles()); + } + + private async isInCurrentStack(clientArgs: IToggleSkipFileStatusArgs): Promise { + const args = this._clientToInternal.toSource(clientArgs); + return args.tryResolving(async resolvedSource => { + const currentStack = await this.stackTracesLogic.stackTrace({ threadId: undefined }); + + return currentStack.stackFrames.some(frame => { + return (frame instanceof CallFramePresentation) + && frame.codeFlow.location.source + && frame.codeFlow.location.source.isEquivalentTo(resolvedSource); + }); + + }, + async () => { + return false; + }); + } + + private makeRegexesNotSkip(noSkipPath: string): void { + let somethingChanged = false; + this._blackboxedRegexes = this._blackboxedRegexes.map(regex => { + const result = utils.makeRegexNotMatchPath(regex, noSkipPath); + somethingChanged = somethingChanged || (result !== regex); + return result; + }); + + if (somethingChanged) { + this.refreshBlackboxPatterns(); + } + } + + public async resolveSkipFiles(script: IScript, mappedUrl: IResourceIdentifier, sources: IResourceIdentifier[], toggling?: boolean): Promise { + if (sources && sources.length) { + const parentIsSkipped = this.shouldSkipSource(script.runtimeSource.identifier); + const libPositions: IPositionInScript[] = []; + + // Figure out skip/noskip transitions within script + let inLibRange = parentIsSkipped; + for (let s of sources) { + let isSkippedFile = this.shouldSkipSource(s); + if (typeof isSkippedFile !== 'boolean') { + // Inherit the parent's status + isSkippedFile = parentIsSkipped; + } + + this._skipFileStatuses.set(s, isSkippedFile); + + if ((isSkippedFile && !inLibRange) || (!isSkippedFile && inLibRange)) { + const sourcesMapper = script.sourcesMapper; + const pos = sourcesMapper.getPositionInScript({ source: s.canonicalized, line: createLineNumber(0) }); + if (!pos) { + throw new Error(`Source '${s.canonicalized}' start not found in script.`) + } + + libPositions.push(pos); + inLibRange = !inLibRange; + } + } + + // If there's any change from the default, set proper blackboxed ranges + if (libPositions.length || toggling) { + if (parentIsSkipped) { + libPositions.splice(0, 0, { line: createLineNumber(0), column: createColumnNumber(0) }); + } + + if (libPositions[0].line !== 0 || libPositions[0].column !== 0) { + // The list of blackboxed ranges must start with 0,0 for some reason. + // https://github.com/Microsoft/vscode-chrome-debug/issues/667 + libPositions[0] = { + line: createLineNumber(0), + column: createColumnNumber(0) + }; + } + + await this._blackboxPatternsConfigurer.setBlackboxedRanges(script, []).catch(() => this.warnNoSkipFiles()); + + if (libPositions.length) { + this._blackboxPatternsConfigurer.setBlackboxedRanges(script, libPositions).catch(() => this.warnNoSkipFiles()); + } + } + } else { + const status = await this.getSkipStatus(mappedUrl); + const skippedByPattern = this.matchesSkipFilesPatterns(mappedUrl); + if (typeof status === 'boolean' && status !== skippedByPattern) { + const positions = status ? [{ line: createLineNumber(0), column: createColumnNumber(0) }] : []; + this._blackboxPatternsConfigurer.setBlackboxedRanges(script, positions).catch(() => this.warnNoSkipFiles()); + } + } + } + + private warnNoSkipFiles(): void { + logger.log('Warning: this runtime does not support skipFiles'); + } + + private async onScriptParsed(scriptEvent: IScriptParsedEvent): Promise { + const script = scriptEvent.script; + const sources = script.mappedSources; + await this.resolveSkipFiles(script, script.developmentSource.identifier, sources.map(source => source.identifier)); + } + + public install(): this { + this._dependencies.onScriptParsed(scriptParsed => this.onScriptParsed(scriptParsed)); + this.configure(); + return this; + } + + private configure(): SkipFilesLogic { + const _launchAttachArgs: ISkipFilesConfiguration = this._configuration.args; + let patterns: string[] = []; + + if (_launchAttachArgs.skipFiles) { + const skipFilesArgs = _launchAttachArgs.skipFiles.filter(glob => { + if (glob.startsWith('!')) { + logger.warn(`Warning: skipFiles entries starting with '!' aren't supported and will be ignored. ("${glob}")`); + return false; + } + + return true; + }); + + patterns = skipFilesArgs.map(glob => utils.pathGlobToBlackboxedRegex(glob)); + } + + if (_launchAttachArgs.skipFileRegExps) { + patterns = patterns.concat(_launchAttachArgs.skipFileRegExps); + } + + if (patterns.length) { + this._blackboxedRegexes = patterns.map(pattern => new RegExp(pattern, 'i')); + this.refreshBlackboxPatterns(); + } + + return this; + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IEventsConsumedBySkipFilesLogic, + @inject(new LazyServiceIdentifer(() => TYPES.StackTracesLogic)) private readonly stackTracesLogic: StackTracesLogic, + @inject(TYPES.ClientToInternal) private readonly _clientToInternal: ClientToInternal, + @inject(TYPES.ConnectedCDAConfiguration) private readonly _configuration: ConnectedCDAConfiguration, + @inject(TYPES.IBlackboxPatternsConfigurer) private readonly _blackboxPatternsConfigurer: IBlackboxPatternsConfigurer, + ) { } +} \ No newline at end of file diff --git a/src/chrome/internal/features/smartStep.ts b/src/chrome/internal/features/smartStep.ts new file mode 100644 index 000000000..3d4f7c10f --- /dev/null +++ b/src/chrome/internal/features/smartStep.ts @@ -0,0 +1,101 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { inject, injectable } from 'inversify'; +import { logger } from 'vscode-debugadapter'; +import * as nls from 'vscode-nls'; +import { ConnectedCDAConfiguration, utils } from '../../..'; +import { PausedEvent } from '../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { Abstained, IVote, VoteOverride } from '../../communication/collaborativeDecision'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { LocationInLoadedSource } from '../locations/location'; +import { ICallFramePresentationDetails } from '../stackTraces/callFramePresentation'; +import { IStackTracePresentationLogicProvider } from '../stackTraces/stackTracesLogic'; +import { Stepping } from '../stepping/stepping'; +import { IComponent } from './feature'; +import { InformationAboutPausedProvider } from './takeProperActionOnPausedEvent'; + +const localize = nls.loadMessageBundle(); + +export interface IEventsConsumedBySmartStepLogic { + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; +} + +export interface ISmartStepLogicConfiguration { + isEnabled: boolean; +} + +@injectable() +export class SmartStepLogic implements IComponent, IStackTracePresentationLogicProvider { + private _smartStepCount = 0; + private _isEnabled = false; + + public async toggleSmartStep(): Promise { + this._isEnabled = !this._isEnabled; + this.sendUpdatedPause(); + } + + public async askForInformationAboutPaused(paused: PausedEvent): Promise> { + if (this._isEnabled && await this.shouldSkip(paused)) { + return new VoteOverride(() => { + this._smartStepCount++; + return this._stepping.stepIn(); + }); + } else { + if (this._smartStepCount > 0) { + logger.log(`SmartStep: Skipped ${this._smartStepCount} steps`); + this._smartStepCount = 0; + } + return new Abstained(this); + } + } + + public sendUpdatedPause(): void { + // TODO + // this._eventsToClientReporter.sendDebuggeeIsStopped({ reason: Reason}) + } + + public async shouldSkip(paused: PausedEvent): Promise { + if (!this._isEnabled) return false; + + if (paused.reason !== 'other') return false; + + const frame = paused.callFrames[0]; + if (frame.location.mappedToSource().resource.isMappedSource()) { + return false; + } + + if (frame.location.script.mappedSources.length) { + return true; + } + + return false; + } + + public getCallFrameAdditionalDetails(locationInLoadedSource: LocationInLoadedSource): ICallFramePresentationDetails[] { + return this._isEnabled && !locationInLoadedSource.source.isMappedSource() + ? [{ + additionalSourceOrigins: [localize('smartStepFeatureName', 'smartStep')], + sourcePresentationHint: 'deemphasize' + }] + : []; + } + + public install(): this { + this._dependencies.subscriberForAskForInformationAboutPaused(paused => this.askForInformationAboutPaused(paused)); + this.configure(); + return this; + } + + public configure(): void { + this._isEnabled = !!utils.defaultIfUndefined(this._configuration.args.smartStep, this._configuration.isVSClient); + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IEventsConsumedBySmartStepLogic, + @inject(TYPES.ConnectedCDAConfiguration) private readonly _configuration: ConnectedCDAConfiguration, + @inject(TYPES.Stepping) private readonly _stepping: Stepping + ) { + } +} \ No newline at end of file diff --git a/src/chrome/internal/features/takeProperActionOnPausedEvent.ts b/src/chrome/internal/features/takeProperActionOnPausedEvent.ts new file mode 100644 index 000000000..b748f1988 --- /dev/null +++ b/src/chrome/internal/features/takeProperActionOnPausedEvent.ts @@ -0,0 +1,95 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IComponent } from './feature'; +import { PausedEvent } from '../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { IEventsToClientReporter } from '../../client/eventSender'; +import { ReasonType } from '../../stoppedEvent'; +import { PromiseOrNot } from '../../utils/promises'; +import { IVote, VoteCommonLogic, VoteRelevance, ExecuteDecisionBasedOnVotes } from '../../communication/collaborativeDecision'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { IDebugeeExecutionController } from '../../cdtpDebuggee/features/cdtpDebugeeExecutionController'; +import { ICDTDebuggeeExecutionEventsProvider } from '../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; + +export abstract class ResumeCommonLogic extends VoteCommonLogic { + protected readonly abstract _debugeeExecutionControl: IDebugeeExecutionController; + + public async execute(): Promise { + this._debugeeExecutionControl.resume(); + } +} + +export abstract class NotifyStoppedCommonLogic extends VoteCommonLogic { + protected readonly exception: any; + protected readonly abstract reason: ReasonType; + protected readonly abstract _eventsToClientReporter: IEventsToClientReporter; + protected readonly abstract _publishGoingToPauseClient: () => void; + + public async execute(): Promise { + this._publishGoingToPauseClient(); + this._eventsToClientReporter.sendDebuggeeIsStopped({ reason: this.reason, exception: this.exception }); + } +} + +export type InformationAboutPausedProvider = (paused: PausedEvent) => Promise>; + +export interface IEventsConsumedByTakeProperActionOnPausedEvent extends ITakeActionBasedOnInformationDependencies { + // onPaused(listener: (paused: PausedEvent) => Promise | void): void; +} + +@injectable() +export class TakeProperActionOnPausedEvent implements IComponent { + public async onPause(paused: PausedEvent): Promise { + // Ask all the listeners what information they can provide + const infoPieces = await this._dependencies.askForInformationAboutPause(paused); + + // Remove pieces without any relevant information + const relevantInfoPieces = infoPieces.filter(response => response.isRelevant()); + + await new TakeActionBasedOnInformation(relevantInfoPieces, this._eventsToClientReporter).takeAction(); + } + + public install(): this { + this._cdtpDebuggerEventsProvider.onPaused(paused => this.onPause(paused)); + return this; + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: ITakeActionBasedOnInformationDependencies, + @inject(TYPES.ICDTPDebuggerEventsProvider) private readonly _cdtpDebuggerEventsProvider: ICDTDebuggeeExecutionEventsProvider, + @inject(TYPES.IEventsToClientReporter) private readonly _eventsToClientReporter: IEventsToClientReporter) { } +} + +export interface ITakeActionBasedOnInformationDependencies { + askForInformationAboutPause(paused: PausedEvent): PromiseOrNot[]>; +} + +export class TakeActionBasedOnInformation { + private readonly _takeActionBasedOnVotes: ExecuteDecisionBasedOnVotes; + + public async takeAction(): Promise { + this.validatePieces(); + return this._takeActionBasedOnVotes.execute(); + } + + public validatePieces(): void { + // DIEGO TODO: Change this to send telemetry instead + if (this._takeActionBasedOnVotes.getCountOfVotesWithCertainRelevance(VoteRelevance.OverrideOtherVotes) > 1) { + throw new Error(`Didn't expect to have multiple override information pieces`); + } + + if (this._takeActionBasedOnVotes.getCountOfVotesWithCertainRelevance(VoteRelevance.NormalVote) > 1) { + throw new Error(`Didn't expect to have multiple information pieces`); + } + } + + constructor(piecesOfInformation: IVote[], + private readonly _eventsToClientReporter: IEventsToClientReporter) { + this._takeActionBasedOnVotes = new ExecuteDecisionBasedOnVotes(async () => { + // If we don't have any information whatsoever, then we assume that we stopped due to a debugger statement + return this._eventsToClientReporter.sendDebuggeeIsStopped({ reason: 'debugger_statement' }); + }, piecesOfInformation); + } +} \ No newline at end of file diff --git a/src/chrome/internal/formattedExceptionParser.ts b/src/chrome/internal/formattedExceptionParser.ts new file mode 100644 index 000000000..ce6293ce0 --- /dev/null +++ b/src/chrome/internal/formattedExceptionParser.ts @@ -0,0 +1,73 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { parseResourceIdentifier } from '../..'; +import { LocationInScript, Position, LocationInLoadedSource } from './locations/location'; +import { IResourceIdentifier } from './sources/resourceIdentifier'; +import { CDTPScriptUrl } from './sources/resourceIdentifierSubtypes'; +import { createLineNumber, createColumnNumber } from './locations/subtypes'; +import { DeleteMeScriptsRegistry } from './scripts/scriptsRegistry'; + +export interface IFormattedExceptionLineDescription { + generateDescription(zeroBaseNumbers: boolean): string; +} + +class CodeFlowFrameDescription implements IFormattedExceptionLineDescription { + public generateDescription(zeroBaseNumbers: boolean): string { + return this.cdtpDescription.replace( + this.printLocation(this.scriptLocation.script.url, this.scriptLocation.position, false), + this.printLocation(this.sourceLocation.source.identifier.textRepresentation, this.sourceLocation.position, zeroBaseNumbers)); + } + + private printLocation(locationIdentifier: string, position: Position, zeroBaseNumbers: boolean): string { + const constantToAdd = zeroBaseNumbers ? 0 : 1; + return `${locationIdentifier}:${position.lineNumber + constantToAdd}:${position.columnNumber + constantToAdd}`; + } + + constructor( + public readonly cdtpDescription: string, + public readonly scriptLocation: LocationInScript, + public readonly sourceLocation: LocationInLoadedSource) { } +} + +class UnparsableFrameDescription implements IFormattedExceptionLineDescription { + public generateDescription(_zeroBaseNumbers: boolean): string { + return this.cdtpDescription; + } + + constructor( + public readonly cdtpDescription: string) { } +} + +export class FormattedExceptionParser { + // We parse stack trace from `this.formattedException`, source map it and return a new string + public async parse(): Promise { + return this.exceptionLines().map(line => { + const matches = line.match(/^\s+at (.*?)\s*\(?([^ ]+):(\d+):(\d+)\)?$/); + if (matches) { + const url = parseResourceIdentifier(matches[2]) as IResourceIdentifier; + const lineNumber = parseInt(matches[3], 10); + const zeroBasedLineNumber = createLineNumber(lineNumber - 1); + const columnNumber = createColumnNumber(parseInt(matches[4], 10)); + const zeroBasedColumnNumber = createColumnNumber(columnNumber - 1); + const scripts = this._scriptsLogic.getScriptsByPath(url); + if (scripts.length > 0) { + const scriptLocation = new LocationInScript(scripts[0], new Position(zeroBasedLineNumber, zeroBasedColumnNumber)); + const location = scriptLocation.mappedToSource(); + return new CodeFlowFrameDescription(line, scriptLocation, location); + } + } + + return new UnparsableFrameDescription(line); + }); + } + + private exceptionLines() { + return this._formattedException.split(/\r?\n/); + } + + constructor( + private readonly _scriptsLogic: DeleteMeScriptsRegistry, + private readonly _formattedException: string) { } +} \ No newline at end of file diff --git a/src/chrome/internal/locations/location.ts b/src/chrome/internal/locations/location.ts index 8da9fbc2f..4fd877eb2 100644 --- a/src/chrome/internal/locations/location.ts +++ b/src/chrome/internal/locations/location.ts @@ -3,13 +3,13 @@ *--------------------------------------------------------*/ import * as Validation from '../../../validation'; -import { IScript } from '../scripts/script'; -import { ISource } from '../sources/source'; -import { ILoadedSource } from '../sources/loadedSource'; +import { IScript, Script } from '../scripts/script'; +import { ISource, isSource } from '../sources/source'; +import { ILoadedSource, isLoadedSource } from '../sources/loadedSource'; import { logger } from 'vscode-debugadapter'; -import { ColumnNumber, LineNumber, URLRegexp } from './subtypes'; +import { ColumnNumber, LineNumber, URLRegexp, createURLRegexp } from './subtypes'; import { CDTPScriptUrl } from '../sources/resourceIdentifierSubtypes'; -import { IResourceIdentifier, parseResourceIdentifier, IURL } from '../sources/resourceIdentifier'; +import { IResourceIdentifier, parseResourceIdentifier, IURL, isResourceIdentifier } from '../sources/resourceIdentifier'; import { IEquivalenceComparable } from '../../utils/equivalence'; export type integer = number; @@ -41,21 +41,35 @@ export interface ILocation extends IEq readonly resource: T; } -export type ScriptOrSourceOrURLOrURLRegexp = IScript | ILoadedSource | ISource | URLRegexp | IURL; +// The LocationInUrl is used with the URL that is associated with each Script in CDTP. This should be a URL, but it could also be a string that is not a valid URL +// For that reason we use IResourceIdentifier for this type, instead of IURL +export type ScriptOrSourceOrURLOrURLRegexp = ISource | ILoadedSource | IScript | URLRegexp | IResourceIdentifier; export type Location = - T extends ISource ? LocationInSource : // Used when receiving locations from the client - T extends ILoadedSource ? LocationInLoadedSource : // Used to translate between locations on the client and the debuggee - T extends IScript ? LocationInScript : // Used when receiving locations from the debuggee - T extends URLRegexp ? LocationInUrlRegexp : // Used when setting a breakpoint by URL in a local file path in windows, to make it case insensitive - T extends IURL ? LocationInUrl : // Used when setting a breakpoint by URL for case-insensitive URLs - ILocation; // TODO: Figure out how to replace this by never (We run into some issues with the isEquivalentTo call if we do) + ILocation & (T extends ISource ? LocationInSource : // Used when receiving locations from the client + T extends ILoadedSource ? LocationInLoadedSource : // Used to translate between locations on the client and the debugee + T extends IScript ? LocationInScript : // Used when receiving locations from the debugee + T extends URLRegexp ? LocationInUrlRegexp : // Used when setting a breakpoint by URL in a local file path in windows, to make it case insensitive + T extends IResourceIdentifier ? LocationInUrl : // Used when setting a breakpoint by URL for case-insensitive URLs + ILocation); // TODO: Figure out how to replace this by never (We run into some issues with the isEquivalentTo call if we do) + +export function createLocation(resource: T, position: Position): Location { + if (isSource(resource)) { + return >new LocationInSource(resource, position); // TODO: Figure out way to remove this cast + } else if (isLoadedSource(resource)) { + return >new LocationInLoadedSource(resource, position); // TODO: Figure out way to remove this cast + } else if (resource instanceof Script) { + return >new LocationInScript(resource, position); // TODO: Figure out way to remove this cast + } else if (typeof resource === 'string') { + return >new LocationInUrlRegexp(createURLRegexp(resource), position); // TODO: Figure out way to remove this cast + } else if (isResourceIdentifier(resource)) { + return >new LocationInUrl(>resource, position); // TODO: Figure out way to remove this cast + } else { + throw Error(`Can't create a location because the type of resource ${resource} wasn't recognized`); + } +} abstract class BaseLocation implements ILocation { - constructor( - public readonly resource: T, - public readonly position: Position) { } - public isEquivalentTo(right: this): boolean { if (this.position.isEquivalentTo(right.position)) { if (typeof this.resource === 'string' || typeof right.resource === 'string') { @@ -71,9 +85,13 @@ abstract class BaseLocation implements public toString(): string { return `${this.resource}:${this.position}`; } + + constructor( + public readonly resource: T, + public readonly position: Position) { } } -export class LocationInSource extends BaseLocation implements ILocation { +export class LocationInSource extends BaseLocation { public get identifier(): ISource { return this.resource; } @@ -151,8 +169,10 @@ export class LocationInLoadedSource extends BaseLocation { } } +// The LocationInUrl is used with the URL that is associated with each Script in CDTP. This should be a URL, but it could also be a string that is not a valid URL +// For that reason we use IResourceIdentifier for this type, instead of IURL export class LocationInUrl extends BaseLocation> { - public get url(): IURL { + public get url(): IResourceIdentifier { return this.resource; } } diff --git a/src/chrome/internal/requests.ts b/src/chrome/internal/requests.ts new file mode 100644 index 000000000..277106d50 --- /dev/null +++ b/src/chrome/internal/requests.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { LoadedSourceCallFrame } from './stackTraces/callFrame'; + +export interface IEvaluateArguments { + readonly expression: string; + readonly frame?: LoadedSourceCallFrame; + readonly context?: string; + readonly format?: { + /** Display the value in hex. */ + readonly hex?: boolean; + }; +} + +export interface ICompletionsArguments { + readonly frame?: LoadedSourceCallFrame; + readonly text: string; + readonly column: number; + readonly line?: number; +} diff --git a/src/chrome/internal/scripts/script.ts b/src/chrome/internal/scripts/script.ts index 7c1db06cc..be9fa8581 100644 --- a/src/chrome/internal/scripts/script.ts +++ b/src/chrome/internal/scripts/script.ts @@ -13,10 +13,11 @@ import { printArray } from '../../collections/printing'; import { ISourcesMapper } from './sourcesMapper'; import { IResourceIdentifier, IResourceLocation, newResourceIdentifierMap, parseResourceIdentifier, ResourceName } from '../sources/resourceIdentifier'; import { IExecutionContext } from './executionContext'; +import { Lazy1 } from '../../utils/lazy'; import { IEquivalenceComparable } from '../../utils/equivalence'; /** - * This interface represents a piece of code that is being executed in the debuggee. Usually a script matches to a file or a url, but that is not always the case. + * This interface represents a piece of code that is being executed in the debugee. Usually a script matches to a file or a url, but that is not always the case. * This interface solves the problem of finding the different loaded sources associated with a script, and being able to identify and compare both scripts and sources easily. */ export interface IScript extends IEquivalenceComparable { @@ -58,11 +59,11 @@ export class Script implements IScript { let developmentSource: (script: IScript) => ILoadedSource; if (locationInDevelopmentEnvinronment.isEquivalentTo(locationInRuntimeEnvironment) || locationInDevelopmentEnvinronment.textRepresentation === '') { if (fs.existsSync(locationInRuntimeEnvironment.textRepresentation)) { - developmentSource = runtimeSource = (script: IScript) => - new ScriptRunFromLocalStorage(script, locationInRuntimeEnvironment, 'TODO DIEGO'); + developmentSource = runtimeSource = new Lazy1((script: IScript) => // Using Lazy1 will ensure both calls return the same instance + new ScriptRunFromLocalStorage(script, locationInRuntimeEnvironment, 'TODO DIEGO')).function; } else { - developmentSource = runtimeSource = (script: IScript) => - new DynamicScript(script, locationInRuntimeEnvironment, 'TODO DIEGO'); + developmentSource = runtimeSource = new Lazy1((script: IScript) => // Using Lazy1 will ensure both calls return the same instance + new DynamicScript(script, locationInRuntimeEnvironment, 'TODO DIEGO')).function; } } else { // The script is served from one location, and it's on the workspace on a different location @@ -73,8 +74,9 @@ export class Script implements IScript { } public static createEval(executionContext: IExecutionContext, name: ResourceName, sourcesMapper: ISourcesMapper): Script { - let getNoURLScript = (script: IScript) => new NoURLScriptSource(script, name, 'TODO DIEGO'); - return new Script(executionContext, getNoURLScript, getNoURLScript, _ => new Map(), sourcesMapper); + // Using Lazy1 will ensure both calls return the same instance + let getNoURLScript = new Lazy1((script: IScript) => new NoURLScriptSource(script, name, 'TODO DIEGO')); + return new Script(executionContext, getNoURLScript.function, getNoURLScript.function, _ => new Map(), sourcesMapper); } constructor(public readonly executionContext: IExecutionContext, getRuntimeSource: (script: IScript) => ILoadedSource, getDevelopmentSource: (script: IScript) => ILoadedSource, diff --git a/src/chrome/internal/scripts/scriptsRegistry.ts b/src/chrome/internal/scripts/scriptsRegistry.ts new file mode 100644 index 000000000..bf85b631e --- /dev/null +++ b/src/chrome/internal/scripts/scriptsRegistry.ts @@ -0,0 +1,99 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Protocol as CDTP } from 'devtools-protocol'; + +import { IScript } from './script'; +import { ValidatedMap } from '../../collections/validatedMap'; +import { IResourceIdentifier, newResourceIdentifierMap } from '../sources/resourceIdentifier'; +import { ExecutionContext, IExecutionContext } from './executionContext'; +import { injectable } from 'inversify'; + +@injectable() +export class DeleteMeScriptsRegistry { + private readonly _scriptsGeneration = new ScriptsGeneration(); + private readonly _idToExecutionContext = new ValidatedMap(); + + public registerExecutionContext(executionContextId: CDTP.Runtime.ExecutionContextId): IExecutionContext { + const executionContext = new ExecutionContext(); + this._idToExecutionContext.set(executionContextId, executionContext); + return executionContext; + } + + public getExecutionContextById(executionContextId: CDTP.Runtime.ExecutionContextId): IExecutionContext { + return this._idToExecutionContext.get(executionContextId); + } + + public registerNewScript(scriptId: CDTP.Runtime.ScriptId, obtainScript: () => Promise): Promise { + return this._scriptsGeneration.registerNewScript(scriptId, obtainScript); + } + + public getCrdpId(script: IScript): any { + return this._scriptsGeneration.getCdtpId(script); + } + + public getScriptById(runtimeScriptCrdpId: CDTP.Runtime.ScriptId): Promise { + return this._scriptsGeneration.scriptById(runtimeScriptCrdpId); + } + + public getScriptsByPath(nameOrLocation: IResourceIdentifier): IScript[] { + return this._scriptsGeneration.getScriptByPath(nameOrLocation); + } + + public getAllScripts(): Promise { + return this._scriptsGeneration.getAllScripts(); + } +} + +export class ScriptsGeneration { + private readonly _cdtpIdByScript = new ValidatedMap>(); + private readonly _scriptByCdtpId = new ValidatedMap(); + private readonly _scriptByPath = newResourceIdentifierMap(); + + private createScriptInitialConfiguration(scriptId: CDTP.Runtime.ScriptId, script: IScript): void { + this._scriptByCdtpId.set(script, scriptId); + + let scriptsWithSamePath = this._scriptByPath.getOrAdd(script.runtimeSource.identifier, () => []); + scriptsWithSamePath.push(script); + } + + public async registerNewScript(scriptId: CDTP.Runtime.ScriptId, obtainScript: () => Promise): Promise { + const scriptWithConfigurationPromise = obtainScript().then(script => { + /** + * We need to configure the script here, so we can guarantee that clients who try to use a script will get + * blocked until the script is created, and all the initial configuration is done, so they can use APIs to get + * the script id, search by URL, etc... + */ + this.createScriptInitialConfiguration(scriptId, script); + return script; + }); + + this._cdtpIdByScript.set(scriptId, scriptWithConfigurationPromise); + + return await scriptWithConfigurationPromise; + } + + public getCdtpId(script: IScript): CDTP.Runtime.ScriptId { + const scriptId = this._scriptByCdtpId.get(script); + + if (script === undefined) { + throw new Error(`Couldn't find a CRDP id for script ${script}`); + } + + return scriptId; + } + + public scriptById(runtimeScriptCrdpId: string): Promise { + return this._cdtpIdByScript.get(runtimeScriptCrdpId); + } + + public getScriptByPath(path: IResourceIdentifier): IScript[] { + const runtimeScript = this._scriptByPath.tryGetting(path); + return runtimeScript || []; + } + + public getAllScripts(): Promise { + return Promise.all(Array.from(this._cdtpIdByScript.values())); + } +} diff --git a/src/chrome/internal/scripts/sourcesMapper.ts b/src/chrome/internal/scripts/sourcesMapper.ts index bd1efdd7a..2387fd055 100644 --- a/src/chrome/internal/scripts/sourcesMapper.ts +++ b/src/chrome/internal/scripts/sourcesMapper.ts @@ -11,13 +11,13 @@ export interface ISourcesMapper { getPositionInScript(positionInSource: IPositionInSource): IPositionInScript | null; } -interface IPositionInSource { +export interface IPositionInSource { readonly source: string; readonly line: LineNumber; readonly column?: ColumnNumber; } -interface IPositionInScript { +export interface IPositionInScript { readonly line: LineNumber; readonly column?: ColumnNumber; } @@ -34,7 +34,7 @@ export class SourcesMapper implements ISourcesMapper { public getPositionInScript(positionInSource: IPositionInSource): IPositionInScript | null { const mappedPosition = this._sourceMap.generatedPositionFor(positionInSource.source, positionInSource.line, positionInSource.column || 0); - return mappedPosition && mappedPosition.line + return mappedPosition && typeof mappedPosition.line === 'number' ? { line: createLineNumber(mappedPosition.line), column: createColumnNumber(mappedPosition.column) } : null; } diff --git a/src/chrome/internal/services/logging.ts b/src/chrome/internal/services/logging.ts new file mode 100644 index 000000000..a8717ce3f --- /dev/null +++ b/src/chrome/internal/services/logging.ts @@ -0,0 +1,32 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { Logger, logger } from 'vscode-debugadapter'; +import { IExtensibilityPoints } from '../../extensibility/extensibilityPoints'; + +export interface ILoggingConfiguration { + logLevel?: Logger.LogLevel; + shouldLogTimestamps: boolean; + logFilePath: string; +} + +export class Logging { + public verbose(entry: string): void { + logger.verbose(entry); + } + + public install(extensibilityPoints: IExtensibilityPoints, configuration: ILoggingConfiguration): this { + this.configure(extensibilityPoints, configuration); + return this; + } + + public configure(extensibilityPoints: IExtensibilityPoints, configuration: ILoggingConfiguration): void { + const logToFile = !!configuration.logLevel; + + // The debug configuration provider should have set logFilePath on the launch config. If not, default to 'true' to use the + // "legacy" log file path from the CDA subclass + const logFilePath = configuration.logFilePath || extensibilityPoints.logFilePath || logToFile; + logger.setup(configuration.logLevel || Logger.LogLevel.Warn, logFilePath, configuration.shouldLogTimestamps); + } +} \ No newline at end of file diff --git a/src/chrome/internal/sources/features/dotScriptsCommand.ts b/src/chrome/internal/sources/features/dotScriptsCommand.ts new file mode 100644 index 000000000..65dff7338 --- /dev/null +++ b/src/chrome/internal/sources/features/dotScriptsCommand.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { inject, injectable } from 'inversify'; +import { parseResourceIdentifier } from '../../../..'; +import { BaseSourceMapTransformer } from '../../../../transformers/baseSourceMapTransformer'; +import { IEventsToClientReporter } from '../../../client/eventSender'; +import { determineOrderingOfStrings } from '../../../collections/utilities'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { IScript } from '../../scripts/script'; +import { IScriptSourcesRetriever } from '../../../cdtpDebuggee/features/cdtpScriptSourcesRetriever'; +import { CDTPScriptsRegistry } from '../../../cdtpDebuggee/registries/cdtpScriptsRegistry'; + +@injectable() +export class DotScriptCommand { + constructor( + @inject(TYPES.BaseSourceMapTransformer) private readonly _sourceMapTransformer: BaseSourceMapTransformer, + @inject(TYPES.IScriptSources) private readonly _scriptSources: IScriptSourcesRetriever, + @inject(TYPES.EventSender) private readonly _eventsToClientReporter: IEventsToClientReporter, + @inject(TYPES.CDTPScriptsRegistry) private readonly _scriptsRegistry: CDTPScriptsRegistry) { } + + /** + * Handle the .scripts command, which can be used as `.scripts` to return a list of all script details, + * or `.scripts ` to show the contents of the given script. + */ + public handleScriptsCommand(scriptsRest: string): Promise { + let outputStringP: Promise; + if (scriptsRest) { + // `.scripts ` was used, look up the script by url + const requestedScript = this._scriptsRegistry.getScriptsByPath(parseResourceIdentifier(scriptsRest)); + if (requestedScript) { + outputStringP = this._scriptSources.getScriptSource(requestedScript[0]) + .then(result => { + const maxLength = 1e5; + return result.length > maxLength ? + result.substr(0, maxLength) + '[⋯]' : + result; + }); + } else { + outputStringP = Promise.resolve(`No runtime script with url: ${scriptsRest}\n`); + } + } else { + outputStringP = this.getAllScriptsString(); + } + + return outputStringP.then(scriptsStr => { + this._eventsToClientReporter.sendOutput({ output: scriptsStr, category: null }); + }); + } + + private async getAllScriptsString(): Promise { + const scripts = (await Promise.all([ + ...this._scriptsRegistry.getAllScripts() + ])).sort((left, script) => determineOrderingOfStrings(left.url, script.url)); + + const scriptsPrinted = await Promise.all(scripts.map(script => this.getOneScriptString(script))); + return scriptsPrinted.join('\n'); + } + + private async getOneScriptString(script: IScript): Promise { + let result = '› ' + script.runtimeSource.identifier.textRepresentation; + const clientPath = script.developmentSource.identifier.textRepresentation; + if (script.developmentSource !== script.runtimeSource) result += ` (${clientPath})`; + + const sourcePathDetails = await this._sourceMapTransformer.allSourcePathDetails(script.runtimeSource.identifier.canonicalized); + let mappedSourcesStr = sourcePathDetails.map(details => ` - ${details.originalPath} (${details.inferredPath})`).join('\n'); + if (sourcePathDetails.length) { + mappedSourcesStr = '\n' + mappedSourcesStr; + } + + return result + mappedSourcesStr; + } +} diff --git a/src/chrome/internal/sources/features/notifyClientOfLoadedSources.ts b/src/chrome/internal/sources/features/notifyClientOfLoadedSources.ts new file mode 100644 index 000000000..ef7b415fb --- /dev/null +++ b/src/chrome/internal/sources/features/notifyClientOfLoadedSources.ts @@ -0,0 +1,77 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IComponent } from '../../features/feature'; +import { IScript } from '../../scripts/script'; +import { telemetry } from '../../../../telemetry'; +import { ISourceWasLoadedParameters, IEventsToClientReporter } from '../../../client/eventSender'; +import { ValidatedMap } from '../../../collections/validatedMap'; +import { CDTPScriptUrl } from '../resourceIdentifierSubtypes'; +import { LoadedSourceEventReason, utils } from '../../../..'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { IScriptParsedEvent } from '../../../cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider'; + +export interface INotifyClientOfLoadedSourcesDependencies { + sendSourceWasLoaded(params: ISourceWasLoadedParameters): Promise; + onScriptParsed(listener: (scriptEvent: IScriptParsedEvent) => Promise): void; +} + +@injectable() +export class NotifyClientOfLoadedSources implements IComponent { + // TODO DIEGO: Ask VS what index do they use internally to verify if the source is the same or a new one + private _notifiedSourceByUrl = new ValidatedMap(); + + public install(): this { + this._dependencies.onScriptParsed(async scriptParsed => this.sendLoadedSourceEvent(scriptParsed.script, 'new')); + return this; + } + + /** + * e.g. the target navigated + */ + protected onExecutionContextsCleared(): void { + for (const script of this._notifiedSourceByUrl.values()) { + this.sendLoadedSourceEvent(script, 'removed'); + } + } + + protected async sendLoadedSourceEvent(script: IScript, loadedSourceEventReason: LoadedSourceEventReason): Promise { + switch (loadedSourceEventReason) { + case 'new': + case 'changed': + if (script.executionContext.isDestroyed()) { + return; // We processed the events out of order, and this event got here after we destroyed the context. ignore it. + } + + if (this._notifiedSourceByUrl.tryGetting(script.url) !== undefined) { + const exists = await utils.existsAsync(script.developmentSource.identifier.canonicalized); + if (exists) { + // We only need to send changed events for dynamic scripts. The client tracks files on storage on it's own, so this notification is not needed + loadedSourceEventReason = 'changed'; + } else { + return; // VS is strict about the changed notifications, and it will fail if we send a changed notification for a file on storage, so we omit it on purpose + } + } else { + loadedSourceEventReason = 'new'; + } + this._notifiedSourceByUrl.set(script.url, script); + break; + case 'removed': + if (!this._notifiedSourceByUrl.delete(script.url)) { + telemetry.reportEvent('LoadedSourceEventError', { issue: 'Tried to remove non-existent script', scriptId: script }); + return; + } + break; + default: + telemetry.reportEvent('LoadedSourceEventError', { issue: 'Unknown reason', reason: loadedSourceEventReason }); + } + + this._eventsToClientReporter.sendSourceWasLoaded({ reason: loadedSourceEventReason, source: script.runtimeSource }); + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: INotifyClientOfLoadedSourcesDependencies, + @inject(TYPES.EventSender) private readonly _eventsToClientReporter: IEventsToClientReporter) { } +} \ No newline at end of file diff --git a/src/chrome/internal/sources/loadedSource.ts b/src/chrome/internal/sources/loadedSource.ts index a09e139ff..b4b13e240 100644 --- a/src/chrome/internal/sources/loadedSource.ts +++ b/src/chrome/internal/sources/loadedSource.ts @@ -9,15 +9,24 @@ import { determineOrderingOfStrings } from '../../collections/utilities'; import { IEquivalenceComparable } from '../../utils/equivalence'; /** - * This interface represents a source or text that is related to a script that the debuggee is executing. The text can be the contents of the script itself, + * This interface represents a source or text that is related to a script that the debugee is executing. The text can be the contents of the script itself, * or a file from which the script was loaded, or a file that was compiled to generate the contents of the script */ +const ImplementsLoadedSource = Symbol(); export interface ILoadedSource extends IEquivalenceComparable { + [ImplementsLoadedSource]: void; + readonly script: IScript; readonly identifier: IResourceIdentifier; readonly origin: string; doesScriptHasUrl(): boolean; // TODO DIEGO: Figure out if we can delete this property isMappedSource(): boolean; + + isEquivalentTo(right: ILoadedSource): boolean; +} + +export function isLoadedSource(object: object): object is ILoadedSource { + return object.hasOwnProperty(ImplementsLoadedSource); } /** @@ -29,7 +38,9 @@ export interface ILoadedSource extends IEquivalenceComparable * 2. Two: We assume one path is from the webserver, and the other path is in the workspace: RuntimeScriptWithSourceOnWorkspace */ -abstract class LoadedSourceWithURLCommonLogic implements ILoadedSource { +abstract class BaseLoadedSourceWithURL implements ILoadedSource { + [ImplementsLoadedSource]: void; + public isMappedSource(): boolean { return false; } @@ -52,12 +63,14 @@ abstract class LoadedSourceWithURLCommonLogic implements ILoad public readonly origin: string) { } } -export class ScriptRunFromLocalStorage extends LoadedSourceWithURLCommonLogic implements ILoadedSource { } -export class DynamicScript extends LoadedSourceWithURLCommonLogic implements ILoadedSource { } -export class ScriptRuntimeSource extends LoadedSourceWithURLCommonLogic implements ILoadedSource { } -export class ScriptDevelopmentSource extends LoadedSourceWithURLCommonLogic implements ILoadedSource { } +export class ScriptRunFromLocalStorage extends BaseLoadedSourceWithURL implements ILoadedSource { } +export class DynamicScript extends BaseLoadedSourceWithURL implements ILoadedSource { } +export class ScriptRuntimeSource extends BaseLoadedSourceWithURL implements ILoadedSource { } +export class ScriptDevelopmentSource extends BaseLoadedSourceWithURL implements ILoadedSource { } export class NoURLScriptSource implements ILoadedSource { + [ImplementsLoadedSource]: void; + public get identifier(): IResourceIdentifier { return parseResourceIdentifier(`${NoURLScriptSource.EVAL_PSEUDO_PREFIX}${this.name.textRepresentation}` as any); } @@ -90,7 +103,7 @@ export class NoURLScriptSource implements ILoadedSource { } // This represents a path to a development source that was compiled to generate the runtime code of the script -export class MappedSource extends LoadedSourceWithURLCommonLogic implements ILoadedSource { +export class MappedSource extends BaseLoadedSourceWithURL implements ILoadedSource { public isMappedSource(): boolean { return true; } diff --git a/src/chrome/internal/sources/resourceIdentifier.ts b/src/chrome/internal/sources/resourceIdentifier.ts index 21ddaf375..0672594cc 100644 --- a/src/chrome/internal/sources/resourceIdentifier.ts +++ b/src/chrome/internal/sources/resourceIdentifier.ts @@ -3,10 +3,10 @@ *--------------------------------------------------------*/ import * as path from 'path'; -import { utils } from '../../..'; import { IValidatedMap } from '../../collections/validatedMap'; import { MapUsingProjection } from '../../collections/mapUsingProjection'; import { IEquivalenceComparable } from '../../utils/equivalence'; +import * as utils from '../../../utils'; /** * Hierarchy: @@ -23,14 +23,23 @@ import { IEquivalenceComparable } from '../../utils/equivalence'; */ /** This interface represents a text to identify a particular resource. This class will properly compare urls and file paths, while preserving the original case that was used for the identifier */ +const ImplementsResourceIdentifier = Symbol(); export interface IResourceIdentifier extends IEquivalenceComparable { + [ImplementsResourceIdentifier]: void; + readonly textRepresentation: TString; readonly canonicalized: string; isEquivalentTo(right: IResourceIdentifier): boolean; isLocalFilePath(): boolean; } +export function isResourceIdentifier(object: object): object is IResourceIdentifier { + return object.hasOwnProperty(ImplementsResourceIdentifier); +} + abstract class IsEquivalentCommonLogic { + [ImplementsResourceIdentifier]: void; + public abstract get canonicalized(): string; public isEquivalentTo(right: IResourceIdentifier): boolean { @@ -102,6 +111,8 @@ export class LocalFileURL extends IsEquivalentC // Any URL that is not a 'file:///' url export class NonLocalFileURL extends IsEquivalentAndConstructorCommonLogic implements IURL { + [ImplementsResourceIdentifier]: void; + public toString(): string { return path.basename(this.textRepresentation); } diff --git a/src/chrome/internal/sources/source.ts b/src/chrome/internal/sources/source.ts index cdfbb9ca8..5f7e4c89c 100644 --- a/src/chrome/internal/sources/source.ts +++ b/src/chrome/internal/sources/source.ts @@ -11,12 +11,23 @@ import { IEquivalenceComparable } from '../../utils/equivalence'; * VS Code debug protocol sends breakpoint requests with a path?: string; or sourceReference?: number; Before we can use the path, we need to wait for the related script to be loaded so we can match it with a script id. * This set of classes will let us represent the information we get from either a path or a sourceReference, and then let us try to resolve it to a script id when possible. */ +const ImplementsSource = Symbol(); export interface ISource extends IEquivalenceComparable { + [ImplementsSource]: void; + readonly sourceIdentifier: IResourceIdentifier; tryResolving(succesfulAction: (resolvedSource: ILoadedSource) => R, failedAction: (sourceIdentifier: IResourceIdentifier) => R): R; + + isEquivalentTo(right: ISource): boolean; } -abstract class SourceCommonLogic implements ISource { +export function isSource(object: object): object is ISource { + return object.hasOwnProperty(ImplementsSource); +} + +abstract class BaseSource implements ISource { + [ImplementsSource]: void; + public abstract tryResolving(succesfulAction: (loadedSource: ILoadedSource) => R, failedAction: (identifier: IResourceIdentifier) => R): R; public abstract get sourceIdentifier(): IResourceIdentifier; @@ -26,7 +37,7 @@ abstract class SourceCommonLogic implements ISource { } // Find the related source by using the source's path -export class SourceToBeResolvedViaPath extends SourceCommonLogic implements ISource { +export class SourceToBeResolvedViaPath extends BaseSource implements ISource { public tryResolving(succesfulAction: (resolvedSource: ILoadedSource) => R, failedAction: (sourceIdentifier: IResourceIdentifier) => R) { return this._sourceResolver.tryResolving(this.sourceIdentifier, succesfulAction, failedAction); } @@ -41,7 +52,7 @@ export class SourceToBeResolvedViaPath extends SourceCommonLogic implements ISou } // This source was already loaded, so we store it in this class -export class SourceAlreadyResolvedToLoadedSource extends SourceCommonLogic implements ISource { +export class SourceAlreadyResolvedToLoadedSource extends BaseSource implements ISource { public tryResolving(succesfulAction: (resolvedSource: ILoadedSource) => R, _failedAction: (sourceIdentifier: IResourceIdentifier) => R) { return succesfulAction(this.loadedSource); } diff --git a/src/chrome/internal/sources/sourceResolver.ts b/src/chrome/internal/sources/sourceResolver.ts index 28ea841f7..a6c5c531a 100644 --- a/src/chrome/internal/sources/sourceResolver.ts +++ b/src/chrome/internal/sources/sourceResolver.ts @@ -8,12 +8,7 @@ import { newResourceIdentifierMap, IResourceIdentifier } from './resourceIdentif import { IComponent } from '../features/feature'; import { injectable, inject } from 'inversify'; import { TYPES } from '../../dependencyInjection.ts/types'; -import { IScript } from '../scripts/script'; - -// TODO: Delete this and use the proper interface -interface IScriptParsedEvent { - script: IScript; -} +import { IScriptParsedEvent } from '../../cdtpDebuggee/eventsProviders/cdtpOnScriptParsedEventProvider'; export interface IEventsConsumedBySourceResolver { onScriptParsed(listener: (scriptEvent: IScriptParsedEvent) => Promise): void; diff --git a/src/chrome/internal/sources/sourcesLogic.ts b/src/chrome/internal/sources/sourcesLogic.ts new file mode 100644 index 000000000..f1829e7a9 --- /dev/null +++ b/src/chrome/internal/sources/sourcesLogic.ts @@ -0,0 +1,64 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { SourceTextLogic } from './sourcesTextLogic'; +import { SourcesTreeNodeLogic } from './sourcesTreeNodeLogic'; +import { SourceResolver } from './sourceResolver'; +import { ILoadedSource, ILoadedSourceTreeNode } from './loadedSource'; +import { ISource } from './source'; +import { IScript } from '../scripts/script'; +import { IResourceIdentifier } from './resourceIdentifier'; +import { IComponent } from '../features/feature'; +import { injectable } from 'inversify'; + +@injectable() +export class SourcesLogic implements IComponent { + public tryResolving(sourceIdentifier: IResourceIdentifier, + ifSuccesfulDo: (resolvedSource: ILoadedSource) => R, + ifFailedDo?: (sourceIdentifier: IResourceIdentifier) => R): R { + return this._sourceResolverLogic.tryResolving(sourceIdentifier, ifSuccesfulDo, ifFailedDo); + } + + public createSourceResolver(sourceIdentifier: IResourceIdentifier): ISource { + return this._sourceResolverLogic.createUnresolvedSource(sourceIdentifier); + } + + public async getLoadedSourcesTrees(): Promise { + return this._sourceTreeNodeLogic.getLoadedSourcesTrees(); + } + + public getLoadedSourcesTreeForScript(script: IScript): ILoadedSourceTreeNode { + return this._sourceTreeNodeLogic.getLoadedSourcesTreeForScript(script); + } + + public async getScriptText(script: IScript): Promise { + return await this._sourceTextLogic.text(script.runtimeSource); + } + + public async getText(source: ISource): Promise { + return await source.tryResolving( + async loadedSource => await this._sourceTextLogic.text(loadedSource), + identifier => { + throw new Error(`Couldn't resolve the source with the path: ${identifier.textRepresentation}`); + }); + } + + public toString(): string { + return `Sources logic {\nResolver:\n${this._sourceResolverLogic}\n` + + `Text:\n${this._sourceTextLogic}\nTree node:\n${this._sourceTreeNodeLogic}\n}`; + } + + public async install(): Promise { + await this._sourceResolverLogic.install(); + await this._sourceTextLogic.install(); + await this._sourceTreeNodeLogic.install(); + return this; + } + + constructor( + private readonly _sourceResolverLogic: SourceResolver, + private readonly _sourceTextLogic: SourceTextLogic, + private readonly _sourceTreeNodeLogic: SourcesTreeNodeLogic + ) { } +} \ No newline at end of file diff --git a/src/chrome/internal/sources/sourcesTextLogic.ts b/src/chrome/internal/sources/sourcesTextLogic.ts new file mode 100644 index 000000000..b9e6de765 --- /dev/null +++ b/src/chrome/internal/sources/sourcesTextLogic.ts @@ -0,0 +1,37 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ILoadedSource } from './loadedSource'; +import { ValidatedMap } from '../../collections/validatedMap'; +import { printIterable } from '../../collections/printting'; +import { IComponent } from '../features/feature'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { IScriptSourcesRetriever } from '../../cdtpDebuggee/features/cdtpScriptSourcesRetriever'; + +@injectable() +export class SourceTextLogic implements IComponent { + private _sourceToText = new ValidatedMap(); + + public async text(loadedSource: ILoadedSource): Promise { + let text = this._sourceToText.tryGetting(loadedSource); + + if (text !== null) { + text = await this._scriptSources.getScriptSource(loadedSource.script); + this._sourceToText.set(loadedSource, text); + } + + return text; + } + + public toString(): string { + return `Sources text logic\n${printIterable('sources in cache', this._sourceToText.keys())}`; + } + + public install(): this { + return this; + } + + constructor(@inject(TYPES.IScriptSources) private readonly _scriptSources: IScriptSourcesRetriever) { } +} \ No newline at end of file diff --git a/src/chrome/internal/sources/sourcesTreeNodeLogic.ts b/src/chrome/internal/sources/sourcesTreeNodeLogic.ts new file mode 100644 index 000000000..98c65d9bd --- /dev/null +++ b/src/chrome/internal/sources/sourcesTreeNodeLogic.ts @@ -0,0 +1,48 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ILoadedSource, ILoadedSourceTreeNode, determineOrderingOfLoadedSources } from './loadedSource'; +import { IScript } from '../scripts/script'; +import { IComponent } from '../features/feature'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { CDTPScriptsRegistry } from '../../cdtpDebuggee/registries/cdtpScriptsRegistry'; + +@injectable() +export class SourcesTreeNodeLogic implements IComponent { + /* + We create a tree like: + + RuntimeSource_1 + + RuntimeSource_2 + - Source of Compiled_2_a + - Source of Compiled_2_b + */ + // TODO DIEGO: Verify if this is the format we should use for the tree + public async getLoadedSourcesTrees(): Promise { + const scripts = await Promise.all(Array.from(await this._cdtpScriptsRegistry.getAllScripts())); + const sourceMetadataTree = scripts.map(script => this.getLoadedSourcesTreeForScript(script)); + return sourceMetadataTree; + } + + public getLoadedSourcesTreeForScript(script: IScript): ILoadedSourceTreeNode { + const sortedSourcesOfCompiled = script.mappedSources.sort(determineOrderingOfLoadedSources); + return this.toTreeNode(script.runtimeSource, this.toTreeNodes(sortedSourcesOfCompiled)); + } + + private toTreeNodes(sources: ILoadedSource[]): ILoadedSourceTreeNode[] { + return sources.map(source => this.toTreeNode(source, [])); + } + + private toTreeNode(source: ILoadedSource, relatedSources: ILoadedSourceTreeNode[] = []): ILoadedSourceTreeNode { + // TODO DIEGO: MAKE ORIGIN WORK + // const origin = [this._chromeDebugAdapter.getReadonlyOrigin(source.script.runtimeSource.identifier.textRepresentation)]; + return { mainSource: source, relatedSources: relatedSources }; + } + + public install(): this { + return this; + } + + constructor(@inject(TYPES.CDTPScriptsRegistry) private readonly _cdtpScriptsRegistry: CDTPScriptsRegistry) { } +} \ No newline at end of file diff --git a/src/chrome/internal/sources/unresolvedSource.ts b/src/chrome/internal/sources/unresolvedSource.ts new file mode 100644 index 000000000..c6fc6af54 --- /dev/null +++ b/src/chrome/internal/sources/unresolvedSource.ts @@ -0,0 +1,56 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IResourceIdentifier } from './resourceIdentifier'; +import { ILoadedSource } from './loadedSource'; +import { SourceResolver } from './sourceResolver'; +import { IEquivalenceComparable } from '../../utils/equivalence'; + +export interface IUnresolvedSource extends IEquivalenceComparable { + readonly sourceIdentifier: IResourceIdentifier; + isEquivalentTo(right: IUnresolvedSource): boolean; + tryResolving(succesfulAction: (resolvedSource: ILoadedSource) => R, failedAction: (sourceIdentifier: IResourceIdentifier) => R): R; +} + +abstract class BaseUnresolvedSource implements IUnresolvedSource { + public abstract tryResolving(succesfulAction: (loadedSource: ILoadedSource) => R, failedAction: (identifier: IResourceIdentifier) => R): R; + public abstract get sourceIdentifier(): IResourceIdentifier; + + public isEquivalentTo(right: IUnresolvedSource): boolean { + return this.sourceIdentifier.isEquivalentTo(right.sourceIdentifier); + } +} + +// Find the source to resolve to by using the path +export class SourceToBeResolvedViaPath extends BaseUnresolvedSource implements IUnresolvedSource { + public tryResolving(succesfulAction: (resolvedSource: ILoadedSource) => R, failedAction: (sourceIdentifier: IResourceIdentifier) => R) { + return this._sourceResolver.tryResolving(this.sourceIdentifier, succesfulAction, failedAction); + } + + public toString(): string { + return `Resolve source using #${this.sourceIdentifier}`; + } + + constructor(public readonly sourceIdentifier: IResourceIdentifier, private readonly _sourceResolver: SourceResolver) { + super(); + } +} + +export class SourceAlreadyResolvedToLoadedSource extends BaseUnresolvedSource implements IUnresolvedSource { + public tryResolving(succesfulAction: (resolvedSource: ILoadedSource) => R, _failedAction: (sourceIdentifier: IResourceIdentifier) => R) { + return succesfulAction(this.loadedSource); + } + + public get sourceIdentifier(): IResourceIdentifier { + return this.loadedSource.identifier; + } + + public toString(): string { + return `${this.loadedSource}`; + } + + constructor(public readonly loadedSource: ILoadedSource) { + super(); + } +} diff --git a/src/chrome/internal/stackTraces/formatCallFrameDescription.ts b/src/chrome/internal/stackTraces/formatCallFrameDescription.ts new file mode 100644 index 000000000..903679f31 --- /dev/null +++ b/src/chrome/internal/stackTraces/formatCallFrameDescription.ts @@ -0,0 +1,30 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import * as path from 'path'; +import { DebugProtocol } from 'vscode-debugprotocol'; +import { LoadedSourceCallFrame } from './callFrame'; +import { functionDescription } from './callFramePresentation'; + +/** + * The clients can requests the stack traces frames descriptions in different formats. + * We use this function to create the description for the call frame according to the parameters supplied by the client. + */ +export function formatCallFrameDescription(callFrame: LoadedSourceCallFrame, formatArgs?: DebugProtocol.StackFrameFormat): string { + const location = callFrame.location; + + let formattedDescription = functionDescription(callFrame.codeFlow.functionName, location.source.script); + + if (formatArgs) { + if (formatArgs.module) { + formattedDescription += ` [${path.basename(location.source.identifier.textRepresentation)}]`; + } + + if (formatArgs.line) { + formattedDescription += ` Line ${location.position.lineNumber}`; + } + } + + return formattedDescription; +} diff --git a/src/chrome/internal/stackTraces/stackTracesLogic.ts b/src/chrome/internal/stackTraces/stackTracesLogic.ts new file mode 100644 index 000000000..5e19feb32 --- /dev/null +++ b/src/chrome/internal/stackTraces/stackTracesLogic.ts @@ -0,0 +1,147 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { DebugProtocol } from 'vscode-debugprotocol'; +import { injectable, inject } from 'inversify'; + +import * as errors from '../../../errors'; + +import * as nls from 'vscode-nls'; +const localize = nls.loadMessageBundle(); +import { PausedEvent } from '../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { CodeFlowStackTrace } from './codeFlowStackTrace'; +import { IScript } from '../scripts/script'; +import { CodeFlowFrame, ScriptCallFrame } from './callFrame'; +import { LocationInLoadedSource } from '../locations/location'; +import { CallFramePresentation, SourcePresentationHint, ICallFramePresentationDetails } from './callFramePresentation'; +import { IComponent, ComponentConfiguration } from '../features/feature'; +import { InformationAboutPausedProvider } from '../features/takeProperActionOnPausedEvent'; +import { asyncMap } from '../../collections/async'; +import { TYPES } from '../../dependencyInjection.ts/types'; +import { ConnectedCDAConfiguration } from '../../..'; +import { IVote, Abstained } from '../../communication/collaborativeDecision'; +import { IAsyncDebuggingConfigurer } from '../../cdtpDebuggee/features/cdtpAsyncDebuggingConfigurer'; +import { IStackTracePresentationRow, StackTraceLabel, CallFramePresentationHint } from './stackTracePresentationRow'; +import { IStackTracePresentation } from './stackTracePresentation'; + +export interface IEventsConsumedByStackTrace { + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; + onResumed(listener: () => void): void; +} + +export interface IStackTracePresentationLogicProvider { + getCallFrameAdditionalDetails(locationInLoadedSource: LocationInLoadedSource): ICallFramePresentationDetails[]; +} + +export interface IStackTracesConfiguration { + showAsyncStacks: boolean; +} + +@injectable() +export class StackTracesLogic implements IComponent { + public static ASYNC_CALL_STACK_DEPTH = 4; + + private _currentPauseEvent: PausedEvent | null = null; + + public onResumed(): any { + this._currentPauseEvent = null; + } + + public async onPaused(pausedEvent: PausedEvent): Promise> { + this._currentPauseEvent = pausedEvent; + return new Abstained(this); + } + + public async stackTrace(args: DebugProtocol.StackTraceArguments): Promise { + if (!this._currentPauseEvent) { + return Promise.reject(errors.noCallStackAvailable()); + } + + const syncFames: IStackTracePresentationRow[] = await asyncMap(this._currentPauseEvent.callFrames, frame => this.toPresentation(frame, args.format)); + const asyncStackTrace = this._currentPauseEvent.asyncStackTrace; + let stackFrames = asyncStackTrace ? syncFames.concat(await this.asyncCallFrames(asyncStackTrace, args.format)) : syncFames; + + const totalFrames = stackFrames.length; + if (typeof args.startFrame === 'number') { + stackFrames = stackFrames.slice(args.startFrame); + } + + if (typeof args.levels === 'number') { + stackFrames = stackFrames.slice(0, args.levels); + } + + const stackTraceResponse: IStackTracePresentation = { + stackFrames, + totalFrames + }; + + return stackTraceResponse; + } + + private async asyncCallFrames(stackTrace: CodeFlowStackTrace, formatArgs?: DebugProtocol.StackFrameFormat): Promise { + const asyncFrames: IStackTracePresentationRow[] = await asyncMap(stackTrace.codeFlowFrames, + frame => this.toPresentation(this.codeFlowToCallFrame(frame), formatArgs)); + + asyncFrames.unshift(new StackTraceLabel(stackTrace.description)); + + return asyncFrames.concat(stackTrace.parent ? await this.asyncCallFrames(stackTrace.parent, formatArgs) : []); + } + + private codeFlowToCallFrame(frame: CodeFlowFrame): ScriptCallFrame { + return new ScriptCallFrame(frame, [], undefined, undefined); + } + + private async toPresentation(frame: ScriptCallFrame, formatArgs?: DebugProtocol.StackFrameFormat): Promise { + // DIEGO TODO: Make getReadonlyOrigin work again + // this.getReadonlyOrigin(frame.location.script.runtimeSource.identifier.textRepresentation) + let presentationHint: CallFramePresentationHint = 'normal'; + + // Apply hints to skipped frames + const getSkipReason = (reason: string) => localize('skipReason', "(skipped by '{0}')", reason); + const locationInLoadedSource = frame.location.mappedToSource(); + const providedDetails: ICallFramePresentationDetails[] = [].concat(await asyncMap([this._stackTracePresentationLogicProviders], provider => + provider.getCallFrameAdditionalDetails(locationInLoadedSource))); + const actualDetails = providedDetails.length === 0 + ? [{ + additionalSourceOrigins: [] as string[], + sourcePresentationHint: 'normal' as SourcePresentationHint + }] + : providedDetails; // Here we guarantee that actualDetails.length > 0 + const allAdditionalSourceOrigins = await asyncMap(actualDetails, detail => detail.additionalSourceOrigins); + + const presentationDetails: ICallFramePresentationDetails = { + additionalSourceOrigins: [getSkipReason(allAdditionalSourceOrigins.join(','))], + sourcePresentationHint: actualDetails[0].sourcePresentationHint // We know that actualDetails.length > 0 + }; + + return new CallFramePresentation(frame.mappedToSource(), + formatArgs, presentationDetails, presentationHint); + } + + public async install(): Promise { + this._dependencies.subscriberForAskForInformationAboutPaused(params => this.onPaused(params)); + this._dependencies.onResumed(() => this.onResumed()); + return await this.configure(this._configuration); + } + + private async configure(configuration: ComponentConfiguration): Promise { + const showAsyncStacks = typeof configuration.args.showAsyncStacks === 'undefined' || configuration.args.showAsyncStacks; + const maxDepth = showAsyncStacks ? StackTracesLogic.ASYNC_CALL_STACK_DEPTH : 0; + + try { + await this._breakpointFeaturesSupport.setAsyncCallStackDepth(maxDepth); + } catch (e) { + // Not supported by older runtimes, ignore it. + } + return this; + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IEventsConsumedByStackTrace, + // TODO DIEGO: @multiInject(new LazyServiceIdentifer(() => TYPES.IStackTracePresentationLogicProvider)) private readonly _stackTracePresentationLogicProviders: IStackTracePresentationLogicProvider[], + @inject(TYPES.IStackTracePresentationLogicProvider) private readonly _stackTracePresentationLogicProviders: IStackTracePresentationLogicProvider, + @inject(TYPES.IAsyncDebuggingConfiguration) private readonly _breakpointFeaturesSupport: IAsyncDebuggingConfigurer, + @inject(TYPES.ConnectedCDAConfiguration) private readonly _configuration: ConnectedCDAConfiguration) { + } +} \ No newline at end of file diff --git a/src/chrome/internal/stepping/features/asyncStepping.ts b/src/chrome/internal/stepping/features/asyncStepping.ts new file mode 100644 index 000000000..2ca7be129 --- /dev/null +++ b/src/chrome/internal/stepping/features/asyncStepping.ts @@ -0,0 +1,45 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IComponent } from '../../features/feature'; +import { InformationAboutPausedProvider, ResumeCommonLogic } from '../../features/takeProperActionOnPausedEvent'; +import { VoteRelevance, IVote, Abstained } from '../../../communication/collaborativeDecision'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { PausedEvent } from '../../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { IDebugeeExecutionController } from '../../../cdtpDebuggee/features/cdtpDebugeeExecutionController'; +import { IDebugeeSteppingController } from '../../../cdtpDebuggee/features/cdtpDebugeeSteppingController'; + +export interface IEventsConsumedByAsyncStepping { + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; +} + +export class PausedBecauseAsyncCallWasScheduled extends ResumeCommonLogic { + public readonly relevance = VoteRelevance.FallbackVote; + + constructor(protected _debugeeExecutionControl: IDebugeeExecutionController) { + super(); + } +} + +@injectable() +export class AsyncStepping implements IComponent { + public async askForInformationAboutPaused(paused: PausedEvent): Promise> { + if (paused.asyncCallStackTraceId) { + await this._debugeeStepping.pauseOnAsyncCall({ parentStackTraceId: paused.asyncCallStackTraceId }); + return new PausedBecauseAsyncCallWasScheduled(this._debugeeExecutionControl); + } + + return new Abstained(this); + } + + public install(): void { + this._dependencies.subscriberForAskForInformationAboutPaused(paused => this.askForInformationAboutPaused(paused)); + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: IEventsConsumedByAsyncStepping, + @inject(TYPES.IDebugeeExecutionControl) private readonly _debugeeExecutionControl: IDebugeeExecutionController, + @inject(TYPES.IDebugeeSteppingController) private readonly _debugeeStepping: IDebugeeSteppingController) { } +} \ No newline at end of file diff --git a/src/chrome/internal/stepping/features/syncStepping.ts b/src/chrome/internal/stepping/features/syncStepping.ts new file mode 100644 index 000000000..f3f900e9d --- /dev/null +++ b/src/chrome/internal/stepping/features/syncStepping.ts @@ -0,0 +1,80 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ScriptCallFrame } from '../../stackTraces/callFrame'; +import { InformationAboutPausedProvider, } from '../../features/takeProperActionOnPausedEvent'; +import { IComponent } from '../../features/feature'; +import { Abstained, IVote } from '../../../communication/collaborativeDecision'; +import { injectable, inject } from 'inversify'; +import { IDebugeeExecutionController } from '../../../cdtpDebuggee/features/cdtpDebugeeExecutionController'; +import { TYPES } from '../../../dependencyInjection.ts/types'; +import { PausedEvent } from '../../../cdtpDebuggee/eventsProviders/cdtpDebuggeeExecutionEventsProvider'; +import { IDebugeeSteppingController } from '../../../cdtpDebuggee/features/cdtpDebugeeSteppingController'; + +type SteppingAction = () => Promise; + +interface SyncSteppingStatus { + startStepping(): SyncSteppingStatus; +} + +class CurrentlyStepping implements SyncSteppingStatus { + public startStepping(): SyncSteppingStatus { + throw new Error('Cannot start stepping again while the program is already stepping'); + } + +} + +class CurrentlyIdle implements SyncSteppingStatus { + public startStepping(): SyncSteppingStatus { + return new CurrentlyStepping(); + } +} + +export interface ISyncSteppingDependencies { + subscriberForAskForInformationAboutPaused(listener: InformationAboutPausedProvider): void; +} + +@injectable() +export class SyncStepping implements IComponent { + private _status: SyncSteppingStatus = new CurrentlyIdle(); + + public stepOver = this.createSteppingMethod(() => this._debugeeStepping.stepOver()); + public stepInto = this.createSteppingMethod(() => this._debugeeStepping.stepInto({ breakOnAsyncCall: true })); + public stepOut = this.createSteppingMethod(() => this._debugeeStepping.stepOut()); + + public continue(): Promise { + return this._debugeeExecutionControl.resume(); + } + + public pause(): Promise { + return this._debugeeExecutionControl.pause(); + } + + private async askForInformationAboutPaused(_paused: PausedEvent): Promise> { + return new Abstained(this); + } + + public async restartFrame(callFrame: ScriptCallFrame): Promise { + this._status = this._status.startStepping(); + await this._debugeeStepping.restartFrame(callFrame); + await this._debugeeStepping.stepInto({ breakOnAsyncCall: true }); + } + + private createSteppingMethod(steppingAction: SteppingAction): (() => Promise) { + return async () => { + this._status = this._status.startStepping(); + await steppingAction(); + this._status = new CurrentlyIdle(); + }; + } + + public install(): void { + this._dependencies.subscriberForAskForInformationAboutPaused(paused => this.askForInformationAboutPaused(paused)); + } + + constructor( + @inject(TYPES.EventsConsumedByConnectedCDA) private readonly _dependencies: ISyncSteppingDependencies, + @inject(TYPES.IDebugeeSteppingController) private readonly _debugeeStepping: IDebugeeSteppingController, + @inject(TYPES.IDebugeeExecutionControl) private readonly _debugeeExecutionControl: IDebugeeExecutionController) { } +} \ No newline at end of file diff --git a/src/chrome/internal/stepping/stepping.ts b/src/chrome/internal/stepping/stepping.ts new file mode 100644 index 000000000..ebce9cfb0 --- /dev/null +++ b/src/chrome/internal/stepping/stepping.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { IComponent } from '../features/feature'; +import { AsyncStepping } from './features/asyncStepping'; +import { SyncStepping } from './features/syncStepping'; +import { ScriptCallFrame } from '../stackTraces/callFrame'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../../dependencyInjection.ts/types'; + +@injectable() +export class Stepping implements IComponent { + public continue(): Promise { + return this._syncStepping.continue(); + } + + public next(): Promise { + return this._syncStepping.stepOver(); + } + + public stepIn(): Promise { + return this._syncStepping.stepInto(); + } + + public stepOut(): Promise { + return this._syncStepping.stepOut(); + } + + public pause(): Promise { + return this._syncStepping.pause(); + } + + public restartFrame(callFrame: ScriptCallFrame): Promise { + return this._syncStepping.restartFrame(callFrame); + } + + public install(): this { + this._asyncStepping.install(); + return this; + } + + constructor( + @inject(TYPES.SyncStepping) private readonly _syncStepping: SyncStepping, + @inject(TYPES.AsyncStepping) private readonly _asyncStepping: AsyncStepping + ) { } +} \ No newline at end of file diff --git a/src/chrome/internalSourceBreakpoint.ts b/src/chrome/internalSourceBreakpoint.ts index ba42322b1..e4a5b39f4 100644 --- a/src/chrome/internalSourceBreakpoint.ts +++ b/src/chrome/internalSourceBreakpoint.ts @@ -2,8 +2,10 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import { Protocol as Crdp } from 'devtools-protocol'; import { DebugProtocol } from 'vscode-debugprotocol'; +import { CodeFlowStackTrace } from './internal/stackTraces/codeFlowStackTrace'; +import { parseResourceIdentifier } from './internal/sources/resourceIdentifier'; +import { createCDTPScriptUrl } from './internal/sources/resourceIdentifierSubtypes'; export class InternalSourceBreakpoint { static readonly LOGPOINT_URL = 'vscode.logpoint.js'; @@ -29,15 +31,16 @@ export class InternalSourceBreakpoint { } } -function isLogpointStack(stackTrace: Crdp.Runtime.StackTrace | null): boolean { - return stackTrace && stackTrace.callFrames.length > 0 && stackTrace.callFrames[0].url === InternalSourceBreakpoint.LOGPOINT_URL; +function isLogpointStack(stackTrace: CodeFlowStackTrace | null): boolean { + return stackTrace && stackTrace.codeFlowFrames.length > 0 + && stackTrace.codeFlowFrames[0].script.runtimeSource.identifier.isEquivalentTo(parseResourceIdentifier(createCDTPScriptUrl(InternalSourceBreakpoint.LOGPOINT_URL))); } -export function stackTraceWithoutLogpointFrame(stackTrace: Crdp.Runtime.StackTrace): Crdp.Runtime.StackTrace { +export function stackTraceWithoutLogpointFrame(stackTrace: CodeFlowStackTrace): CodeFlowStackTrace { if (isLogpointStack(stackTrace)) { return { ...stackTrace, - callFrames: stackTrace.callFrames.slice(1) + codeFlowFrames: stackTrace.codeFlowFrames.slice(1) }; } @@ -50,7 +53,7 @@ function logMessageToExpression(msg: string): string { msg = msg.replace('%', '%%'); const args: string[] = []; - let format = msg.replace(LOGMESSAGE_VARIABLE_REGEXP, (match, group) => { + let format = msg.replace(LOGMESSAGE_VARIABLE_REGEXP, (_match, group) => { const a = group.trim(); if (a) { args.push(`(${a})`); diff --git a/src/chrome/logging/executionLogger.ts b/src/chrome/logging/executionLogger.ts new file mode 100644 index 000000000..0f06c747b --- /dev/null +++ b/src/chrome/logging/executionLogger.ts @@ -0,0 +1,47 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { PromiseOrNot } from '../utils/promises'; +import { Logging } from '../internal/services/logging'; + +export interface IExecutionLogger { + logAsyncFunctionCall(description: string, functionToCall: (parameters: T) => PromiseOrNot, parameters: T): PromiseOrNot; +} + +export class ExecutionLogger implements IExecutionLogger { + private _depth = 0; + + public async logAsyncFunctionCall(description: string, functionToCall: (parameters: T) => PromiseOrNot, parameters: T): Promise { + this._logging.verbose(`${this.indentationForDepth()}${description}(${this.printParameters(parameters)})`); + this._depth++; + try { + const result = await functionToCall(parameters); + this._depth--; + this._logging.verbose(`${this.indentationForDepth()}${description} = ${this.printResult(result)}`); + return result; + } catch (exception) { + this._depth--; + this._logging.verbose(`${this.indentationForDepth()}${description} throws ${this.printException(exception)}`); + throw exception; + } + } + + private indentationForDepth(): string { + return ' '.repeat(this._depth); + } + + private printParameters(parameters: T): string { + return `${parameters}`; + } + + private printResult(parameters: T): string { + return `${parameters}`; + } + + private printException(exception: unknown): string { + return `${exception}`; + } + + constructor(private readonly _logging: Logging) { } +} \ No newline at end of file diff --git a/src/chrome/logging/methodsCalledLogger.ts b/src/chrome/logging/methodsCalledLogger.ts new file mode 100644 index 000000000..a7e4c80f2 --- /dev/null +++ b/src/chrome/logging/methodsCalledLogger.ts @@ -0,0 +1,74 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +enum Synchronicity { + Sync, + Async +} + +enum Outcome { + Succesful, + Failure +} + +export class MethodsCalledLogger { + public wrapped(): T { + const handler = { + get: (target: T, propertyKey: K, _receiver: any) => { + const originalPropertyValue = target[propertyKey]; + if (typeof originalPropertyValue === 'function') { + return (...args: any) => { + try { + const result = originalPropertyValue.apply(target, args); + if (!result.then) { + this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Succesful, result); + } else { + return result.then((promiseResult: unknown) => { + this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Succesful, promiseResult); + return promiseResult; + }, (rejection: unknown) => { + this.logCall(propertyKey, Synchronicity.Async, args, Outcome.Failure, rejection); + return rejection; + }); + } + } catch (exception) { + this.logCall(propertyKey, Synchronicity.Sync, args, Outcome.Failure, exception); + } + }; + } else { + return originalPropertyValue; + } + } + }; + + return new Proxy(this._objectToWrap, handler); + } + + private printMethodCall(propertyKey: PropertyKey, methodCallArguments: any[]): string { + return `${this._objectToWrapName}.${String(propertyKey)}(${this.printArguments(methodCallArguments)})`; + } + + private printMethodResponse(outcome: Outcome, resultOrException: unknown): string { + return `${outcome === Outcome.Succesful ? '->' : 'threw'} ${this.printObject(resultOrException)}`; + } + + private printMethodSynchronicity(synchronicity: Synchronicity): string { + return `${synchronicity === Synchronicity.Sync ? '' : ' async'}`; + } + + private logCall(propertyKey: PropertyKey, synchronicity: Synchronicity, methodCallArguments: any[], outcome: Outcome, resultOrException: unknown): void { + const message = `${this.printMethodCall(propertyKey, methodCallArguments)} ${this.printMethodSynchronicity(synchronicity)} ${this.printMethodResponse(outcome, resultOrException)}`; + console.log(message); + } + + private printArguments(methodCallArguments: any[]): string { + return methodCallArguments.map(methodCallArgument => this.printObject(methodCallArgument)).join(', '); + } + + private printObject(objectToPrint: unknown): string { + return `${objectToPrint}`; + } + + constructor(private readonly _objectToWrap: T, private readonly _objectToWrapName: string) { } +} diff --git a/src/chrome/stoppedEvent.ts b/src/chrome/stoppedEvent.ts index 058b8ccf6..76d67d65c 100644 --- a/src/chrome/stoppedEvent.ts +++ b/src/chrome/stoppedEvent.ts @@ -5,7 +5,7 @@ import { DebugProtocol } from 'vscode-debugprotocol'; import { StoppedEvent } from 'vscode-debugadapter'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import * as utils from '../utils'; import * as nls from 'vscode-nls'; @@ -14,7 +14,7 @@ const localize = nls.loadMessageBundle(); export type ReasonType = 'step' | 'breakpoint' | 'exception' | 'pause' | 'entry' | 'debugger_statement' | 'frame_entry' | 'promise_rejection'; export class StoppedEvent2 extends StoppedEvent { - constructor(reason: ReasonType, threadId: number, exception?: Crdp.Runtime.RemoteObject) { + constructor(reason: ReasonType, threadId: number, exception?: CDTP.Runtime.RemoteObject) { const exceptionText = exception && exception.description && utils.firstLine(exception.description); super(reason, threadId, exceptionText); diff --git a/src/chrome/target/events.ts b/src/chrome/target/events.ts index f0a29c492..130f2dd41 100644 --- a/src/chrome/target/events.ts +++ b/src/chrome/target/events.ts @@ -1,7 +1,6 @@ +import { Protocol as CDTP } from 'devtools-protocol'; import { IScript } from '../internal/scripts/script'; -import { Crdp } from '../..'; - export type integer = number; /** @@ -14,7 +13,7 @@ export interface ScriptParsedEvent { readonly startColumn: integer; readonly endLine: integer; readonly endColumn: integer; - readonly executionContextId: Crdp.Runtime.ExecutionContextId; + readonly executionContextId: CDTP.Runtime.ExecutionContextId; readonly hash: string; readonly executionContextAuxData?: any; readonly isLiveEdit?: boolean; diff --git a/src/chrome/utils/combine.ts b/src/chrome/utils/combine.ts new file mode 100644 index 000000000..5fe77bdca --- /dev/null +++ b/src/chrome/utils/combine.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ValidatedMap } from '../collections/validatedMap'; + +export function combine(object1: T1, object2: T2): T1 & T2; +export function combine(...objects: object[]): any { + const keyToObject = new ValidatedMap(); + for (const object of objects) { + for (const key in object) { + if (!keyToObject.has(key)) { + keyToObject.set(key, object); + } else { + throw new Error(`Can't combine objects into a proxy because both ${object} and ${keyToObject.get(key)} have a property named ${key}`); + } + } + } + + return new Proxy({}, { + get: (_target: any, key: PropertyKey, _receiver: any): any => { + const choosenReceiver = keyToObject.get(key) as any; + return choosenReceiver[key].bind(choosenReceiver); + } + }); +} + +export function combineProperties(object1: T1, object2: T2): T1 & T2; +export function combineProperties(...objects: object[]): any { + return Object.assign({}, ...objects); +} \ No newline at end of file diff --git a/src/chrome/utils/lazy.ts b/src/chrome/utils/lazy.ts new file mode 100644 index 000000000..9fc1a7d9a --- /dev/null +++ b/src/chrome/utils/lazy.ts @@ -0,0 +1,28 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +const empty = Symbol(); + +export class Lazy1 { + private _value: T | typeof empty = empty; + private _parameter: P | typeof empty = empty; + private _function = (parameter: P) => this.value(parameter); + + public value(parameter: P): T { + if (this._value === empty) { + this._value = this._obtainValue(parameter); + this._parameter = parameter; + } else if (this._parameter !== parameter) { + throw new Error(`Can't obtain a lazy value with parameter ${parameter} when the previous call used parameter ${String(this._parameter)}`); + } + + return this._value; + } + + public get function(): (parameter: P) => T { + return this._function; + } + + constructor(private readonly _obtainValue: (parameter: P) => T) { } +} \ No newline at end of file diff --git a/src/chrome/utils/localization.ts b/src/chrome/utils/localization.ts new file mode 100644 index 000000000..b089c3950 --- /dev/null +++ b/src/chrome/utils/localization.ts @@ -0,0 +1,22 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +// import { LocalizeInfo, loadMessageBundle, config } from 'vscode-nls'; +// let _localize = loadMessageBundle(); // Initialize to an unlocalized version until we know which locale to use + +// // TODO DIEGO: Make sure this works + +// export function localize(info: LocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]): string; +// export function localize(key: string, message: string, ...args: (string | number | boolean | undefined | null)[]): string; +// export function localize(infoOrKey: string | LocalizeInfo, message: string, ...args: (string | number | boolean | undefined | null)[]) { +// if (typeof infoOrKey === 'string') { // The compiler doesn't like it if we just make a single call +// return _localize(infoOrKey, message, ...args); +// } else { +// return _localize(infoOrKey, message, ...args); +// } +// } + +// export function setLocale(locale: string): void { +// _localize = config({ locale: locale })(); // Replace with the proper locale +// } diff --git a/src/chrome/utils/namespaceReverseLookupCreator.ts b/src/chrome/utils/namespaceReverseLookupCreator.ts new file mode 100644 index 000000000..734a0d092 --- /dev/null +++ b/src/chrome/utils/namespaceReverseLookupCreator.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export type NamespaceTree = { [name: string]: NamespaceTree | T }; + +export class NamespaceReverseLookupCreator { + private readonly _leafToNameMapping = new Map(); + + constructor( + private readonly _root: NamespaceTree, + private readonly _isLeaf: (node: NamespaceTree | T) => node is T, + private readonly _namesPrefix: string) { } + + public create(): Map { + this.exploreLeaf(this._root, this._namesPrefix); + return this._leafToNameMapping; + } + + private exploreLeaf(currentRoot: NamespaceTree, namePrefix: string): void { + for (const propertyNamme in currentRoot) { + const propertyName = namePrefix ? `${namePrefix}.${propertyNamme}` : propertyNamme; + const propertyValue = currentRoot[propertyNamme]; + if (this._isLeaf(propertyValue)) { + this._leafToNameMapping.set(propertyValue as T, propertyName); + } else { + this.exploreLeaf(propertyValue, propertyName); + } + } + } +} \ No newline at end of file diff --git a/src/chrome/variables.ts b/src/chrome/variables.ts index 9184fe5ae..041212c16 100644 --- a/src/chrome/variables.ts +++ b/src/chrome/variables.ts @@ -5,51 +5,52 @@ import { DebugProtocol } from 'vscode-debugprotocol'; import { Handles } from 'vscode-debugadapter'; -import { ChromeDebugAdapter, VariableContext } from './chromeDebugAdapter'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { ChromeDebugLogic, VariableContext } from './chromeDebugAdapter'; +import { Protocol as CDTP } from 'devtools-protocol'; import * as utils from '../utils'; +import { LoadedSourceCallFrame } from './internal/stackTraces/callFrame'; export interface IVariableContainer { - expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise; - setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise; + expand(adapter: ChromeDebugLogic, filter?: string, start?: number, count?: number): Promise; + setValue(adapter: ChromeDebugLogic, name: string, value: string): Promise; } export abstract class BaseVariableContainer implements IVariableContainer { constructor(protected objectId: string, protected evaluateName?: string) { } - public expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise { + public expand(adapter: ChromeDebugLogic, filter?: string, start?: number, count?: number): Promise { return adapter.getVariablesForObjectId(this.objectId, this.evaluateName, filter, start, count); } - public setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise { + public setValue(_adapter: ChromeDebugLogic, _name: string, _value: string): Promise { return utils.errP('setValue not supported by this variable type'); } } export class PropertyContainer extends BaseVariableContainer { - public setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise { + public setValue(adapter: ChromeDebugLogic, name: string, value: string): Promise { return adapter.setPropertyValue(this.objectId, name, value); } } export class LoggedObjects extends BaseVariableContainer { - constructor(private args: Crdp.Runtime.RemoteObject[]) { + constructor(private args: CDTP.Runtime.RemoteObject[]) { super(undefined); } - public expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise { + public expand(adapter: ChromeDebugLogic, _filter?: string, _start?: number, _count?: number): Promise { return Promise.all(this.args.map((arg, i) => adapter.remoteObjectToVariable('' + i, arg, undefined, /*stringify=*/false, 'repl'))); } } export class ScopeContainer extends BaseVariableContainer { - private _thisObj: Crdp.Runtime.RemoteObject; - private _returnValue: Crdp.Runtime.RemoteObject; - private _frameId: string; + private _thisObj: CDTP.Runtime.RemoteObject; + private _returnValue: CDTP.Runtime.RemoteObject; + private _frameId: LoadedSourceCallFrame; private _origScopeIndex: number; - public constructor(frameId: string, origScopeIndex: number, objectId: string, thisObj?: Crdp.Runtime.RemoteObject, returnValue?: Crdp.Runtime.RemoteObject) { + public constructor(frameId: LoadedSourceCallFrame, origScopeIndex: number, objectId: string, thisObj?: CDTP.Runtime.RemoteObject, returnValue?: CDTP.Runtime.RemoteObject) { super(objectId, ''); this._thisObj = thisObj; this._returnValue = returnValue; @@ -60,10 +61,10 @@ export class ScopeContainer extends BaseVariableContainer { /** * Call super then insert the 'this' object if needed */ - public expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise { + public expand(adapter: ChromeDebugLogic, _filter?: string, start?: number, count?: number): Promise { // No filtering in scopes right now return super.expand(adapter, 'all', start, count).then(variables => { - if (this._thisObj) { + if (this._thisObj && !variables.find(v => v.name === 'this')) { // If this is a scope that should have the 'this', prop, insert it at the top of the list return this.insertRemoteObject(adapter, variables, 'this', this._thisObj); } @@ -78,11 +79,11 @@ export class ScopeContainer extends BaseVariableContainer { }); } - public setValue(adapter: ChromeDebugAdapter, name: string, value: string): Promise { + public setValue(adapter: ChromeDebugLogic, name: string, value: string): Promise { return adapter.setVariableValue(this._frameId, this._origScopeIndex, name, value); } - private insertRemoteObject(adapter: ChromeDebugAdapter, variables: DebugProtocol.Variable[], name: string, obj: Crdp.Runtime.RemoteObject): Promise { + private insertRemoteObject(adapter: ChromeDebugLogic, variables: DebugProtocol.Variable[], name: string, obj: CDTP.Runtime.RemoteObject): Promise { return adapter.remoteObjectToVariable(name, obj).then(variable => { variables.unshift(variable); return variables; @@ -91,9 +92,9 @@ export class ScopeContainer extends BaseVariableContainer { } export class ExceptionContainer extends PropertyContainer { - protected _exception: Crdp.Runtime.RemoteObject; + protected _exception: CDTP.Runtime.RemoteObject; - protected constructor(objectId: string, exception: Crdp.Runtime.RemoteObject) { + protected constructor(_objectId: string, exception: CDTP.Runtime.RemoteObject) { super(exception.objectId, undefined); this._exception = exception; } @@ -101,7 +102,7 @@ export class ExceptionContainer extends PropertyContainer { /** * Expand the exception as if it were a Scope */ - public static create(exception: Crdp.Runtime.RemoteObject): ExceptionContainer { + public static create(exception: CDTP.Runtime.RemoteObject): ExceptionContainer { return exception.objectId ? new ExceptionContainer(exception.objectId, exception) : new ExceptionValueContainer(exception); @@ -112,15 +113,15 @@ export class ExceptionContainer extends PropertyContainer { * For when a value is thrown instead of an object */ export class ExceptionValueContainer extends ExceptionContainer { - public constructor(exception: Crdp.Runtime.RemoteObject) { + public constructor(exception: CDTP.Runtime.RemoteObject) { super('EXCEPTION_ID', exception); } /** * Make up a fake 'Exception' property to hold the thrown value, displayed under the Exception Scope */ - public expand(adapter: ChromeDebugAdapter, filter?: string, start?: number, count?: number): Promise { - const excValuePropDescriptor: Crdp.Runtime.PropertyDescriptor = { name: 'Exception', value: this._exception }; + public expand(adapter: ChromeDebugLogic, _filter?: string, _start?: number, _count?: number): Promise { + const excValuePropDescriptor: CDTP.Runtime.PropertyDescriptor = { name: 'Exception', value: this._exception }; return adapter.propertyDescriptorToVariable(excValuePropDescriptor) .then(variable => [variable]); } @@ -134,7 +135,7 @@ const PREVIEW_PROPS_DEFAULT = 3; const PREVIEW_PROPS_CONSOLE = 8; const PREVIEW_PROP_LENGTH = 50; const ELLIPSIS = '…'; -function getArrayPreview(object: Crdp.Runtime.RemoteObject, context?: string): string { +function getArrayPreview(object: CDTP.Runtime.RemoteObject, context?: string): string { let value = object.description; if (object.preview) { const numProps = context === 'repl' ? PREVIEW_PROPS_CONSOLE : PREVIEW_PROPS_DEFAULT; @@ -143,7 +144,7 @@ function getArrayPreview(object: Crdp.Runtime.RemoteObject, context?: string): s // Take the first 3 props, and parse the indexes const propsWithIdx = indexedProps.slice(0, numProps) - .map((prop, i) => { + .map((prop, _i) => { return { idx: parseInt(prop.name, 10), value: propertyPreviewToString(prop) @@ -174,7 +175,7 @@ function getArrayPreview(object: Crdp.Runtime.RemoteObject, context?: string): s return value; } -function getObjectPreview(object: Crdp.Runtime.RemoteObject, context?: string): string { +function getObjectPreview(object: CDTP.Runtime.RemoteObject, context?: string): string { let value = object.description; if (object.preview) { const numProps = context === 'repl' ? PREVIEW_PROPS_CONSOLE : PREVIEW_PROPS_DEFAULT; @@ -196,7 +197,7 @@ function getObjectPreview(object: Crdp.Runtime.RemoteObject, context?: string): return value; } -function propertyPreviewToString(prop: Crdp.Runtime.PropertyPreview): string { +function propertyPreviewToString(prop: CDTP.Runtime.PropertyPreview): string { const value = typeof prop.value === 'undefined' ? `<${prop.type}>` : trimProperty(prop.value); @@ -212,7 +213,7 @@ function trimProperty(value: string): string { value; } -export function getRemoteObjectPreview(object: Crdp.Runtime.RemoteObject, stringify = true, context?: string): string { +export function getRemoteObjectPreview(object: CDTP.Runtime.RemoteObject, stringify = true, context?: string): string { if (object) { if (object.type === 'object') { return getRemoteObjectPreview_object(object, context); @@ -226,7 +227,7 @@ export function getRemoteObjectPreview(object: Crdp.Runtime.RemoteObject, string return ''; } -export function getRemoteObjectPreview_object(object: Crdp.Runtime.RemoteObject, context?: string): string { +export function getRemoteObjectPreview_object(object: CDTP.Runtime.RemoteObject, context?: string): string { const objectDescription = object.description || ''; if ((object.subtype) === 'internal#location') { // Could format this nicely later, see #110 @@ -259,7 +260,7 @@ export function getRemoteObjectPreview_object(object: Crdp.Runtime.RemoteObject, } } -export function getRemoteObjectPreview_primitive(object: Crdp.Runtime.RemoteObject, stringify?: boolean): string { +export function getRemoteObjectPreview_primitive(object: CDTP.Runtime.RemoteObject, stringify?: boolean): string { // The value is a primitive value, or something that has a description (not object, primitive, or undefined). And force to be string if (typeof object.value === 'undefined') { return object.description + ''; @@ -275,7 +276,7 @@ export function getRemoteObjectPreview_primitive(object: Crdp.Runtime.RemoteObje } } -export function getRemoteObjectPreview_function(object: Crdp.Runtime.RemoteObject, context?: string): string { +export function getRemoteObjectPreview_function(object: CDTP.Runtime.RemoteObject, _context?: string): string { const firstBraceIdx = object.description.indexOf('{'); if (firstBraceIdx >= 0) { return object.description.substring(0, firstBraceIdx) + '{ … }'; diff --git a/src/debugAdapterInterfaces.d.ts b/src/debugAdapterInterfaces.d.ts index e184e61f0..a1f982b35 100644 --- a/src/debugAdapterInterfaces.d.ts +++ b/src/debugAdapterInterfaces.d.ts @@ -7,10 +7,14 @@ */ import { DebugProtocol } from 'vscode-debugprotocol'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import { ITelemetryPropertyCollector } from './telemetry'; import { IStringDictionary } from './utils'; import { ITargetFilter } from './chrome/chromeConnection'; +import { LocationInScript } from './chrome/internal/locations/location'; +import { IScript } from './chrome/internal/scripts/script'; +import { ILoadedSource } from './chrome/internal/sources/loadedSource'; +import { IResourceIdentifier } from './chrome/internal/sources/resourceIdentifier'; export type ISourceMapPathOverrides = IStringDictionary; export type IPathMapping = IStringDictionary; @@ -47,6 +51,8 @@ export interface ICommonRequestArgs { breakOnLoadStrategy?: BreakOnLoadStrategy; _suppressConsoleOutput?: boolean; + + port?: number; } export interface IInitializeRequestArgs extends DebugProtocol.InitializeRequestArguments { @@ -73,7 +79,10 @@ export interface IAttachRequestArgs extends DebugProtocol.AttachRequestArguments websocketUrl?: string; } +export interface ISetBreakpointsRequestArgs extends DebugProtocol.SetBreakpointsArguments {} + export interface IToggleSkipFileStatusArgs { + /** This requests comes from the debug extension, so it's on a pseudo-vscode protocol format which can be both path or source reference */ path?: string; sourceReference?: number; } @@ -91,8 +100,8 @@ export type ISetBreakpointsResponseBody = DebugProtocol.SetBreakpointsResponse[' * If a breakpoint is set but Chrome returns no locations, actualLocation is not set. */ export interface ISetBreakpointResult { - breakpointId?: Crdp.Debugger.BreakpointId; - actualLocation?: Crdp.Debugger.Location; + breakpointId?: CDTP.Debugger.BreakpointId; + actualLocation?: CDTP.Debugger.Location; } export type ISourceResponseBody = DebugProtocol.SourceResponse['body']; @@ -101,14 +110,6 @@ export type IThreadsResponseBody = DebugProtocol.ThreadsResponse['body']; export type IStackTraceResponseBody = DebugProtocol.StackTraceResponse['body']; -export interface IInternalStackTraceResponseBody extends IStackTraceResponseBody { - stackFrames: IInternalStackFrame[]; -} - -export interface IInternalStackFrame extends DebugProtocol.StackFrame { - isSourceMapped?: boolean; -} - export type IScopesResponseBody = DebugProtocol.ScopesResponse['body']; export type IVariablesResponseBody = DebugProtocol.VariablesResponse['body']; @@ -133,26 +134,41 @@ export interface IExceptionInfoResponseBody extends DAPExceptionInfoResponseBody export declare type PromiseOrNot = T | Promise; -export interface TimeTravelClient { +export interface ITimeTravelClient { stepBack(): Promise; reverse(): Promise; } -export interface TimeTravelRuntime extends Crdp.ProtocolApi { - TimeTravel: TimeTravelClient; +export interface ITimeTravelRuntime extends CDTP.ProtocolApi { + TimeTravel: ITimeTravelClient; +} + +export interface IUninitializedDebugAdapter { + initialize(args: DebugProtocol.InitializeRequestArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; +} + +export interface IUninitializedDebugAdapterState { + initialize(args: DebugProtocol.InitializeRequestArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot<{capabilities: DebugProtocol.Capabilities, newState: IDebugAdapterState}>; +} + +export interface IUnconnectedDebugAdapter { + launch(args: ILaunchRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; + attach(args: IAttachRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; +} + +export interface IUnconnectedDebugAdapterState { + launch(args: ILaunchRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; + attach(args: IAttachRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; } /** * All methods returning PromiseOrNot can either return a Promise or a value, and if they reject the Promise, it can be with an Error or a * DebugProtocol.Message object, which will be sent to sendErrorResponse. */ -export interface IDebugAdapter { +export interface IConnectedDebugAdapter { // From DebugSession shutdown(): void; - initialize(args: DebugProtocol.InitializeRequestArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; - launch(args: ILaunchRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; - attach(args: IAttachRequestArgs, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; disconnect(args: DebugProtocol.DisconnectArguments): PromiseOrNot; setBreakpoints(args: DebugProtocol.SetBreakpointsArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; @@ -163,6 +179,7 @@ export interface IDebugAdapter { stepIn(): PromiseOrNot; stepOut(): PromiseOrNot; pause(): PromiseOrNot; + restartFrame(args: DebugProtocol.RestartFrameRequest): Promise; stackTrace(args: DebugProtocol.StackTraceArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; scopes(args: DebugProtocol.ScopesArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; @@ -170,13 +187,27 @@ export interface IDebugAdapter { source(args: DebugProtocol.SourceArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; threads(): PromiseOrNot; evaluate(args: DebugProtocol.EvaluateArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; + + exceptionInfo(args: DebugProtocol.ExceptionInfoArguments): Promise; + loadedSources(args: DebugProtocol.LoadedSourcesArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; + + setFunctionBreakpoints(args: DebugProtocol.SetFunctionBreakpointsArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; + setVariable(args: DebugProtocol.SetVariableArguments, telemetryPropertyCollector?: ITelemetryPropertyCollector, requestSeq?: number): PromiseOrNot; + + toggleSkipFileStatus(args: IToggleSkipFileStatusArgs): Promise; + toggleSmartStep(): Promise; } +export type IDebugAdapter = IConnectedDebugAdapter & IUnconnectedDebugAdapter & IUninitializedDebugAdapter; +export type IClientCapabilities = IInitializeRequestArgs; + +export type IDebugAdapterState = IConnectedDebugAdapter & IUnconnectedDebugAdapterState & IUninitializedDebugAdapterState; + export interface IDebugTransformer { initialize?(args: DebugProtocol.InitializeRequestArguments, requestSeq?: number): PromiseOrNot; launch?(args: ILaunchRequestArgs, requestSeq?: number): PromiseOrNot; attach?(args: IAttachRequestArgs, requestSeq?: number): PromiseOrNot; - setBreakpoints?(args: DebugProtocol.SetBreakpointsArguments, requestSeq?: number): PromiseOrNot; + setBreakpoints?(args: ISetBreakpointsRequestArgs, requestSeq?: number): PromiseOrNot; setExceptionBreakpoints?(args: DebugProtocol.SetExceptionBreakpointsArguments, requestSeq?: number): PromiseOrNot; stackTrace?(args: DebugProtocol.StackTraceArguments, requestSeq?: number): PromiseOrNot; diff --git a/src/executionTimingsReporter.ts b/src/executionTimingsReporter.ts index 649939ec1..1e4385b82 100644 --- a/src/executionTimingsReporter.ts +++ b/src/executionTimingsReporter.ts @@ -41,14 +41,14 @@ export interface IStepStartedEventsEmitter { removeListener(event: 'milestoneReached', listener: (args: IMilestoneReachedEventArguments) => void): this; } -export interface FinishedStartingUpEventArguments { +export interface IFinishedStartingUpEventArguments { requestedContentWasDetected: boolean; reasonForNotDetected: string; } export interface IFinishedStartingUpEventsEmitter { - on(event: 'finishedStartingUp', listener: (args: FinishedStartingUpEventArguments) => void): this; - once(event: 'finishedStartingUp', listener: (args: FinishedStartingUpEventArguments) => void): this; + on(event: 'finishedStartingUp', listener: (args: IFinishedStartingUpEventArguments) => void): this; + once(event: 'finishedStartingUp', listener: (args: IFinishedStartingUpEventArguments) => void): this; removeListener(event: 'finishedStartingUp', listener: () => void): this; removeListener(event: 'finishedStartingUp', listener: () => void): this; } diff --git a/src/index.ts b/src/index.ts index d39132409..9c3866a90 100644 --- a/src/index.ts +++ b/src/index.ts @@ -2,11 +2,13 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ +import 'reflect-metadata'; // We need to import this before any inject attempts to use it + /** Normally, a consumer could require and use this and get the same instance. But if -core is npm linked, there may be two instances of file in play. */ import { logger } from 'vscode-debugadapter'; import * as chromeConnection from './chrome/chromeConnection'; -import { ChromeDebugAdapter, LoadedSourceEventReason, IOnPausedResult } from './chrome/chromeDebugAdapter'; +import { ChromeDebugLogic, LoadedSourceEventReason } from './chrome/chromeDebugAdapter'; import { ChromeDebugSession, IChromeDebugSessionOpts } from './chrome/chromeDebugSession'; import * as chromeTargetDiscoveryStrategy from './chrome/chromeTargetDiscoveryStrategy'; import * as chromeUtils from './chrome/chromeUtils'; @@ -27,14 +29,32 @@ import * as variables from './chrome/variables'; import { NullLogger } from './nullLogger'; import * as executionTimingsReporter from './executionTimingsReporter'; -import { Protocol as Crdp } from 'devtools-protocol'; -import { Version, TargetVersions } from './chrome/chromeTargetDiscoveryStrategy'; +import { Protocol as CDTP } from 'devtools-protocol'; +import { TargetVersions } from './chrome/chromeTargetDiscoveryStrategy'; +import { Version } from "./chrome/utils/version"; +import { parseResourceIdentifier } from './chrome/internal/sources/resourceIdentifier'; +import { ChromeDebugAdapter } from './chrome/client/chromeDebugAdapter/chromeDebugAdapterV2'; +import { IExtensibilityPoints, OnlyProvideCustomLauncherExtensibilityPoints } from './chrome/extensibility/extensibilityPoints'; +import { IDebuggeeLauncher, ILaunchResult, IDebuggeeRunner } from './chrome/debugeeStartup/debugeeLauncher'; +import { inject, injectable, postConstruct } from 'inversify'; +import { ConnectedCDAConfiguration } from './chrome/client/chromeDebugAdapter/cdaConfiguration'; +import { IComponent } from './chrome/internal/features/feature'; +import { TYPES } from './chrome/dependencyInjection.ts/types'; +import { IInspectDebugeeState } from './chrome/cdtpDebuggee/features/cdtpInspectDebugeeState'; +import { CDTPEventsEmitterDiagnosticsModule } from './chrome/cdtpDebuggee/infrastructure/cdtpDiagnosticsModule'; +import { ICommunicator } from './chrome/communication/communicator'; +import { ISupportedDomains } from './chrome/internal/domains/supportedDomains'; +import { Internal } from './chrome/communication/internalChannels'; +import { ISession } from './chrome/client/session'; +import { IPausedOverlay } from './chrome/cdtpDebuggee/features/cdtpPausedOverlay'; +import { INetworkCacheConfiguration } from './chrome/cdtpDebuggee/features/cdtpNetworkCacheConfiguration'; +import { IDebugeeRuntimeVersionProvider } from './chrome/cdtpDebuggee/features/cdtpDebugeeRuntimeVersionProvider'; +import { IBrowserNavigator } from './chrome/cdtpDebuggee/features/cdtpBrowserNavigator'; export { chromeConnection, - ChromeDebugAdapter, + ChromeDebugLogic, ChromeDebugSession, - IOnPausedResult, IChromeDebugSessionOpts, chromeTargetDiscoveryStrategy, chromeUtils, @@ -44,19 +64,52 @@ export { InternalSourceBreakpoint, ErrorWithMessage, + ChromeDebugAdapter, + IExtensibilityPoints, + OnlyProvideCustomLauncherExtensibilityPoints, + + IDebuggeeLauncher, + IDebuggeeRunner, + ILaunchResult, + ConnectedCDAConfiguration, + inject, + injectable, + IComponent, + + postConstruct, + UrlPathTransformer, BasePathTransformer, LineColTransformer, BaseSourceMapTransformer, + CDTPEventsEmitterDiagnosticsModule, utils, telemetry, variables, NullLogger, executionTimingsReporter, + ISupportedDomains, + IPausedOverlay, + Version, TargetVersions, - Crdp + ICommunicator, + + Internal, + + INetworkCacheConfiguration, + IDebugeeRuntimeVersionProvider as IDebugeeVersionProvider, + + parseResourceIdentifier, + IBrowserNavigator as IBrowserNavigation, + + ISession, + TYPES, + + IInspectDebugeeState, + + CDTP }; diff --git a/src/nullLogger.ts b/src/nullLogger.ts index 51f87787c..b07b171b9 100644 --- a/src/nullLogger.ts +++ b/src/nullLogger.ts @@ -8,19 +8,19 @@ import { Logger } from 'vscode-debugadapter'; * Implements ILogger as a no-op */ export class NullLogger implements Logger.ILogger { - log(msg: string, level?: Logger.LogLevel): void { + log(_msg: string, _level?: Logger.LogLevel): void { // no-op } - verbose(msg: string): void { + verbose(_msg: string): void { // no-op } - warn(msg: string): void { + warn(_msg: string): void { // no-op } - error(msg: string): void { + error(_msg: string): void { // no-op } diff --git a/src/sourceMaps/sourceMap.ts b/src/sourceMaps/sourceMap.ts index 776525671..ccfa112bc 100644 --- a/src/sourceMaps/sourceMap.ts +++ b/src/sourceMaps/sourceMap.ts @@ -100,7 +100,7 @@ export class SourceMap { // sm.sources are initially relative paths, file:/// urls, made-up urls like webpack:///./app.js, or paths that start with /. // resolve them to file:/// urls, using computedSourceRoot, to be simpler and unambiguous, since // it needs to look them up later in exactly the same format. - this._sources = sm.sources.map(sourcePath => { + this._sources = sm.sources.map((sourcePath: string) => { if (sourceMapPathOverrides) { const fullSourceEntry = sourceMapUtils.getFullSourceEntry(this._originalSourceRoot, sourcePath); const mappedFullSourceEntry = sourceMapUtils.applySourceMapPathOverrides(fullSourceEntry, sourceMapPathOverrides, isVSClient); @@ -159,7 +159,7 @@ export class SourceMap { * Finds the nearest source location for the given location in the generated file. * Will return null instead of a mapping on the next line (different from generatedPositionFor). */ - public authoredPositionFor(line: number, column: number): MappedPosition { + public authoredPositionFor(line: number, column: number): MappedPosition | null { // source-map lib uses 1-indexed lines. line++; @@ -196,7 +196,7 @@ export class SourceMap { * Finds the nearest location in the generated file for the given source location. * Will return a mapping on the next line, if there is no subsequent mapping on the expected line. */ - public generatedPositionFor(source: string, line: number, column: number): MappedPosition { + public generatedPositionFor(source: string, line: number, column: number): MappedPosition | null { // source-map lib uses 1-indexed lines. line++; diff --git a/src/sourceMaps/sourceMapFactory.ts b/src/sourceMaps/sourceMapFactory.ts index ca1aeb6c9..448b52eb5 100644 --- a/src/sourceMaps/sourceMapFactory.ts +++ b/src/sourceMaps/sourceMapFactory.ts @@ -112,13 +112,13 @@ export class SourceMapFactory { let contentsP: Promise; if (utils.isURL(mapPathOrURL) && !utils.isFileUrl(mapPathOrURL)) { logger.log(`SourceMaps.loadSourceMapContents: Downloading sourcemap file from ${mapPathOrURL}`); - contentsP = this.downloadSourceMapContents(mapPathOrURL).catch(e => { + contentsP = this.downloadSourceMapContents(mapPathOrURL).catch(_e => { logger.log(`SourceMaps.loadSourceMapContents: Could not download sourcemap from ${mapPathOrURL}`); return null; }); } else { mapPathOrURL = utils.canonicalizeUrl(mapPathOrURL); - contentsP = new Promise((resolve, reject) => { + contentsP = new Promise((resolve) => { logger.log(`SourceMaps.loadSourceMapContents: Reading local sourcemap file from ${mapPathOrURL}`); fs.readFile(mapPathOrURL, (err, data) => { if (err) { diff --git a/src/sourceMaps/sourceMaps.ts b/src/sourceMaps/sourceMaps.ts index 63931a956..2d7cf2587 100644 --- a/src/sourceMaps/sourceMaps.ts +++ b/src/sourceMaps/sourceMaps.ts @@ -69,11 +69,14 @@ export class SourceMaps { /** * Given a new path to a new script file, finds and loads the sourcemap for that file */ - public async processNewSourceMap(pathToGenerated: string, sourceMapURL: string, isVSClient = false): Promise { + public async processNewSourceMap(pathToGenerated: string, sourceMapURL: string, isVSClient = false): Promise { const sourceMap = await this._sourceMapFactory.getMapForGeneratedPath(pathToGenerated, sourceMapURL, isVSClient); if (sourceMap) { this._generatedPathToSourceMap.set(pathToGenerated.toLowerCase(), sourceMap); sourceMap.authoredSources.forEach(authoredSource => this._authoredPathToSourceMap.set(authoredSource.toLowerCase(), sourceMap)); + return sourceMap; + } else { + return null; } } } diff --git a/src/telemetry.ts b/src/telemetry.ts index f5d31d12f..fd6b1b6f7 100644 --- a/src/telemetry.ts +++ b/src/telemetry.ts @@ -106,7 +106,7 @@ export class AsyncGlobalPropertiesTelemetryReporter implements ITelemetryReporte } export class NullTelemetryReporter implements ITelemetryReporter { - reportEvent(name: string, data?: any): void { + reportEvent(_name: string, _data?: any): void { // no-op } @@ -181,23 +181,25 @@ export class BatchTelemetryReporter { */ private static transfromBucketData(bucketForEventType: any[]): {[groupedPropertyValue: string]: string} { const allPropertyNamesInTheBucket = BatchTelemetryReporter.collectPropertyNamesFromAllEvents(bucketForEventType); - let properties = {}; + let propertiesAsArray: {[groupedPropertyValue: string]: string[]} = {}; // Create a holder for all potential property names. for (const key of allPropertyNamesInTheBucket) { - properties[`aggregated.${key}`] = []; + propertiesAsArray[`aggregated.${key}`] = []; } // Run through all the events in the bucket, collect the values for each property name. for (const event of bucketForEventType) { for (const propertyName of allPropertyNamesInTheBucket) { - properties[`aggregated.${propertyName}`].push(event[propertyName] === undefined ? null : event[propertyName]); + propertiesAsArray[`aggregated.${propertyName}`].push(event[propertyName] === undefined ? null : event[propertyName]); } } + let properties: {[groupedPropertyValue: string]: string} = {}; + // Serialize each array as the final aggregated property value. for (const propertyName of allPropertyNamesInTheBucket) { - properties[`aggregated.${propertyName}`] = JSON.stringify(properties[`aggregated.${propertyName}`]); + properties[`aggregated.${propertyName}`] = JSON.stringify(propertiesAsArray[`aggregated.${propertyName}`]); } return properties; @@ -220,7 +222,7 @@ export class BatchTelemetryReporter { * will return ['p1', 'p2', 'p3'] */ private static collectPropertyNamesFromAllEvents(bucket: any[]): string[] { - let propertyNamesSet = {}; + let propertyNamesSet: {[property: string]: boolean} = {}; for (const entry of bucket) { for (const key of Object.keys(entry)) { propertyNamesSet[key] = true; diff --git a/src/transformers/basePathTransformer.ts b/src/transformers/basePathTransformer.ts index 1b1b8c71b..fb835108c 100644 --- a/src/transformers/basePathTransformer.ts +++ b/src/transformers/basePathTransformer.ts @@ -4,46 +4,41 @@ import { DebugProtocol } from 'vscode-debugprotocol'; -import { ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, IStackTraceResponseBody } from '../debugAdapterInterfaces'; +import { IResourceIdentifier } from '../chrome/internal/sources/resourceIdentifier'; +import { IStackTracePresentation } from '../chrome/internal/stackTraces/stackTracePresentation'; +import { IComponent } from '../chrome/internal/features/feature'; +import { injectable } from 'inversify'; /** * Converts a local path from Code to a path on the target. */ -export class BasePathTransformer { - public launch(args: ILaunchRequestArgs): Promise { - return Promise.resolve(); - } - - public attach(args: IAttachRequestArgs): Promise { - return Promise.resolve(); - } - - public setBreakpoints(args: ISetBreakpointsArgs): ISetBreakpointsArgs { - return args; +@injectable() +export class BasePathTransformer implements IComponent { + public async install(): Promise { } public clearTargetContext(): void { } - public scriptParsed(scriptPath: string): Promise { + public scriptParsed(scriptPath: IResourceIdentifier): Promise { return Promise.resolve(scriptPath); } - public breakpointResolved(bp: DebugProtocol.Breakpoint, targetPath: string): string { + public breakpointResolved(_bp: DebugProtocol.Breakpoint, targetPath: IResourceIdentifier): IResourceIdentifier { return this.getClientPathFromTargetPath(targetPath) || targetPath; } - public stackTraceResponse(response: IStackTraceResponseBody): void { + public stackTraceResponse(_response: IStackTracePresentation): void { } - public async fixSource(source: DebugProtocol.Source): Promise { + public async fixSource(_source: DebugProtocol.Source): Promise { } - public getTargetPathFromClientPath(clientPath: string): string { + public getTargetPathFromClientPath(clientPath: IResourceIdentifier): IResourceIdentifier { return clientPath; } - public getClientPathFromTargetPath(targetPath: string): string { + public getClientPathFromTargetPath(targetPath: IResourceIdentifier): IResourceIdentifier { return targetPath; } } diff --git a/src/transformers/baseSourceMapTransformer.ts b/src/transformers/baseSourceMapTransformer.ts index bf62aa0f6..f1fc10edf 100644 --- a/src/transformers/baseSourceMapTransformer.ts +++ b/src/transformers/baseSourceMapTransformer.ts @@ -2,19 +2,20 @@ * Copyright (C) Microsoft Corporation. All rights reserved. *--------------------------------------------------------*/ -import * as path from 'path'; import { DebugProtocol } from 'vscode-debugprotocol'; -import { ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, - ISetBreakpointsResponseBody, IInternalStackTraceResponseBody, IScopesResponseBody, IInternalStackFrame } from '../debugAdapterInterfaces'; -import { MappedPosition, ISourcePathDetails } from '../sourceMaps/sourceMap'; +import { ILaunchRequestArgs, IAttachRequestArgs, + ISetBreakpointsResponseBody, IScopesResponseBody } from '../debugAdapterInterfaces'; +import { MappedPosition, ISourcePathDetails, SourceMap } from '../sourceMaps/sourceMap'; import { SourceMaps } from '../sourceMaps/sourceMaps'; -import * as utils from '../utils'; import { logger } from 'vscode-debugadapter'; -import { ISourceContainer } from '../chrome/chromeDebugAdapter'; -import * as nls from 'vscode-nls'; -const localize = nls.loadMessageBundle(); +import { ILoadedSource } from '../chrome/internal/sources/loadedSource'; +import { IComponent } from '../chrome/internal/features/feature'; +import { TYPES } from '../chrome/dependencyInjection.ts/types'; +import { ConnectedCDAConfiguration, IConnectedCDAConfiguration } from '../chrome/client/chromeDebugAdapter/cdaConfiguration'; +// import { injectable, inject } from 'inversify'; +import { inject } from 'inversify'; interface ISavedSetBreakpointsArgs { generatedPath: string; @@ -23,7 +24,7 @@ interface ISavedSetBreakpointsArgs { } export interface ISourceLocation { - source: DebugProtocol.Source; + source: ILoadedSource; line: number; column: number; isSourceMapped?: boolean; // compat with stack frame @@ -32,15 +33,12 @@ export interface ISourceLocation { /** * If sourcemaps are enabled, converts from source files on the client side to runtime files on the target side */ -export class BaseSourceMapTransformer { +export class BaseSourceMapTransformer implements IComponent { protected _sourceMaps: SourceMaps; - protected _sourceHandles: utils.ReverseHandles; private _enableSourceMapCaching: boolean; private _requestSeqToSetBreakpointsArgs: Map; private _allRuntimeScriptPaths: Set; - private _authoredPathsToMappedBPs: Map; - private _authoredPathsToClientBreakpointIds: Map; protected _preLoad = Promise.resolve(); private _processingNewSourceMap: Promise = Promise.resolve(); @@ -49,8 +47,14 @@ export class BaseSourceMapTransformer { protected _isVSClient = false; - constructor(sourceHandles: utils.ReverseHandles) { - this._sourceHandles = sourceHandles; + constructor(@inject(TYPES.ConnectedCDAConfiguration) configuration: IConnectedCDAConfiguration) { + this._enableSourceMapCaching = configuration.args.enableSourceMapCaching; + this.init(configuration.args); + this.isVSClient = configuration._clientCapabilities.clientID === 'visualstudio'; + } + + public install(_configuration: ConnectedCDAConfiguration): this { + return this; } public get sourceMaps(): SourceMaps { @@ -61,22 +65,14 @@ export class BaseSourceMapTransformer { this._isVSClient = newValue; } - public launch(args: ILaunchRequestArgs): void { - this.init(args); - } - - public attach(args: IAttachRequestArgs): void { - this.init(args); - } - protected init(args: ILaunchRequestArgs | IAttachRequestArgs): void { - if (args.sourceMaps) { + // Enable sourcemaps and async callstacks by default + const areSourceMapsEnabled = typeof args.sourceMaps === 'undefined' || args.sourceMaps; + if (areSourceMapsEnabled) { this._enableSourceMapCaching = args.enableSourceMapCaching; this._sourceMaps = new SourceMaps(args.pathMapping, args.sourceMapPathOverrides, this._enableSourceMapCaching); this._requestSeqToSetBreakpointsArgs = new Map(); this._allRuntimeScriptPaths = new Set(); - this._authoredPathsToMappedBPs = new Map(); - this._authoredPathsToClientBreakpointIds = new Map(); } } @@ -84,94 +80,6 @@ export class BaseSourceMapTransformer { this._allRuntimeScriptPaths = new Set(); } - /** - * Apply sourcemapping to the setBreakpoints request path/lines. - * Returns true if completed successfully, and setBreakpoint should continue. - */ - public setBreakpoints(args: ISetBreakpointsArgs, requestSeq: number, ids?: number[]): { args: ISetBreakpointsArgs, ids: number[] } { - if (!this._sourceMaps) { - return { args, ids }; - } - - const originalBPs = JSON.parse(JSON.stringify(args.breakpoints)); - - if (args.source.sourceReference) { - // If the source contents were inlined, then args.source has no path, but we - // stored it in the handle - const handle = this._sourceHandles.get(args.source.sourceReference); - if (handle && handle.mappedPath) { - args.source.path = handle.mappedPath; - } - } - - if (args.source.path) { - const argsPath = args.source.path; - const mappedPath = this._sourceMaps.getGeneratedPathFromAuthoredPath(argsPath); - if (mappedPath) { - logger.log(`SourceMaps.setBP: Mapped ${argsPath} to ${mappedPath}`); - args.authoredPath = argsPath; - args.source.path = mappedPath; - - // DebugProtocol doesn't send cols yet, but they need to be added from sourcemaps - args.breakpoints.forEach(bp => { - const { line, column = 0 } = bp; - const mapped = this._sourceMaps.mapToGenerated(argsPath, line, column); - if (mapped) { - logger.log(`SourceMaps.setBP: Mapped ${argsPath}:${line + 1}:${column + 1} to ${mappedPath}:${mapped.line + 1}:${mapped.column + 1}`); - bp.line = mapped.line; - bp.column = mapped.column; - } else { - logger.log(`SourceMaps.setBP: Mapped ${argsPath} but not line ${line + 1}, column 1`); - bp.column = column; // take 0 default if needed - } - }); - - this._authoredPathsToMappedBPs.set(argsPath, args.breakpoints); - - // Store the client breakpoint Ids for the mapped BPs as well - if (ids) { - this._authoredPathsToClientBreakpointIds.set(argsPath, ids); - } - - // Include BPs from other files that map to the same file. Ensure the current file's breakpoints go first - this._sourceMaps.allMappedSources(mappedPath).forEach(sourcePath => { - if (sourcePath === argsPath) { - return; - } - - const sourceBPs = this._authoredPathsToMappedBPs.get(sourcePath); - if (sourceBPs) { - // Don't modify the cached array - args.breakpoints = args.breakpoints.concat(sourceBPs); - - // We need to assign the client IDs we generated for the mapped breakpoints becuase the runtime IDs may change - // So make sure we concat the client ids to the ids array so that they get mapped to the respective breakpoints later - const clientBreakpointIds = this._authoredPathsToClientBreakpointIds.get(sourcePath); - if (ids) { - ids = ids.concat(clientBreakpointIds); - } - } - }); - } else if (this.isRuntimeScript(argsPath)) { - // It's a generated file which is loaded - logger.log(`SourceMaps.setBP: SourceMaps are enabled but ${argsPath} is a runtime script`); - } else { - // Source (or generated) file which is not loaded. - logger.log(`SourceMaps.setBP: ${argsPath} can't be resolved to a loaded script. It may just not be loaded yet.`); - } - } else { - // No source.path - } - - this._requestSeqToSetBreakpointsArgs.set(requestSeq, { - originalBPs, - authoredPath: args.authoredPath, - generatedPath: args.source.path - }); - - return { args, ids }; - } - /** * Apply sourcemapping back to authored files from the response */ @@ -200,67 +108,7 @@ export class BaseSourceMapTransformer { } } - /** - * Apply sourcemapping to the stacktrace response - */ - public async stackTraceResponse(response: IInternalStackTraceResponseBody): Promise { - if (this._sourceMaps) { - await this._processingNewSourceMap; - for (let stackFrame of response.stackFrames) { - await this.fixSourceLocation(stackFrame); - } - } - } - - public async fixSourceLocation(sourceLocation: ISourceLocation|IInternalStackFrame): Promise { - if (!this._sourceMaps) { - return; - } - - if (!sourceLocation.source) { - return; - } - - await this._processingNewSourceMap; - - const mapped = this._sourceMaps.mapToAuthored(sourceLocation.source.path, sourceLocation.line, sourceLocation.column); - if (mapped && utils.existsSync(mapped.source)) { - // Script was mapped to a valid path - sourceLocation.source.path = mapped.source; - sourceLocation.source.sourceReference = undefined; - sourceLocation.source.name = path.basename(mapped.source); - sourceLocation.line = mapped.line; - sourceLocation.column = mapped.column; - sourceLocation.isSourceMapped = true; - } else { - const inlinedSource = mapped && this._sourceMaps.sourceContentFor(mapped.source); - if (mapped && inlinedSource) { - // Clear the path and set the sourceReference - the client will ask for - // the source later and it will be returned from the sourcemap - sourceLocation.source.name = path.basename(mapped.source); - sourceLocation.source.path = mapped.source; - sourceLocation.source.sourceReference = this.getSourceReferenceForScriptPath(mapped.source, inlinedSource); - sourceLocation.source.origin = localize('origin.inlined.source.map', 'read-only inlined content from source map'); - sourceLocation.line = mapped.line; - sourceLocation.column = mapped.column; - sourceLocation.isSourceMapped = true; - } else if (utils.existsSync(sourceLocation.source.path)) { - // Script could not be mapped, but does exist on disk. Keep it and clear the sourceReference. - sourceLocation.source.sourceReference = undefined; - sourceLocation.source.origin = undefined; - } - } - } - - /** - * Get the existing handle for this script, identified by runtime scriptId, or create a new one - */ - private getSourceReferenceForScriptPath(mappedPath: string, contents: string): number { - return this._sourceHandles.lookupF(container => container.mappedPath === mappedPath) || - this._sourceHandles.create({ contents, mappedPath }); - } - - public async scriptParsed(pathToGenerated: string, sourceMapURL: string): Promise { + public async scriptParsed(pathToGenerated: string, sourceMapURL: string | undefined): Promise { if (this._sourceMaps) { this._allRuntimeScriptPaths.add(this.fixPathCasing(pathToGenerated)); @@ -276,7 +124,7 @@ export class BaseSourceMapTransformer { logger.log(`SourceMaps.scriptParsed: ${pathToGenerated} was just loaded and has mapped sources: ${JSON.stringify(sources) }`); } - return sources; + return processNewSourceMapP; } else { return null; } diff --git a/src/transformers/eagerSourceMapTransformer.ts b/src/transformers/eagerSourceMapTransformer.ts index c4ea62aad..4c5d6a3fa 100644 --- a/src/transformers/eagerSourceMapTransformer.ts +++ b/src/transformers/eagerSourceMapTransformer.ts @@ -19,7 +19,9 @@ export class EagerSourceMapTransformer extends BaseSourceMapTransformer { protected init(args: ILaunchRequestArgs | IAttachRequestArgs): void { super.init(args); - if (args.sourceMaps) { + // Enable sourcemaps and async callstacks by default + const areSourceMapsEnabled = typeof args.sourceMaps === 'undefined' || args.sourceMaps; + if (areSourceMapsEnabled) { const generatedCodeGlobs = args.outFiles ? args.outFiles : args.outDir ? @@ -43,10 +45,10 @@ export class EagerSourceMapTransformer extends BaseSourceMapTransformer { private discoverSourceMapForGeneratedScript(generatedScriptPath: string): Promise { return this.findSourceMapUrlInFile(generatedScriptPath) - .then(uri => { + .then(async uri => { if (uri) { logger.log(`SourceMaps: sourcemap url parsed from end of generated content: ${uri}`); - return this._sourceMaps.processNewSourceMap(generatedScriptPath, uri, this._isVSClient); + await this._sourceMaps.processNewSourceMap(generatedScriptPath, uri, this._isVSClient); } else { logger.log(`SourceMaps: no sourcemap url found in generated script: ${generatedScriptPath}`); return undefined; diff --git a/src/transformers/fallbackToClientPathTransformer.ts b/src/transformers/fallbackToClientPathTransformer.ts index b5bfe010a..854384297 100644 --- a/src/transformers/fallbackToClientPathTransformer.ts +++ b/src/transformers/fallbackToClientPathTransformer.ts @@ -4,20 +4,26 @@ import { logger } from 'vscode-debugadapter'; import { UrlPathTransformer } from './urlPathTransformer'; -import { ChromeDebugSession } from '../chrome/chromeDebugSession'; import * as ChromeUtils from '../chrome/chromeUtils'; +import { IResourceIdentifier } from '../chrome/internal/sources/resourceIdentifier'; +import { IConnectedCDAConfiguration } from '../chrome/client/chromeDebugAdapter/cdaConfiguration'; +import { inject } from 'inversify'; +import { TYPES } from '../chrome/dependencyInjection.ts/types'; /** * Converts a local path from Code to a path on the target. Uses the UrlPathTransforme logic and fallbacks to asking the client if neccesary */ export class FallbackToClientPathTransformer extends UrlPathTransformer { private static ASK_CLIENT_TO_MAP_URL_TO_FILE_PATH_TIMEOUT = 500; + private readonly _session = this.configuration._session; - constructor(private _session: ChromeDebugSession) { - super(); + constructor( + @inject(TYPES.ConnectedCDAConfiguration) private readonly configuration: IConnectedCDAConfiguration, + ) { + super(configuration); } - protected async targetUrlToClientPath(scriptUrl: string): Promise { + protected async targetUrlToClientPath(scriptUrl: IResourceIdentifier): Promise { // First try the default UrlPathTransformer transformation return super.targetUrlToClientPath(scriptUrl).then(filePath => { // If it returns a valid non empty file path then that should be a valid result, so we use that @@ -32,8 +38,8 @@ export class FallbackToClientPathTransformer extends UrlPathTransformer { }); } - private async requestClientToMapURLToFilePath(url: string): Promise { - return new Promise((resolve, reject) => { + private async requestClientToMapURLToFilePath(url: IResourceIdentifier): Promise { + return new Promise((resolve, reject) => { this._session.sendRequest('mapURLToFilePath', {url: url}, FallbackToClientPathTransformer.ASK_CLIENT_TO_MAP_URL_TO_FILE_PATH_TIMEOUT, response => { if (response.success) { logger.log(`The client responded that the url "${url}" maps to the file path "${response.body.filePath}"`); diff --git a/src/transformers/lineNumberTransformer.ts b/src/transformers/lineNumberTransformer.ts index c841dbc4f..441523ca8 100644 --- a/src/transformers/lineNumberTransformer.ts +++ b/src/transformers/lineNumberTransformer.ts @@ -4,25 +4,23 @@ import { DebugProtocol } from 'vscode-debugprotocol'; -import { ChromeDebugSession } from '../chrome/chromeDebugSession'; -import { IDebugTransformer, ISetBreakpointsResponseBody, IStackTraceResponseBody, IScopesResponseBody } from '../debugAdapterInterfaces'; +import { IDebugTransformer, ISetBreakpointsResponseBody, IScopesResponseBody, IStackTraceResponseBody } from '../debugAdapterInterfaces'; +import { ComponentConfiguration } from '../chrome/internal/features/feature'; +import { inject, injectable } from 'inversify'; +import { TYPES } from '../chrome/dependencyInjection.ts/types'; /** * Converts from 1 based lines/cols on the client side to 0 based lines/cols on the target side */ -export class LineColTransformer implements IDebugTransformer { - columnBreakpointsEnabled: boolean; - - constructor(private _session: ChromeDebugSession) { - } - - public setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): DebugProtocol.SetBreakpointsArguments { - args.breakpoints.forEach(bp => this.convertClientLocationToDebugger(bp)); - if (!this.columnBreakpointsEnabled) { - args.breakpoints.forEach(bp => bp.column = undefined); - } - - return args; +@injectable() +export class LineColTransformer implements IDebugTransformer { + private columnBreakpointsEnabled: boolean; + private _clientToDebuggerLineNumberDifference: number; // Client line number - debugger line number. 0 if client line number is 0-based, 1 otherwise + private _clientToDebuggerColumnsDifference: number; // Similar to line numbers + + constructor(@inject(TYPES.ConnectedCDAConfiguration) configuration: ComponentConfiguration) { + this._clientToDebuggerLineNumberDifference = configuration._clientCapabilities.linesStartAt1 ? 1 : 0; + this._clientToDebuggerColumnsDifference = configuration._clientCapabilities.columnsStartAt1 ? 1 : 0; } public setBreakpointsResponse(response: ISetBreakpointsResponseBody): void { @@ -47,10 +45,6 @@ export class LineColTransformer implements IDebugTransformer { scopeResponse.scopes.forEach(scope => this.mapScopeLocations(scope)); } - public mappedExceptionStack(location: { line: number; column: number }): void { - this.convertDebuggerLocationToClient(location); - } - private mapScopeLocations(scope: DebugProtocol.Scope): void { this.convertDebuggerLocationToClient(scope); @@ -83,18 +77,18 @@ export class LineColTransformer implements IDebugTransformer { } public convertClientLineToDebugger(line: number): number { - return (this._session).convertClientLineToDebugger(line); + return line - this._clientToDebuggerLineNumberDifference; } public convertDebuggerLineToClient(line: number): number { - return (this._session).convertDebuggerLineToClient(line); + return line + this._clientToDebuggerLineNumberDifference; } public convertClientColumnToDebugger(column: number): number { - return (this._session).convertClientColumnToDebugger(column); + return column - this._clientToDebuggerColumnsDifference; } public convertDebuggerColumnToClient(column: number): number { - return (this._session).convertDebuggerColumnToClient(column); + return column + this._clientToDebuggerColumnsDifference; } } diff --git a/src/transformers/remotePathTransformer.ts b/src/transformers/remotePathTransformer.ts index 98123caa1..ac772eda5 100644 --- a/src/transformers/remotePathTransformer.ts +++ b/src/transformers/remotePathTransformer.ts @@ -5,14 +5,17 @@ import * as fs from 'fs'; import * as path from 'path'; import { logger } from 'vscode-debugadapter'; -import { DebugProtocol } from 'vscode-debugprotocol'; -import { IAttachRequestArgs, ICommonRequestArgs, ILaunchRequestArgs, IStackTraceResponseBody } from '../debugAdapterInterfaces'; +import { ICommonRequestArgs } from '../debugAdapterInterfaces'; import * as errors from '../errors'; import { UrlPathTransformer } from '../transformers/urlPathTransformer'; import * as utils from '../utils'; import * as nls from 'vscode-nls'; const localize = nls.loadMessageBundle(); +import { IResourceIdentifier, parseResourceIdentifier } from '../chrome/internal/sources/resourceIdentifier'; +import { inject } from 'inversify'; +import { TYPES } from '../chrome/dependencyInjection.ts/types'; +import { ConnectedCDAConfiguration } from '../chrome/client/chromeDebugAdapter/cdaConfiguration'; /** * Converts a local path from Code to a path on the target. @@ -21,14 +24,9 @@ export class RemotePathTransformer extends UrlPathTransformer { private _localRoot: string; private _remoteRoot: string; - public async launch(args: ILaunchRequestArgs): Promise { - await super.launch(args); - return this.init(args); - } - - public async attach(args: IAttachRequestArgs): Promise { - await super.attach(args); - return this.init(args); + constructor(@inject(TYPES.ConnectedCDAConfiguration) configuration: ConnectedCDAConfiguration) { + super(configuration); + this.init(configuration.args); } private async init(args: ICommonRequestArgs): Promise { @@ -62,61 +60,43 @@ export class RemotePathTransformer extends UrlPathTransformer { return localRootP; } - public async scriptParsed(scriptPath: string): Promise { + public async scriptParsed(scriptPath: IResourceIdentifier): Promise { scriptPath = await super.scriptParsed(scriptPath); scriptPath = this.getClientPathFromTargetPath(scriptPath) || scriptPath; return scriptPath; } - public async stackTraceResponse(response: IStackTraceResponseBody): Promise { - await Promise.all(response.stackFrames.map(stackFrame => this.fixSource(stackFrame.source))); - } - - public async fixSource(source: DebugProtocol.Source): Promise { - await super.fixSource(source); - - const remotePath = source && source.path; - if (remotePath) { - const localPath = this.getClientPathFromTargetPath(remotePath) || remotePath; - if (utils.existsSync(localPath)) { - source.path = localPath; - source.sourceReference = undefined; - source.origin = undefined; - } - } - } - - private shouldMapPaths(remotePath: string): boolean { + private shouldMapPaths(remotePath: IResourceIdentifier): boolean { // Map paths only if localRoot/remoteRoot are set, and the remote path is absolute on some system - return !!this._localRoot && !!this._remoteRoot && (path.posix.isAbsolute(remotePath) || path.win32.isAbsolute(remotePath)); + return !!this._localRoot && !!this._remoteRoot && (path.posix.isAbsolute(remotePath.canonicalized) || path.win32.isAbsolute(remotePath.canonicalized)); } - public getClientPathFromTargetPath(remotePath: string): string { + public getClientPathFromTargetPath(remotePath: IResourceIdentifier): IResourceIdentifier { remotePath = super.getClientPathFromTargetPath(remotePath) || remotePath; // Map as non-file-uri because remoteRoot won't expect a file uri - remotePath = utils.fileUrlToPath(remotePath); - if (!this.shouldMapPaths(remotePath)) return ''; + remotePath = parseResourceIdentifier(utils.fileUrlToPath(remotePath.canonicalized)); + if (!this.shouldMapPaths(remotePath)) return parseResourceIdentifier(''); - const relPath = relative(this._remoteRoot, remotePath); + const relPath = relative(this._remoteRoot, remotePath.canonicalized); let localPath = join(this._localRoot, relPath); localPath = utils.fixDriveLetterAndSlashes(localPath); logger.log(`Mapped remoteToLocal: ${remotePath} -> ${localPath}`); - return localPath; + return parseResourceIdentifier(localPath); } - public getTargetPathFromClientPath(localPath: string): string { + public getTargetPathFromClientPath(localPath: IResourceIdentifier): IResourceIdentifier { localPath = super.getTargetPathFromClientPath(localPath) || localPath; if (!this.shouldMapPaths(localPath)) return localPath; - const relPath = relative(this._localRoot, localPath); + const relPath = relative(this._localRoot, localPath.canonicalized); let remotePath = join(this._remoteRoot, relPath); remotePath = utils.fixDriveLetterAndSlashes(remotePath, /*uppercaseDriveLetter=*/true); logger.log(`Mapped localToRemote: ${localPath} -> ${remotePath}`); - return remotePath; + return parseResourceIdentifier(remotePath); } } diff --git a/src/transformers/urlPathTransformer.ts b/src/transformers/urlPathTransformer.ts index 12dbac7dd..d4d495446 100644 --- a/src/transformers/urlPathTransformer.ts +++ b/src/transformers/urlPathTransformer.ts @@ -4,75 +4,51 @@ import { BasePathTransformer } from './basePathTransformer'; -import { ISetBreakpointsArgs, ILaunchRequestArgs, IAttachRequestArgs, IStackTraceResponseBody, IPathMapping } from '../debugAdapterInterfaces'; -import * as utils from '../utils'; +import { IPathMapping } from '../debugAdapterInterfaces'; import { logger } from 'vscode-debugadapter'; -import { DebugProtocol } from 'vscode-debugprotocol'; import * as ChromeUtils from '../chrome/chromeUtils'; import * as path from 'path'; +import { newResourceIdentifierMap, IResourceIdentifier } from '../chrome/internal/sources/resourceIdentifier'; +import { parseResourceIdentifier } from '..'; +import { injectable, inject } from 'inversify'; +import { TYPES } from '../chrome/dependencyInjection.ts/types'; +import { IConnectedCDAConfiguration } from '../chrome/client/chromeDebugAdapter/cdaConfiguration'; /** * Converts a local path from Code to a path on the target. */ +@injectable() export class UrlPathTransformer extends BasePathTransformer { private _pathMapping: IPathMapping; - private _clientPathToTargetUrl = new Map(); - private _targetUrlToClientPath = new Map(); + private _clientPathToTargetUrl = newResourceIdentifierMap(); + private _targetUrlToClientPath = newResourceIdentifierMap(); - public launch(args: ILaunchRequestArgs): Promise { - this._pathMapping = args.pathMapping; - return super.launch(args); - } - - public attach(args: IAttachRequestArgs): Promise { - this._pathMapping = args.pathMapping; - return super.attach(args); - } - - public setBreakpoints(args: ISetBreakpointsArgs): ISetBreakpointsArgs { - if (!args.source.path) { - // sourceReference script, nothing to do - return args; - } - - if (utils.isURL(args.source.path)) { - // already a url, use as-is - logger.log(`Paths.setBP: ${args.source.path} is already a URL`); - return args; - } - - const path = utils.canonicalizeUrl(args.source.path); - const url = this.getTargetPathFromClientPath(path); - if (url) { - args.source.path = url; - logger.log(`Paths.setBP: Resolved ${path} to ${args.source.path}`); - return args; - } else { - logger.log(`Paths.setBP: No target url cached yet for client path: ${path}.`); - args.source.path = path; - return args; - } + constructor(@inject(TYPES.ConnectedCDAConfiguration) configuration: IConnectedCDAConfiguration) { + super(); + this._pathMapping = configuration.args.pathMapping; } public clearTargetContext(): void { - this._clientPathToTargetUrl = new Map(); - this._targetUrlToClientPath = new Map(); + this._clientPathToTargetUrl = newResourceIdentifierMap(); + this._targetUrlToClientPath = newResourceIdentifierMap(); } - public async scriptParsed(scriptUrl: string): Promise { + public async scriptParsed(scriptUrl: IResourceIdentifier): Promise { const clientPath = await this.targetUrlToClientPath(scriptUrl); - if (!clientPath) { + if (clientPath.canonicalized === '') { // It's expected that eval scripts (eval://) won't be resolved - if (!scriptUrl.startsWith(ChromeUtils.EVAL_NAME_PREFIX)) { + if (!scriptUrl.canonicalized.startsWith(ChromeUtils.EVAL_NAME_PREFIX)) { logger.log(`Paths.scriptParsed: could not resolve ${scriptUrl} to a file with pathMapping/webRoot: ${JSON.stringify(this._pathMapping)}. It may be external or served directly from the server's memory (and that's OK).`); } } else { logger.log(`Paths.scriptParsed: resolved ${scriptUrl} to ${clientPath}. pathMapping/webroot: ${JSON.stringify(this._pathMapping)}`); - const canonicalizedClientPath = utils.canonicalizeUrl(clientPath); - this._clientPathToTargetUrl.set(canonicalizedClientPath, scriptUrl); - this._targetUrlToClientPath.set(scriptUrl, clientPath); + const canonicalizedClientPath = clientPath; + + // an HTML file with multiple script tags will call this method several times with the same scriptUrl, so we use setAndReplaceIfExist + this._clientPathToTargetUrl.setAndReplaceIfExist(canonicalizedClientPath, scriptUrl); + this._targetUrlToClientPath.setAndReplaceIfExist(scriptUrl, clientPath); scriptUrl = clientPath; } @@ -80,43 +56,21 @@ export class UrlPathTransformer extends BasePathTransformer { return Promise.resolve(scriptUrl); } - public async stackTraceResponse(response: IStackTraceResponseBody): Promise { - await Promise.all(response.stackFrames.map(frame => this.fixSource(frame.source))); - } - - public async fixSource(source: DebugProtocol.Source): Promise { - if (source && source.path) { - // Try to resolve the url to a path in the workspace. If it's not in the workspace, - // just use the script.url as-is. It will be resolved or cleared by the SourceMapTransformer. - const clientPath = this.getClientPathFromTargetPath(source.path) || - await this.targetUrlToClientPath(source.path); - - // Incoming stackFrames have sourceReference and path set. If the path was resolved to a file in the workspace, - // clear the sourceReference since it's not needed. - if (clientPath) { - source.path = clientPath; - source.sourceReference = undefined; - source.origin = undefined; - source.name = path.basename(clientPath); - } - } - } - - public getTargetPathFromClientPath(clientPath: string): string { + public getTargetPathFromClientPath(clientPath: IResourceIdentifier): IResourceIdentifier { // If it's already a URL, skip the Map - return path.isAbsolute(clientPath) ? - this._clientPathToTargetUrl.get(utils.canonicalizeUrl(clientPath)) : + return path.isAbsolute(clientPath.canonicalized) ? + this._clientPathToTargetUrl.get(clientPath) : clientPath; } - public getClientPathFromTargetPath(targetPath: string): string { - return this._targetUrlToClientPath.get(targetPath); + public getClientPathFromTargetPath(targetPath: IResourceIdentifier): IResourceIdentifier { + return this._targetUrlToClientPath.tryGetting(targetPath); } /** * Overridable for VS to ask Client to resolve path */ - protected async targetUrlToClientPath(scriptUrl: string): Promise { - return Promise.resolve(ChromeUtils.targetUrlToClientPath(scriptUrl, this._pathMapping)); + protected async targetUrlToClientPath(scriptUrl: IResourceIdentifier): Promise { + return Promise.resolve(parseResourceIdentifier(ChromeUtils.targetUrlToClientPath(scriptUrl.canonicalized, this._pathMapping))); } } diff --git a/src/typeUtils.ts b/src/typeUtils.ts new file mode 100644 index 000000000..ef5a3cc14 --- /dev/null +++ b/src/typeUtils.ts @@ -0,0 +1,31 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +export type MakePropertyRequired = T & { [P in K]-?: T[K] }; +export type RemoveProperty = Pick>; +export type AllRequired = { [P in keyof T]-?: T[P] }; +export type AllOptional = { [P in keyof T]+?: T[P] }; + +// export type Omit = Pick>; + +// interface A { +// a?: string; +// b?: number; +// c: object; +// } + +// const laaf: MakePropertyRequired; +// laaf. + +// const lala: RemoveProperty; +// lala. +// let obj: A = null as A; +// obj.b++; + +// let objc: Record<'a' | 'b', A>; +// objc. + +export function isIterable(possiblyIterable: any): possiblyIterable is Iterable { + return possiblyIterable && typeof possiblyIterable[Symbol.iterator] === 'function'; +} \ No newline at end of file diff --git a/src/utils.ts b/src/utils.ts index ac0871e6c..f2cc51481 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -667,4 +667,4 @@ export function makeUnique(elements: T[]): T[] { export function defaultIfUndefined(value: T | undefined, defaultValue: T): T { return value !== undefined ? value : defaultValue; -} \ No newline at end of file +} diff --git a/test/chrome/chromeDebugAdapter.test.ts b/test/chrome/chromeDebugAdapter.test.ts index 644ef3202..f2acc2d7b 100644 --- a/test/chrome/chromeDebugAdapter.test.ts +++ b/test/chrome/chromeDebugAdapter.test.ts @@ -18,14 +18,14 @@ import * as mockery from 'mockery'; import { EventEmitter } from 'events'; import * as assert from 'assert'; import { Mock, MockBehavior, It, IMock, Times } from 'typemoq'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import * as testUtils from '../testUtils'; import * as utils from '../../src/utils'; import * as fs from 'fs'; /** Not mocked - use for type only */ -import {ChromeDebugAdapter as _ChromeDebugAdapter } from '../../src/chrome/chromeDebugAdapter'; +import {ChromeDebugLogic as _ChromeDebugAdapter } from '../../src/chrome/chromeDebugAdapter'; import { InitializedEvent, LoadedSourceEvent, Source, BreakpointEvent } from 'vscode-debugadapter/lib/debugSession'; import { Version, TargetVersions } from '../../src'; @@ -136,7 +136,7 @@ suite('ChromeDebugAdapter', () => { Promise.resolve(''); }); - mockEventEmitter.emit('Debugger.scriptParsed', { scriptId, url }); + mockEventEmitter.emit('Debugger.scriptParsed', { scriptId, url }); } // Helper to run async asserts inside promises so they can be correctly awaited @@ -195,7 +195,7 @@ suite('ChromeDebugAdapter', () => { mockChrome.Debugger .setup(x => x.setBreakpointByUrl(It.isValue({ urlRegex, lineNumber, columnNumber, condition }))) .returns(() => Promise.resolve( - { + { breakpointId: BP_ID + i, locations: success ? [location] : [] })) @@ -204,7 +204,7 @@ suite('ChromeDebugAdapter', () => { mockChrome.Debugger .setup(x => x.setBreakpoint(It.isValue({ location: { lineNumber, columnNumber, scriptId }, condition }))) .returns(() => Promise.resolve( - { + { breakpointId: BP_ID + i, actualLocation: success ? location : null })) @@ -342,9 +342,9 @@ suite('ChromeDebugAdapter', () => { .then(response => { expectRemoveBreakpoint([0, 1]); mockEventEmitter.emit('Debugger.globalObjectCleared'); - mockEventEmitter.emit('Debugger.scriptParsed', { scriptId: 'afterRefreshScriptId', url: FILE_NAME }); - mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 0, location: { scriptId: 'afterRefreshScriptId' } }); - mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 1, location: { scriptId: 'afterRefreshScriptId' } }); + mockEventEmitter.emit('Debugger.scriptParsed', { scriptId: 'afterRefreshScriptId', url: FILE_NAME }); + mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 0, location: { scriptId: 'afterRefreshScriptId' } }); + mockEventEmitter.emit('Debugger.breakpointResolved', { breakpointId: BP_ID + 1, location: { scriptId: 'afterRefreshScriptId' } }); breakpoints.push({ line: 321, column: 123 }); expectSetBreakpoint(breakpoints, FILE_NAME, 'afterRefreshScriptId'); @@ -359,7 +359,7 @@ suite('ChromeDebugAdapter', () => { ]; // Set up the mock to return a different location - const location: Crdp.Debugger.Location = { + const location: CDTP.Debugger.Location = { scriptId: SCRIPT_ID, lineNumber: breakpoints[0].line + 10, columnNumber: breakpoints[0].column + 10 }; const expectedResponse: ISetBreakpointsResponseBody = { breakpoints: [{ line: location.lineNumber, column: location.columnNumber, verified: true, id: 1000 }]}; @@ -368,7 +368,7 @@ suite('ChromeDebugAdapter', () => { mockChrome.Debugger .setup(x => x.setBreakpointByUrl(It.isValue({ urlRegex: expectedRegex, lineNumber: breakpoints[0].line, columnNumber: breakpoints[0].column, condition: undefined }))) .returns(() => Promise.resolve( - { breakpointId: BP_ID, locations: [location] })) + { breakpointId: BP_ID, locations: [location] })) .verifiable(); return chromeDebugAdapter.attach(ATTACH_ARGS) @@ -715,7 +715,7 @@ suite('ChromeDebugAdapter', () => { }); suite('evaluate()', () => { - function getExpectedValueResponse(resultObj: Crdp.Runtime.RemoteObject): IEvaluateResponseBody { + function getExpectedValueResponse(resultObj: CDTP.Runtime.RemoteObject): IEvaluateResponseBody { let result: string; let variablesReference = 0; if (resultObj.type === 'string') { @@ -731,21 +731,21 @@ suite('ChromeDebugAdapter', () => { }; } - function setupEvalMock(expression: string, result: Crdp.Runtime.RemoteObject): void { + function setupEvalMock(expression: string, result: CDTP.Runtime.RemoteObject): void { mockChrome.Runtime - .setup(x => x.evaluate(It.isValue({ expression, silent: true, generatePreview: true, includeCommandLineAPI: true, objectGroup: 'console', userGesture: true }))) - .returns(() => Promise.resolve({ result })); + .setup(x => x.evaluate(It.isValue({ expression, silent: true, generatePreview: true, includeCommandLineAPI: true, objectGroup: 'console', userGesture: true }))) + .returns(() => Promise.resolve({ result })); } - function setupEvalOnCallFrameMock(expression: string, callFrameId: string, result: Crdp.Runtime.RemoteObject): void { + function setupEvalOnCallFrameMock(expression: string, callFrameId: string, result: CDTP.Runtime.RemoteObject): void { mockChrome.Debugger .setup(x => x.evaluateOnCallFrame(It.isValue({ expression, callFrameId, silent: true, generatePreview: true, includeCommandLineAPI: true, objectGroup: 'console' }))) - .returns(() => Promise.resolve({ result })); + .returns(() => Promise.resolve({ result })); } test('calls Runtime.evaluate when not paused', () => { const expression = '1+1'; - const result: Crdp.Runtime.RemoteObject = { type: 'string', description: '2' }; + const result: CDTP.Runtime.RemoteObject = { type: 'string', description: '2' }; setupEvalMock(expression, result); return chromeDebugAdapter.evaluate({ expression }).then(response => { @@ -756,7 +756,7 @@ suite('ChromeDebugAdapter', () => { test('calls Debugger.evaluateOnCallFrame when paused', () => { const callFrameId = '1'; const expression = '1+1'; - const result: Crdp.Runtime.RemoteObject = { type: 'string', description: '2' }; + const result: CDTP.Runtime.RemoteObject = { type: 'string', description: '2' }; setupEvalOnCallFrameMock(expression, callFrameId, result); // Sue me (just easier than sending a Debugger.paused event) @@ -773,10 +773,10 @@ suite('ChromeDebugAdapter', () => { await chromeDebugAdapter.attach(ATTACH_ARGS); const scriptId = 'script1'; - const location: Crdp.Debugger.Location = { lineNumber: 0, columnNumber: 0, scriptId }; + const location: CDTP.Debugger.Location = { lineNumber: 0, columnNumber: 0, scriptId }; const callFrame = { callFrameId: 'id1', location }; emitScriptParsed('', scriptId); - mockEventEmitter.emit('Debugger.paused', { callFrames: [callFrame, callFrame] }); + mockEventEmitter.emit('Debugger.paused', { callFrames: [callFrame, callFrame] }); const { stackFrames } = await chromeDebugAdapter.stackTrace({ threadId: THREAD_ID }); @@ -786,7 +786,7 @@ suite('ChromeDebugAdapter', () => { const sourceReference = stackFrames[0].source.sourceReference; // If it pauses a second time, and we request another stackTrace, should have the same result - mockEventEmitter.emit('Debugger.paused', {callFrames: [callFrame, callFrame]}); + mockEventEmitter.emit('Debugger.paused', {callFrames: [callFrame, callFrame]}); const { stackFrames: stackFrames2 } = await chromeDebugAdapter.stackTrace({ threadId: THREAD_ID }); assert.equal(stackFrames2.length, 2); @@ -806,7 +806,7 @@ suite('ChromeDebugAdapter', () => { const generatedExceptionStr = getExceptionStr(generatedPath, 6); const authoredExceptionStr = getExceptionStr(authoredPath, 12); - const exceptionEvent: Crdp.Runtime.ExceptionThrownEvent = { + const exceptionEvent: CDTP.Runtime.ExceptionThrownEvent = { 'timestamp': 1490164925297, 'exceptionDetails': { 'exceptionId': 21, diff --git a/test/chrome/consoleHelper.test.ts b/test/chrome/consoleHelper.test.ts index a406687a0..b19403ceb 100644 --- a/test/chrome/consoleHelper.test.ts +++ b/test/chrome/consoleHelper.test.ts @@ -3,7 +3,7 @@ *--------------------------------------------------------*/ import * as assert from 'assert'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import * as testUtils from '../testUtils'; import * as ConsoleHelper from '../../src/chrome/consoleHelper'; @@ -20,7 +20,7 @@ suite('ConsoleHelper', () => { /** * Test helper valid when the message consists only of strings that will be collapsed to one string */ - function doAssertForString(params: Crdp.Runtime.ConsoleAPICalledEvent, expectedText: string, expectedIsError = false): void { + function doAssertForString(params: CDTP.Runtime.ConsoleAPICalledEvent, expectedText: string, expectedIsError = false): void { const result = ConsoleHelper.formatConsoleArguments(params.type, params.args, params.stackTrace); // Strings are collapsed to one string @@ -164,8 +164,8 @@ namespace Runtime { * @param params - The list of parameters passed to the log function * @param overrideProps - An object of props that the message should have. The rest are filled in with defaults. */ - function makeMockMessage(type: string, args: (string | number | null | undefined)[], overrideProps?: any): Crdp.Runtime.ConsoleAPICalledEvent { - const message: Crdp.Runtime.ConsoleAPICalledEvent = { + function makeMockMessage(type: string, args: (string | number | null | undefined)[], overrideProps?: any): CDTP.Runtime.ConsoleAPICalledEvent { + const message: CDTP.Runtime.ConsoleAPICalledEvent = { type, executionContextId: 2, timestamp: Date.now(), @@ -187,15 +187,15 @@ namespace Runtime { * Returns a mock ConsoleAPICalledEvent with the given argument values. * You can pass '$obj' to get an object. */ - export function makeLog(...args: (string | number | null | undefined)[]): Crdp.Runtime.ConsoleAPICalledEvent { + export function makeLog(...args: (string | number | null | undefined)[]): CDTP.Runtime.ConsoleAPICalledEvent { const msg = makeMockMessage('log', args); return msg; } - export function makeArgs(...args: (string | number | null | undefined)[]): Crdp.Runtime.RemoteObject[] { + export function makeArgs(...args: (string | number | null | undefined)[]): CDTP.Runtime.RemoteObject[] { return args.map(arg => { if (arg === '$obj') { - return { + return { value: undefined, type: 'object', description: 'Object', @@ -212,14 +212,14 @@ namespace Runtime { }); } - export function makeAssert(...args: any[]): Crdp.Runtime.ConsoleAPICalledEvent { - const fakeStackTrace: Crdp.Runtime.StackTrace = { + export function makeAssert(...args: any[]): CDTP.Runtime.ConsoleAPICalledEvent { + const fakeStackTrace: CDTP.Runtime.StackTrace = { callFrames: [{ url: '/script/a.js', lineNumber: 4, columnNumber: 1, scriptId: '1', functionName: 'myFn' }] }; return makeMockMessage('assert', args, { level: 'error', stackTrace: fakeStackTrace }); } - export function makeNetworkLog(text: string, url: string): Crdp.Runtime.ConsoleAPICalledEvent { + export function makeNetworkLog(text: string, url: string): CDTP.Runtime.ConsoleAPICalledEvent { return makeMockMessage('log', [text], { source: 'network', url, level: 'error' }); } } diff --git a/test/chrome/variables.test.ts b/test/chrome/variables.test.ts index 2b4595a06..dea05ec67 100644 --- a/test/chrome/variables.test.ts +++ b/test/chrome/variables.test.ts @@ -3,7 +3,7 @@ *--------------------------------------------------------*/ import * as assert from 'assert'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; import * as Variables from '../../src/chrome/variables'; @@ -24,10 +24,10 @@ suite('Variables', () => { } suite('getArrayPreview()', () => { - function getRemoteArray(props: any[]): Crdp.Runtime.RemoteObject { - const preview = { + function getRemoteArray(props: any[]): CDTP.Runtime.RemoteObject { + const preview = { properties: props.map((prop, i) => { - return { + return { name: i.toString(), type: getPropType(prop), value: getPropValue(prop) @@ -82,11 +82,11 @@ suite('Variables', () => { }); suite('getObjectPreview()', () => { - function getRemoteObject(obj: any): Crdp.Runtime.RemoteObject { - const preview = { + function getRemoteObject(obj: any): CDTP.Runtime.RemoteObject { + const preview = { properties: Object.keys(obj).map((prop) => { const value = obj[prop]; - return { + return { name: prop.toString(), type: getPropType(value), value: getPropValue(value) diff --git a/test/mocks/debugProtocolMocks.ts b/test/mocks/debugProtocolMocks.ts index 289eb3acd..d327a0ff7 100644 --- a/test/mocks/debugProtocolMocks.ts +++ b/test/mocks/debugProtocolMocks.ts @@ -5,16 +5,16 @@ import { EventEmitter } from 'events'; import { Mock, IMock } from 'typemoq'; -import { Protocol as Crdp } from 'devtools-protocol'; +import { Protocol as CDTP } from 'devtools-protocol'; export interface IMockChromeConnectionAPI { - apiObjects: Crdp.ProtocolApi; + apiObjects: CDTP.ProtocolApi; - Console: IMock; - Debugger: IMock; - Runtime: IMock; - Inspector: IMock; - Log: IMock; + Console: IMock; + Debugger: IMock; + Runtime: IMock; + Inspector: IMock; + Log: IMock; mockEventEmitter: EventEmitter; } @@ -65,31 +65,31 @@ function getLogStubs(mockEventEmitter) { export function getMockChromeConnectionApi(): IMockChromeConnectionAPI { const mockEventEmitter = new EventEmitter(); - const mockConsole = Mock.ofInstance(getConsoleStubs(mockEventEmitter)); + const mockConsole = Mock.ofInstance(getConsoleStubs(mockEventEmitter)); mockConsole.callBase = true; mockConsole .setup(x => x.enable()) .returns(() => Promise.resolve()); - const mockDebugger = Mock.ofInstance(getDebuggerStubs(mockEventEmitter)); + const mockDebugger = Mock.ofInstance(getDebuggerStubs(mockEventEmitter)); mockDebugger.callBase = true; mockDebugger .setup(x => x.enable()) .returns(() => Promise.resolve(null)); - const mockRuntime = Mock.ofInstance(getRuntimeStubs(mockEventEmitter)); + const mockRuntime = Mock.ofInstance(getRuntimeStubs(mockEventEmitter)); mockRuntime.callBase = true; mockRuntime .setup(x => x.enable()) .returns(() => Promise.resolve()); - const mockInspector = Mock.ofInstance(getInspectorStubs(mockEventEmitter)); + const mockInspector = Mock.ofInstance(getInspectorStubs(mockEventEmitter)); mockInspector.callBase = true; - const mockLog = Mock.ofInstance(getLogStubs(mockEventEmitter)); + const mockLog = Mock.ofInstance(getLogStubs(mockEventEmitter)); mockLog.callBase = true; - const chromeConnectionAPI: Crdp.ProtocolApi = { + const chromeConnectionAPI: CDTP.ProtocolApi = { Console: mockConsole.object, Debugger: mockDebugger.object, Runtime: mockRuntime.object, diff --git a/test/testDebug/testDebug.ts b/test/testDebug/testDebug.ts new file mode 100644 index 000000000..7e93daaf9 --- /dev/null +++ b/test/testDebug/testDebug.ts @@ -0,0 +1,40 @@ +/*--------------------------------------------------------- + * Copyright (C) Microsoft Corporation. All rights reserved. + *--------------------------------------------------------*/ + +import { ChromeDebugSession, logger, UrlPathTransformer, BaseSourceMapTransformer, telemetry } from '../../src/index'; +import * as path from 'path'; +import * as os from 'os'; + +import { TestDebugAdapter } from './testDebugAdapter'; +import { OnlyProvideCustomLauncherExtensibilityPoints } from '../../src/chrome/extensibility/extensibilityPoints'; +import { TestDebugeeLauncher } from './testDebugeeLauncher'; +import { TestDebugeeRunner } from './testDebugeeRunner'; + +const EXTENSION_NAME = 'debugger-for-chrome'; + +// Start a ChromeDebugSession configured to only match 'page' targets, which are Chrome tabs. +// Cast because DebugSession is declared twice - in this repo's vscode-debugadapter, and that of -core... TODO +const logFilePath = path.resolve(os.tmpdir(), 'vscode-chrome-debug.txt'); +ChromeDebugSession.run(ChromeDebugSession.getSession( + { + adapter: TestDebugAdapter, + extensionName: EXTENSION_NAME, + extensibilityPoints: new OnlyProvideCustomLauncherExtensibilityPoints(TestDebugeeLauncher, TestDebugeeRunner, logFilePath), + logFilePath: logFilePath, + // targetFilter: defaultTargetFilter, + + pathTransformer: UrlPathTransformer, + sourceMapTransformer: BaseSourceMapTransformer, + })); + +/* tslint:disable:no-var-requires */ +const debugAdapterVersion = require('../../../package.json').version; +logger.log(EXTENSION_NAME + ': ' + debugAdapterVersion); + +/* __GDPR__FRAGMENT__ + "DebugCommonProperties" : { + "Versions.DebugAdapter" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" } + } +*/ +telemetry.telemetry.addCustomGlobalProperty({ 'Versions.DebugAdapter': debugAdapterVersion }); diff --git a/test/testDebug/testDebugAdapter.ts b/test/testDebug/testDebugAdapter.ts new file mode 100644 index 000000000..2710e3b97 --- /dev/null +++ b/test/testDebug/testDebugAdapter.ts @@ -0,0 +1,4 @@ +import { ChromeDebugAdapter as CoreDebugAdapter } from '../../src/index'; + +export class TestDebugAdapter extends CoreDebugAdapter { +} \ No newline at end of file diff --git a/test/testDebug/testDebugeeLauncher.ts b/test/testDebug/testDebugeeLauncher.ts new file mode 100644 index 000000000..354a3e428 --- /dev/null +++ b/test/testDebug/testDebugeeLauncher.ts @@ -0,0 +1,12 @@ +import { IDebuggeeLauncher, ILaunchRequestArgs, ILaunchResult, ITelemetryPropertyCollector } from '../../src'; + +export class TestDebugeeLauncher implements IDebuggeeLauncher { + public async launch(_args: ILaunchRequestArgs, _telemetryPropertyCollector: ITelemetryPropertyCollector): Promise { + return { + + }; + } + + public async waitForDebugeeToBeReady(): Promise { + } +} \ No newline at end of file diff --git a/test/testDebug/testDebugeeRunner.ts b/test/testDebug/testDebugeeRunner.ts new file mode 100644 index 000000000..6195f635b --- /dev/null +++ b/test/testDebug/testDebugeeRunner.ts @@ -0,0 +1,9 @@ +import { ITelemetryPropertyCollector } from '../../src'; +import { IDebuggeeRunner } from '../../src/chrome/debugeeStartup/debugeeLauncher'; + +export class TestDebugeeRunner implements IDebuggeeRunner { + public async run(_telemetryPropertyCollector: ITelemetryPropertyCollector): Promise { + } + + constructor() { } +} \ No newline at end of file diff --git a/tsconfig.json b/tsconfig.json index 8ba06cf81..98d82aad4 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -2,23 +2,33 @@ "compilerOptions": { "module": "commonjs", "moduleResolution": "node", - "target": "es2015", + "target": "ES6", "sourceMap": true, "outDir": "out", "declaration": true, - + "types": [ + "reflect-metadata" + ], + "experimentalDecorators": true, + "emitDecoratorMetadata": true, "noUnusedLocals": true, "noImplicitThis": true, "noFallthroughCasesInSwitch": true, "noImplicitReturns": true, + "noUnusedParameters": true, + "strictFunctionTypes": true, + "noImplicitAny": true, + "strictNullChecks": false, + "strictPropertyInitialization": false, "lib": [ - "es2015" - ], - - "experimentalDecorators": true + "es6", + "dom" + ] }, "include": [ "src/**/*.ts", + "test/testDebug/**/*.ts", + "test-DoNot-/**/*.ts", "node_modules/@types/**/*.ts" ] -} +} \ No newline at end of file