[
  {
    "path": ".gitignore",
    "content": "node_modules/\n.nyc_output/\ncoverage/\ndist/*.bundle.js\ndist/*.bundle.js.map\ndist/mappings.wasm\ndist/*LICENSE*\n"
  },
  {
    "path": ".mocharc.json",
    "content": "{\n\t\"require\": [\"ts-node/register/transpile-only\", \"src/test/setup.ts\"],\n\t\"timeout\": 20000,\n\t\"slow\": 6000,\n\t\"exit\": true,\n\t\"spec\": \"src/test/test*.ts\"\n}\n"
  },
  {
    "path": ".vscode/extensions.json",
    "content": "{\n\t// See https://go.microsoft.com/fwlink/?LinkId=827846 to learn about workspace recommendations.\n\t// Extension identifier format: ${publisher}.${name}. Example: vscode.csharp\n\n\t// List of extensions which should be recommended for users of this workspace.\n\t\"recommendations\": [\n\t\t\"hbenl.vscode-mocha-test-adapter\"\n\t],\n\t// List of extensions recommended by VS Code that should not be recommended for users of this workspace.\n\t\"unwantedRecommendations\": [\n\t]\n}"
  },
  {
    "path": ".vscode/launch.json",
    "content": "{\n\t\"version\": \"0.2.0\",\n\t\"configurations\": [\n\t\t{\n\t\t\t\"type\": \"pwa-node\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"name\": \"debug server\",\n\t\t\t\"program\": \"${workspaceFolder}/dist/adapter.bundle.js\",\n\t\t\t\"args\": [ \"--server=4711\" ],\n\t\t\t\"cwd\": \"${workspaceFolder}\",\n\t\t\t\"sourceMaps\": true\n\t\t},\n\t\t{\n\t\t\t\"type\": \"pwa-extensionHost\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"name\": \"extension host\",\n\t\t\t\"args\": [\n\t\t\t\t\"--extensionDevelopmentPath=${workspaceFolder}\"\n\t\t\t],\n\t\t\t\"sourceMaps\": true\n\t\t},\n\t\t{\n\t\t\t\"type\": \"firefox\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"name\": \"web test\",\n\t\t\t\"debugServer\": 4711,\n\t\t\t\"file\": \"${workspaceFolder}/testdata/web/index.html\"\n\t\t},\n\t\t{\n\t\t\t\"type\": \"firefox\",\n\t\t\t\"request\": \"launch\",\n\t\t\t\"name\": \"webextension test\",\n\t\t\t\"debugServer\": 4711,\n\t\t\t\"addonPath\": \"${workspaceFolder}/testdata/webExtension/addOn\",\n\t\t\t\"file\": \"${workspaceFolder}/testdata/webExtension/index.html\"\n\t\t}\n\t],\n\t\"compounds\": [\n\t\t{\n\t\t\t\"name\": \"server & extension\",\n\t\t\t\"configurations\": [\n\t\t\t\t\"debug server\",\n\t\t\t\t\"extension host\"\n\t\t\t]\n\t\t}\n\t]\n}"
  },
  {
    "path": ".vscode/settings.json",
    "content": "{\n\t\"editor.insertSpaces\": false,\n\t\"files.exclude\": {\n\t\t\".git\": true,\n\t\t\"node_modules\": true,\n\t\t\".nyc_output\": true,\n\t\t\"coverage\": true\n\t},\n\t\"editor.rulers\": [100],\n\t\"typescript.tsdk\": \"./node_modules/typescript/lib\",\n\t\"editor.tabSize\": 4\n}\n"
  },
  {
    "path": ".vscode/tasks.json",
    "content": "{\n\t\"version\": \"2.0.0\",\n\t\"tasks\": [\n\t\t{\n\t\t\t\"type\": \"npm\",\n\t\t\t\"script\": \"watch\",\n\t\t\t\"label\": \"watch\",\n\t\t\t\"isBackground\": true,\n\t\t\t\"runOptions\": {\n\t\t\t\t\"runOn\": \"folderOpen\"\n\t\t\t}\n\t\t},\n\t\t{\n\t\t\t\"type\": \"npm\",\n\t\t\t\"script\": \"typecheck-watch\",\n\t\t\t\"label\": \"typecheck-watch\",\n\t\t\t\"isBackground\": true,\n\t\t\t\"problemMatcher\": \"$tsc-watch\",\n\t\t\t\"runOptions\": {\n\t\t\t\t\"runOn\": \"folderOpen\"\n\t\t\t}\n\t\t}\n\t]\n}\n"
  },
  {
    "path": ".vscodeignore",
    "content": "src/**\nnode_modules/**\n**/*.map\npackage-lock.json\ntsconfig.json\nwebpack.config.js\n.vscode/**\n.gitignore\n.nyc_output/**\ncoverage/**\ntestdata/**\ndist/package.json\ndist/README.md\ndist/LICENSE\n"
  },
  {
    "path": "CHANGELOG.md",
    "content": "### Version 2.15.0\n* fix various issues when debugging multiple tabs\n* use the URL in thread names\n\n### Version 2.14.1\n* fix truncated console messages\n* fix threads not showing up in the loaded scripts panel\n* only show content scripts when debugging a WebExtension\n* don't open a new tab in attach configurations\n* resume detached threads when they pause\n\n### Version 2.14.0\n* improve how console messages are rendered\n\n### Version 2.13.0\n* fix `clearConsoleOnReload`\n* fix output of styled console messages\n* fix showing the locations of early console messages\n* fix breakpoint issues with `reloadOnAttach`\n* add pathMappingIndex configuration property\n\n### Version 2.12.1\n* fix debugging of web extension content scripts in Firefox 135\n\n### Version 2.12.0\n* add support for debugging without a launch configuration\n* add support for event listener breakpoints\n* fix handling of exited tabs and threads\n\n### Version 2.11.0\n* support restarting frames\n* support skipping debugger statements\n* fix breakpoints with hit counts on Windows\n\n### Version 2.10.1\n* fix path mapping on Windows\n\n### Version 2.10.0\n* migrate to new debug protocol methods\n* fix breakpoints not working after navigating\n\n### Version 2.9.11\n* handle console.time/timeEnd\n* fix the loaded scripts panel\n\n### Version 2.9.10\n* fix marketplace badge link in the README\n\n### Version 2.9.9\n* compatibility fix for Node 17 / VS Code 1.82\n\n### Version 2.9.8\n* debug protocol update for Firefox 104\n* fix the build on MacOS\n\n### Version 2.9.7\n* debug protocol update for Firefox 102\n\n### Version 2.9.6\n* debug protocol update for Firefox 96\n\n### Version 2.9.5\n* compatibility fix for VS Code 1.62.1\n\n### Version 2.9.4\n* fix for breakpoints not being set in some situations\n* add the URL to a thread's name\n\n### Version 2.9.3\n* fix breakpoints only working after reloading the page\n* fix missing console messages\n* fix debugging WebWorkers\n* fix data breakpoints\n\n### Version 2.9.2\n* fix terminating Firefox at the end of the debug session\n* fix function scopes being with the name `[unknown]`\n* show an error message if the path mapping wizard couldn't update the launch configuration\n* set the webRoot configuration property to its default if necessary\n* support overriding the debugging port in the settings\n* add workaround for the snap version of VS Code\n\n### Version 2.9.1\n* add `enableCRAWorkaround` configuration property\n* fix the conditions for the `keepProfileChanges` configuration property on MacOS\n* fix the sorting of Arrays in the Variables view\n\n### Version 2.9.0\n* add `tabFilter` configuration property\n* open a new Firefox tab if no tab matches the `tabFilter`\n* allow `stable`, `developer` and `nightly` as values for firefoxExecutable\n* only suggest the Path Mapping Wizard if it can create a pathMapping\n* debug protocol fix for Firefox 78\n\n### Version 2.8.0\n* debug protocol fix for Firefox 77\n* add `tmpDir` configuration property\n* fix for skipping external URLs containing a query string\n* fixes for object previews\n\n### Version 2.7.2\n* debug protocol fixes for Firefox 76\n\n### Version 2.7.1\n* debug protocol fix for Firefox 75\n\n### Version 2.7.0\n* bugfix for breakpoints in Vue.js projects\n* add default pathMappings for Next.js projects\n* add support for wildcards in pathMappings\n* add `suggestPathMappingWizard` configuration property\n* various bugfixes\n\n### Version 2.6.1\n* fix for showing the launch configuration in a remote workspace\n* fix for re-enabling a watchpoint\n\n### Version 2.6.0\n* add the Path Mapping Wizard\n* extend the Loaded Scripts Explorer\n\n### Version 2.5.5\n* debug protocol fix for Firefox 72\n\n### Version 2.5.4\n* fix version detection for Firefox 72\n\n### Version 2.5.3\n* fix for create-react-app projects on Windows\n\n### Version 2.5.2\n* improve stepping performance\n* fix for debugging a WebExtension in a workspace subfolder on Windows\n* fix for debugging in a remote workspace on Windows\n\n### Version 2.5.1\n* fix removal of breakpoints from sourcemapped sources\n* improved default `pathMappings` for Angular CLI projects\n* sourcemapping accuracy improvements\n* fix initialization of exception breakpoints\n\n### Version 2.5.0\n* performance improvement for setting breakpoints in bundled sources\n* add support for the new column breakpoint UI introduced in VS Code 1.39\n* remove support for Firefox <68\n\n### Version 2.4.0\n* add support for `console.clear()`\n* add `clearConsoleOnReload` configuration property\n* try to load sourcemaps from disk first\n* fix WebExtension debugging in Firefox 71\n\n### Version 2.3.4\n* fix a performance issue when reloading the page in the browser\n\n### Version 2.3.3\n* fix for hot module replacement: when a module was replaced, breakpoints in that module stopped working\n* fix for callstacks sometimes showing an error message \"Couldn't find source adapter\"\n\n### Version 2.3.2\n* fix support for data breakpoints with Firefox 70\n\n### Version 2.3.1\n* update README.md\n\n### Version 2.3.0\n* disable the prompt to configure telemetry in temporary debug profiles\n* objects referenced in logpoints can now be inspected in the debug console\n* add support for the BigInt primitive type\n\n### Version 2.2.0\n* add support for VS Code remote development\n* add support for data breakpoints on object properties if Firefox supports watchpoints (an upcoming Firefox feature that will appear in Nightlies soon)\n\n### Version 2.1.0\n* add `timeout` launch configuration property\n\n### Version 2.0.1\n* build with babel and bundle with webpack - the resulting package is much smaller and loads faster\nVersion 2.0.1 was only released on npm\n\n### Version 2.0.0\n* branding updated - this extension is now an official Firefox DevTool!\n\n### Version 1.8.3\n* fix for launching Firefox on MacOS and some Linux distros\n* increase the timeout for connecting to Firefox to 10 seconds and fix the handling of that timeout\n\n### Version 1.8.2\n* bugfix: globstars (`**`) in the skipFiles configuration didn't match path segments that contain `/./`\n* bugfix: show the generated source location of a stack frame if it can't be mapped to an original source location\n* bugfix: the debug adapter kept running after the end of the debug session until Firefox was closed\n\n### Version 1.8.1\n* bugfix: the temporary debug profile wasn't removed at the end of the debug session when using a recent Firefox version on Windows\n\n### Version 1.8.0\n* fix for Firefox 68\n* remove support for legacy add-ons\n\n### Version 1.7.11\n* fix handling of case-insensitive paths on Windows when setting breakpoints\n* add workaround for broken sourcemaps generated by create-react-app\n\n### Version 1.7.10\n* bugfix: sometimes the exception view wasn't shown\n* further fixes for Firefox 67\n\n### Version 1.7.9\n* further fixes for Firefox 67\n\n### Version 1.7.8\n* fix for Firefox 66\n\n### Version 1.7.7\n* fix for debug protocol changes in Firefox 67\n\n### Version 1.7.6\n* fix for a debug protocol change in Firefox 66\n\n### Version 1.7.5\n* fix for sourcemaps in WebExtensions\n* fix for `reAttach` on MacOS\n\n### Version 1.7.4\n* fix path mapping for configurations where `url` contains no path and no trailing slash (e.g. `\"url\": \"http://localhost:8080\"`)\n\n### Version 1.7.3\n* fix handling of relative sourceRoot in sourcemaps\n\n### Version 1.7.2\n* add log messages to monitor source-mapping performance\n\n### Version 1.7.1\n* WebExtension debugging: allow comments in manifest.json\n\n### Version 1.7.0\n* add the ability to override some launch configuration properties in the settings\n\n### Version 1.6.0\n* change the default for `sourceMaps` to `client`\n* improve the performance of client-side source-map handling\n* fix client-side source-map handling for source-maps with relative paths\n* fix detection of Firefox Developer Edition on MacOS\n\n### Version 1.5.0\n* WebExtension debugging: add commands and status bar button for toggling popup auto-hide\n\n### Version 1.4.3\n* improve accuracy of client-side sourcemap support\n\n### Version 1.4.2\n* compute the original locations of console and error events when `sourceMaps` is set to `client`\n\n### Version 1.4.1\n* also look for `firefox-developer-edition` when searching the Firefox executable on Linux\n\n### Version 1.4.0\n* add `liftAccessorsFromPrototypes` configuration property\n\n### Version 1.3.0\n* allow WebExtensions without an ID if they're installed via RDP\n* install WebExtensions via RDP by default\n\n### Version 1.2.1\n* fix `reloadOnChange` on Windows (thanks @Misiur)\n\n### Version 1.2.0\n* add support for breakpoints with hit counts\n* add support for log points\n\n### Version 1.1.4\n* add support for evaluating getter functions\n* fix path mapping of URLs that contain encoded characters\n\n### Version 1.1.3\n* path mapping bugfixes\n\n### Version 1.1.2\n* workaround for a timing issue with early beta versions of Firefox 60\n* improve WebAssembly debugging support\n\n### Version 1.1.1\n* experimental support for WebAssembly debugging\n\n### Version 1.1.0\n* add support for creating `pathMappings` from the Loaded Scripts Explorer\n* bugfix for breakpoints being shown unverified (gray) even when they were successfully set\n* change default `pathMappings` for webpack to support Angular CLI projects\n\n### Version 1.0.1\n* fix debugging of WebExtensions that contain a `package.json` file\n* set the default `addonType` to `webExtension` in configuration snippets and documentation\n\n### Version 1.0.0\n* add default `pathMappings` for webpack\n* harmonize trailing slashes in user-specified `pathMappings`\n* Linux: search the Firefox executable in all directories in the user's `PATH` (thanks @agathver)\n* `addonType` now defaults to `webExtension`\n\n### Version 0.17.0\n* show object previews in the Variables and Watch sections of the Debug view\n* fix the Loaded Scripts Explorer when navigating in Firefox\n\n### Version 0.16.1\n* fix opening remote scripts from the Loaded Scripts Explorer\n* skip exceptions triggered from the debug console\n* add the ability to configure URLs that should not be mapped to local paths\n* remove deprecated VS Code APIs\n\n### Version 0.16.0\n* add Loaded Scripts Explorer\n* add support for Symbol-keyed properties (Firefox 56+)\n\n### Version 0.15.4\n* bugfix: `pathMappings` were ignored in `attach` configurations\n\n### Version 0.15.3\n* performance improvements\n\n### Version 0.15.2\n* handle absolute urls in source-maps, including a workaround for webpack weirdness\n\n### Version 0.15.1\n* on Windows the debug adapter sometimes didn't attach to WebExtensions that were installed as temporary add-ons - fixed\n\n### Version 0.15.0\n* add support for toggling the skip flag for single files while debugging\n* make `webRoot` optional if `pathMappings` are specified\n\n### Version 0.14.1\n* compatibility update for the upcoming VS Code 1.14 release\n \n### Version 0.14.0\n* fix WebExtension debugging in recent Firefox builds\n* add experimental `sourceMaps` configuration property\n\n### Version 0.13.1\n* add support for setting variable values in the debug side bar\n* add support for IntelliSense in the debug console\n\n### Version 0.13.0\n* add `reloadOnChange` configuration property\n\n### Version 0.12.1\n* fix temporary add-on installation on Windows\n\n### Version 0.12.0\n* add support for reloading add-ons\n* add `installAddonInProfile` configuration property\n\n### Version 0.11.1\n* bugfix: some function names were not shown in the call stack\n* bugfix: the tooltips of tabs for external source files didn't show the full url\n* bugfix: some accessor properties (e.g. window.window) were shown as undefined\n* bugfix for sporadical failures to attach to Firefox after launching it\n\n### Version 0.11.0\n* add `keepProfileChanges` configuration property\n* bugfix: the temporary profiles are now deleted reliably\n\n### Version 0.10.0\n* add `preferences` configuration property\n* add `showConsoleCallLocation` configuration property\n* support sending objects to the console (e.g. `console.log(document)`)\n* change the display of call stack, return values and exceptions to be more in line with other VS Code javascript debuggers\n\n### Version 0.9.3\n* fix slow initial startup of add-on debugging with the `reAttach` option\n\n### Version 0.9.2\n* support `reAttach` for add-on debugging\n\n### Version 0.9.1\n* fix `reAttach` on Windows\n\n### Version 0.9.0\n* Add `reAttach` and `reloadOnAttach` configuration properties\n\n### Version 0.8.8\n* bugfix: source files were not mapped to local files in VS Code 1.9\n\n### Version 0.8.7\n* workaround for Firefox sending inaccurate source information in certain situations, which can break the `skipFiles` feature\n\n### Version 0.8.6\n* bugfix: some URLs were not handled correctly when processing sourcemapped sources\n\n### Version 0.8.5\n* send log messages from add-ons to the debug console\n\n### Version 0.8.4\n* bugfix: exceptions were not shown\n\n### Version 0.8.3\n* strip query strings from urls when converting them to local file paths\n\n### Version 0.8.2\n* fix skipFiles on Windows\n\n### Version 0.8.1\n* bugfix: sources could not be skipped during their first execution\n\n### Version 0.8.0\n* Add `skipFiles` configuration property\n* Add `pathMappings` configuration property\n* Add configuration snippets\n* Fix several bugs when evaluating watches and expressions entered in the debug console\n\n### Version 0.7.7\n* fix debugging of WebExtension content scripts in recent Firefox builds\n\n### Version 0.7.6\n* bugfix: breakpoints were sometimes not hit after a page reload\n\n### Version 0.7.5\n* bugfix: support javascript values of type Symbol\n* bugfix: evaluating expressions in the VS Code debug console sometimes stopped working\n\n### Version 0.7.2\n* Terminate the debug session when Firefox is closed\n\n### Version 0.7.1\n* Show the full url of sources that do not correspond to local files\n* bugfix for setting breakpoints in content scripts of `addonSdk` browser extensions\n\n### Version 0.7.0\n* Debugging Firefox add-ons\n* Launch mode now always creates a temporary profile: if a profile is specified in the launch\n  configuration, it will be copied and modified to allow remote debugging\n* Launch mode now uses the developer edition of Firefox if it is found\n\n### Version 0.6.5\n* bugfix for sourcemaps with embedded source files\n\n### Version 0.6.4\n* Fix breakpoint handling when a Firefox tab is reloaded\n* Only send javascript-related warnings and errors from Firefox to the debug console\n\n### Version 0.6.3\n* Add configuration option for diagnostic logging\n* Make conversion between paths and urls more robust\n\n### Version 0.6.2\n* bugfix: stepping and resuming stopped working if a breakpoint was hit immediately after loading the page\n\n### Version 0.6.1\n* Fix debugging WebWorkers and multiple browser tabs in VSCode 1.2.0\n\n### Version 0.6.0\n* Add support for evaluating javascript expressions in the debug console even if Firefox isn't paused\n* Add support for debugger statements\n\n### Version 0.5.0\n* Add support for call stack paging\n\n### Version 0.4.0\n* Add support for debugging WebWorkers\n* Add support for debugging multiple browser tabs\n* Fix exception breakpoints in VSCode 1.1.0\n* Re-create the Firefox profile on every launch, unless a profile name or directory is configured\n\n### Version 0.3.0\n* Print messages from the Firefox console in the VS Code debug console\n* bugfix: resume the VS Code debugger when Firefox resumes, e.g. if the user reloads the page in \n  Firefox while the debugger is paused\n\n### Version 0.2.0\n* Automatically create a Firefox profile for debugging\n\n### Version 0.1.0\n* Initial release\n"
  },
  {
    "path": "CODE_OF_CONDUCT.md",
    "content": "## Mozilla Community Participation Guidelines\n\nThis repository is governed by Mozilla's code of conduct and etiquette guidelines. \nFor more details, please read the\n[Mozilla Community Participation Guidelines](https://www.mozilla.org/about/governance/policies/participation/). \n\n## How to Report\nFor more information on how to report violations of the CPG, please read our '[How to Report](https://www.mozilla.org/en-US/about/governance/policies/participation/reporting/)' page.\n\n## Project Specific Etiquette\n\n### Our Responsibilities\n\nProject maintainers are responsible for clarifying the standards of acceptable\nbehavior and are expected to take appropriate and fair corrective action in\nresponse to any instances of unacceptable behavior.\n\nProject maintainers have the right and responsibility to remove, edit, or\nreject comments, commits, code, wiki edits, issues, and other contributions\nthat are not aligned to this Code of Conduct, or to ban temporarily or\npermanently any contributor for other behaviors that they deem inappropriate,\nthreatening, offensive, or harmful.\n\nProject maintainers who do not follow or enforce Mozilla's Participation Guidelines in good\nfaith may face temporary or permanent repercussions.\n"
  },
  {
    "path": "CONTRIBUTING.md",
    "content": "# Hacking the VS Code Debug Adapter for Firefox\n\n## Prerequisites\nFirst of all, you should familiarize yourself with the\n[debugging architecture of VS Code](https://code.visualstudio.com/api/extension-guides/debugger-extension#debugging-architecture-of-vs-code)\nand the [Firefox Remote Debugging Protocol](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md).\n\n## Setup\n* fork and clone this repository and open it in VS Code\n* run `npm install`\n* run `npm run watch` or start the `watch` task in VS Code\n* optional but recommended: run `npm run typecheck-watch` or start the `typecheck-watch` task in VS Code\n\nMost source folders contain a `README` file giving an overview of the contained code.\nStart with the [top-level README](./src).\n\n## Debugging\nThis repository contains several launch configurations for debugging different parts of the code:\n* to debug the debug adapter itself (i.e. the code under `src/adapter/`), use the `debug server`\n  configuration to start it as a stand-alone server in the node debugger. Then open a web application\n  in a second VS Code window and add a launch configuration of type `firefox` to it. Add\n  `\"debugServer\": 4711` to this launch configuration, so that VS Code will use the debug adapter\n  that you just launched in the other VS Code window. Set a breakpoint in the `startSession()` of \n  [`FirefoxDebugAdapter`](./src/adapter/firefoxDebugAdapter.ts) and start debugging the web application.\n* to debug the extension code (under `src/extension/`), use the `extension host` configuration.\n  It will open a second VS Code window with the extension in it running in the node debugger.\n* the compound configuration `server & extension` will launch both of the configurations above\n* the `web test` and `webextension test` configurations can be used if you need to manually\n  reproduce the mocha tests. You'll need to start the `debug server` configuration first.\n"
  },
  {
    "path": "LICENSE",
    "content": "The MIT License (MIT)\n\nCopyright (c) 2015-2017 Holger Benl <hbenl@evandor.de>\n\nPermission is hereby granted, free of charge, to any person obtaining a copy\nof this software and associated documentation files (the \"Software\"), to deal\nin the Software without restriction, including without limitation the rights\nto use, copy, modify, merge, publish, distribute, sublicense, and/or sell\ncopies of the Software, and to permit persons to whom the Software is\nfurnished to do so, subject to the following conditions:\n\nThe above copyright notice and this permission notice shall be included in all\ncopies or substantial portions of the Software.\n\nTHE SOFTWARE IS PROVIDED \"AS IS\", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR\nIMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,\nFITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE\nAUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER\nLIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,\nOUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE\nSOFTWARE.\n"
  },
  {
    "path": "README.md",
    "content": "<h1 align=\"center\">\n  <br>\n    <img src=\"https://github.com/firefox-devtools/vscode-firefox-debug/blob/master/icon.png?raw=true\" alt=\"logo\" width=\"200\">\n  <br>\n  VS Code Debugger for Firefox\n  <br>\n  <br>\n</h1>\n\n<h4 align=\"center\">Debug your JavaScript code running in Firefox from VS Code.</h4>\n\n<p align=\"center\">\n  <a href=\"https://marketplace.visualstudio.com/items?itemName=firefox-devtools.vscode-firefox-debug\"><img src=\"https://vsmarketplacebadges.dev/version/firefox-devtools.vscode-firefox-debug.png?label=Debugger%20for%20Firefox\" alt=\"Marketplace badge\"></a>\n</p>\n\nA VS Code extension to debug web applications and extensions running in the [Mozilla Firefox browser](https://www.mozilla.org/en-US/firefox/developer/?utm_medium=vscode_extension&utm_source=devtools). [📦 Install from VS Code Marketplace](https://marketplace.visualstudio.com/items?itemName=firefox-devtools.vscode-firefox-debug).\n\n### Supported features\n\n* Pause [breakpoints](https://code.visualstudio.com/docs/editor/debugging#_breakpoints), including advanced [conditional](https://code.visualstudio.com/docs/editor/debugging#_conditional-breakpoints) and [inline](https://code.visualstudio.com/docs/editor/debugging#_inline-breakpoints) modes\n* Pause on object property changes with [Data breakpoints](https://code.visualstudio.com/docs/editor/debugging#_data-breakpoints)\n* Inject logging during debugging using [logpoints](https://code.visualstudio.com/docs/editor/debugging#_logpoints)\n* Debugging eval scripts, script tags, and scripts that are added dynamically and/or source mapped\n* *Variables* pane for inspecting and setting values\n* *Watch* pane for evaluating and watching expressions\n* *Console* for logging and REPL\n* Debugging Firefox extensions\n* Debugging Web Workers\n* Compatible with [remote development](https://code.visualstudio.com/docs/remote/remote-overview)\n\n## Getting Started\n\nYou can use this extension in **launch** or **attach** mode.\n\nIn **launch** mode it will start an instance of Firefox navigated to the start page of your application\nand terminate it when you stop debugging. You can also set the `reAttach` option in your launch configuration to `true`, in this case Firefox won't be terminated at the end of your debugging session and the debugger will re-attach to it when\nyou start the next debugging session - this is a lot faster than restarting Firefox every time. `reAttach` also works for WebExtension debugging: in this case, the WebExtension is (re-)installed as a [temporary add-on](https://developer.mozilla.org/en-US/Add-ons/WebExtensions/Temporary_Installation_in_Firefox).\n\nIn **attach** mode the extension connects to a running instance of Firefox (which must be manually\nconfigured to allow remote debugging - see [below](#attach)).\n\nTo configure these modes you must create a file `.vscode/launch.json` in the root directory of your\nproject. You can do so manually or let VS Code create an example configuration for you by clicking \nthe gear icon at the top of the Debug pane.\n\nFinally, if `.vscode/launch.json` already exists in your project, you can open it and add a \nconfiguration snippet to it using the *\"Add Configuration\"* button in the lower right corner of the\neditor.\n\n### Launch\nHere's an example configuration for launching Firefox navigated to the local file `index.html` \nin the root directory of your project:\n```json\n{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Launch index.html\",\n            \"type\": \"firefox\",\n            \"request\": \"launch\",\n            \"reAttach\": true,\n            \"file\": \"${workspaceFolder}/index.html\"\n        }\n    ]\n}\n```\n\nYou may want (or need) to debug your application running on a Webserver (especially if it interacts\nwith server-side components like Webservices). In this case replace the `file` property in your\n`launch` configuration with a `url` and a `webRoot` property. These properties are used to map\nurls to local files:\n```json\n{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Launch localhost\",\n            \"type\": \"firefox\",\n            \"request\": \"launch\",\n            \"reAttach\": true,\n            \"url\": \"http://localhost/index.html\",\n            \"webRoot\": \"${workspaceFolder}\"\n        }\n    ]\n}\n```\nThe `url` property may point to a file or a directory, if it points to a directory it must end with\na trailing `/` (e.g. `http://localhost/my-app/`).\nYou may omit the `webRoot` property if you specify the `pathMappings` manually. For example, the\nabove configuration would be equivalent to\n```json\n{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Launch localhost\",\n            \"type\": \"firefox\",\n            \"request\": \"launch\",\n            \"reAttach\": true,\n            \"url\": \"http://localhost/index.html\",\n            \"pathMappings\": [{\n                \"url\": \"http://localhost\",\n                \"path\": \"${workspaceFolder}\"\n            }]\n        }\n    ]\n}\n```\nSetting the `pathMappings` manually becomes necessary if the `url` points to a file or resource in a\nsubdirectory of your project, e.g. `http://localhost/login/index.html`.\n\n### Attach\nTo use attach mode, you have to launch Firefox manually from a terminal with remote debugging enabled.\nNote that if you don't use Firefox Developer Edition, you must first configure Firefox to allow\nremote debugging. To do this, open the Developer Tools Settings and check the checkboxes labeled\n\"Enable browser chrome and add-on debugging toolboxes\" and \"Enable remote debugging\"\n(as described [here](https://developer.mozilla.org/en-US/docs/Tools/Remote_Debugging/Debugging_Firefox_Desktop#Enable_remote_debugging)).\nAlternatively you can set the following values in `about:config`:\n\nPreference Name                       | Value   | Comment\n--------------------------------------|---------|---------\n`devtools.debugger.remote-enabled`    | `true`  | Required\n`devtools.chrome.enabled`             | `true`  | Required\n`devtools.debugger.prompt-connection` | `false` | Recommended\n`devtools.debugger.force-local`       | `false` | Set this only if you want to attach VS Code to Firefox running on a different machine (using the `host` property in the `attach` configuration)\n\nThen close Firefox and start it from a terminal like this:\n\n__Windows__\n\n`\"C:\\Program Files\\Mozilla Firefox\\firefox.exe\" -start-debugger-server`\n\n(This syntax is for a regular command prompt (cmd.exe), not PowerShell!)\n\n__OS X__\n\n`/Applications/Firefox.app/Contents/MacOS/firefox -start-debugger-server`\n\n__Linux__\n\n`firefox -start-debugger-server`\n\nNavigate to your web application and use this `launch.json` configuration to attach to Firefox:\n```json\n{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Launch index.html\",\n            \"type\": \"firefox\",\n            \"request\": \"attach\"\n        }\n    ]\n}\n```\n\nIf your application is running on a Webserver, you need to add the `url` and `webRoot` properties\nto the configuration (as in the second `launch` configuration example above).\n\n### Skipping (\"blackboxing\") files\nYou can tell the debugger to ignore certain files while debugging: When a file is ignored, the\ndebugger won't break in that file and will skip it when you're stepping through your code. This is\nthe same as \"black boxing\" scripts in the Firefox Developer Tools.\n\nThere are two ways to enable this feature:\n* You can enable/disable this for single files while debugging by choosing \"Toggle skipping this file\"\n  from the context menu of a frame in the call stack.\n* You can use the `skipFiles` configuration property, which takes an array of glob patterns\n  specifying the files to be ignored.\n  If the URL of a file can't be mapped to a local file path, the URL will be matched against these\n  glob patterns, otherwise the local file path will be matched.\n  Examples for glob patterns:\n  * `\"${workspaceFolder}/skipThis.js\"` - will skip the file `skipThis.js` in the root folder of your project\n  * `\"**/skipThis.js\"` - will skip files called `skipThis.js` in any folder\n  * `\"**/node_modules/**\"` - will skip all files in `node_modules` folders anywhere in the project\n  * `\"http?(s):/**\"` - will skip files that could not be mapped to local files\n  * `\"**/google.com/**\"` - will skip files containing `/google.com/` in their url, in particular\n    all files from the domain `google.com` (that could not be mapped to local files)\n\n### Path mapping\nThe debug adapter needs to map the URLs of javascript files (as seen by Firefox) to local file paths\n(as seen by VS Code). It creates a set of default path mappings from the configuration that work\nfor most projects. However, depending on the setup of your project, they may not work for you,\nresulting in breakpoints being shown in gray (and Firefox not breaking on them) even after Firefox\nhas loaded the corresponding file. In this case, you will have to define them manually using the\n`pathMappings` configuration property.\n\nThe easiest way to do this is through the Path Mapping Wizard: when you try to set a breakpoint \nduring a debug session in a file that couldn't be mapped to a URL, the debug adapter will offer to\nautomatically create a path mapping for you. If you click \"Yes\" it will analyze the URLs loaded by\nFirefox and try to find a path mapping that maps this file and as many other workspace files as\npossible to URLs loaded by Firefox and it will add this mapping to your debug configuration.\nNote that this path mapping is just a guess, so you should check if it looks plausible to you.\nYou can also call the Path Mapping Wizard from the command palette during a debug session.\n\nYou can look at the Firefox URLs and how they are mapped to paths in the Loaded Scripts Explorer,\nwhich appears at the bottom of the debug side bar of VS Code during a debug session.\nBy choosing \"Map to local file\" or \"Map to local directory\" from the context menu of a file or\na directory, you can pick the corresponding local file or directory and a path mapping will\nautomatically be added to your configuration.\n\nIf you specify more than one mapping, the first mappings in the list will take precedence over\nsubsequent ones and all of them will take precedence over the default mappings.\n\nThe most common source of path mapping problems is webpack because the URLs that it generates\ndepend on its configuration and different URL styles are in use. If your configuration contains a\n`webroot` property, the following mappings will be added by default in order to support most webpack\nsetups:\n```\n{ \"url\": \"webpack:///~/\", \"path\": \"${webRoot}/node_modules/\" }\n{ \"url\": \"webpack:///./~/\", \"path\": \"${webRoot}/node_modules/\" }\n{ \"url\": \"webpack:///./\", \"path\": \"${webRoot}/\" }\n{ \"url\": \"webpack:///src/\", \"path\": \"${webRoot}/src/\" }\n{ \"url\": \"webpack:///node_modules/\", \"path\": \"${webRoot}/node_modules/\" }\n{ \"url\": \"webpack:///webpack\", \"path\": null }\n{ \"url\": \"webpack:///(webpack)\", \"path\": null }\n{ \"url\": \"webpack:///pages/\", \"path\": \"${webRoot}/pages/\" }\n{ \"url\": \"webpack://[name]_[chunkhash]/node_modules/\", \"path\": \"${webRoot}/node_modules/\" }\n{ \"url\": \"webpack://[name]_[chunkhash]/\", \"path\": null }\n{ \"url\": \"webpack:///\", \"path\": \"\" }\n```\n\nWhen the `path` argument of a mapping is set to `null`, the corresponding URLs are prevented from\nbeing mapped to local files. In the webpack mappings shown above this is used to specify that\nURLs starting with `webpack:///webpack` or `webpack:///(webpack)` do not correspond to files in\nyour workspace (because they are dynamically generated by webpack). It could also be used for URLs\nthat dynamically generate their content on the server (e.g. PHP scripts) or if the content on the\nserver is different from the local file content. For these URLs the debugger will show the content\nfetched from the server instead of the local file content.\n\nYou can also use `*` as a wildcard in the `url` of a pathMapping. It will match any number of\narbitrary characters except `/`.\n\n### Debugging WebExtensions\nHere's an example configuration for WebExtension debugging:\n```json\n{\n    \"version\": \"0.2.0\",\n    \"configurations\": [\n        {\n            \"name\": \"Launch WebExtension\",\n            \"type\": \"firefox\",\n            \"request\": \"launch\",\n            \"reAttach\": true,\n            \"addonPath\": \"${workspaceFolder}\"\n        }\n    ]\n}\n```\nThe `addonPath` property must be the absolute path to the directory containing `manifest.json`.\n\nYou can reload your WebExtension using the command \"Firefox: Reload add-on\" (`extension.firefox.reloadAddon`)\nfrom the VS Code command palette.\nThe WebExtension will also be reloaded when you restart the debugging session, unless you have set\n`reloadOnAttach` to `false`.\nYou can also use the `reloadOnChange` property to let VS Code reload your WebExtension automatically\nwhenever you change a file.\n\nYou can enable/disable/toggle popup auto-hide using the commands \"Firefox: Enable/Disable/Toggle\npopup auto-hide\" (`extension.firefox.enablePopupAutohide` / `disablePopupAutohide` / `togglePopupAutohide`).\n\n### Further optional configuration properties\n* `reAttach`: If you set this option to `true` in a `launch` configuration, Firefox won't be \n  terminated at the end of your debugging session and the debugger will re-attach to it at the\n  start of your next debugging session.\n* `reloadOnAttach`: This flag controls whether the web page(s) should be automatically reloaded\n  after attaching to Firefox. The default is to reload in a `launch` configuration with the\n  `reAttach` flag set to `true` and to not reload in an `attach` configuration.\n* `reloadOnChange`: Automatically reload the Firefox tabs or your WebExtension whenever files change.\n  You can specify single files, directories or glob patterns to watch for file changes and\n  additionally specify files to be ignored. Since watching files consumes system resources,\n  make sure that you are not watching more files than necessary.\n  The following example will watch all javascript files in your workspace except those under\n  `node_modules`:\n  ```json\n    \"reloadOnChange\": {\n        \"watch\": [ \"${workspaceFolder}/**/*.js\" ],\n        \"ignore\": [ \"${workspaceFolder}/node_modules/**\" ]\n    }\n  ```\n  By default, the reloading will be \"debounced\": the debug adapter will wait until the last file\n  change was 100 milliseconds ago before reloading. This is useful if your project uses a build\n  system that generates multiple files - without debouncing, each file would trigger a separate\n  reload. You can use `reloadOnChange.debounce` to change the debounce time span or to disable\n  debouncing (by setting it to `0` or `false`).\n\n  Instead of string arrays, you can also use a single string for `watch` and `ignore` and if you\n  don't need to specify `ignore` or `debounce`, you can specify the `watch` value directly, e.g.\n  ```json\n  \"reloadOnChange\": \"${workspaceFolder}/lib/*.js\"\n  ```\n* `tabFilter`: Only attach to Firefox tabs with matching URLs. You can specify one or more URLs to\n  include and/or URLs to exclude and the URLs can contain `*` wildcards.\n  By default, a `tabFilter` is constructed from the `url` in your `launch` or `attach` configuration\n  by replacing the last path segment with `*`. For example, if your configuration contains\n  `\"url\": \"http://localhost:3000/app/index.html\"`, the default `tabFilter` will be\n  `\"http://localhost:3000/app/*\"`.\n* `clearConsoleOnReload`: Clear the debug console in VS Code when the page is reloaded in Firefox.\n* `tmpDir`: The path of the directory to use for temporary files\n* `profileDir`, `profile`: You can specify a Firefox profile directory or the name of a profile\n  created with the Firefox profile manager. The extension will create a copy of this profile in the\n  system's temporary directory and modify the settings in this copy to allow remote debugging.\n  You can also override these properties in your settings (see below). The default profile names\n  used by Firefox are `default`, `dev-edition-default` and `default-nightly` for the stable,\n  developer and nightly editions, respectively.\n* `keepProfileChanges`: Use the specified profile directly instead of creating a temporary copy.\n  Since this profile will be permanently modified for debugging, you should only use this option\n  with a dedicated debugging profile. You can also override this property in your settings (see below).\n* `port`: Firefox uses port 6000 for the debugger protocol by default. If you want to use a different\n  port, you can set it with this property. You can also override this property in your settings\n  (see below).\n* `timeout`: The timeout in seconds for the adapter to connect to Firefox after launching it.\n* `firefoxExecutable`: The absolute path to the Firefox executable or the name of a Firefox edition\n  (`stable`, `developer` or `nightly`) to look for in its default installation path. If not specified,\n  this extension will look for both stable and developer editions of Firefox; if both are available,\n  it will use the developer edition. You can also override this property in your settings (see below).\n* `firefoxArgs`: An array of additional arguments used when launching Firefox (`launch` configuration only).\n  You can also override this property in your settings (see below).\n* `host`: If you want to debug with Firefox running on a different machine, you can specify the \n  device's address using this property (`attach` configuration only).\n* `log`: Configures diagnostic logging for this extension. This may be useful for troubleshooting\n  (see below for examples).\n* `showConsoleCallLocation`: Set this option to `true` to append the source location of `console`\n  calls to their output\n* `preferences`: Set additional Firefox preferences in the debugging profile\n* `popupAutohideButton`: Show a button in the status bar for toggling popup auto-hide\n  (enabled by default when debugging a WebExtension)\n* `liftAccessorsFromPrototypes`: If there are accessor properties (getters and setters) defined\n  on an object's prototype chain, you can \"lift\" them so they are displayed on the object itself.\n  This is usually necessary in order to execute the getters, because otherwise they would be\n  executed with `this` set to the object's prototype instead of the object itself. This property\n  lets you set the number of prototype levels that should be scanned for accessor properties to lift.\n  Note that this will slow the debugger down, so it's set to `0` by default.\n* `pathMappingIndex`: The name of the directory index file configured in the webserver, defaults\n  to `index.html`. This will be appended to all URLs pointing to a directory (i.e. URLs ending\n  with `/`) before trying to map them to file paths.\n* `suggestPathMappingWizard`: Suggest using the Path Mapping Wizard when you try to set a\n  breakpoint in an unmapped source during a debug session. You may want to turn this off if some\n  of the sources in your project are loaded on-demand (e.g. if you create multiple bundles with\n  webpack and some of these bundles are only loaded as needed).\n* `enableCRAWorkaround`: Enable a workaround for facebook/create-react-app#6074: Adding/removing\n  breakpoints doesn't work for sources that were changed after the dev-server was started\n\n### Overriding configuration properties in your settings\nYou can override some of the `launch.json` configuration properties in your user, workspace or\nfolder settings. This can be useful to make machine-specific changes to your launch configuration\nwithout sharing them with other users.\n\nThis setting                 | overrides this `launch.json` property\n-----------------------------|-------------------------------------\n`firefox.executable`         | `firefoxExecutable`\n`firefox.args`               | `firefoxArgs`\n`firefox.profileDir`         | `profileDir`\n`firefox.profile`            | `profile`\n`firefox.keepProfileChanges` | `keepProfileChanges`\n`firefox.port`               | `port`\n\n### Diagnostic logging\nThe following example for the `log` property will write all log messages to the file `log.txt` in\nyour workspace:\n```json\n...\n    \"log\": {\n        \"fileName\": \"${workspaceFolder}/log.txt\",\n        \"fileLevel\": {\n            \"default\": \"Debug\"\n        }\n    }\n...\n```\n\nThis example will write all messages about conversions from URLs to paths and all error messages\nto the VS Code console:\n```json\n...\n    \"log\": {\n        \"consoleLevel\": {\n            \"PathConversion\": \"Debug\",\n            \"default\": \"Error\"\n        }\n    }\n...\n```\n \n## Troubleshooting\n* Breakpoints that should get hit immediately after the javascript file is loaded may not work the\n  first time: You will have to click \"Reload\" in Firefox for the debugger to stop at such a\n  breakpoint. This is a weakness of the Firefox debug protocol: VS Code can't tell Firefox about\n  breakpoints in a file before the execution of that file starts.\n* If your breakpoints remain unverified after launching the debugger (i.e. they appear gray instead\n  of red), the conversion between file paths and urls may not work. The messages from the \n  `PathConversion` logger may contain clues how to fix your configuration. Have a look at the \n  \"Diagnostic Logging\" section for an example how to enable this logger.\n* If you think you've found a bug in this adapter please [file a bug report](https://github.com/firefox-devtools/vscode-firefox-debug/issues).\n  It may be helpful if you create a log file (as described in the \"Diagnostic Logging\" section) and\n  attach it to the bug report.\n"
  },
  {
    "path": "dist/README.md",
    "content": "# Debug Adapter for Firefox\n\nThis package implements the [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/) for Firefox.\nIt is part of the [VS Code Debugger for Firefox](https://marketplace.visualstudio.com/items?itemName=firefox-devtools.vscode-firefox-debug) but can be used by other IDEs as well to integrate with Firefox's debugger.\n\n## Custom requests and events\n\nIn addition to the standard Debug Adapter Protocol, the debug adapter implements some custom requests and events:\n\n### Requests\n\nThe following custom requests can be sent to the debug adapter:\n\n* `toggleSkippingFile` : toggle whether a particular file (identified by its URL) is skipped (aka blackboxed) during debugging\n* `reloadAddon` : reload the WebExtension that is being debugged\n* `setPopupAutohide` : set the popup auto-hide flag (config key `ui.popup.disable_autohide`, WebExtension debugging only)\n* `togglePopupAutohide` : toggle the popup auto-hide flag\n* `setActiveEventBreakpoints` : set event listener breakpoints\n\n### Events\n\nThe debug adapter sends these custom events:\n\n* `popupAutohide` : contains the initial state of the popup auto-hide flag (WebExtension debugging only)\n* `threadStarted` : sent when a thread-like actor (for a Tab, Worker or WebExtension) is started\n* `threadExited` : sent when a thread-like actor exited\n* `newSource` : sent when Firefox loaded a new source file\n* `removeSources` : sent when a thread-like actor discards its previously loaded sources, i.e. when a Tab is navigated to a new URL\n* `unknownSource` : sent when the user tries to set a breakpoint in a file that could not be mapped to a URL loaded by Firefox\n* `availableEvents` : contains the events for which event listener breakpoints can be set\n\nThe event body types are defined [here](../src/common/customEvents.ts).\nThe source events may be replaced by the [LoadedSourceEvent](https://microsoft.github.io/debug-adapter-protocol/specification#Events_LoadedSource) in the future.\n"
  },
  {
    "path": "dist/package.json",
    "content": "{\n  \"name\": \"firefox-debugadapter\",\n  \"version\": \"2.15.0\",\n  \"author\": \"Holger Benl <hbenl@evandor.de>\",\n  \"description\": \"Debug adapter for Firefox\",\n  \"files\": [\n    \"adapter.bundle.js\",\n    \"launcher.bundle.js\",\n    \"mappings.wasm\",\n    \"terminator/*\",\n    \"README.md\",\n    \"LICENSE\"\n  ],\n  \"main\": \"adapter.bundle.js\",\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"git+https://github.com/firefox-devtools/vscode-firefox-debug.git\"\n  },\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/firefox-devtools/vscode-firefox-debug/issues\"\n  },\n  \"homepage\": \"https://github.com/firefox-devtools/vscode-firefox-debug\"\n}\n"
  },
  {
    "path": "dist/terminator/main.js",
    "content": "browser.windows.getAll().then((windows) => {\n\tfor (const window of windows) {\n\t\tbrowser.windows.remove(window.id);\n\t}\n});\n"
  },
  {
    "path": "dist/terminator/manifest.json",
    "content": "{\n\t\"description\": \"A WebExtension that terminates Firefox by closing all its windows\",\n\t\"manifest_version\": 2,\n\t\"name\": \"Terminator\",\n\t\"version\": \"1.0\",\n\t\"homepage_url\": \"https://github.com/firefox-devtools/vscode-firefox-debug\",\n\t\"applications\": {\n\t\t\"gecko\": {\n\t\t\t\"id\": \"{5b498dbd-f841-423d-9e46-a735486a0eb7}\"\n\t\t}\n\t},\n\t\"background\": {\n\t\t\"scripts\": [\n\t\t\t\"main.js\"\n\t\t]\n\t}\n}\n"
  },
  {
    "path": "package.json",
    "content": "{\n  \"name\": \"vscode-firefox-debug\",\n  \"displayName\": \"Debugger for Firefox\",\n  \"version\": \"2.15.0\",\n  \"author\": \"Holger Benl <hbenl@evandor.de>\",\n  \"publisher\": \"firefox-devtools\",\n  \"description\": \"Debug your web application or browser extension in Firefox\",\n  \"icon\": \"icon.png\",\n  \"engines\": {\n    \"vscode\": \"^1.80.0\"\n  },\n  \"categories\": [\n    \"Debuggers\"\n  ],\n  \"scripts\": {\n    \"reinstall\": \"rimraf node_modules package-lock.json && npm install\",\n    \"clean\": \"rimraf dist/*.bundle.js dist/*.bundle.js.map dist/mappings.wasm coverage .nyc_output vscode-firefox-debug-*.vsix\",\n    \"build\": \"webpack --mode=production\",\n    \"watch\": \"webpack --watch --mode=development\",\n    \"rebuild\": \"npm run clean && npm run build\",\n    \"typecheck\": \"tsc\",\n    \"typecheck-watch\": \"tsc -w\",\n    \"test\": \"mocha\",\n    \"cover\": \"nyc npm test && nyc report --reporter=lcov && nyc report --reporter=html\",\n    \"package\": \"vsce package\",\n    \"publish\": \"npm run rebuild && vsce publish\",\n    \"package-npm\": \"cd dist && npm pack\",\n    \"publish-npm\": \"npm run rebuild && cd dist && npm publish\"\n  },\n  \"dependencies\": {\n    \"@babel/polyfill\": \"^7.12.1\",\n    \"@vscode/debugadapter\": \"^1.68.0\",\n    \"chokidar\": \"^3.6.0\",\n    \"core-js\": \"^3.39.0\",\n    \"data-uri-to-buffer\": \"3.0.1\",\n    \"debounce\": \"^2.2.0\",\n    \"escape-string-regexp\": \"4.0.0\",\n    \"file-uri-to-path\": \"^2.0.0\",\n    \"file-url\": \"^4.0.0\",\n    \"firefox-profile\": \"^4.7.0\",\n    \"fs-extra\": \"^11.2.0\",\n    \"is-absolute-url\": \"3.0.3\",\n    \"minimatch\": \"^9.0.5\",\n    \"source-map\": \"^0.7.4\",\n    \"strip-json-comments\": \"3.1.1\",\n    \"uuid\": \"^11.0.3\",\n    \"vscode-uri\": \"^3.0.8\"\n  },\n  \"devDependencies\": {\n    \"@babel/cli\": \"^7.26.4\",\n    \"@babel/core\": \"^7.26.0\",\n    \"@babel/plugin-proposal-class-properties\": \"^7.18.6\",\n    \"@babel/plugin-proposal-object-rest-spread\": \"^7.20.7\",\n    \"@babel/preset-env\": \"^7.26.0\",\n    \"@babel/preset-typescript\": \"^7.26.0\",\n    \"@gulp-sourcemaps/map-sources\": \"^1.0.0\",\n    \"@types/debounce\": \"^1.2.4\",\n    \"@types/fs-extra\": \"^11.0.4\",\n    \"@types/gulp\": \"^4.0.17\",\n    \"@types/gulp-concat\": \"^0.0.37\",\n    \"@types/gulp-rename\": \"^2.0.6\",\n    \"@types/gulp-sourcemaps\": \"^0.0.38\",\n    \"@types/gulp-uglify\": \"^3.0.11\",\n    \"@types/mocha\": \"^10.0.10\",\n    \"@types/node\": \"^16.18.122\",\n    \"@types/vscode\": \"~1.80.0\",\n    \"@vscode/debugadapter-testsupport\": \"^1.68.0\",\n    \"babel-loader\": \"^9.2.1\",\n    \"copy-webpack-plugin\": \"^12.0.2\",\n    \"dotenv\": \"^16.4.7\",\n    \"gulp\": \"^5.0.0\",\n    \"gulp-concat\": \"^2.6.1\",\n    \"gulp-nop\": \"0.0.3\",\n    \"gulp-rename\": \"^2.0.0\",\n    \"gulp-sourcemaps\": \"^3.0.0\",\n    \"gulp-uglify\": \"^3.0.2\",\n    \"mocha\": \"^11.0.1\",\n    \"nyc\": \"^17.1.0\",\n    \"original-fs\": \"^1.2.0\",\n    \"rimraf\": \"^6.0.1\",\n    \"terser-webpack-plugin\": \"^5.3.11\",\n    \"ts-node\": \"^10.9.2\",\n    \"typescript\": \"^5.7.2\",\n    \"vsce\": \"^2.15.0\",\n    \"webpack\": \"^5.97.1\",\n    \"webpack-cli\": \"^5.1.4\"\n  },\n  \"babel\": {\n    \"presets\": [\n      \"@babel/typescript\",\n      [\n        \"@babel/env\",\n        {\n          \"modules\": false,\n          \"useBuiltIns\": \"usage\",\n          \"corejs\": 3\n        }\n      ]\n    ],\n    \"plugins\": [\n      \"@babel/proposal-class-properties\",\n      \"@babel/proposal-object-rest-spread\"\n    ]\n  },\n  \"browserslist\": [\n    \"node 8\"\n  ],\n  \"nyc\": {\n    \"include\": [\n      \"out/**/*.js\"\n    ],\n    \"exclude\": [\n      \"out/test/**/*.js\"\n    ]\n  },\n  \"repository\": {\n    \"type\": \"git\",\n    \"url\": \"https://github.com/firefox-devtools/vscode-firefox-debug.git\"\n  },\n  \"keywords\": [\n    \"vscode\",\n    \"firefox\",\n    \"debug\"\n  ],\n  \"license\": \"MIT\",\n  \"bugs\": {\n    \"url\": \"https://github.com/firefox-devtools/vscode-firefox-debug/issues\"\n  },\n  \"homepage\": \"https://github.com/firefox-devtools/vscode-firefox-debug\",\n  \"extensionKind\": [\n    \"ui\"\n  ],\n  \"main\": \"./dist/extension.bundle.js\",\n  \"activationEvents\": [\n    \"onDebug\"\n  ],\n  \"contributes\": {\n    \"commands\": [\n      {\n        \"command\": \"extension.firefox.reloadAddon\",\n        \"title\": \"Firefox: Reload add-on\"\n      },\n      {\n        \"command\": \"extension.firefox.toggleSkippingFile\",\n        \"title\": \"Toggle skipping this file\"\n      },\n      {\n        \"command\": \"extension.firefox.openScript\",\n        \"title\": \"Open script\"\n      },\n      {\n        \"command\": \"extension.firefox.addPathMapping\",\n        \"title\": \"Map to local directory\"\n      },\n      {\n        \"command\": \"extension.firefox.addFilePathMapping\",\n        \"title\": \"Map to local file\"\n      },\n      {\n        \"command\": \"extension.firefox.addNullPathMapping\",\n        \"title\": \"Don't map this directory\"\n      },\n      {\n        \"command\": \"extension.firefox.addNullFilePathMapping\",\n        \"title\": \"Don't map this file\"\n      },\n      {\n        \"command\": \"extension.firefox.enablePopupAutohide\",\n        \"title\": \"Firefox: Enable popup auto-hide\"\n      },\n      {\n        \"command\": \"extension.firefox.disablePopupAutohide\",\n        \"title\": \"Firefox: Disable popup auto-hide\"\n      },\n      {\n        \"command\": \"extension.firefox.togglePopupAutohide\",\n        \"title\": \"Firefox: Toggle popup auto-hide\"\n      },\n      {\n        \"command\": \"extension.firefox.pathMappingWizard\",\n        \"title\": \"Firefox: Run the path mapping wizard\"\n      }\n    ],\n    \"menus\": {\n      \"debug/callstack/context\": [\n        {\n          \"command\": \"extension.firefox.toggleSkippingFile\",\n          \"when\": \"inDebugMode && debugType == 'firefox' && callStackItemType == 'stackFrame'\"\n        }\n      ],\n      \"view/item/context\": [\n        {\n          \"command\": \"extension.firefox.addPathMapping\",\n          \"group\": \"addPathMapping@1\",\n          \"when\": \"view == extension.firefox.loadedScripts && viewItem == directory\"\n        },\n        {\n          \"command\": \"extension.firefox.addFilePathMapping\",\n          \"group\": \"addPathMapping@1\",\n          \"when\": \"view == extension.firefox.loadedScripts && viewItem == file\"\n        },\n        {\n          \"command\": \"extension.firefox.addNullPathMapping\",\n          \"group\": \"addPathMapping@2\",\n          \"when\": \"view == extension.firefox.loadedScripts && viewItem == directory\"\n        },\n        {\n          \"command\": \"extension.firefox.addNullFilePathMapping\",\n          \"group\": \"addPathMapping@2\",\n          \"when\": \"view == extension.firefox.loadedScripts && viewItem == file\"\n        }\n      ],\n      \"commandPalette\": [\n        {\n          \"command\": \"extension.firefox.pathMappingWizard\",\n          \"when\": \"editorIsOpen && inDebugMode && debugType == 'firefox'\"\n        },\n        {\n          \"command\": \"extension.firefox.toggleSkippingFile\",\n          \"when\": \"false\"\n        },\n        {\n          \"command\": \"extension.firefox.openScript\",\n          \"when\": \"false\"\n        },\n        {\n          \"command\": \"extension.firefox.addPathMapping\",\n          \"when\": \"false\"\n        },\n        {\n          \"command\": \"extension.firefox.addFilePathMapping\",\n          \"when\": \"false\"\n        },\n        {\n          \"command\": \"extension.firefox.addNullPathMapping\",\n          \"when\": \"false\"\n        },\n        {\n          \"command\": \"extension.firefox.addNullFilePathMapping\",\n          \"when\": \"false\"\n        }\n      ]\n    },\n    \"configuration\": {\n      \"title\": \"Firefox debug\",\n      \"properties\": {\n        \"firefox.executable\": {\n          \"description\": \"Absolute path to the Firefox executable\",\n          \"type\": \"string\",\n          \"scope\": \"resource\"\n        },\n        \"firefox.args\": {\n          \"description\": \"Additional arguments passed to Firefox\",\n          \"type\": \"array\",\n          \"items\": {\n            \"type\": \"string\"\n          },\n          \"scope\": \"resource\"\n        },\n        \"firefox.profileDir\": {\n          \"description\": \"The path of the Firefox profile directory to use\",\n          \"type\": \"string\",\n          \"scope\": \"resource\"\n        },\n        \"firefox.profile\": {\n          \"description\": \"The name of the Firefox profile to use\",\n          \"type\": \"string\",\n          \"scope\": \"resource\"\n        },\n        \"firefox.keepProfileChanges\": {\n          \"description\": \"Use the specified profile directly instead of a temporary copy\",\n          \"type\": \"boolean\",\n          \"scope\": \"resource\"\n        },\n        \"firefox.port\": {\n          \"description\": \"The remote debugging port to use\",\n          \"type\": \"number\",\n          \"scope\": \"resource\"\n        }\n      }\n    },\n    \"views\": {\n      \"debug\": [\n        {\n          \"id\": \"extension.firefox.loadedScripts\",\n          \"name\": \"Loaded Scripts\",\n          \"when\": \"inDebugMode && debugType == 'firefox'\"\n        },\n        {\n          \"id\": \"extension.firefox.eventBreakpoints\",\n          \"name\": \"Event Listener Breakpoints\",\n          \"when\": \"inDebugMode && debugType == 'firefox'\"\n        }\n      ]\n    },\n    \"debuggers\": [\n      {\n        \"type\": \"firefox\",\n        \"label\": \"Firefox\",\n        \"program\": \"./dist/adapter.bundle.js\",\n        \"runtime\": \"node\",\n        \"languages\": [\n          \"html\"\n        ],\n        \"initialConfigurations\": [\n          {\n            \"name\": \"Launch index.html\",\n            \"type\": \"firefox\",\n            \"request\": \"launch\",\n            \"reAttach\": true,\n            \"file\": \"${workspaceFolder}/index.html\"\n          },\n          {\n            \"name\": \"Launch localhost\",\n            \"type\": \"firefox\",\n            \"request\": \"launch\",\n            \"reAttach\": true,\n            \"url\": \"http://localhost/index.html\",\n            \"webRoot\": \"${workspaceFolder}\"\n          },\n          {\n            \"name\": \"Attach\",\n            \"type\": \"firefox\",\n            \"request\": \"attach\"\n          },\n          {\n            \"name\": \"Launch WebExtension\",\n            \"type\": \"firefox\",\n            \"request\": \"launch\",\n            \"reAttach\": true,\n            \"addonPath\": \"${workspaceFolder}\"\n          }\n        ],\n        \"configurationSnippets\": [\n          {\n            \"label\": \"Firefox: Launch (file)\",\n            \"description\": \"Launch Firefox navigated to a local file in your project\",\n            \"body\": {\n              \"type\": \"firefox\",\n              \"request\": \"launch\",\n              \"reAttach\": true,\n              \"name\": \"${1:Launch index.html}\",\n              \"file\": \"^\\\"\\\\${workspaceFolder}/${2:index.html}\\\"\"\n            }\n          },\n          {\n            \"label\": \"Firefox: Launch (server)\",\n            \"description\": \"Launch Firefox navigated to your project running on a server\",\n            \"body\": {\n              \"type\": \"firefox\",\n              \"request\": \"launch\",\n              \"reAttach\": true,\n              \"name\": \"${1:Launch localhost}\",\n              \"url\": \"${2:http://localhost/index.html}\",\n              \"webRoot\": \"^\\\"\\\\${workspaceFolder}${3:}\\\"\"\n            }\n          },\n          {\n            \"label\": \"Firefox: Attach\",\n            \"description\": \"Attach to a running Firefox process\",\n            \"body\": {\n              \"type\": \"firefox\",\n              \"request\": \"attach\",\n              \"name\": \"${1:Attach}\"\n            }\n          },\n          {\n            \"label\": \"Firefox: WebExtension\",\n            \"description\": \"Launch Firefox with your WebExtension project installed\",\n            \"body\": {\n              \"type\": \"firefox\",\n              \"request\": \"launch\",\n              \"reAttach\": true,\n              \"name\": \"${1:Launch add-on}\",\n              \"addonPath\": \"^\\\"\\\\${workspaceFolder}${2:}\\\"\"\n            }\n          }\n        ],\n        \"configurationAttributes\": {\n          \"launch\": {\n            \"required\": [],\n            \"properties\": {\n              \"file\": {\n                \"type\": \"string\",\n                \"description\": \"The file to open in the browser\",\n                \"default\": \"${workspaceFolder}/index.html\"\n              },\n              \"url\": {\n                \"type\": \"string\",\n                \"description\": \"The url to open in the browser\"\n              },\n              \"webRoot\": {\n                \"type\": \"string\",\n                \"description\": \"If the 'url' property is specified, this property specifies the workspace absolute path corresponding to the path of the url\",\n                \"default\": \"${workspaceFolder}\"\n              },\n              \"firefoxExecutable\": {\n                \"type\": \"string\",\n                \"description\": \"Absolute path to the Firefox executable\"\n              },\n              \"tmpDir\": {\n                \"type\": \"string\",\n                \"description\": \"The path of the directory to use for temporary files\"\n              },\n              \"profileDir\": {\n                \"type\": \"string\",\n                \"description\": \"The path of the Firefox profile directory to use\"\n              },\n              \"profile\": {\n                \"type\": \"string\",\n                \"description\": \"The name of the Firefox profile to use\"\n              },\n              \"keepProfileChanges\": {\n                \"type\": \"boolean\",\n                \"description\": \"Use the specified profile directly instead of a temporary copy\",\n                \"default\": true\n              },\n              \"port\": {\n                \"type\": \"number\",\n                \"description\": \"The remote debugging port to use\",\n                \"default\": 6000\n              },\n              \"timeout\": {\n                \"type\": \"number\",\n                \"description\": \"The timeout in seconds for the adapter to connect to Firefox after launching it\",\n                \"default\": 5\n              },\n              \"firefoxArgs\": {\n                \"type\": \"array\",\n                \"description\": \"Additional arguments passed to Firefox\",\n                \"items\": {\n                  \"type\": \"string\"\n                },\n                \"default\": []\n              },\n              \"reAttach\": {\n                \"type\": \"boolean\",\n                \"description\": \"Don't terminate Firefox at the end of the debugging session and re-attach to it when starting the next session\",\n                \"default\": true\n              },\n              \"reloadOnAttach\": {\n                \"type\": \"boolean\",\n                \"description\": \"Reload all tabs after re-attaching to Firefox\",\n                \"default\": true\n              },\n              \"reloadOnChange\": {\n                \"description\": \"Watch the specified files, directories or glob patterns and reload the tabs or add-on when they change\",\n                \"type\": [\n                  \"string\",\n                  \"array\",\n                  \"object\"\n                ],\n                \"items\": {\n                  \"type\": \"string\"\n                },\n                \"properties\": {\n                  \"watch\": {\n                    \"description\": \"Files, directories or glob patterns to be watched for file changes\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": \"${workspaceFolder}/**/*.js\"\n                  },\n                  \"ignore\": {\n                    \"description\": \"Files, directories or glob patterns to be ignored\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": \"**/node_modules/**\"\n                  },\n                  \"debounce\": {\n                    \"description\": \"The time in milliseconds to wait after a file change before reloading, or false to start reloading immediately\",\n                    \"type\": [\n                      \"number\",\n                      \"boolean\"\n                    ]\n                  }\n                },\n                \"default\": {\n                  \"watch\": \"${workspaceFolder}/**/*.js\",\n                  \"ignore\": \"**/node_modules/**\"\n                }\n              },\n              \"clearConsoleOnReload\": {\n                \"type\": \"boolean\",\n                \"description\": \"Clear the debug console in VS Code when the page is reloaded in Firefox\",\n                \"default\": false\n              },\n              \"pathMappings\": {\n                \"type\": \"array\",\n                \"description\": \"Additional mappings from URLs (as seen by Firefox) to filesystem paths (as seen by VS Code)\",\n                \"items\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"url\": {\n                      \"type\": \"string\",\n                      \"description\": \"The URL as seen by Firefox\"\n                    },\n                    \"path\": {\n                      \"type\": [\n                        \"string\",\n                        \"null\"\n                      ],\n                      \"description\": \"The corresponding filesystem path as seen by VS Code\"\n                    }\n                  }\n                }\n              },\n              \"pathMappingIndex\": {\n                \"type\": \"string\",\n                \"description\": \"The name of the directory index file configured in the webserver\",\n                \"default\": \"index.html\"\n              },\n              \"skipFiles\": {\n                \"type\": \"array\",\n                \"description\": \"An array of glob patterns to skip when debugging\",\n                \"items\": {\n                  \"type\": \"string\"\n                }\n              },\n              \"preferences\": {\n                \"type\": \"object\",\n                \"description\": \"Set additional Firefox preferences\",\n                \"additionalProperties\": {\n                  \"type\": [\n                    \"boolean\",\n                    \"integer\",\n                    \"string\",\n                    \"null\"\n                  ]\n                }\n              },\n              \"tabFilter\": {\n                \"description\": \"Only attach to tabs whose URL matches this\",\n                \"type\": [\n                  \"string\",\n                  \"array\",\n                  \"object\"\n                ],\n                \"items\": {\n                  \"type\": \"string\"\n                },\n                \"properties\": {\n                  \"include\": {\n                    \"description\": \"URLs to attach to\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": \"*\"\n                  },\n                  \"exclude\": {\n                    \"description\": \"URLs not to attach to\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": []\n                  }\n                },\n                \"default\": \"*\"\n              },\n              \"showConsoleCallLocation\": {\n                \"type\": \"boolean\",\n                \"description\": \"Show the location of console API calls\",\n                \"default\": true\n              },\n              \"addonPath\": {\n                \"type\": \"string\",\n                \"description\": \"The path of the directory containing the WebExtension\",\n                \"default\": \"${workspaceFolder}\"\n              },\n              \"popupAutohideButton\": {\n                \"type\": \"boolean\",\n                \"description\": \"Show a button in the status bar for toggling popup auto-hide (WebExtension debugging)\",\n                \"default\": false\n              },\n              \"liftAccessorsFromPrototypes\": {\n                \"type\": \"number\",\n                \"description\": \"The number of prototype levels that should be scanned for accessor properties\",\n                \"default\": 0\n              },\n              \"suggestPathMappingWizard\": {\n                \"type\": \"boolean\",\n                \"description\": \"Suggest using the Path Mapping Wizard when the user tries to set a breakpoint in an unmapped source during a debug session\",\n                \"default\": true\n              },\n              \"enableCRAWorkaround\": {\n                \"type\": \"boolean\",\n                \"description\": \"Enable a workaround for breakpoints not working in projects created using create-react-app\",\n                \"default\": true\n              },\n              \"log\": {\n                \"type\": \"object\",\n                \"description\": \"Configuration for diagnostic logging of the debug adapter\",\n                \"properties\": {\n                  \"fileName\": {\n                    \"type\": \"string\",\n                    \"description\": \"The name of the logfile\",\n                    \"default\": \"${workspaceFolder}/vscode-firefox-debug.log\"\n                  },\n                  \"fileLevel\": {\n                    \"type\": \"object\",\n                    \"description\": \"The minimum loglevel(s) for messages written to the logfile\",\n                    \"properties\": {\n                      \"default\": {\n                        \"type\": \"string\",\n                        \"enum\": [\n                          \"Debug\",\n                          \"Info\",\n                          \"Warn\",\n                          \"Error\"\n                        ],\n                        \"description\": \"The default loglevel\"\n                      }\n                    },\n                    \"additionalProperties\": {\n                      \"type\": \"string\",\n                      \"enum\": [\n                        \"Debug\",\n                        \"Info\",\n                        \"Warn\",\n                        \"Error\"\n                      ]\n                    },\n                    \"default\": {\n                      \"default\": \"Debug\"\n                    }\n                  },\n                  \"consoleLevel\": {\n                    \"type\": \"object\",\n                    \"description\": \"The minimum loglevel(s) for messages written to the console\",\n                    \"properties\": {\n                      \"default\": {\n                        \"type\": \"string\",\n                        \"enum\": [\n                          \"Debug\",\n                          \"Info\",\n                          \"Warn\",\n                          \"Error\"\n                        ],\n                        \"description\": \"The default loglevel\"\n                      }\n                    },\n                    \"additionalProperties\": {\n                      \"type\": \"string\",\n                      \"enum\": [\n                        \"Debug\",\n                        \"Info\",\n                        \"Warn\",\n                        \"Error\"\n                      ]\n                    },\n                    \"default\": {\n                      \"default\": \"Debug\"\n                    }\n                  }\n                },\n                \"default\": {\n                  \"fileName\": \"${workspaceFolder}/vscode-firefox-debug.log\",\n                  \"fileLevel\": {\n                    \"default\": \"Debug\"\n                  },\n                  \"consoleLevel\": {\n                    \"default\": \"Warn\"\n                  }\n                }\n              }\n            }\n          },\n          \"attach\": {\n            \"required\": [],\n            \"properties\": {\n              \"url\": {\n                \"type\": \"string\",\n                \"description\": \"The url to open in the browser\"\n              },\n              \"webRoot\": {\n                \"type\": \"string\",\n                \"description\": \"If the 'url' property is specified, this property specifies the workspace absolute path corresponding to the path of the url\",\n                \"default\": \"${workspaceFolder}\"\n              },\n              \"firefoxExecutable\": {\n                \"type\": \"string\",\n                \"description\": \"Absolute path to the Firefox executable\"\n              },\n              \"profileDir\": {\n                \"type\": \"string\",\n                \"description\": \"The path of the Firefox profile directory to use\"\n              },\n              \"port\": {\n                \"type\": \"number\",\n                \"description\": \"The remote debugging port to use\",\n                \"default\": 6000\n              },\n              \"host\": {\n                \"type\": \"string\",\n                \"description\": \"The remote debugging host to use\",\n                \"default\": \"localhost\"\n              },\n              \"reloadOnAttach\": {\n                \"type\": \"boolean\",\n                \"description\": \"Reload all tabs after attaching to Firefox\",\n                \"default\": false\n              },\n              \"reloadOnChange\": {\n                \"description\": \"Watch the specified files, directories or glob patterns and reload the tabs or add-on when they change\",\n                \"type\": [\n                  \"string\",\n                  \"array\",\n                  \"object\"\n                ],\n                \"items\": {\n                  \"type\": \"string\"\n                },\n                \"properties\": {\n                  \"watch\": {\n                    \"description\": \"Files, directories or glob patterns to be watched for file changes\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": \"${workspaceFolder}/**/*.js\"\n                  },\n                  \"ignore\": {\n                    \"description\": \"Files, directories or glob patterns to be ignored\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": \"**/node_modules/**\"\n                  },\n                  \"debounce\": {\n                    \"description\": \"The time in milliseconds to wait after a file change before reloading, or false to start reloading immediately\",\n                    \"type\": [\n                      \"number\",\n                      \"boolean\"\n                    ]\n                  }\n                },\n                \"default\": {\n                  \"watch\": \"${workspaceFolder}/**/*.js\",\n                  \"ignore\": \"**/node_modules/**\"\n                }\n              },\n              \"clearConsoleOnReload\": {\n                \"type\": \"boolean\",\n                \"description\": \"Clear the debug console in VS Code when the page is reloaded in Firefox\",\n                \"default\": false\n              },\n              \"pathMappings\": {\n                \"type\": \"array\",\n                \"description\": \"Additional mappings from URLs (as seen by Firefox) to filesystem paths (as seen by VS Code)\",\n                \"items\": {\n                  \"type\": \"object\",\n                  \"properties\": {\n                    \"url\": {\n                      \"type\": \"string\",\n                      \"description\": \"The URL as seen by Firefox\"\n                    },\n                    \"path\": {\n                      \"type\": [\n                        \"string\",\n                        \"null\"\n                      ],\n                      \"description\": \"The corresponding filesystem path as seen by VS Code\"\n                    }\n                  }\n                }\n              },\n              \"pathMappingIndex\": {\n                \"type\": \"string\",\n                \"description\": \"The name of the directory index file configured in the webserver\",\n                \"default\": \"index.html\"\n              },\n              \"skipFiles\": {\n                \"type\": \"array\",\n                \"description\": \"An array of glob patterns to skip when debugging\",\n                \"items\": {\n                  \"type\": \"string\"\n                },\n                \"default\": [\n                  \"${workspaceFolder}/node_modules/**/*\"\n                ]\n              },\n              \"tabFilter\": {\n                \"description\": \"Only attach to tabs whose URL matches this\",\n                \"type\": [\n                  \"string\",\n                  \"array\",\n                  \"object\"\n                ],\n                \"items\": {\n                  \"type\": \"string\"\n                },\n                \"properties\": {\n                  \"include\": {\n                    \"description\": \"URLs to attach to\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": \"*\"\n                  },\n                  \"exclude\": {\n                    \"description\": \"URLs not to attach to\",\n                    \"type\": [\n                      \"string\",\n                      \"array\"\n                    ],\n                    \"items\": {\n                      \"type\": \"string\"\n                    },\n                    \"default\": []\n                  }\n                },\n                \"default\": \"*\"\n              },\n              \"showConsoleCallLocation\": {\n                \"type\": \"boolean\",\n                \"description\": \"Show the location of console API calls\",\n                \"default\": true\n              },\n              \"addonPath\": {\n                \"type\": \"string\",\n                \"description\": \"The path of the directory containing the WebExtension\",\n                \"default\": \"${workspaceFolder}\"\n              },\n              \"popupAutohideButton\": {\n                \"type\": \"boolean\",\n                \"description\": \"Show a button in the status bar for toggling popup auto-hide (WebExtension debugging)\",\n                \"default\": false\n              },\n              \"liftAccessorsFromPrototypes\": {\n                \"type\": \"number\",\n                \"description\": \"The number of prototype levels that should be scanned for accessor properties\",\n                \"default\": 0\n              },\n              \"suggestPathMappingWizard\": {\n                \"type\": \"boolean\",\n                \"description\": \"Suggest using the Path Mapping Wizard when the user tries to set a breakpoint in an unmapped source during a debug session\",\n                \"default\": true\n              },\n              \"enableCRAWorkaround\": {\n                \"type\": \"boolean\",\n                \"description\": \"Enable a workaround for breakpoints not working in projects created using create-react-app\",\n                \"default\": true\n              },\n              \"log\": {\n                \"type\": \"object\",\n                \"description\": \"Configuration for diagnostic logging of the debug adapter\",\n                \"properties\": {\n                  \"fileName\": {\n                    \"type\": \"string\",\n                    \"description\": \"The name of the logfile\",\n                    \"default\": \"${workspaceFolder}/vscode-firefox-debug.log\"\n                  },\n                  \"fileLevel\": {\n                    \"type\": \"object\",\n                    \"description\": \"The minimum loglevel(s) for messages written to the logfile\",\n                    \"properties\": {\n                      \"default\": {\n                        \"type\": \"string\",\n                        \"enum\": [\n                          \"Debug\",\n                          \"Info\",\n                          \"Warn\",\n                          \"Error\"\n                        ],\n                        \"description\": \"The default loglevel\"\n                      }\n                    },\n                    \"additionalProperties\": {\n                      \"type\": \"string\",\n                      \"enum\": [\n                        \"Debug\",\n                        \"Info\",\n                        \"Warn\",\n                        \"Error\"\n                      ]\n                    },\n                    \"default\": {\n                      \"default\": \"Debug\"\n                    }\n                  },\n                  \"consoleLevel\": {\n                    \"type\": \"object\",\n                    \"description\": \"The minimum loglevel(s) for messages written to the console\",\n                    \"properties\": {\n                      \"default\": {\n                        \"type\": \"string\",\n                        \"enum\": [\n                          \"Debug\",\n                          \"Info\",\n                          \"Warn\",\n                          \"Error\"\n                        ],\n                        \"description\": \"The default loglevel\"\n                      }\n                    },\n                    \"additionalProperties\": {\n                      \"type\": \"string\",\n                      \"enum\": [\n                        \"Debug\",\n                        \"Info\",\n                        \"Warn\",\n                        \"Error\"\n                      ]\n                    },\n                    \"default\": {\n                      \"default\": \"Debug\"\n                    }\n                  }\n                },\n                \"default\": {\n                  \"fileName\": \"${workspaceFolder}/vscode-firefox-debug.log\",\n                  \"fileLevel\": {\n                    \"default\": \"Debug\"\n                  },\n                  \"consoleLevel\": {\n                    \"default\": \"Warn\"\n                  }\n                }\n              }\n            }\n          }\n        }\n      }\n    ]\n  }\n}\n"
  },
  {
    "path": "src/README.md",
    "content": "This folder contains the sources for the Debug Adapter for Firefox extension.\nThey are grouped into the following subfolders:\n\n* [`adapter`](./adapter) -\n  the [debug adapter](https://code.visualstudio.com/api/extension-guides/debugger-extension#debugging-architecture-of-vs-code)\n  itself, which is run in a separate process and talks to VS Code using the\n  [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/)\n\n* [`extension`](./extension) - contains additional integration with VS Code (this code will be run in the VS Code extension host):\n  * the Loaded Scripts Explorer, which is shown at the bottom of the debug view\n  * a [DebugConfigurationProvider](https://code.visualstudio.com/api/extension-guides/debugger-extension#using-a-debugconfigurationprovider)\n    that adds [machine-specific configuration](https://github.com/firefox-devtools/vscode-firefox-debug#overriding-configuration-properties-in-your-settings)\n    (like the location of the Firefox executable, the name of the profile to use for debugging, etc.)\n    from the user's VS Code settings to debug configurations\n  * a button to toggle the “Disable Popup Auto-Hide” flag (for WebExtension debugging) from VS Code\n\n* [`test`](./test) - mocha tests for this extension\n\n* [`common`](./common) - a few interfaces and functions used throughout this extension\n\n* [`typings`](./typings) - type definition files for npm modules for which no type definitions exist on npm\n"
  },
  {
    "path": "src/adapter/README.md",
    "content": "This folder contains the sources for the Firefox\n[debug adapter](https://code.visualstudio.com/api/extension-guides/debugger-extension#debugging-architecture-of-vs-code).\n\nThe entry point is the [`FirefoxDebugAdapter`](./firefoxDebugAdapter.ts) class, which receives the\nrequests from VS Code and delegates most of the work to the [`FirefoxDebugSession`](./firefoxDebugSession.ts) class.\n\nThe [`firefox`](./firefox) folder contains the code for launching and talking to Firefox.\n\nThe [`coordinator`](./coordinator) folder contains classes that manage the states of\n[\"threads\"](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#interacting-with-thread-like-actors)\nin Firefox.\n\nThe [`adapter`](./adapter) folder contains classes that translate between the debugging protocols of Firefox and VS Code.\n"
  },
  {
    "path": "src/adapter/adapter/README.md",
    "content": "This folder contains classes that translate between the\n[Firefox Remote Debugging Protocol](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md)\nand VS Code's [Debug Adapter Protocol](https://microsoft.github.io/debug-adapter-protocol/).\nFurthermore, there are classes for managing [breakpoints](./breakpointsManager.ts),\n[skipping](./skipFilesManager.ts) (or blackboxing) files in the debugger and\n[addon debugging](./addonManager.ts).\n"
  },
  {
    "path": "src/adapter/adapter/addonManager.ts",
    "content": "import { Log } from '../util/log';\nimport * as path from 'path';\nimport { ParsedAddonConfiguration } from '../configuration';\nimport { RootActorProxy } from '../firefox/actorProxy/root';\nimport { AddonsActorProxy } from '../firefox/actorProxy/addons';\nimport { PreferenceActorProxy } from '../firefox/actorProxy/preference';\nimport { FirefoxDebugSession } from '../firefoxDebugSession';\nimport { PopupAutohideEventBody } from '../../common/customEvents';\nimport { isWindowsPlatform } from '../../common/util';\nimport { DescriptorActorProxy } from '../firefox/actorProxy/descriptor';\n\nconst log = Log.create('AddonManager');\n\nexport const popupAutohidePreferenceKey = 'ui.popup.disable_autohide';\n\n/**\n * When debugging a WebExtension, this class installs the WebExtension, attaches to it, reloads it\n * when desired and tells the [`PopupAutohideManager`](../../extension/popupAutohideManager.ts) the\n * initial state of the popup auto-hide flag by sending a custom event.\n */\nexport class AddonManager {\n\n\tprivate resolveAddonId!: (addonId: string) => void;\n\tpublic readonly addonId = new Promise<string>(resolve => this.resolveAddonId = resolve);\n\n\tprivate readonly config: ParsedAddonConfiguration;\n\n\tprivate descriptorActor: DescriptorActorProxy | undefined = undefined;\n\n\tconstructor(\n\t\tprivate readonly debugSession: FirefoxDebugSession\n\t) {\n\t\tthis.config = debugSession.config.addon!;\n\t}\n\n\tpublic async sessionStarted(\n\t\trootActor: RootActorProxy,\n\t\taddonsActor: AddonsActorProxy,\n\t\tpreferenceActor: PreferenceActorProxy\n\t): Promise<void> {\n\n\t\tconst addonPath = isWindowsPlatform() ? path.normalize(this.config.path) : this.config.path;\n\t\tlet result = await addonsActor.installAddon(addonPath);\n\t\tthis.resolveAddonId(result.addon.id);\n\n\t\tawait this.fetchDescriptor(rootActor);\n\n\t\tif (this.config.popupAutohideButton) {\n\t\t\tconst popupAutohide = !(await preferenceActor.getBoolPref(popupAutohidePreferenceKey));\n\t\t\tthis.debugSession.sendCustomEvent('popupAutohide', <PopupAutohideEventBody>{ popupAutohide });\n\t\t}\n\t}\n\n\tpublic async reloadAddon(): Promise<void> {\n\t\tif (!this.descriptorActor) {\n\t\t\tthrow 'Addon isn\\'t attached';\n\t\t}\n\n\t\tawait this.descriptorActor.reload();\n\t}\n\n\tprivate async fetchDescriptor(rootActor: RootActorProxy): Promise<void> {\n\n\t\tconst addons = await rootActor.fetchAddons();\n\n\t\taddons.forEach(async addon => {\n\t\t\tif (addon.id === await this.addonId) {\n\t\t\t\tthis.descriptorActor = new DescriptorActorProxy(\n\t\t\t\t\taddon.actor,\n\t\t\t\t\t'webExtension',\n\t\t\t\t\tthis.debugSession.firefoxDebugConnection\n\t\t\t\t);\n\n\t\t\t\tif (!this.debugSession.processDescriptorMode) {\n\t\t\t\t\tconst adapter = await this.debugSession.attachDescriptor(this.descriptorActor);\n\t\t\t\t\tawait adapter.watcherActor.watchResources(['console-message', 'error-message', 'source', 'thread-state']);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/breakpoint.ts",
    "content": "import { DebugProtocol } from '@vscode/debugprotocol';\nimport { MappedLocation } from '../location';\n\nexport class BreakpointInfo {\n\n\t/**\n\t * the actual location where the breakpoint was set (which may be different from the requested location)\n\t */\n\tpublic actualLocation: MappedLocation | undefined;\n\n\t/** true if the breakpoint was successfully set */\n\tpublic verified: boolean;\n\n\t/** how many times the breakpoint should be skipped initially */\n\tpublic readonly hitLimit: number;\n\tpublic hitCount = 0;\n\n\tpublic constructor(\n\t\tpublic readonly id: number,\n\t\tpublic readonly requestedBreakpoint: DebugProtocol.SourceBreakpoint\n\t) {\n\t\tthis.verified = false;\n\t\tthis.hitLimit = +(requestedBreakpoint.hitCondition || '');\n\t}\n\n\tpublic isEquivalent(other: BreakpointInfo | DebugProtocol.SourceBreakpoint): boolean {\n\n\t\tconst bp1 = this.requestedBreakpoint;\n\t\tconst bp2 = (other instanceof BreakpointInfo) ? other.requestedBreakpoint : other;\n\n\t\treturn (bp1.line === bp2.line) && (bp1.column === bp2.column) &&\n\t\t\t(bp1.condition === bp2.condition) && (bp1.logMessage === bp2.logMessage);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/breakpointsManager.ts",
    "content": "import { Log } from '../util/log';\nimport { BreakpointInfo } from './breakpoint';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { Event, Breakpoint, BreakpointEvent } from '@vscode/debugadapter';\nimport { FirefoxDebugSession } from '../firefoxDebugSession';\nimport { SourceMappingSourceActorProxy } from '../firefox/sourceMaps/source';\nimport { normalizePath } from '../util/fs';\n\nlet log = Log.create('BreakpointsManager');\n\n/**\n * This class holds all breakpoints that have been set in VS Code and synchronizes them with all\n * sources in all threads in Firefox using [`SourceAdapter#updateBreakpoints()`](./source.ts).\n */\nexport class BreakpointsManager {\n\n\tprivate nextBreakpointId = 1;\n\tprivate readonly breakpointsBySourcePathOrUrl = new Map<string, BreakpointInfo[]>();\n\n\tconstructor(\n\t\tprivate readonly session: FirefoxDebugSession\n\t) {\n\t\tsession.breakpointLists.onRegistered(breakpointListActor => {\n\t\t\t[...this.breakpointsBySourcePathOrUrl.entries()].forEach(async ([sourcePath, breakpoints]) => {\n\t\t\t\tconst sourceAdapter = await session.sources.getAdapterForPath(sourcePath);\n\t\t\t\tif (sourceAdapter.url) {\n\t\t\t\t\tbreakpoints.forEach(async breakpointInfo => {\n\t\t\t\t\t\tconst actualLocation = await sourceAdapter.findNextBreakableLocation(\n\t\t\t\t\t\t\tbreakpointInfo.requestedBreakpoint.line,\n\t\t\t\t\t\t\t(breakpointInfo.requestedBreakpoint.column || 1) - 1\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (actualLocation) {\n\t\t\t\t\t\t\tbreakpointInfo.actualLocation = actualLocation;\n\n\t\t\t\t\t\t\tlet logValue: string | undefined;\n\t\t\t\t\t\t\tif (breakpointInfo.requestedBreakpoint.logMessage) {\n\t\t\t\t\t\t\t\tlogValue = '...' + convertLogpointMessage(breakpointInfo.requestedBreakpoint.logMessage);\n\t\t\t\t\t\t\t}\n\t\t\t\t\n\t\t\t\t\t\t\tconst location = actualLocation.generated ?? actualLocation;\n\t\t\t\t\t\t\tconst url = actualLocation.generated ? sourceAdapter.generatedUrl : sourceAdapter.url;\n\t\t\t\t\t\t\tif (url) {\n\t\t\t\t\t\t\t\tbreakpointListActor.setBreakpoint(\n\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\tlocation.line,\n\t\t\t\t\t\t\t\t\tlocation.column,\n\t\t\t\t\t\t\t\t\tbreakpointInfo.requestedBreakpoint.condition,\n\t\t\t\t\t\t\t\t\tlogValue\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\tif (!breakpointInfo.verified) {\n\t\t\t\t\t\t\t\tthis.verifyBreakpoint(breakpointInfo);\n\t\t\t\t\t\t\t}\t\n\t\t\t\t\t\t}\n\t\t\t\t\t});\n\t\t\t\t}\n\t\t\t});\n\t\t});\n\t}\n\n\t/**\n\t * called by [`FirefoxDebugAdapter#setBreakpoints()`](../firefoxDebugAdapter.ts) whenever the\n\t * breakpoints have been changed by the user in VS Code\n\t */\n\tpublic setBreakpoints(\n\t\tbreakpoints: DebugProtocol.SourceBreakpoint[],\n\t\tsourcePathOrUrl: string\n\t): BreakpointInfo[] {\n\n\t\tlog.debug(`Setting ${breakpoints.length} breakpoints for ${sourcePathOrUrl}`);\n\n\t\tconst normalizedPathOrUrl = normalizePath(sourcePathOrUrl);\n\t\tconst oldBreakpointInfos = this.breakpointsBySourcePathOrUrl.get(normalizedPathOrUrl);\n\t\tconst breakpointInfos = breakpoints.map(\n\t\t\tbreakpoint => this.getOrCreateBreakpointInfo(breakpoint, oldBreakpointInfos)\n\t\t);\n\n\t\tthis.breakpointsBySourcePathOrUrl.set(normalizedPathOrUrl, breakpointInfos);\n\n\t\tbreakpointInfos.forEach(async breakpointInfo => {\n\t\t\tif (!oldBreakpointInfos?.some(oldBreakpointInfo => oldBreakpointInfo === breakpointInfo)) {\n\t\t\t\tlet sourceAdapter = this.session.sources.getExistingAdapterForPath(normalizedPathOrUrl);\n\t\t\t\tif (!sourceAdapter) {\n\t\t\t\t\tthis.session.sendEvent(new Event('unknownSource', sourcePathOrUrl));\n\t\t\t\t\tsourceAdapter = await this.session.sources.getAdapterForPath(normalizedPathOrUrl);\n\t\t\t\t}\n\t\t\t\tif (sourceAdapter.url) {\n\t\t\t\t\tconst actualLocation = await sourceAdapter.findNextBreakableLocation(\n\t\t\t\t\t\tbreakpointInfo.requestedBreakpoint.line,\n\t\t\t\t\t\t(breakpointInfo.requestedBreakpoint.column || 1) - 1\n\t\t\t\t\t);\n\t\t\t\t\tif (actualLocation) {\n\t\t\t\t\t\tbreakpointInfo.actualLocation = actualLocation;\n\n\t\t\t\t\t\tlet logValue: string | undefined;\n\t\t\t\t\t\tif (breakpointInfo.requestedBreakpoint.logMessage) {\n\t\t\t\t\t\t\tlogValue = '...' + convertLogpointMessage(breakpointInfo.requestedBreakpoint.logMessage);\n\t\t\t\t\t\t}\n\t\t\t\n\t\t\t\t\t\tconst location = actualLocation.generated ?? actualLocation;\n\t\t\t\t\t\tconst url = actualLocation.generated ? sourceAdapter.generatedUrl : sourceAdapter.url;\n\t\t\t\t\t\tif (url) {\n\t\t\t\t\t\t\tfor (const [, breakpointListActor] of this.session.breakpointLists) {\n\t\t\t\t\t\t\t\tbreakpointListActor.setBreakpoint(\n\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\tlocation.line,\n\t\t\t\t\t\t\t\t\tlocation.column,\n\t\t\t\t\t\t\t\t\tbreakpointInfo.requestedBreakpoint.condition,\n\t\t\t\t\t\t\t\t\tlogValue\n\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t\tif (!breakpointInfo.verified) {\n\t\t\t\t\t\t\tthis.verifyBreakpoint(breakpointInfo);\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\tif (oldBreakpointInfos) {\n\t\t\toldBreakpointInfos.forEach(async oldBreakpointInfo => {\n\t\t\t\tif (!breakpointInfos.some(breakpointInfo => \n\t\t\t\t\tbreakpointInfo.requestedBreakpoint.line === oldBreakpointInfo.requestedBreakpoint.line &&\n\t\t\t\t\tbreakpointInfo.requestedBreakpoint.column === oldBreakpointInfo.requestedBreakpoint.column\n\t\t\t\t)) {\n\t\t\t\t\tconst sourceAdapter = await this.session.sources.getAdapterForPath(normalizedPathOrUrl);\n\t\t\t\t\tif (sourceAdapter.url) {\n\t\t\t\t\t\tconst actualLocation = await sourceAdapter.findNextBreakableLocation(\n\t\t\t\t\t\t\toldBreakpointInfo.requestedBreakpoint.line,\n\t\t\t\t\t\t\t(oldBreakpointInfo.requestedBreakpoint.column || 1) - 1\n\t\t\t\t\t\t);\n\t\t\t\t\t\tif (actualLocation) {\n\t\t\t\t\t\t\tconst location = actualLocation.generated ?? actualLocation;\n\t\t\t\t\t\t\tconst url = actualLocation.generated ? sourceAdapter.generatedUrl : sourceAdapter.url;\n\t\t\t\t\t\t\tif (url) {\n\t\t\t\t\t\t\t\tfor (const [, breakpointListActor] of this.session.breakpointLists) {\n\t\t\t\t\t\t\t\t\tbreakpointListActor.removeBreakpoint(\n\t\t\t\t\t\t\t\t\t\turl,\n\t\t\t\t\t\t\t\t\t\tlocation.line,\n\t\t\t\t\t\t\t\t\t\tlocation.column\n\t\t\t\t\t\t\t\t\t);\n\t\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t});\n\t\t}\n\n\t\treturn breakpointInfos;\n\t}\n\n\tpublic getBreakpoints(sourcePathOrUrl: string) {\n\t\treturn this.breakpointsBySourcePathOrUrl.get(normalizePath(sourcePathOrUrl));\n\t}\n\n\tprivate verifyBreakpoint(breakpointInfo: BreakpointInfo): void {\n\n\t\tif (!breakpointInfo.actualLocation) return;\n\n\t\tlet breakpoint: DebugProtocol.Breakpoint = new Breakpoint(\n\t\t\ttrue, breakpointInfo.actualLocation.line, breakpointInfo.actualLocation.column + 1);\n\t\tbreakpoint.id = breakpointInfo.id;\n\t\tthis.session.sendEvent(new BreakpointEvent('changed', breakpoint));\n\n\t\tbreakpointInfo.verified = true;\n\t}\n\n\tprivate getOrCreateBreakpointInfo(\n\t\trequestedBreakpoint: DebugProtocol.SourceBreakpoint,\n\t\toldBreakpointInfos: BreakpointInfo[] | undefined\n\t): BreakpointInfo {\n\n\t\tif (oldBreakpointInfos) {\n\n\t\t\tconst oldBreakpointInfo = oldBreakpointInfos.find(\n\t\t\t\tbreakpointInfo => breakpointInfo.isEquivalent(requestedBreakpoint)\n\t\t\t);\n\n\t\t\tif (oldBreakpointInfo) {\n\t\t\t\treturn oldBreakpointInfo;\n\t\t\t}\n\t\t}\n\n\t\treturn new BreakpointInfo(this.nextBreakpointId++, requestedBreakpoint);\n\t}\n}\n\n/**\n * convert the message of a logpoint (which can contain javascript expressions in curly braces)\n * to a javascript expression that evaluates to an array of values to be displayed in the debug console\n * (doesn't support escaping or nested curly braces)\n */\nexport function convertLogpointMessage(msg: string): string {\n\n\t// split `msg` into string literals and javascript expressions\n\tconst items: string[] = [];\n\tlet currentPos = 0;\n\twhile (true) {\n\n\t\tconst leftBrace = msg.indexOf('{', currentPos);\n\n\t\tif (leftBrace < 0) {\n\n\t\t\titems.push(JSON.stringify(msg.substring(currentPos)));\n\t\t\tbreak;\n\n\t\t} else {\n\n\t\t\tlet rightBrace = msg.indexOf('}', leftBrace + 1);\n\t\t\tif (rightBrace < 0) rightBrace = msg.length;\n\n\t\t\titems.push(JSON.stringify(msg.substring(currentPos, leftBrace)));\n\t\t\titems.push(msg.substring(leftBrace + 1, rightBrace));\n\t\t\tcurrentPos = rightBrace + 1;\n\t\t}\n\t}\n\n\t// the appended `reduce()` call will convert all non-object values to strings and concatenate consecutive strings\n\treturn `[${items.join(',')}].reduce((a,c)=>{if(typeof c==='object'&&c){a.push(c,'')}else{a.push(a.pop()+c)}return a},[''])`;\n}\n"
  },
  {
    "path": "src/adapter/adapter/consoleAPICall.ts",
    "content": "import { VariablesProvider } from './variablesProvider';\nimport { VariableAdapter } from './variable';\nimport { ThreadAdapter } from './thread';\n\n/**\n * Adapter class for representing a `consoleAPICall` event from Firefox.\n */\nexport class ConsoleAPICallAdapter implements VariablesProvider {\n\n\tpublic readonly variablesProviderId: number;\n\tpublic readonly referenceExpression = undefined;\n\tpublic readonly referenceFrame = undefined;\n\tprivate readonly argsAdapter: VariableAdapter;\n\n\tpublic constructor(\n\t\targs: VariableAdapter[],\n\t\tpreview: string,\n\t\tpublic readonly threadAdapter: ThreadAdapter\n\t) {\n\t\tthis.variablesProviderId = threadAdapter.debugSession.variablesProviders.register(this);\n\t\tthis.argsAdapter = VariableAdapter.fromArgumentList(args, preview, threadAdapter);\n\t}\n\n\tpublic getVariables(): Promise<VariableAdapter[]> {\n\t\treturn Promise.resolve(this.argsAdapter ? [this.argsAdapter] : []);\n\t}\n}\n\nexport class ArgumentListAdapter implements VariablesProvider {\n\n\tpublic readonly variablesProviderId: number;\n\tpublic readonly referenceExpression = undefined;\n\tpublic readonly referenceFrame = undefined;\n\n\tpublic constructor(\n\t\tprivate readonly args: VariableAdapter[],\n\t\tpublic readonly threadAdapter: ThreadAdapter\n\t) {\n\t\tthis.variablesProviderId = threadAdapter.debugSession.variablesProviders.register(this);\n\t}\n\n\tpublic getVariables(): Promise<VariableAdapter[]> {\n\t\treturn Promise.resolve(this.args);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/dataBreakpointsManager.ts",
    "content": "import { Log } from '../util/log';\nimport { VariablesProvider } from './variablesProvider';\nimport { Registry } from './registry';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { ObjectGripAdapter } from './objectGrip';\n\nconst log = Log.create('DataBreakpointsManager');\n\nexport class DataBreakpointsManager {\n\n\tprivate dataBreakpoints = new Set<string>();\n\n\tconstructor(\n\t\tprivate readonly variablesProviders: Registry<VariablesProvider>\n\t) {}\n\n\tpublic static encodeDataId(variablesProviderId: number, property: string): string {\n\t\treturn `${variablesProviderId}.${property}`;\n\t}\n\n\tpublic static decodeDataId(dataId: string): { variablesProviderId: number, property: string } {\n\n\t\tconst separatorIndex = dataId.indexOf('.');\n\n\t\treturn {\n\t\t\tvariablesProviderId: +dataId.substring(0, separatorIndex),\n\t\t\tproperty: dataId.substring(separatorIndex + 1)\n\t\t};\n\t}\n\n\tpublic async setDataBreakpoints(newDataBreakpoints: DebugProtocol.DataBreakpoint[]): Promise<void> {\n\n\t\tlog.debug(`Setting ${newDataBreakpoints.length} data breakpoints`);\n\n\t\tconst oldDataBreakpoints = new Set<string>(this.dataBreakpoints);\n\n\t\tfor (const dataBreakpoint of newDataBreakpoints) {\n\t\t\tif (!oldDataBreakpoints.has(dataBreakpoint.dataId)) {\n\n\t\t\t\tconst type = (dataBreakpoint.accessType === 'read') ? 'get' : 'set';\n\t\t\t\tawait this.addDataBreakpoint(dataBreakpoint.dataId, type);\n\n\t\t\t} else {\n\t\t\t\toldDataBreakpoints.delete(dataBreakpoint.dataId);\n\t\t\t}\n\t\t}\n\n\t\tfor (const dataBreakpoint of oldDataBreakpoints) {\n\t\t\tawait this.removeDataBreakpoint(dataBreakpoint);\n\t\t}\n\n\t\tthis.dataBreakpoints = new Set<string>(newDataBreakpoints.map(dataBreakpoint => dataBreakpoint.dataId));\n\t}\n\n\tprivate async addDataBreakpoint(dataId: string, type: 'get' | 'set'): Promise<void> {\n\n\t\tconst { variablesProviderId, property } = DataBreakpointsManager.decodeDataId(dataId);\n\t\tconst variablesProvider = this.variablesProviders.find(variablesProviderId);\n\n\t\tif (variablesProvider instanceof ObjectGripAdapter) {\n\n\t\t\tlog.debug(`Adding data breakpoint for property ${property} of object #${variablesProviderId}`);\n\n\t\t\tawait variablesProvider.actor.addWatchpoint(property, dataId, type);\n\n\t\t} else {\n\t\t\tlog.warn(`Couldn't find object #${variablesProviderId}`);\n\t\t}\n\t}\n\n\tprivate async removeDataBreakpoint(dataId: string): Promise<void> {\n\n\t\tconst { variablesProviderId, property } = DataBreakpointsManager.decodeDataId(dataId);\n\t\tconst variablesProvider = this.variablesProviders.find(variablesProviderId);\n\n\t\tif (variablesProvider instanceof ObjectGripAdapter) {\n\n\t\t\tlog.debug(`Removing data breakpoint for property ${property} of object #${variablesProviderId}`);\n\n\t\t\tawait variablesProvider.actor.removeWatchpoint(property);\n\n\t\t} else {\n\t\t\tlog.warn(`Couldn't find object #${variablesProviderId}`);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/descriptor.ts",
    "content": "import { Registry } from './registry';\nimport { DescriptorActorProxy } from '../firefox/actorProxy/descriptor';\nimport { BreakpointListActorProxy } from '../firefox/actorProxy/breakpointList';\nimport { WatcherActorProxy } from '../firefox/actorProxy/watcher';\nimport { ThreadConfigurationActorProxy } from '../firefox/actorProxy/threadConfiguration';\nimport { ThreadAdapter } from './thread';\n\nexport class DescriptorAdapter {\n\n\tpublic readonly id: number;\n\tprivate readonly configuratorId: number;\n\tprivate readonly breakpointListId: number;\n\tpublic readonly threads = new Set<ThreadAdapter>();\n\n\tpublic constructor(\n\t\tprivate readonly descriptorRegistry: Registry<DescriptorAdapter>,\n\t\tprivate readonly configurators: Registry<ThreadConfigurationActorProxy>,\n\t\tprivate readonly breakpointLists: Registry<BreakpointListActorProxy>,\n\t\tpublic readonly descriptorActor: DescriptorActorProxy,\n\t\tpublic readonly watcherActor: WatcherActorProxy,\n\t\tprivate readonly configurator: ThreadConfigurationActorProxy,\n\t\tprivate readonly breakpointList: BreakpointListActorProxy\n\t) {\n\t\tthis.id = descriptorRegistry.register(this);\n\t\tthis.configuratorId = configurators.register(configurator);\n\t\tthis.breakpointListId = breakpointLists.register(breakpointList);\n\t}\n\n\tpublic dispose() {\n\t\tfor (const thread of this.threads) {\n\t\t\tthread.dispose();\n\t\t}\n\t\tthis.descriptorRegistry.unregister(this.id);\n\t\tthis.configurators.unregister(this.configuratorId);\n\t\tthis.breakpointLists.unregister(this.breakpointListId);\n\t\tthis.descriptorActor.dispose();\n\t\tthis.configurator.dispose();\n\t\tthis.breakpointList.dispose();\n\t\tthis.watcherActor.dispose();\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/environment.ts",
    "content": "import { Log } from '../util/log';\nimport { ScopeAdapter, ObjectScopeAdapter, LocalVariablesScopeAdapter, FunctionScopeAdapter } from './scope';\nimport { FrameAdapter } from './frame';\n\nlet log = Log.create('EnvironmentAdapter');\n\n/**\n * Abstract adapter base class for a lexical environment.\n * Used to create [`ScopeAdapter`](./scope.ts)s which then create `Scope` objects for VS Code.\n */\nexport abstract class EnvironmentAdapter<T extends FirefoxDebugProtocol.Environment> {\n\n\tprotected environment: T;\n\tprotected parent?: EnvironmentAdapter<FirefoxDebugProtocol.Environment>;\n\n\tpublic constructor(environment: T) {\n\t\tthis.environment = environment;\n\t\tif (environment.parent !== undefined) {\n\t\t\tthis.parent = EnvironmentAdapter.from(environment.parent);\n\t\t}\n\t}\n\n\t/** factory function for creating an EnvironmentAdapter of the appropriate type */\n\tpublic static from(environment: FirefoxDebugProtocol.Environment): EnvironmentAdapter<FirefoxDebugProtocol.Environment> {\n\t\tswitch (environment.type) {\n\t\t\tcase 'object':\n\t\t\t\treturn new ObjectEnvironmentAdapter(<FirefoxDebugProtocol.ObjectEnvironment>environment);\n\t\t\tcase 'function':\n\t\t\t\treturn new FunctionEnvironmentAdapter(<FirefoxDebugProtocol.FunctionEnvironment>environment);\n\t\t\tcase 'with':\n\t\t\t\treturn new WithEnvironmentAdapter(<FirefoxDebugProtocol.WithEnvironment>environment);\n\t\t\tcase 'block':\n\t\t\t\treturn new BlockEnvironmentAdapter(<FirefoxDebugProtocol.BlockEnvironment>environment);\n\t\t\tdefault:\n\t\t\t\tthrow new Error(`Unknown environment type ${environment.type}`);\n\t\t}\n\t}\n\n\tpublic getScopeAdapters(frameAdapter: FrameAdapter): ScopeAdapter[] {\n\n\t\tlet scopes = this.getAllScopeAdapters(frameAdapter);\n\n\t\treturn scopes;\n\t}\n\n\tprotected getAllScopeAdapters(frameAdapter: FrameAdapter): ScopeAdapter[] {\n\n\t\tlet scopes: ScopeAdapter[];\n\n\t\tif (this.parent !== undefined) {\n\t\t\tscopes = this.parent.getAllScopeAdapters(frameAdapter);\n\t\t} else {\n\t\t\tscopes = [];\n\t\t}\n\n\t\tlet ownScope = this.getOwnScopeAdapter(frameAdapter);\n\t\tscopes.unshift(ownScope);\n\n\t\treturn scopes;\n\t}\n\n\tprotected abstract getOwnScopeAdapter(frameAdapter: FrameAdapter): ScopeAdapter;\n}\n\nexport class ObjectEnvironmentAdapter extends EnvironmentAdapter<FirefoxDebugProtocol.ObjectEnvironment> {\n\n\tpublic constructor(environment: FirefoxDebugProtocol.ObjectEnvironment) {\n\t\tsuper(environment);\n\t}\n\n\tprotected getOwnScopeAdapter(frameAdapter: FrameAdapter): ScopeAdapter {\n\n\t\tlet grip = this.environment.object;\n\n\t\tif ((typeof grip === 'boolean') || (typeof grip === 'number') || (typeof grip === 'string')) {\n\n\t\t\tthrow new Error(`Object environment with unexpected grip of type ${typeof grip}`);\n\n\t\t} else if (grip.type !== 'object') {\n\n\t\t\tthrow new Error(`Object environment with unexpected grip of type ${grip.type}`);\n\n\t\t} else {\n\n\t\t\tlet objectGrip = <FirefoxDebugProtocol.ObjectGrip>grip;\n\t\t\tlet name = `Object: ${objectGrip.class}`;\n\t\t\treturn new ObjectScopeAdapter(name, objectGrip, frameAdapter);\n\n\t\t}\n\t}\n}\n\nexport class FunctionEnvironmentAdapter extends EnvironmentAdapter<FirefoxDebugProtocol.FunctionEnvironment> {\n\n\tpublic constructor(environment: FirefoxDebugProtocol.FunctionEnvironment) {\n\t\tsuper(environment);\n\t}\n\n\tprotected getOwnScopeAdapter(frameAdapter: FrameAdapter): ScopeAdapter {\n\n\t\tlet funcName = this.environment.function.displayName;\n\t\tlet scopeName: string;\n\t\tif (funcName) {\n\t\t\tscopeName = `Local: ${funcName}`;\n\t\t} else {\n\t\t\tlog.error(`Unexpected function in function environment: ${JSON.stringify(this.environment.function)}`);\n\t\t\tscopeName = '[unknown]';\n\t\t}\n\n\t\treturn new FunctionScopeAdapter(scopeName, this.environment.bindings, frameAdapter);\n\t}\n}\n\nexport class WithEnvironmentAdapter extends EnvironmentAdapter<FirefoxDebugProtocol.WithEnvironment> {\n\n\tpublic constructor(environment: FirefoxDebugProtocol.WithEnvironment) {\n\t\tsuper(environment);\n\t}\n\n\tprotected getOwnScopeAdapter(frameAdapter: FrameAdapter): ScopeAdapter {\n\n\t\tlet grip = this.environment.object;\n\n\t\tif ((typeof grip === 'boolean') || (typeof grip === 'number') || (typeof grip === 'string')) {\n\n\t\t\tthrow new Error(`\"with\" environment with unexpected grip of type ${typeof grip}`);\n\n\t\t} else if (grip.type !== 'object') {\n\n\t\t\tthrow new Error(`\"with\" environment with unexpected grip of type ${grip.type}`);\n\n\t\t} else {\n\n\t\t\tlet objectGrip = <FirefoxDebugProtocol.ObjectGrip>grip;\n\t\t\tlet name = `With: ${objectGrip.class}`;\n\t\t\treturn new ObjectScopeAdapter(name, objectGrip, frameAdapter);\n\n\t\t}\n\t}\n}\n\nexport class BlockEnvironmentAdapter extends EnvironmentAdapter<FirefoxDebugProtocol.BlockEnvironment> {\n\n\tpublic constructor(environment: FirefoxDebugProtocol.BlockEnvironment) {\n\t\tsuper(environment);\n\t}\n\n\tprotected getOwnScopeAdapter(frameAdapter: FrameAdapter): ScopeAdapter {\n\n\t\treturn new LocalVariablesScopeAdapter('Block', this.environment.bindings.variables, frameAdapter);\n\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/eventBreakpointsManager.ts",
    "content": "import { AvailableEventCategory } from '../../common/customEvents';\nimport { FirefoxDebugSession } from '../firefoxDebugSession';\nimport { compareStrings } from '../util/misc';\n\nexport class EventBreakpointsManager {\n\n\tpublic readonly availableEvents: AvailableEventCategory[] = [];\n\n\tconstructor(\n\t\tprivate readonly session: FirefoxDebugSession\n\t) {\n\t\tsession.threads.onRegistered(async threadAdapter => {\n\t\t\tconst newAvailableEvents = await threadAdapter.actor.getAvailableEventBreakpoints();\n\t\t\tlet categoryWasAdded = false;\n\t\t\tfor (const newCategory of newAvailableEvents) {\n\t\t\t\tconst category = this.availableEvents.find(category => category.name === newCategory.name);\n\n\t\t\t\tif (!category) {\n\t\t\t\t\tthis.availableEvents.push({\n\t\t\t\t\t\tname: newCategory.name,\n\t\t\t\t\t\tevents: newCategory.events.map(newEvent => ({\n\t\t\t\t\t\t\tname: newEvent.name,\n\t\t\t\t\t\t\tid: newEvent.id,\n\t\t\t\t\t\t})),\n\t\t\t\t\t});\n\t\t\t\t\tcategoryWasAdded = true;\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\n\t\t\t\tlet eventWasAdded = false;\n\t\t\t\tfor (const newEvent of newCategory.events) {\n\t\t\t\t\tif (!category.events.find(event => event.id === newEvent.id)) {\n\t\t\t\t\t\tcategory.events.push({\n\t\t\t\t\t\t\tname: newEvent.name,\n\t\t\t\t\t\t\tid: newEvent.id,\n\t\t\t\t\t\t});\n\t\t\t\t\t\teventWasAdded = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (eventWasAdded) {\n\t\t\t\t\tcategory.events.sort((e1, e2) => compareStrings(e1.name, e2.name))\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (categoryWasAdded) {\n\t\t\t\tthis.availableEvents.sort((c1, c2) => compareStrings(c1.name, c2.name));\n\t\t\t}\n\n\t\t\tthis.session.sendCustomEvent('availableEvents', this.availableEvents);\n\t\t});\n\t}\n\n\tpublic async setActiveEventBreakpoints(ids: string[]) {\n\t\tawait Promise.all(this.session.breakpointLists.map(breakpointList => \n\t\t\tbreakpointList.setActiveEventBreakpoints(ids)\n\t\t));\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/frame.ts",
    "content": "import { Log } from '../util/log';\nimport { ThreadAdapter } from './thread';\nimport { EnvironmentAdapter } from './environment';\nimport { ScopeAdapter } from './scope';\nimport { StackFrame } from '@vscode/debugadapter';\nimport { Registry } from './registry';\nimport { FrameActorProxy } from '../firefox/actorProxy/frame';\n\nlet log = Log.create('FrameAdapter');\n\n/**\n * Adapter class for a stackframe.\n */\nexport class FrameAdapter {\n\n\tpublic readonly id: number;\n\tprivate _scopeAdapters?: ScopeAdapter[];\n\n\tpublic constructor(\n\t\tprivate readonly frameRegistry: Registry<FrameAdapter>,\n\t\tpublic readonly frame: FirefoxDebugProtocol.Frame,\n\t\tpublic readonly threadAdapter: ThreadAdapter\n\t) {\n\t\tthis.id = frameRegistry.register(this);\n\t}\n\n\tpublic async getStackframe(): Promise<StackFrame> {\n\n\t\tlet sourceActorName = this.frame.where.actor;\n\t\tlet sourceAdapter = await this.threadAdapter.debugSession.sources.getAdapterForActor(sourceActorName);\n\n\t\tlet name: string;\n\t\tswitch (this.frame.type) {\n\n\t\t\tcase 'call':\n\t\t\t\tconst callFrame = this.frame as FirefoxDebugProtocol.CallFrame;\n\t\t\t\tname = callFrame.displayName || '[anonymous function]';\n\t\t\t\tbreak;\n\n\t\t\tcase 'global':\n\t\t\t\tname = '[Global]';\n\t\t\t\tbreak;\n\n\t\t\tcase 'eval':\n\t\t\tcase 'clientEvaluate':\n\t\t\t\tname = '[eval]';\n\t\t\t\tbreak;\n\n\t\t\tcase 'wasmcall':\n\t\t\t\tname = '[wasm]';\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tname = `[${this.frame.type}]`;\n\t\t\t\tlog.error(`Unexpected frame type ${this.frame.type}`);\n\t\t\t\tbreak;\n\t\t}\n\n\t\treturn new StackFrame(this.id, name, sourceAdapter.source,\n\t\t\tthis.frame.where.line, (this.frame.where.column || 0) + 1);\n\t}\n\n\tpublic async getScopeAdapters(): Promise<ScopeAdapter[]> {\n\n\t\tif (!this._scopeAdapters) {\n\n\t\t\tconst frameActor = new FrameActorProxy(this.frame.actor, this.threadAdapter.debugSession.firefoxDebugConnection);\n\t\t\tconst environment = await frameActor.getEnvironment();\n\t\t\tframeActor.dispose();\n\n\t\t\tif (environment.type) {\n\t\t\t\tconst environmentAdapter = EnvironmentAdapter.from(environment);\n\t\t\t\tthis._scopeAdapters = environmentAdapter.getScopeAdapters(this);\n\t\t\t\tif (this.frame.this !== undefined) {\n\t\t\t\t\tthis._scopeAdapters[0].addThis(this.frame.this);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis._scopeAdapters = [];\n\t\t\t}\n\t\t}\n\n\t\treturn this._scopeAdapters;\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.frameRegistry.unregister(this.id);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/getterValue.ts",
    "content": "import { VariablesProvider } from './variablesProvider';\nimport { ThreadAdapter } from './thread';\nimport { FrameAdapter } from './frame';\nimport { VariableAdapter } from './variable';\n\n/**\n * Adapter class for an accessor property with a getter (i.e. a property defined using\n * [`Object.defineProperty()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/defineProperty)\n * with an accessor descriptor containing a `get` function or using the new ES6 syntax for defining\n * accessors).\n * The value of such a property can only be determined by executing the getter function, but that may\n * have side-effects. Therefore it is not executed and the corresponding [`VariableAdapter`](./variable.ts)\n * will show a text like \"Getter & Setter - expand to execute Getter\" to the user. When the user\n * clicks on this text, the getter will be executed by the `getVariables()` method and the value\n * will be displayed.\n * \n * Note that if the accessor property is not defined on the object itself but on one of its\n * prototypes, the user would have to navigate to the prototype to find it and if he then executed\n * the getter, it would be executed with `this` set to the prototype, which is usually not the\n * desired behavior. Therefore it is possible to \"lift\" accessor properties to an object from its\n * prototype chain using the `liftAccessorsFromPrototypes` configuration property.\n */\nexport class GetterValueAdapter implements VariablesProvider {\n\n\tpublic readonly variablesProviderId: number;\n\tpublic get threadAdapter(): ThreadAdapter {\n\t\treturn this.variableAdapter.threadAdapter;\n\t}\n\t/** a javascript expression that will execute the getter */\n\tpublic get referenceExpression(): string | undefined {\n\t\treturn this.variableAdapter.referenceExpression;\n\t}\n\t/** the stackframe to use when executing the `referenceExpression` */\n\tpublic get referenceFrame(): FrameAdapter | undefined {\n\t\treturn this.variableAdapter.referenceFrame;\n\t}\n\n\tpublic constructor(private readonly variableAdapter: VariableAdapter) {\n\t\tthis.variablesProviderId = this.threadAdapter.debugSession.variablesProviders.register(this);\n\t}\n\n\t/** execute the getter and return a VariableAdapter for the value returned by the getter */\n\tpublic async getVariables(): Promise<VariableAdapter[]> {\n\t\tif (this.referenceExpression && this.referenceFrame) {\n\n\t\t\tconst grip = await this.threadAdapter.evaluateRaw(\n\t\t\t\tthis.referenceExpression, true, this.referenceFrame.frame.actor\n\t\t\t);\n\n\t\t\tconst variableAdapter = VariableAdapter.fromGrip(\n\t\t\t\t'Value from Getter', this.referenceExpression, this.referenceFrame,\n\t\t\t\tgrip, false, this.threadAdapter, true\n\t\t\t);\n\n\t\t\treturn [ variableAdapter ];\n\n\t\t} else {\n\t\t\treturn [];\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/objectGrip.ts",
    "content": "import { VariablesProvider } from './variablesProvider';\nimport { VariableAdapter } from './variable';\nimport { FrameAdapter } from './frame';\nimport { ThreadAdapter } from './thread';\nimport { ObjectGripActorProxy } from '../firefox/actorProxy/objectGrip';\n\n/**\n * Adapter class for a javascript object.\n */\nexport class ObjectGripAdapter implements VariablesProvider {\n\n\tpublic readonly variablesProviderId: number;\n\tpublic readonly actor: ObjectGripActorProxy;\n\tpublic get threadAdapter(): ThreadAdapter {\n\t\treturn this.variableAdapter.threadAdapter;\n\t}\n\t/** a javascript expression for accessing the object represented by this adapter */\n\tpublic get referenceExpression(): string | undefined {\n\t\treturn this.variableAdapter.referenceExpression;\n\t}\n\t/** the stackframe to use when executing the `referenceExpression` */\n\tpublic get referenceFrame(): FrameAdapter | undefined {\n\t\treturn this.variableAdapter.referenceFrame;\n\t}\n\n\tpublic constructor(\n\t\tprivate readonly variableAdapter: VariableAdapter,\n\t\tobjectGrip: FirefoxDebugProtocol.ObjectGrip,\n\t\tpublic threadLifetime: boolean,\n\t\tprivate readonly isPrototype: boolean\n\t) {\n\t\tthis.actor = this.threadAdapter.debugSession.getOrCreateObjectGripActorProxy(objectGrip);\n\t\tthis.actor.increaseRefCount();\n\t\tthis.variablesProviderId = this.threadAdapter.debugSession.variablesProviders.register(this);\n\t\tthis.threadAdapter.registerObjectGripAdapter(this);\n\t}\n\n\t/**\n\t * get the referenced object's properties and its prototype as an array of Variables.\n\t * This method can only be called when the thread is paused.\n\t */\n\tpublic async getVariables(): Promise<VariableAdapter[]> {\n\n\t\tlet prototypeAndProperties = await this.actor.fetchPrototypeAndProperties();\n\n\t\tlet variables: VariableAdapter[] = [];\n\t\tlet symbolVariables: VariableAdapter[] = [];\n\t\tlet safeGetterValues = prototypeAndProperties.safeGetterValues || {};\n\t\tlet symbolProperties = prototypeAndProperties.ownSymbols || [];\n\n\t\tfor (let varname in prototypeAndProperties.ownProperties) {\n\t\t\tif (!safeGetterValues[varname]) {\n\t\t\t\tvariables.push(VariableAdapter.fromPropertyDescriptor(\n\t\t\t\t\tvarname, this.referenceExpression, this.referenceFrame,\n\t\t\t\t\tprototypeAndProperties.ownProperties[varname],\n\t\t\t\t\tthis.threadLifetime, this.threadAdapter));\n\t\t\t}\n\t\t}\n\n\t\tfor (let varname in safeGetterValues) {\n\t\t\tvariables.push(VariableAdapter.fromSafeGetterValueDescriptor(\n\t\t\t\tvarname, this.referenceExpression, this.referenceFrame,\n\t\t\t\tsafeGetterValues[varname],\n\t\t\t\tthis.threadLifetime, this.threadAdapter));\n\t\t}\n\n\t\tfor (let symbolProperty of symbolProperties) {\n\t\t\tsymbolVariables.push(VariableAdapter.fromPropertyDescriptor(\n\t\t\t\tsymbolProperty.name, undefined, undefined,\n\t\t\t\tsymbolProperty.descriptor, this.threadLifetime, this.threadAdapter));\n\t\t}\n\n\t\tlet prototypeVariable: VariableAdapter | undefined = undefined;\n\t\tlet accessorsFromPrototypes: VariableAdapter[] = [];\n\t\tif (prototypeAndProperties.prototype.type !== 'null') {\n\t\t\tprototypeVariable = VariableAdapter.fromGrip(\n\t\t\t\t'__proto__', this.referenceExpression, this.referenceFrame,\n\t\t\t\tprototypeAndProperties.prototype,\n\t\t\t\tthis.threadLifetime, this.threadAdapter\n\t\t\t);\n\n\t\t\tif (!this.isPrototype) {\n\t\t\t\tconst prototypeLevels = this.threadAdapter.debugSession.config.liftAccessorsFromPrototypes;\n\t\t\t\tif (prototypeLevels > 0) {\n\t\t\t\t\taccessorsFromPrototypes = await this.fetchAccessorsFromPrototypes(prototypeVariable, prototypeLevels);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\t/** Array-Objects are already sorted, sorting them again as strings messes up the order */\n\t\tlet isArray = (prototypeAndProperties.prototype.type == 'object' && prototypeAndProperties.prototype.class == 'Array');\n\t\tif (!isArray) {\n\t\t\tVariableAdapter.sortVariables(variables);\n\t\t}\n\t\tVariableAdapter.sortVariables(symbolVariables);\n\t\tVariableAdapter.sortVariables(accessorsFromPrototypes);\n\t\tvariables.push(...symbolVariables);\n\t\tvariables.push(...accessorsFromPrototypes);\n\n\t\tif (prototypeVariable) {\n\t\t\tvariables.push(prototypeVariable);\n\t\t}\n\n\t\treturn variables;\n\t}\n\n\t/**\n\t * used to \"lift\" accessor properties from the prototype chain to an object if the\n\t * `liftAccessorsFromPrototypes` configuration property is set.\n\t * Have a look at the [`GetterValueAdapter`](./getterValue.ts) for more info.\n\t */\n\tprivate async fetchAccessorsFromPrototypes(\n\t\tprototypeVariable: VariableAdapter,\n\t\tlevels: number\n\t): Promise<VariableAdapter[]> {\n\n\t\tlet objectGripAdapter: ObjectGripAdapter | undefined = <any>prototypeVariable.variablesProvider;\n\t\tlet variables: VariableAdapter[] = [];\n\t\tlet level = 0;\n\t\twhile ((level < levels) && objectGripAdapter) {\n\n\t\t\tlet prototypeAndProperties = await objectGripAdapter.actor.fetchPrototypeAndProperties();\n\n\t\t\tfor (const varname in prototypeAndProperties.ownProperties) {\n\n\t\t\t\tconst propertyDescriptor = prototypeAndProperties.ownProperties[varname];\n\t\t\t\tif ((varname !== '__proto__') && \n\t\t\t\t\t(<FirefoxDebugProtocol.AccessorPropertyDescriptor>propertyDescriptor).get) {\n\n\t\t\t\t\tvariables.push(VariableAdapter.fromPropertyDescriptor(\n\t\t\t\t\t\tvarname, this.referenceExpression, this.referenceFrame,\n\t\t\t\t\t\tpropertyDescriptor, this.threadLifetime, this.threadAdapter\n\t\t\t\t\t));\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tprototypeVariable = VariableAdapter.fromGrip(\n\t\t\t\t'__proto__', this.referenceExpression, this.referenceFrame,\n\t\t\t\tprototypeAndProperties.prototype,\n\t\t\t\tthis.threadLifetime, this.threadAdapter\n\t\t\t);\n\t\t\tobjectGripAdapter = <any>prototypeVariable.variablesProvider;\n\n\t\t\tlevel++;\n\t\t}\n\n\t\treturn variables;\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.actor.decreaseRefCount();\n\t\tthis.threadAdapter.debugSession.variablesProviders.unregister(this.variablesProviderId);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/preview.ts",
    "content": "import { Log } from '../util/log';\n\nconst log = Log.create('Preview');\n\nconst maxProperties = 5;\nconst maxArrayItems = 5;\nconst maxStringChars = 20;\nconst maxAttributes = 5;\nconst maxParameters = 5;\n\n/** generates the preview string for an object shown in the debugger's Variables view */\nexport function renderPreview(objectGrip: FirefoxDebugProtocol.ObjectGrip): string {\n\ttry {\n\n\t\tif ((objectGrip.class === 'Function') || \n\t\t\t(objectGrip as FirefoxDebugProtocol.FunctionGrip).parameterNames) {\n\n\t\t\tif (objectGrip.class !== 'Function') {\n\t\t\t\tlog.warn(`Looks like a FunctionGrip but has a different class: ${JSON.stringify(objectGrip)}`);\n\t\t\t}\n\t\t\treturn renderFunctionGrip(<FirefoxDebugProtocol.FunctionGrip>objectGrip);\n\t\t}\n\n\t\tconst preview = objectGrip.preview;\n\t\tif (!preview) {\n\t\t\treturn objectGrip.class;\n\t\t}\n\n\t\tif (preview.kind === 'Object') {\n\n\t\t\treturn renderObjectPreview(preview, objectGrip.class);\n\n\t\t} else if (preview.kind === 'ArrayLike') {\n\n\t\t\treturn renderArrayLikePreview(preview, objectGrip.class);\n\n\t\t} else if ((objectGrip.class === 'Date') && (preview.kind === undefined)) {\n\n\t\t\tconst date = new Date(preview.timestamp);\n\t\t\treturn date.toString();\n\n\t\t} else if (preview.kind === 'ObjectWithURL') {\n\n\t\t\treturn `${objectGrip.class} ${preview.url}`;\n\n\t\t} else if ((preview.kind === 'DOMNode') && (preview.nodeType === 1)) {\n\n\t\t\treturn renderDOMElementPreview(preview);\n\n\t\t} else if (preview.kind === 'Error') {\n\n\t\t\treturn `${objectGrip.class}: ${preview.message}`;\n\n\t\t} else {\n\n\t\t\treturn objectGrip.class;\n\n\t\t}\n\n\t} catch (e) {\n\t\tlog.error(`renderPreview failed for ${JSON.stringify(objectGrip)}: ${e}`);\n\t\treturn '';\n\t}\n}\n\nfunction renderObjectPreview(preview: FirefoxDebugProtocol.ObjectPreview, className: string): string {\n\n\tconst renderedProperties: string[] = [];\n\tlet i = 0;\n\tfor (const property in preview.ownProperties) {\n\n\t\tvar valueGrip = preview.ownProperties[property].value;\n\t\tif (!valueGrip) {\n\t\t\tcontinue;\n\t\t}\n\n\t\tconst renderedValue = renderGrip(valueGrip);\n\t\trenderedProperties.push(`${property}: ${renderedValue}`);\n\n\t\tif (++i >= maxProperties) {\n\t\t\trenderedProperties.push('\\u2026');\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (i < maxProperties && preview.ownSymbols) {\n\t\tfor (const symbolProperty of preview.ownSymbols) {\n\n\t\t\tconst renderedValue = renderGrip(symbolProperty.descriptor.value);\n\t\t\trenderedProperties.push(`Symbol(${symbolProperty.name}): ${renderedValue}`);\n\n\t\t\tif (++i >= maxProperties) {\n\t\t\t\trenderedProperties.push('\\u2026');\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\t}\n\n\tconst renderedObject = `{${renderedProperties.join(', ')}}`;\n\n\tif (className === 'Object') {\n\t\treturn renderedObject;\n\t} else {\n\t\treturn `${className} ${renderedObject}`;\n\t}\n}\n\nfunction renderDOMElementPreview(preview: FirefoxDebugProtocol.DOMNodePreview): string {\n\n\tif (!preview.attributes) {\n\t\treturn `<${preview.nodeName}>`;\n\t}\n\n\tconst renderedAttributes: string[] = [];\n\tlet i = 0;\n\tfor (const attribute in preview.attributes) {\n\n\t\tconst renderedValue = renderGrip(preview.attributes[attribute]);\n\t\trenderedAttributes.push(`${attribute}=${renderedValue}`);\n\n\t\tif (++i >= maxAttributes) {\n\t\t\trenderedAttributes.push('\\u2026');\n\t\t\tbreak;\n\t\t}\n\t}\n\n\tif (renderedAttributes.length === 0) {\n\t\treturn `<${preview.nodeName}>`;\n\t} else {\n\t\treturn `<${preview.nodeName} ${renderedAttributes.join(' ')}>`;\n\t}\n}\n\nfunction renderArrayLikePreview(preview: FirefoxDebugProtocol.ArrayLikePreview, className: string): string {\n\n\tlet result = `${className}(${preview.length})`;\n\n\tif (preview.items && preview.items.length > 0) {\n\n\t\tconst renderCount = Math.min(preview.items.length, maxArrayItems);\n\t\tconst itemsToRender = preview.items.slice(0, renderCount);\n\t\tconst renderedItems = itemsToRender.map(item => renderGripOrNull(item));\n\n\t\tif (renderCount < preview.items.length) {\n\t\t\trenderedItems.push('\\u2026');\n\t\t}\n\n\t\tresult += ` [${renderedItems.join(', ')}]`;\n\n\t}\n\n\treturn result;\n}\n\nfunction renderFunctionGrip(functionGrip: FirefoxDebugProtocol.FunctionGrip): string {\n\n\tlet parameters = '';\n\n\tif (functionGrip.parameterNames &&\n\t\tfunctionGrip.parameterNames.every(parameterName => typeof parameterName === 'string')) {\n\n\t\tlet parameterNames = functionGrip.parameterNames;\n\t\tif (parameterNames.length > maxParameters) {\n\t\t\tparameterNames = parameterNames.slice(0, maxParameters);\n\t\t\tparameterNames.push('\\u2026');\n\t\t}\n\n\t\tparameters = parameterNames.join(', ');\n\t}\n\n\tconst functionName = functionGrip.displayName || functionGrip.name || 'function';\n\treturn `${functionName}(${parameters}) {\\u2026}`;\n}\n\nfunction renderGripOrNull(gripOrNull: FirefoxDebugProtocol.Grip | null): string {\n\tif (gripOrNull === null) {\n\t\treturn \"_\";\n\t} else {\n\t\treturn renderGrip(gripOrNull);\n\t}\n}\n\nexport function renderGrip(grip: FirefoxDebugProtocol.Grip): string {\n\n\tif ((typeof grip === 'boolean') || (typeof grip === 'number')) {\n\n\t\treturn grip.toString();\n\n\t} else if (typeof grip === 'string') {\n\n\t\tif (grip.length > maxStringChars) {\n\t\t\treturn `\"${grip.substr(0, maxStringChars)}\\u2026\"`;\n\t\t} else {\n\t\t\treturn `\"${grip}\"`;\n\t\t}\n\n\t} else {\n\n\t\tswitch (grip.type) {\n\n\t\t\tcase 'null':\n\t\t\tcase 'undefined':\n\t\t\tcase 'Infinity':\n\t\t\tcase '-Infinity':\n\t\t\tcase 'NaN':\n\t\t\tcase '-0':\n\n\t\t\t\treturn grip.type;\n\n\t\t\tcase 'BigInt':\n\n\t\t\t\treturn `${(<FirefoxDebugProtocol.BigIntGrip>grip).text}n`;\n\n\t\t\tcase 'longString':\n\n\t\t\t\tconst initial = (<FirefoxDebugProtocol.LongStringGrip>grip).initial;\n\t\t\t\tif (initial.length > maxStringChars) {\n\t\t\t\t\treturn `${initial.substr(0, maxStringChars)}\\u2026`;\n\t\t\t\t} else {\n\t\t\t\t\treturn initial;\n\t\t\t\t}\n\t\t\n\t\t\tcase 'symbol':\n\n\t\t\t\tlet symbolName = (<FirefoxDebugProtocol.SymbolGrip>grip).name;\n\t\t\t\treturn `Symbol(${symbolName})`;\n\n\t\t\tcase 'object':\n\n\t\t\t\tlet objectGrip = <FirefoxDebugProtocol.ObjectGrip>grip;\n\t\t\t\treturn renderPreview(objectGrip);\n\n\t\t\tdefault:\n\n\t\t\t\tlog.warn(`Unexpected object grip of type ${grip.type}: ${JSON.stringify(grip)}`);\n\t\t\t\treturn '';\n\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/registry.ts",
    "content": "import { EventEmitter } from 'events';\n\n/**\n * A generic collection of objects identified by a numerical ID.\n * The ID is generated and returned when an object is added to the collection using `register()`.\n */\nexport class Registry<T> extends EventEmitter implements Iterable<[number, T]> {\n\n\tprivate objectsById = new Map<number, T>();\n\tprivate nextId = 1;\n\n\t/**\n\t * add an object to the registry and return the ID generated for it\n\t */\n\tpublic register(obj: T): number {\n\t\tlet id = this.nextId++;\n\t\tthis.objectsById.set(id, obj);\n\t\tthis.emit('registered', obj);\n\t\treturn id;\n\t}\n\n\tpublic unregister(id: number): boolean {\n\t\treturn this.objectsById.delete(id);\n\t}\n\n\tpublic has(id: number): boolean {\n\t\treturn this.objectsById.has(id);\n\t}\n\n\tpublic find(id: number): T | undefined {\n\t\treturn this.objectsById.get(id);\n\t}\n\n\tpublic get count() {\n\t\treturn this.objectsById.size;\n\t}\n\n\tpublic [Symbol.iterator](): Iterator<[number, T]> {\n\t\treturn this.objectsById[Symbol.iterator]();\n\t}\n\n\tpublic map<S>(f: (obj: T) => S): S[] {\n\t\tlet result: S[] = [];\n\t\tfor (let [, obj] of this.objectsById) {\n\t\t\tresult.push(f(obj));\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic filter(f: (obj: T) => boolean): T[] {\n\t\tlet result: T[] = [];\n\t\tfor (let [, obj] of this.objectsById) {\n\t\t\tif (f(obj)) {\n\t\t\t\tresult.push(obj);\n\t\t\t}\n\t\t}\n\t\treturn result;\n\t}\n\n\tpublic onRegistered(cb: (obj: T) => void) {\n\t\tthis.on('registered', cb);\n\t}\n}"
  },
  {
    "path": "src/adapter/adapter/scope.ts",
    "content": "import { ThreadAdapter } from './thread';\nimport { FrameAdapter } from './frame';\nimport { VariableAdapter } from './variable';\nimport { Scope } from '@vscode/debugadapter';\nimport { VariablesProvider } from './variablesProvider';\n\n/**\n * Abstract adapter base class for a javascript scope.\n */\nexport abstract class ScopeAdapter implements VariablesProvider {\n\n\tpublic readonly variablesProviderId: number;\n\tpublic readonly referenceExpression = '';\n\tpublic get threadAdapter(): ThreadAdapter {\n\t\treturn this.referenceFrame.threadAdapter;\n\t}\n\n\tpublic thisVariable?: VariableAdapter;\n\tpublic returnVariable?: VariableAdapter;\n\n\tprotected constructor(\n\t\tpublic readonly name: string,\n\t\tpublic readonly referenceFrame: FrameAdapter\n\t) {\n\t\tthis.threadAdapter.registerScopeAdapter(this);\n\t\tthis.variablesProviderId = this.threadAdapter.debugSession.variablesProviders.register(this);\n\t}\n\n\tpublic static fromGrip(name: string, grip: FirefoxDebugProtocol.Grip, referenceFrame: FrameAdapter): ScopeAdapter {\n\t\tif ((typeof grip === 'object') && (grip.type === 'object')) {\n\t\t\treturn new ObjectScopeAdapter(name, <FirefoxDebugProtocol.ObjectGrip>grip, referenceFrame);\n\t\t} else {\n\t\t\treturn new SingleValueScopeAdapter(name, grip, referenceFrame);\n\t\t}\n\t}\n\n\tpublic addThis(thisValue: FirefoxDebugProtocol.Grip) {\n\t\tthis.thisVariable = VariableAdapter.fromGrip(\n\t\t\t'this', this.referenceExpression, this.referenceFrame, thisValue, false, this.threadAdapter);\n\t}\n\n\tpublic addReturnValue(returnValue: FirefoxDebugProtocol.Grip) {\n\t\tthis.returnVariable = VariableAdapter.fromGrip(\n\t\t\t'Return value', undefined, this.referenceFrame, returnValue, false, this.threadAdapter);\n\t}\n\n\tpublic getScope(): Scope {\n\t\treturn new Scope(this.name, this.variablesProviderId);\n\t}\n\n\tpublic async getVariables(): Promise<VariableAdapter[]> {\n\n\t\t// we make a (shallow) copy of the variables array because we're going to modify it\n\t\tlet variables = [ ...await this.getVariablesInt() ];\n\n\t\tif (this.thisVariable) {\n\t\t\tvariables.unshift(this.thisVariable);\n\t\t}\n\n\t\tif (this.returnVariable) {\n\t\t\tvariables.unshift(this.returnVariable);\n\t\t}\n\n\t\treturn variables;\n\t}\n\n\tprotected abstract getVariablesInt(): Promise<VariableAdapter[]>;\n\n\tpublic dispose(): void {\n\t\tthis.threadAdapter.debugSession.variablesProviders.unregister(this.variablesProviderId);\n\t}\n}\n\nexport class SingleValueScopeAdapter extends ScopeAdapter {\n\n\tprivate variableAdapter: VariableAdapter;\n\n\tpublic constructor(name: string, grip: FirefoxDebugProtocol.Grip, referenceFrame: FrameAdapter) {\n\t\tsuper(name, referenceFrame);\n\t\tthis.variableAdapter = VariableAdapter.fromGrip(\n\t\t\t'', this.referenceExpression, this.referenceFrame, grip, false, this.threadAdapter);\n\t}\n\n\tprotected getVariablesInt(): Promise<VariableAdapter[]> {\n\t\treturn Promise.resolve([this.variableAdapter]);\n\t}\n}\n\nexport class ObjectScopeAdapter extends ScopeAdapter {\n\n\tprivate variableAdapter: VariableAdapter;\n\n\tpublic constructor(name: string, object: FirefoxDebugProtocol.ObjectGrip, referenceFrame: FrameAdapter) {\n\t\tsuper(name, referenceFrame);\n\t\tthis.variableAdapter = VariableAdapter.fromGrip(\n\t\t\t'', this.referenceExpression, this.referenceFrame, object, false, this.threadAdapter);\n\t}\n\n\tprotected getVariablesInt(): Promise<VariableAdapter[]> {\n\t\treturn this.variableAdapter.variablesProvider!.getVariables();\n\t}\n}\n\nexport class LocalVariablesScopeAdapter extends ScopeAdapter {\n\n\tpublic variables: VariableAdapter[] = [];\n\n\tpublic constructor(name: string, variableDescriptors: FirefoxDebugProtocol.PropertyDescriptors, referenceFrame: FrameAdapter) {\n\t\tsuper(name, referenceFrame);\n\n\t\tfor (let varname in variableDescriptors) {\n\t\t\tthis.variables.push(VariableAdapter.fromPropertyDescriptor(\n\t\t\t\tvarname, this.referenceExpression, this.referenceFrame,\n\t\t\t\tvariableDescriptors[varname], false, this.threadAdapter));\n\t\t}\n\n\t\tVariableAdapter.sortVariables(this.variables);\n\t}\n\n\tprotected getVariablesInt(): Promise<VariableAdapter[]> {\n\t\treturn Promise.resolve(this.variables);\n\t}\n}\n\nexport class FunctionScopeAdapter extends ScopeAdapter {\n\n\tpublic variables: VariableAdapter[] = [];\n\n\tpublic constructor(name: string, bindings: FirefoxDebugProtocol.FunctionBindings, referenceFrame: FrameAdapter) {\n\t\tsuper(name, referenceFrame);\n\n\t\tbindings.arguments.forEach((arg) => {\n\t\t\tfor (let varname in arg) {\n\t\t\t\tthis.variables.push(VariableAdapter.fromPropertyDescriptor(\n\t\t\t\t\tvarname, this.referenceExpression, this.referenceFrame,\n\t\t\t\t\targ[varname], false, this.threadAdapter));\n\t\t\t}\n\t\t});\n\n\t\tfor (let varname in bindings.variables) {\n\t\t\tthis.variables.push(VariableAdapter.fromPropertyDescriptor(\n\t\t\t\tvarname, this.referenceExpression, this.referenceFrame,\n\t\t\t\tbindings.variables[varname], false, this.threadAdapter));\n\t\t}\n\n\t\tVariableAdapter.sortVariables(this.variables);\n\t}\n\n\tprotected getVariablesInt(): Promise<VariableAdapter[]> {\n\t\treturn Promise.resolve(this.variables);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/skipFilesManager.ts",
    "content": "import isAbsoluteUrl from 'is-absolute-url';\nimport { Log } from '../util/log';\nimport { isWindowsPlatform as detectWindowsPlatform } from '../../common/util';\nimport { SourcesManager } from './sourcesManager';\nimport { Registry } from './registry';\nimport { ThreadAdapter } from './thread';\n\nlet log = Log.create('SkipFilesManager');\n\n/**\n * This class determines which files should be skipped (aka blackboxed). Files to be skipped are\n * configured using the `skipFiles` configuration property or by using the context menu on a\n * stackframe in VS Code.\n */\nexport class SkipFilesManager {\n\n\tprivate readonly isWindowsPlatform = detectWindowsPlatform();\n\n\t/**\n\t * Files that were configured to (not) be skipped by using the context menu on a\n\t * stackframe in VS Code. This overrides the `skipFiles` configuration property.\n\t */\n\tprivate readonly dynamicFiles = new Map<string, boolean>();\n\n\tpublic constructor(\n\t\tprivate readonly configuredFilesToSkip: RegExp[],\n\t\tprivate readonly sources: SourcesManager,\n\t\tprivate readonly threads: Registry<ThreadAdapter>\n\t) {}\n\n\tpublic shouldSkip(pathOrUrl: string): boolean {\n\n\t\tif (this.dynamicFiles.has(pathOrUrl)) {\n\n\t\t\tlet result = this.dynamicFiles.get(pathOrUrl)!;\n\n\t\t\tif (log.isDebugEnabled()) {\n\t\t\t\tlog.debug(`skipFile is set dynamically to ${result} for ${pathOrUrl}`);\n\t\t\t}\n\n\t\t\treturn result;\n\t\t}\n\n\t\tlet testee = pathOrUrl.replace('/./', '/');\n\t\tif (this.isWindowsPlatform && !isAbsoluteUrl(pathOrUrl)) {\n\t\t\ttestee = testee.replace(/\\\\/g, '/');\n\t\t}\n\t\tfor (let regExp of this.configuredFilesToSkip) {\n\n\t\t\tif (regExp.test(testee)) {\n\n\t\t\t\tif (log.isDebugEnabled()) {\n\t\t\t\t\tlog.debug(`skipFile is set per configuration to true for ${pathOrUrl}`);\n\t\t\t\t}\n\n\t\t\t\treturn true;\n\t\t\t}\n\t\t}\n\n\t\tif (log.isDebugEnabled()) {\n\t\t\tlog.debug(`skipFile is not set for ${pathOrUrl}`);\n\t\t}\n\n\t\treturn false;\n\t}\n\n\tpublic async toggleSkipping(pathOrUrl: string): Promise<void> {\n\t\t\n\t\tconst skipFile = !this.shouldSkip(pathOrUrl);\n\t\tthis.dynamicFiles.set(pathOrUrl, skipFile);\n\n\t\tlog.info(`Setting skipFile to ${skipFile} for ${pathOrUrl}`);\n\n\t\tlet promises: Promise<void>[] = [];\n\n\t\tconst sourceAdapters = this.sources.findSourceAdaptersForPathOrUrl(pathOrUrl);\n\n\t\tfor (const sourceAdapter of sourceAdapters) {\n\t\t\tpromises.push(sourceAdapter.setBlackBoxed(skipFile));\n\t\t}\n\n\t\tfor (const [, thread] of this.threads) {\n\t\t\tthread.triggerStackframeRefresh();\n\t\t}\n\n\t\tawait Promise.all(promises);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/source.ts",
    "content": "import { Log } from '../util/log';\nimport { MappedLocation } from '../location';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { Source } from '@vscode/debugadapter';\nimport { ISourceActorProxy } from '../firefox/actorProxy/source';\nimport { SourceMappingSourceActorProxy } from '../firefox/sourceMaps/source';\nimport { Registry } from './registry';\n\nconst log = Log.create('SourceAdapter');\n\n/**\n * Adapter class for a javascript source.\n */\nexport class SourceAdapter {\n\n\tpublic readonly url: string | undefined;\n\tpublic readonly generatedUrl: string | undefined;\n\tpublic readonly introductionType: 'scriptElement' | 'eval' | 'Function' | 'debugger eval' | 'wasm' | undefined;\n\tpublic readonly id: number;\n\tpublic readonly source: Source;\n\tpublic readonly actors: SourceActorCollection;\n\tprivate blackboxed = false;\n\n\tpublic get isBlackBoxed() { return this.blackboxed; }\n\n\tpublic constructor(\n\t\tactor: ISourceActorProxy,\n\t\t/** the path or url as seen by VS Code */\n\t\tpublic readonly path: string | undefined,\n\t\tsourceRegistry: Registry<SourceAdapter>\n\t) {\n\t\tthis.url = actor.url ?? undefined;\n\t\tif (actor instanceof SourceMappingSourceActorProxy) {\n\t\t\tthis.generatedUrl = actor.underlyingActor.url ?? undefined;\n\t\t}\n\t\tthis.introductionType = actor.source.introductionType ?? undefined;\n\t\tthis.id = sourceRegistry.register(this);\n\n\t\tlet sourceName = '';\n\t\tif (actor.url) {\n\t\t\tsourceName = actor.url.split('/').pop()!.split('#')[0];\n\t\t} else {\n\t\t\tsourceName = `${actor.source.introductionType || 'Script'} ${this.id}`;\n\t\t}\n\n\t\tif (path !== undefined) {\n\t\t\tthis.source = new Source(sourceName, path);\n\t\t} else {\n\t\t\tthis.source = new Source(sourceName, actor.url || undefined, this.id);\n\t\t}\n\n\t\tthis.actors = new SourceActorCollection(actor);\n\t}\n\n\tpublic async setBlackBoxed(blackboxed: boolean) {\n\t\tif (this.blackboxed === blackboxed) {\n\t\t\treturn;\n\t\t}\n\t\tthis.blackboxed = blackboxed;\n\t\t(<DebugProtocol.Source>this.source).presentationHint = blackboxed ? 'deemphasize' : 'normal';\n\t\tawait this.actors.runWithAllActors(actor => actor.setBlackbox(blackboxed));\n\t}\n\n\tpublic getBreakableLines(): Promise<number[]> {\n\t\treturn this.actors.runWithSomeActor(actor => actor.getBreakableLines());\n\t}\n\n\tpublic getBreakableLocations(line: number): Promise<MappedLocation[]> {\n\t\treturn this.actors.runWithSomeActor(actor => actor.getBreakableLocations(line));\n\t}\n\n\tpublic fetchSource(): Promise<FirefoxDebugProtocol.Grip> {\n\t\treturn this.actors.runWithSomeActor(actor => actor.fetchSource());\n\t}\n\n\tpublic async findNextBreakableLocation(\n\t\trequestedLine: number,\n\t\trequestedColumn: number\n\t): Promise<MappedLocation | undefined> {\n\n\t\tlet breakableLocations = await this.getBreakableLocations(requestedLine);\n\t\tfor (const location of breakableLocations) {\n\t\t\tif (location.column >= requestedColumn) {\n\t\t\t\treturn location;\n\t\t\t}\n\t\t}\n\n\t\tconst breakableLines = await this.getBreakableLines();\n\t\tfor (const line of breakableLines) {\n\t\t\tif (line > requestedLine) {\n\t\t\t\tbreakableLocations = await this.getBreakableLocations(line);\n\t\t\t\tif (breakableLocations.length > 0) {\n\t\t\t\t\treturn breakableLocations[0];\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n\nexport class SourceActorCollection {\n\n\tprivate readonly actors: ISourceActorProxy[];\n\tprivate someActor: Promise<ISourceActorProxy>;\n\tprivate resolveSomeActor: ((actor: ISourceActorProxy) => void) | undefined;\n\n\tpublic constructor(actor: ISourceActorProxy) {\n\t\tthis.actors = [actor];\n\t\tthis.someActor = Promise.resolve(actor);\n\t}\n\n\tpublic add(actor: ISourceActorProxy) {\n\t\tthis.actors.push(actor);\n\t\tif (this.resolveSomeActor) {\n\t\t\tthis.resolveSomeActor(actor);\n\t\t\tthis.resolveSomeActor = undefined;\n\t\t}\n\t}\n\n\tpublic remove(actor: ISourceActorProxy) {\n\t\tconst index = this.actors.indexOf(actor);\n\t\tif (index >= 0) {\n\t\t\tthis.actors.splice(index, 1);\n\t\t\tif (this.actors.length > 0) {\n\t\t\t\tthis.someActor = Promise.resolve(this.actors[0]);\n\t\t\t} else {\n\t\t\t\tthis.someActor = new Promise(resolve => this.resolveSomeActor = resolve);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async runWithSomeActor<T>(fn: (actor: ISourceActorProxy) => Promise<T>): Promise<T> {\n\t\twhile (true) {\n\t\t\tconst actor = await this.someActor;\n\t\t\ttry {\n\t\t\t\treturn await fn(actor);\n\t\t\t} catch (err: any) {\n\t\t\t\tif (err.error === 'noSuchActor') {\n\t\t\t\t\tthis.remove(actor);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t\tthrow err;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async runWithAllActors<T>(fn: (actor: ISourceActorProxy) => Promise<T>): Promise<void> {\n\t\tawait Promise.all(this.actors.map(actor => fn(actor)));\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/sourcesManager.ts",
    "content": "import { Log } from '../util/log';\nimport { ISourceActorProxy } from \"../firefox/actorProxy/source\";\nimport { DeferredMap } from \"../../common/deferredMap\";\nimport { pathsAreEqual } from \"../util/misc\";\nimport { PathMapper } from \"../util/pathMapper\";\nimport { Registry } from \"./registry\";\nimport { SourceAdapter } from './source';\nimport { normalizePath } from '../util/fs';\n\nconst log = Log.create('SourcesManager');\n\nexport class SourcesManager {\n\n\tprivate readonly adapters = new Registry<SourceAdapter>();\n\tprivate readonly adaptersByPath = new DeferredMap<string, SourceAdapter>();\n\tprivate readonly adaptersByActor = new DeferredMap<string, SourceAdapter>();\n\tprivate readonly adaptersByUrl = new DeferredMap<string, SourceAdapter>();\n\n\tconstructor(private readonly pathMapper: PathMapper) {}\n\n\tpublic addActor(actor: ISourceActorProxy) {\n\t\tlog.debug(`Adding source ${actor.name}`);\n\n\t\tlet adapter: SourceAdapter | undefined = actor.url ? this.adaptersByUrl.getExisting(actor.url) : undefined;\n\t\tconst path = this.pathMapper.convertFirefoxSourceToPath(actor.source);\n\t\tconst normalizedPath = path ? normalizePath(path) : undefined;\n\n\t\tif (adapter) {\n\t\t\tadapter.actors.add(actor);\n\t\t} else {\n\t\t\tif (normalizedPath) {\n\t\t\t\tadapter = this.adaptersByPath.getExisting(normalizedPath);\n\t\t\t}\n\t\t\tif (adapter) {\n\t\t\t\tadapter.actors.add(actor);\n\t\t\t} else {\n\t\t\t\tadapter = new SourceAdapter(actor, path, this.adapters);\n\t\t\t}\n\t\t}\n\n\t\tif (normalizedPath) {\n\t\t\tthis.adaptersByPath.set(normalizedPath, adapter);\n\t\t}\n\t\tthis.adaptersByActor.set(actor.name, adapter);\n\t\tif (actor.url) {\n\t\t\tthis.adaptersByUrl.set(actor.url, adapter);\n\t\t}\n\n\t\treturn adapter;\n\t}\n\n\tpublic removeActor(actor: ISourceActorProxy) {\n\t\tlog.info(`Removing source ${actor.name}`);\n\n\t\tconst adapter = this.adaptersByActor.getExisting(actor.name);\n\t\tif (!adapter) return;\n\n\t\tthis.adaptersByActor.delete(actor.name);\n\t\tadapter.actors.remove(actor);\n\t}\n\n\tpublic getAdapterForID(id: number) {\n\t\treturn this.adapters.find(id);\n\t}\n\n\tpublic getAdapterForPath(path: string) {\n\t\treturn this.adaptersByPath.get(path);\n\t}\n\n\tpublic getExistingAdapterForPath(path: string) {\n\t\treturn this.adaptersByPath.getExisting(path);\n\t}\n\n\tpublic getAdapterForActor(actor: string) {\n\t\treturn this.adaptersByActor.get(actor);\n\t}\n\n\tpublic getAdapterForUrl(url: string) {\n\t\treturn this.adaptersByUrl.get(url);\n\t}\n\n\tpublic findSourceAdaptersForPathOrUrl(pathOrUrl: string): SourceAdapter[] {\n\t\tif (!pathOrUrl) return [];\n\n\t\treturn this.adapters.filter((sourceAdapter) =>\n\t\t\tpathsAreEqual(pathOrUrl, sourceAdapter.path) || (sourceAdapter.url === pathOrUrl)\n\t\t);\n\t}\n\n\tpublic findSourceAdaptersForUrlWithoutQuery(url: string): SourceAdapter[] {\n\n\t\treturn this.adapters.filter((sourceAdapter) => {\n\n\t\t\tlet sourceUrl = sourceAdapter.url;\n\t\t\tif (!sourceUrl) return false;\n\n\t\t\tlet queryStringIndex = sourceUrl.indexOf('?');\n\t\t\tif (queryStringIndex >= 0) {\n\t\t\t\tsourceUrl = sourceUrl.substr(0, queryStringIndex);\n\t\t\t}\n\n\t\t\treturn url === sourceUrl;\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/thread.ts",
    "content": "import { EventEmitter } from 'events';\nimport { IThreadActorProxy } from '../firefox/actorProxy/thread';\nimport { ConsoleActorProxy } from '../firefox/actorProxy/console';\nimport { FrameAdapter } from './frame';\nimport { ScopeAdapter } from './scope';\nimport { ObjectGripAdapter } from './objectGrip';\nimport { VariablesProvider } from './variablesProvider';\nimport { VariableAdapter } from './variable';\nimport { Variable } from '@vscode/debugadapter';\nimport { Log } from '../util/log';\nimport { FirefoxDebugSession } from '../firefoxDebugSession';\nimport { TargetActorProxy } from '../firefox/actorProxy/target';\nimport { ISourceActorProxy } from '../firefox/actorProxy/source';\n\nlet log = Log.create('ThreadAdapter');\n\nexport type TargetType = 'tab' | 'iframe' | 'worker' | 'backgroundScript' | 'contentScript';\n\n/**\n * Adapter class for a thread\n */\nexport class ThreadAdapter extends EventEmitter {\n\n\tpublic id: number;\n\tpublic get actorName() {\n\t\treturn this.actor.name;\n\t}\n\tpublic get url(): string | undefined {\n\t\treturn this.targetActor.target.url;\n\t}\n\n\t/**\n\t * All `SourceAdapter`s for this thread. They will be disposed when this `ThreadAdapter` is disposed.\n\t */\n\tpublic readonly sourceActors = new Set<ISourceActorProxy>();\n\n\t/**\n\t * When the thread is paused, this is set to a Promise that resolves to the `FrameAdapter`s for\n\t * the stacktrace for the current thread pause. At the end of the thread pause, these are disposed.\n\t */\n\tprivate framesPromise: Promise<FrameAdapter[]> | undefined = undefined;\n\n\t/**\n\t * All `ScopeAdapter`s that have been created for the current thread pause. They will be disposed\n\t * at the end of the thread pause.\n\t */\n\tprivate scopes: ScopeAdapter[] = [];\n\n\t/**\n\t * All `ObjectGripAdapter`s that should be disposed at the end of the current thread pause\n\t */\n\tprivate pauseLifetimeObjects: ObjectGripAdapter[] = [];\n\n\t/**\n\t * All `ObjectGripAdapter`s that should be disposed when this `ThreadAdapter` is disposed\n\t */\n\tprivate threadLifetimeObjects: ObjectGripAdapter[] = [];\n\n\tpublic threadPausedReason?: FirefoxDebugProtocol.ThreadPausedReason;\n\n\tpublic constructor(\n\t\tpublic readonly type: TargetType,\n\t\tpublic readonly name: string,\n\t\tpublic readonly actor: IThreadActorProxy,\n\t\tpublic readonly targetActor: TargetActorProxy,\n\t\tprivate readonly consoleActor: ConsoleActorProxy,\n\t\tpublic readonly debugSession: FirefoxDebugSession\n\t) {\n\t\tsuper();\n\t\tthis.id = debugSession.threads.register(this);\n\t}\n\n\tpublic registerScopeAdapter(scopeAdapter: ScopeAdapter) {\n\t\tthis.scopes.push(scopeAdapter);\n\t}\n\n\tpublic registerObjectGripAdapter(objectGripAdapter: ObjectGripAdapter) {\n\t\tif (objectGripAdapter.threadLifetime) {\n\t\t\tthis.threadLifetimeObjects.push(objectGripAdapter);\n\t\t} else {\n\t\t\tthis.pauseLifetimeObjects.push(objectGripAdapter);\n\t\t}\n\t}\n\n\t/**\n\t * extend the given adapter's lifetime to threadLifetime (if it isn't already)\n\t */\n\tpublic threadLifetime(objectGripAdapter: ObjectGripAdapter): void {\n\n\t\tif (!objectGripAdapter.threadLifetime) {\n\n\t\t\tconst index = this.pauseLifetimeObjects.indexOf(objectGripAdapter);\n\t\t\tif (index >= 0) {\n\t\t\t\tthis.pauseLifetimeObjects.splice(index, 1);\n\t\t\t}\n\n\t\t\tthis.threadLifetimeObjects.push(objectGripAdapter);\n\t\t\tobjectGripAdapter.threadLifetime = true;\n\t\t}\n\t}\n\n\tpublic interrupt(): Promise<void> {\n\t\treturn this.actor.interrupt();\n\t}\n\n\tpublic resume(): Promise<void> {\n\t\treturn this.actor.resume();\n\t}\n\n\tpublic stepOver(): Promise<void> {\n\t\treturn this.actor.resume('next');\n\t}\n\n\tpublic stepIn(): Promise<void> {\n\t\treturn this.actor.resume('step');\n\t}\n\n\tpublic stepOut(): Promise<void> {\n\t\treturn this.actor.resume('finish');\n\t}\n\n\tpublic restartFrame(frameActor: string): Promise<void> {\n\t\treturn this.actor.resume('restart', frameActor);\n\t}\n\n\tpublic fetchAllStackFrames(): Promise<FrameAdapter[]> {\n\n\t\tif (!this.framesPromise) {\n\t\t\tthis.framesPromise = (async () => {\n\n\t\t\t\tlet frames = await this.actor.fetchStackFrames();\n\n\t\t\t\tlet frameAdapters = frames.map((frame) =>\n\t\t\t\t\tnew FrameAdapter(this.debugSession.frames, frame, this));\n\n\t\t\t\tlet threadPausedReason = this.threadPausedReason;\n\t\t\t\tif ((threadPausedReason !== undefined) && (frameAdapters.length > 0)) {\n\n\t\t\t\t\tconst scopeAdapters = await frameAdapters[0].getScopeAdapters();\n\n\t\t\t\t\tif (threadPausedReason.frameFinished !== undefined) {\n\n\t\t\t\t\t\tif (threadPausedReason.frameFinished.return !== undefined) {\n\n\t\t\t\t\t\t\tscopeAdapters[0].addReturnValue(\n\t\t\t\t\t\t\t\tthreadPausedReason.frameFinished.return);\n\n\t\t\t\t\t\t} else if (threadPausedReason.frameFinished.throw !== undefined) {\n\n\t\t\t\t\t\t\tscopeAdapters.unshift(ScopeAdapter.fromGrip(\n\t\t\t\t\t\t\t\t'Exception', threadPausedReason.frameFinished.throw, frameAdapters[0]));\n\t\t\t\t\t\t}\n\n\t\t\t\t\t} else if (threadPausedReason.exception !== undefined) {\n\n\t\t\t\t\t\tscopeAdapters.unshift(ScopeAdapter.fromGrip(\n\t\t\t\t\t\t\t'Exception', threadPausedReason.exception, frameAdapters[0]));\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\treturn frameAdapters;\n\t\t\t})();\n\t\t}\n\n\t\treturn this.framesPromise;\n\t}\n\n\tpublic async fetchStackFrames(start: number, count: number): Promise<[FrameAdapter[], number]> {\n\n\t\tlet frameAdapters = await this.fetchAllStackFrames();\n\n\t\tlet requestedFrames = (count > 0) ? frameAdapters.slice(start, start + count) : frameAdapters.slice(start);\n\n\t\treturn [requestedFrames, frameAdapters.length];\n\t}\n\n\t/** this will cause VS Code to reload the current stackframes from this adapter */\n\tpublic triggerStackframeRefresh(): void {\n\t\tthis.debugSession.sendStoppedEvent(this, this.threadPausedReason);\n\t}\n\n\tpublic async fetchVariables(variablesProvider: VariablesProvider): Promise<Variable[]> {\n\n\t\tlet variableAdapters = await variablesProvider.getVariables();\n\n\t\treturn variableAdapters.map((variableAdapter) => variableAdapter.getVariable());\n\t}\n\n\tpublic evaluateRaw(expr: string, skipBreakpoints: boolean, frameActorName?: string): Promise<FirefoxDebugProtocol.Grip> {\n\t\treturn this.consoleActor.evaluate(expr, skipBreakpoints, frameActorName);\n\t}\n\n\tpublic async evaluate(expr: string, skipBreakpoints: boolean, frameActorName?: string): Promise<Variable> {\n\t\tlet grip = await this.consoleActor.evaluate(expr, skipBreakpoints, frameActorName);\n\t\tlet variableAdapter = this.variableFromGrip(grip, true);\n\t\treturn variableAdapter.getVariable();\n\t}\n\n\tpublic async autoComplete(text: string, column: number, frameActorName?: string): Promise<string[]> {\n\t\treturn await this.consoleActor.autoComplete(text, column, frameActorName);\n\t}\n\n\tprivate variableFromGrip(grip: FirefoxDebugProtocol.Grip | undefined, threadLifetime: boolean): VariableAdapter {\n\t\tif (grip !== undefined) {\n\t\t\treturn VariableAdapter.fromGrip('', undefined, undefined, grip, threadLifetime, this);\n\t\t} else {\n\t\t\treturn new VariableAdapter('', undefined, undefined, 'undefined', this);\n\t\t}\n\t}\n\n\t/**\n\t * Called by the `ThreadCoordinator` before resuming the thread\n\t */\n\tpublic async disposePauseLifetimeAdapters(): Promise<void> {\n\n\t\tif (this.framesPromise) {\n\t\t\tlet frames = await this.framesPromise;\n\t\t\tframes.forEach((frameAdapter) => {\n\t\t\t\tframeAdapter.dispose();\n\t\t\t});\n\t\t\tthis.framesPromise = undefined;\n\t\t}\n\n\t\tthis.scopes.forEach((scopeAdapter) => {\n\t\t\tscopeAdapter.dispose();\n\t\t});\n\t\tthis.scopes = [];\n\n\t\tthis.pauseLifetimeObjects.forEach((objectGripAdapter) => {\n\t\t\tobjectGripAdapter.dispose();\n\t\t});\n\n\t\tthis.pauseLifetimeObjects = [];\n\t}\n\n\tpublic async dispose(): Promise<void> {\n\n\t\tawait this.disposePauseLifetimeAdapters();\n\n\t\tthis.threadLifetimeObjects.forEach((objectGripAdapter) => {\n\t\t\tobjectGripAdapter.dispose();\n\t\t});\n\n\t\tfor (const sourceActor of this.sourceActors) {\n\t\t\tthis.debugSession.sources.removeActor(sourceActor);\n\t\t\tsourceActor.dispose();\n\t\t}\n\n\t\tthis.actor.dispose();\n\t\tthis.targetActor.dispose();\n\t\tthis.consoleActor.dispose();\n\n\t\tthis.debugSession.threads.unregister(this.id);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/variable.ts",
    "content": "import { Log } from '../util/log';\nimport { ThreadAdapter } from './thread';\nimport { ObjectGripAdapter } from './objectGrip';\nimport { FrameAdapter } from './frame';\nimport { Variable } from '@vscode/debugadapter';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { accessorExpression, compareStrings } from '../util/misc';\nimport { renderPreview } from './preview';\nimport { VariablesProvider } from './variablesProvider';\nimport { GetterValueAdapter } from './getterValue';\nimport { ArgumentListAdapter } from './consoleAPICall';\n\nlet log = Log.create('VariableAdapter');\n\n/**\n * Adapter class for anything that will be sent to VS Code as a Variable.\n * At the very least a Variable needs a name and a string representation of its value.\n * If the VariableAdapter represents anything that can have child variables, it also needs a\n * [`VariablesProvider`](./variablesProvider.ts) that will be used to fetch the child variables when\n * requested by VS Code.\n */\nexport class VariableAdapter {\n\n\tprivate _variablesProvider: VariablesProvider | undefined;\n\n\tpublic get variablesProvider(): VariablesProvider | undefined {\n\t\treturn this._variablesProvider;\n\t}\n\n\tpublic constructor(\n\t\tpublic readonly varname: string,\n\t\tpublic readonly referenceExpression: string | undefined,\n\t\tpublic readonly referenceFrame: FrameAdapter | undefined,\n\t\tpublic readonly displayValue: string,\n\t\tpublic readonly threadAdapter: ThreadAdapter\n\t) {}\n\n\tpublic getVariable(): Variable {\n\n\t\tlet variable = new Variable(this.varname, this.displayValue,\n\t\t\tthis.variablesProvider ? this.variablesProvider.variablesProviderId : undefined);\n\n\t\t(<DebugProtocol.Variable>variable).evaluateName = this.referenceExpression;\n\n\t\treturn variable;\n\t}\n\n\t/**\n\t * factory function for creating a VariableAdapter from an\n\t * [object grip](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#objects)\n\t */\n\tpublic static fromGrip(\n\t\tvarname: string,\n\t\tparentReferenceExpression: string | undefined,\n\t\treferenceFrame: FrameAdapter | undefined,\n\t\tgrip: FirefoxDebugProtocol.Grip,\n\t\tthreadLifetime: boolean,\n\t\tthreadAdapter: ThreadAdapter,\n\t\tuseParentReferenceExpression?: boolean\n\t): VariableAdapter {\n\n\t\tlet referenceExpression =\n\t\t\tuseParentReferenceExpression ?\n\t\t\tparentReferenceExpression :\n\t\t\taccessorExpression(parentReferenceExpression, varname);\n\n\t\tif ((typeof grip === 'boolean') || (typeof grip === 'number')) {\n\n\t\t\treturn new VariableAdapter(varname, referenceExpression, referenceFrame, grip.toString(), threadAdapter);\n\n\t\t} else if (typeof grip === 'string') {\n\n\t\t\treturn new VariableAdapter(varname, referenceExpression, referenceFrame, `\"${grip}\"`, threadAdapter);\n\n\t\t} else {\n\n\t\t\tswitch (grip.type) {\n\n\t\t\t\tcase 'null':\n\t\t\t\tcase 'undefined':\n\t\t\t\tcase 'Infinity':\n\t\t\t\tcase '-Infinity':\n\t\t\t\tcase 'NaN':\n\t\t\t\tcase '-0':\n\n\t\t\t\t\treturn new VariableAdapter(\n\t\t\t\t\t\tvarname, referenceExpression, referenceFrame, grip.type, threadAdapter);\n\n\t\t\t\tcase 'BigInt':\n\n\t\t\t\t\treturn new VariableAdapter(\n\t\t\t\t\t\tvarname, referenceExpression, referenceFrame,\n\t\t\t\t\t\t`${(<FirefoxDebugProtocol.BigIntGrip>grip).text}n`, threadAdapter);\n\n\t\t\t\tcase 'longString':\n\n\t\t\t\t\treturn new VariableAdapter(\n\t\t\t\t\t\tvarname, referenceExpression, referenceFrame,\n\t\t\t\t\t\t(<FirefoxDebugProtocol.LongStringGrip>grip).initial, threadAdapter);\n\n\t\t\t\tcase 'symbol':\n\n\t\t\t\t\tlet symbolName = (<FirefoxDebugProtocol.SymbolGrip>grip).name;\n\t\t\t\t\treturn new VariableAdapter(\n\t\t\t\t\t\tvarname, referenceExpression, referenceFrame,\n\t\t\t\t\t\t`Symbol(${symbolName})`, threadAdapter);\n\n\t\t\t\tcase 'object':\n\n\t\t\t\t\tlet objectGrip = <FirefoxDebugProtocol.ObjectGrip>grip;\n\t\t\t\t\tlet displayValue = renderPreview(objectGrip);\n\t\t\t\t\tlet variableAdapter = new VariableAdapter(\n\t\t\t\t\t\tvarname, referenceExpression, referenceFrame, displayValue, threadAdapter);\n\t\t\t\t\tvariableAdapter._variablesProvider = new ObjectGripAdapter(\n\t\t\t\t\t\tvariableAdapter, objectGrip, threadLifetime, (varname === '__proto__'));\n\t\t\t\t\treturn variableAdapter;\n\n\t\t\t\tdefault:\n\n\t\t\t\t\tlog.warn(`Unexpected object grip of type ${grip.type}: ${JSON.stringify(grip)}`);\n\t\t\t\t\treturn new VariableAdapter(\n\t\t\t\t\t\tvarname, referenceExpression, referenceFrame, grip.type, threadAdapter);\n\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * factory function for creating a VariableAdapter from a\n\t * [property descriptor](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#property-descriptors)\n\t */\n\tpublic static fromPropertyDescriptor(\n\t\tvarname: string,\n\t\tparentReferenceExpression: string | undefined,\n\t\treferenceFrame: FrameAdapter | undefined,\n\t\tpropertyDescriptor: FirefoxDebugProtocol.PropertyDescriptor,\n\t\tthreadLifetime: boolean,\n\t\tthreadAdapter: ThreadAdapter\n\t): VariableAdapter {\n\n\t\tif ((<FirefoxDebugProtocol.DataPropertyDescriptor>propertyDescriptor).value !== undefined) {\n\n\t\t\treturn VariableAdapter.fromGrip(\n\t\t\t\tvarname, parentReferenceExpression, referenceFrame,\n\t\t\t\t(<FirefoxDebugProtocol.DataPropertyDescriptor>propertyDescriptor).value,\n\t\t\t\tthreadLifetime, threadAdapter);\n\n\t\t} else {\n\n\t\t\tlet referenceExpression = accessorExpression(parentReferenceExpression, varname);\n\n\t\t\tlet accessorPropertyDescriptor = <FirefoxDebugProtocol.AccessorPropertyDescriptor>propertyDescriptor;\n\t\t\tlet hasGetter = VariableAdapter.isFunctionGrip(accessorPropertyDescriptor.get);\n\t\t\tlet hasSetter = VariableAdapter.isFunctionGrip(accessorPropertyDescriptor.set);\n\t\t\tlet displayValue: string;\n\t\t\tif (hasGetter) {\n\t\t\t\tdisplayValue = 'Getter';\n\t\t\t\tif (hasSetter) {\n\t\t\t\t\tdisplayValue += ' & Setter';\n\t\t\t\t}\n\t\t\t\tdisplayValue += ' - expand to execute Getter';\n\t\t\t} else if (hasSetter) {\n\t\t\t\tdisplayValue = 'Setter';\n\t\t\t} else {\n\t\t\t\tlog.error(`${referenceExpression} is neither a data property nor does it have a getter or setter`);\n\t\t\t\tdisplayValue = 'Error';\n\t\t\t}\n\n\t\t\tlet variableAdapter = new VariableAdapter(\n\t\t\t\tvarname, referenceExpression, referenceFrame, displayValue, threadAdapter);\n\n\t\t\tif (hasGetter) {\n\t\t\t\tvariableAdapter._variablesProvider = new GetterValueAdapter(variableAdapter);\n\t\t\t}\n\n\t\t\treturn variableAdapter;\n\n\t\t}\n\t}\n\n\t/**\n\t * factory function for creating a VariableAdapter from a\n\t * [safe getter value descriptor](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#property-descriptors)\n\t */\n\tpublic static fromSafeGetterValueDescriptor(\n\t\tvarname: string,\n\t\tparentReferenceExpression: string | undefined,\n\t\treferenceFrame: FrameAdapter | undefined,\n\t\tsafeGetterValueDescriptor: FirefoxDebugProtocol.SafeGetterValueDescriptor,\n\t\tthreadLifetime: boolean,\n\t\tthreadAdapter: ThreadAdapter\n\t): VariableAdapter {\n\n\t\treturn VariableAdapter.fromGrip(\n\t\t\tvarname, parentReferenceExpression, referenceFrame,\n\t\t\tsafeGetterValueDescriptor.getterValue, threadLifetime, threadAdapter);\n\t}\n\n\tpublic static fromArgumentList(\n\t\targs: VariableAdapter[],\n\t\tpreview: string,\n\t\tthreadAdapter: ThreadAdapter\n\t): VariableAdapter {\n\t\tconst variableAdapter = new VariableAdapter('arguments', undefined, undefined, preview, threadAdapter);\n\t\tvariableAdapter._variablesProvider = new ArgumentListAdapter(args, threadAdapter);\n\t\treturn variableAdapter;\n\t}\n\n\tpublic static sortVariables(variables: VariableAdapter[]): void {\n\t\tvariables.sort((var1, var2) => compareStrings(var1.varname, var2.varname));\n\t}\n\n\tprivate static isFunctionGrip(grip: FirefoxDebugProtocol.Grip) {\n\t\treturn (\n\t\t\t(typeof grip === 'object') &&\n\t\t\t(grip.type === 'object') &&\n\t\t\t((<FirefoxDebugProtocol.ObjectGrip>grip).class === 'Function')\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/adapter/variablesProvider.ts",
    "content": "import { ThreadAdapter } from './thread';\nimport { VariableAdapter } from './variable';\nimport { FrameAdapter } from './frame';\n\n/**\n * This interface must be implemented by any adapter that can provide child variables.\n */\nexport interface VariablesProvider {\n\treadonly variablesProviderId: number;\n\treadonly threadAdapter: ThreadAdapter;\n\treadonly referenceFrame: FrameAdapter | undefined;\n\treadonly referenceExpression: string | undefined;\n\tgetVariables(): Promise<VariableAdapter[]>;\n}\n"
  },
  {
    "path": "src/adapter/configuration.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as uuid from 'uuid';\nimport isAbsoluteUrl from 'is-absolute-url';\nimport RegExpEscape from 'escape-string-regexp';\nimport { Log } from './util/log';\nimport { findAddonId, normalizePath } from './util/misc';\nimport { isExecutable } from './util/fs';\nimport { Minimatch } from 'minimatch';\nimport FirefoxProfile from 'firefox-profile';\nimport { isWindowsPlatform } from '../common/util';\nimport { LaunchConfiguration, AttachConfiguration, CommonConfiguration, ReloadConfiguration, DetailedReloadConfiguration, TabFilterConfiguration } from '../common/configuration';\nimport { urlDirname } from './util/net';\n\nlet log = Log.create('ParseConfiguration');\n\nexport interface NormalizedReloadConfiguration {\n\twatch: string[];\n\tignore: string[];\n\tdebounce: number;\n}\n\nexport interface ParsedTabFilterConfiguration {\n\tinclude: RegExp[];\n\texclude: RegExp[];\n}\n\nexport interface ParsedConfiguration {\n\tattach?: ParsedAttachConfiguration;\n\tlaunch?: ParsedLaunchConfiguration;\n\taddon?: ParsedAddonConfiguration;\n\tpathMappings: PathMappings;\n\tpathMappingIndex: string;\n\tfilesToSkip: RegExp[];\n\treloadOnChange?: NormalizedReloadConfiguration,\n\ttabFilter: ParsedTabFilterConfiguration,\n\tclearConsoleOnReload: boolean,\n\tshowConsoleCallLocation: boolean;\n\tliftAccessorsFromPrototypes: number;\n\tsuggestPathMappingWizard: boolean;\n\tterminate: boolean;\n\tenableCRAWorkaround: boolean;\n}\n\nexport interface ParsedAttachConfiguration {\n\thost: string;\n\tport: number;\n\turl?: string;\n\tfirefoxExecutable?: string;\n\tprofileDir?: string;\n\treloadTabs: boolean;\n}\n\nexport interface FirefoxPreferences {\n\t[key: string]: boolean | number | string;\n}\n\ntype PathMapping = { url: string | RegExp, path: string | null };\nexport type PathMappings = PathMapping[];\n\nexport interface ParsedLaunchConfiguration {\n\tfirefoxExecutable: string;\n\tfirefoxArgs: string[];\n\tprofileDir: string;\n\tsrcProfileDir?: string;\n\tpreferences: FirefoxPreferences;\n\ttmpDirs: string[];\n\tport: number;\n\ttimeout: number;\n\tdetached: boolean;\n}\n\nexport interface ParsedAddonConfiguration {\n\tpath: string;\n\tid: string | undefined;\n\tpopupAutohideButton: boolean;\n}\n\n/**\n * Reads the configuration that was provided by VS Code, checks that it's consistent,\n * adds default values and returns it in a form that is easier to work with\n */\nexport async function parseConfiguration(\n\tconfig: LaunchConfiguration | AttachConfiguration\n): Promise<ParsedConfiguration> {\n\n\tlet attach: ParsedAttachConfiguration | undefined = undefined;\n\tlet launch: ParsedLaunchConfiguration | undefined = undefined;\n\tlet addon: ParsedAddonConfiguration | undefined = undefined;\n\tlet port = config.port || 6000;\n\tlet timeout = 5;\n\tlet pathMappings: PathMappings = [];\n\tlet url: string | undefined = undefined;\n\n\tif (config.request === 'launch') {\n\n\t\tlet tmpDirs: string[] = [];\n\n\t\tif (config.reAttach) {\n\t\t\tattach = {\n\t\t\t\thost: 'localhost', port,\n\t\t\t\treloadTabs: (config.reloadOnAttach !== false)\n\t\t\t};\n\t\t}\n\n\t\tlet firefoxExecutable = await findFirefoxExecutable(config.firefoxExecutable);\n\n\t\tlet firefoxArgs: string[] = [ '-start-debugger-server', String(port), '-no-remote' ];\n\t\tif (config.firefoxArgs) {\n\t\t\tfirefoxArgs.push(...config.firefoxArgs);\n\t\t}\n\n\t\tlet { profileDir, srcProfileDir } = await parseProfileConfiguration(config, tmpDirs);\n\n\t\tfirefoxArgs.push('-profile', profileDir);\n\n\t\tlet preferences = createFirefoxPreferences(config.preferences);\n\n\t\tif (config.file) {\n\t\t\tif (!path.isAbsolute(config.file)) {\n\t\t\t\tthrow 'The \"file\" property in the launch configuration has to be an absolute path';\n\t\t\t}\n\n\t\t\tlet fileUrl = config.file;\n\t\t\tif (isWindowsPlatform()) {\n\t\t\t\tfileUrl = 'file:///' + fileUrl.replace(/\\\\/g, '/');\n\t\t\t} else {\n\t\t\t\tfileUrl = 'file://' + fileUrl;\n\t\t\t}\n\t\t\tfirefoxArgs.push(fileUrl);\n\t\t\turl = fileUrl;\n\n\t\t} else if (config.url) {\n\t\t\tfirefoxArgs.push(config.url);\n\t\t\turl = config.url;\n\t\t} else if (config.addonPath) {\n\t\t\tfirefoxArgs.push('about:blank');\n\t\t} else {\n\t\t\tthrow 'You need to set either \"file\" or \"url\" in the launch configuration';\n\t\t}\n\n\t\tif (typeof config.timeout === 'number') {\n\t\t\ttimeout = config.timeout;\n\t\t}\n\n\t\tlet detached = true;\n\t\tif (os.platform() === 'darwin') {\n\t\t\tif (!config.reAttach) {\n\t\t\t\tdetached = false;\n\t\t\t\tif (config.keepProfileChanges) {\n\t\t\t\t\t// on MacOS we need to terminate the browser using `process.kill()`, which may\n\t\t\t\t\t// be interpreted by Firefox as a crash, so we add this option to prevent\n\t\t\t\t\t// starting into safe mode\n\t\t\t\t\tpreferences['toolkit.startup.max_resumed_crashes'] = -1;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlaunch = {\n\t\t\tfirefoxExecutable, firefoxArgs, profileDir, srcProfileDir,\n\t\t\tpreferences, tmpDirs, port, timeout, detached\n\t\t};\n\n\t} else { // config.request === 'attach'\n\n\t\tconst firefoxExecutable = config.firefoxExecutable ? await findFirefoxExecutable(config.firefoxExecutable) : undefined;\n\n\t\turl = config.url;\n\t\tattach = {\n\t\t\thost: config.host || 'localhost', port, url, firefoxExecutable, profileDir: config.profileDir,\n\t\t\treloadTabs: !!config.reloadOnAttach\n\t\t};\n\t}\n\n\tif (config.pathMappings) {\n\t\tpathMappings.push(...config.pathMappings.map(harmonizeTrailingSlashes).map(handleWildcards));\n\t}\n\n\tif (config.addonPath) {\n\t\taddon = await parseAddonConfiguration(config, pathMappings);\n\t}\n\n\tconst webRoot = parseWebRootConfiguration(config, pathMappings);\n\n\tif (webRoot) {\n\t\tpathMappings.push({ url: 'webpack:///~/', path: webRoot + '/node_modules/' });\n\t\tpathMappings.push({ url: 'webpack:///./~/', path: webRoot + '/node_modules/' });\n\t\tpathMappings.push({ url: 'webpack:///./', path: webRoot + '/' });\n\t\tpathMappings.push({ url: 'webpack:///src/', path: webRoot + '/src/' });\n\t\tpathMappings.push({ url: 'webpack:///node_modules/', path: webRoot + '/node_modules/' });\n\t\tpathMappings.push({ url: 'webpack:///webpack', path: null });\n\t\tpathMappings.push({ url: 'webpack:///(webpack)', path: null });\n\t\tpathMappings.push({ url: 'webpack:///pages/', path: webRoot + '/pages/' });\n\t\tpathMappings.push({ url: 'webpack://[name]_[chunkhash]/node_modules/', path: webRoot + '/node_modules/' });\n\t\tpathMappings.push({ url: 'webpack://[name]_[chunkhash]/', path: null });\n\t}\n\tpathMappings.push({ url: (isWindowsPlatform() ? 'webpack:///' : 'webpack://'), path: '' });\n\n\tpathMappings.push({ url: (isWindowsPlatform() ? 'file:///' : 'file://'), path: ''});\n\n\tconst pathMappingIndex = config.pathMappingIndex ?? 'index.html';\n\n\tlet filesToSkip = parseSkipFilesConfiguration(config);\n\n\tlet reloadOnChange = parseReloadConfiguration(config.reloadOnChange);\n\n\tconst tabFilter = parseTabFilterConfiguration(config.tabFilter, url);\n\n\tconst clearConsoleOnReload = !!config.clearConsoleOnReload;\n\n\tlet showConsoleCallLocation = config.showConsoleCallLocation || false;\n\tlet liftAccessorsFromPrototypes = config.liftAccessorsFromPrototypes || 0;\n\tlet suggestPathMappingWizard = config.suggestPathMappingWizard;\n\tif (suggestPathMappingWizard === undefined) {\n\t\tsuggestPathMappingWizard = true;\n\t}\n\tconst terminate = (config.request === 'launch') && !config.reAttach;\n\tconst enableCRAWorkaround = !!config.enableCRAWorkaround;\n\n\treturn {\n\t\tattach, launch, addon, pathMappings, pathMappingIndex, filesToSkip, reloadOnChange, tabFilter, clearConsoleOnReload,\n\t\tshowConsoleCallLocation, liftAccessorsFromPrototypes, suggestPathMappingWizard, terminate,\n\t\tenableCRAWorkaround\n\t};\n}\n\nfunction harmonizeTrailingSlashes(pathMapping: PathMapping): PathMapping {\n\n\tif ((typeof pathMapping.url === 'string') && (typeof pathMapping.path === 'string') &&\n\t\t(pathMapping.path.length > 0)) {\n\n\t\tif (pathMapping.url.endsWith('/')) {\n\t\t\tif (pathMapping.path.endsWith('/')) {\n\t\t\t\treturn pathMapping;\n\t\t\t} else {\n\t\t\t\treturn { url: pathMapping.url, path: pathMapping.path + '/' };\n\t\t\t}\n\t\t} else {\n\t\t\tif (pathMapping.path.endsWith('/')) {\n\t\t\t\treturn { url: pathMapping.url + '/', path: pathMapping.path };\n\t\t\t} else {\n\t\t\t\treturn pathMapping;\n\t\t\t}\n\t\t}\n\n\t} else {\n\t\treturn pathMapping;\n\t}\n}\n\nfunction handleWildcards(pathMapping: PathMapping): PathMapping {\n\n\tif ((typeof pathMapping.url === 'string') && (pathMapping.url.indexOf('*') >= 0)) {\n\n\t\tconst regexp = '^' + pathMapping.url.split('*').map(RegExpEscape).join('[^/]*') + '(.*)$';\n\n\t\treturn {\n\t\t\turl: new RegExp(regexp),\n\t\t\tpath: pathMapping.path\n\t\t};\n\n\t} else {\n\t\treturn pathMapping;\n\t}\n}\n\nasync function findFirefoxExecutable(configuredPath?: string): Promise<string> {\n\n\tlet candidates: string[];\n\tif (configuredPath) {\n\t\tif ([ 'stable', 'developer', 'nightly' ].indexOf(configuredPath) >= 0) {\n\t\t\tcandidates = getExecutableCandidates(configuredPath as any);\n\t\t} else if (await isExecutable(configuredPath)) {\n\t\t\treturn configuredPath;\n\t\t} else {\n\t\t\tthrow 'Couldn\\'t find the Firefox executable. Please correct the path given in your launch configuration.';\n\t\t}\n\t} else {\n\t\tcandidates = getExecutableCandidates();\n\t}\n\n\tfor (let i = 0; i < candidates.length; i++) {\n\t\tif (await isExecutable(candidates[i])) {\n\t\t\treturn candidates[i];\n\t\t}\n\t}\n\n\tthrow 'Couldn\\'t find the Firefox executable. Please specify the path by setting \"firefoxExecutable\" in your launch configuration.';\n}\n\nexport function getExecutableCandidates(edition?: 'stable' | 'developer' | 'nightly'): string[] {\n\n\tif (edition === undefined) {\n\t\treturn [ ...getExecutableCandidates('developer'), ...getExecutableCandidates('stable') ];\n\t}\n\n\tconst platform = os.platform();\n\n\tif ([ 'linux', 'freebsd', 'sunos' ].indexOf(platform) >= 0) {\n\t\tconst paths = process.env.PATH!.split(':');\n\t\tswitch (edition) {\n\n\t\t\tcase 'stable':\n\t\t\t\treturn [\n\t\t\t\t\t...paths.map(dir => path.join(dir, 'firefox'))\n\t\t\t\t];\n\n\t\t\tcase 'developer':\n\t\t\t\treturn [\n\t\t\t\t\t...paths.map(dir => path.join(dir, 'firefox-developer-edition')),\n\t\t\t\t\t...paths.map(dir => path.join(dir, 'firefox-developer')),\n\t\t\t\t];\n\n\t\t\tcase 'nightly':\n\t\t\t\treturn [\n\t\t\t\t\t...paths.map(dir => path.join(dir, 'firefox-nightly')),\n\t\t\t\t];\n\t\t}\n\t}\n\n\tswitch (edition) {\n\n\t\tcase 'stable':\n\t\t\tif (platform === 'darwin') {\n\t\t\t\treturn [ '/Applications/Firefox.app/Contents/MacOS/firefox' ];\n\t\t\t} else if (platform === 'win32') {\n\t\t\t\treturn [\n\t\t\t\t\t'C:\\\\Program Files\\\\Mozilla Firefox\\\\firefox.exe',\n\t\t\t\t\t'C:\\\\Program Files (x86)\\\\Mozilla Firefox\\\\firefox.exe'\n\t\t\t\t];\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'developer':\n\t\t\tif (platform === 'darwin') {\n\t\t\t\treturn [\n\t\t\t\t\t'/Applications/Firefox Developer Edition.app/Contents/MacOS/firefox',\n\t\t\t\t\t'/Applications/FirefoxDeveloperEdition.app/Contents/MacOS/firefox'\n\t\t\t\t];\n\t\t\t} else if (platform === 'win32') {\n\t\t\t\treturn [\n\t\t\t\t\t'C:\\\\Program Files\\\\Firefox Developer Edition\\\\firefox.exe',\n\t\t\t\t\t'C:\\\\Program Files (x86)\\\\Firefox Developer Edition\\\\firefox.exe'\n\t\t\t\t];\n\t\t\t}\n\t\t\tbreak;\n\n\t\tcase 'nightly':\n\t\t\tif (platform === 'darwin') {\n\t\t\t\treturn [ '/Applications/Firefox Nightly.app/Contents/MacOS/firefox' ]\n\t\t\t} else if (platform === 'win32') {\n\t\t\t\treturn [\n\t\t\t\t\t'C:\\\\Program Files\\\\Firefox Nightly\\\\firefox.exe',\n\t\t\t\t\t'C:\\\\Program Files (x86)\\\\Firefox Nightly\\\\firefox.exe'\n\t\t\t\t];\n\t\t\t}\n\t\t\tbreak;\n\t}\n\n\treturn [];\n}\n\nasync function parseProfileConfiguration(config: LaunchConfiguration, tmpDirs: string[])\n: Promise<{ profileDir: string, srcProfileDir?: string }> {\n\n\tlet profileDir: string;\n\tlet srcProfileDir: string | undefined;\n\n\tif (config.profileDir) {\n\t\tif (config.profile) {\n\t\t\tthrow 'You can set either \"profile\" or \"profileDir\", but not both';\n\t\t}\n\t\tsrcProfileDir = config.profileDir;\n\t} else if (config.profile) {\n\t\tsrcProfileDir = await findFirefoxProfileDir(config.profile);\n\t}\n\n\tif (config.keepProfileChanges) {\n\t\tif (srcProfileDir) {\n\t\t\tprofileDir = srcProfileDir;\n\t\t\tsrcProfileDir = undefined;\n\t\t} else {\n\t\t\tthrow 'To enable \"keepProfileChanges\" you need to set either \"profile\" or \"profileDir\"';\n\t\t}\n\t} else {\n\t\tconst tmpDir = config.tmpDir || os.tmpdir();\n\t\tprofileDir = path.join(tmpDir, `vscode-firefox-debug-profile-${uuid.v4()}`);\n\t\ttmpDirs.push(profileDir);\n\t}\n\n\treturn { profileDir, srcProfileDir };\n}\n\nfunction findFirefoxProfileDir(profileName: string): Promise<string | undefined> {\n\treturn new Promise<string | undefined>((resolve, reject) => {\n\n\t\tlet finder = new FirefoxProfile.Finder();\n\n\t\tfinder.getPath(profileName, (err, path) => {\n\t\t\tif (err) {\n\t\t\t\treject(err);\n\t\t\t} else {\n\t\t\t\tresolve(path);\n\t\t\t}\n\t\t});\n\t});\n}\n\nfunction createFirefoxPreferences(\n\tadditionalPreferences?: { [key: string]: boolean | number | string | null }\n): FirefoxPreferences {\n\n\tlet preferences: FirefoxPreferences = {};\n\n\t// Remote debugging settings\n\tpreferences['devtools.chrome.enabled'] = true;\n\tpreferences['devtools.debugger.prompt-connection'] = false;\n\tpreferences['devtools.debugger.remote-enabled'] = true;\n\tpreferences['extensions.autoDisableScopes'] = 10;\n\tpreferences['xpinstall.signatures.required'] = false;\n\tpreferences['extensions.sdk.console.logLevel'] = 'all';\n\t// Skip check for default browser on startup\n\tpreferences['browser.shell.checkDefaultBrowser'] = false;\n\t// Hide the telemetry infobar\n\tpreferences['datareporting.policy.dataSubmissionPolicyBypassNotification'] = true;\n\t// Do not redirect user when a milestone upgrade of Firefox is detected\n\tpreferences['browser.startup.homepage_override.mstone'] = 'ignore';\n\t// Disable the UI tour\n\tpreferences['browser.uitour.enabled'] = false;\n\t// Do not warn on quitting Firefox\n\tpreferences['browser.warnOnQuit'] = false;\n\n\tif (additionalPreferences !== undefined) {\n\t\tfor (let key in additionalPreferences) {\n\t\t\tlet value = additionalPreferences[key];\n\t\t\tif (value !== null) {\n\t\t\t\tpreferences[key] = value;\n\t\t\t} else {\n\t\t\t\tdelete preferences[key];\n\t\t\t}\n\t\t}\n\t}\n\n\treturn preferences;\n}\n\nfunction parseWebRootConfiguration(config: CommonConfiguration, pathMappings: PathMappings): string | undefined {\n\n\tif (config.url) {\n\t\tif (!config.webRoot) {\n\t\t\tif ((config.request === 'launch') && !config.pathMappings) {\n\t\t\t\tthrow `If you set \"url\" you also have to set \"webRoot\" or \"pathMappings\" in the ${config.request} configuration`;\n\t\t\t}\n\t\t\treturn undefined;\n\t\t} else if (!path.isAbsolute(config.webRoot) && !isAbsoluteUrl(config.webRoot)) {\n\t\t\tthrow `The \"webRoot\" property in the ${config.request} configuration has to be an absolute path`;\n\t\t}\n\n\t\tlet webRootUrl = config.url;\n\t\tif (webRootUrl.lastIndexOf('/') > 7) {\n\t\t\twebRootUrl = webRootUrl.substr(0, webRootUrl.lastIndexOf('/'));\n\t\t}\n\n\t\tlet webRoot = isAbsoluteUrl(config.webRoot) ? config.webRoot : normalizePath(config.webRoot);\n\n\t\tpathMappings.forEach((pathMapping) => {\n\t\t\tconst to = pathMapping.path;\n\t\t\tif ((typeof to === 'string') && (to.substr(0, 10) === '${webRoot}')) {\n\t\t\t\tpathMapping.path = webRoot + to.substr(10);\n\t\t\t}\n\t\t});\n\n\t\tpathMappings.push({ url: webRootUrl, path: webRoot });\n\n\t\treturn webRoot;\n\n\t} else if (config.webRoot) {\n\t\tthrow `If you set \"webRoot\" you also have to set \"url\" in the ${config.request} configuration`;\n\t}\n\n\treturn undefined;\n}\n\nfunction parseSkipFilesConfiguration(config: CommonConfiguration): RegExp[] {\n\n\tlet filesToSkip: RegExp[] = [];\n\n\tif (config.skipFiles) {\n\t\tconfig.skipFiles.forEach((glob) => {\n\n\t\t\tlet minimatch = new Minimatch(glob);\n\t\t\tlet regExp = minimatch.makeRe();\n\n\t\t\tif (regExp) {\n\t\t\t\tfilesToSkip.push(regExp);\n\t\t\t} else {\n\t\t\t\tlog.warn(`Invalid glob pattern \"${glob}\" specified in \"skipFiles\"`);\n\t\t\t}\n\t\t})\n\t}\n\n\treturn filesToSkip;\n}\n\nfunction parseReloadConfiguration(\n\treloadConfig: ReloadConfiguration | undefined\n): NormalizedReloadConfiguration | undefined {\n\n\tif (reloadConfig === undefined) {\n\t\treturn undefined;\n\t}\n\n\tconst defaultDebounce = 100;\n\n\tif (typeof reloadConfig === 'string') {\n\n\t\treturn {\n\t\t\twatch: [ normalizePath(reloadConfig) ],\n\t\t\tignore: [],\n\t\t\tdebounce: defaultDebounce\n\t\t};\n\n\t} else if (Array.isArray(reloadConfig)) {\n\n\t\treturn {\n\t\t\twatch: reloadConfig.map(path => normalizePath(path)),\n\t\t\tignore: [],\n\t\t\tdebounce: defaultDebounce\n\t\t};\n\n\t} else {\n\n\t\tlet _config = <DetailedReloadConfiguration>reloadConfig;\n\n\t\tlet watch: string[];\n\t\tif (typeof _config.watch === 'string') {\n\t\t\twatch = [ _config.watch ];\n\t\t} else {\n\t\t\twatch = _config.watch;\n\t\t}\n\n\t\twatch = watch.map((path) => normalizePath(path));\n\n\t\tlet ignore: string[];\n\t\tif (_config.ignore === undefined) {\n\t\t\tignore = [];\n\t\t} else if (typeof _config.ignore === 'string') {\n\t\t\tignore = [ _config.ignore ];\n\t\t} else {\n\t\t\tignore = _config.ignore;\n\t\t}\n\n\t\tignore = ignore.map((path) => normalizePath(path));\n\n\t\tlet debounce: number;\n\t\tif (typeof _config.debounce === 'number') {\n\t\t\tdebounce = _config.debounce;\n\t\t} else {\n\t\t\tdebounce = (_config.debounce !== false) ? defaultDebounce : 0;\n\t\t}\n\n\t\treturn { watch, ignore, debounce };\n\t}\n}\n\nfunction parseTabFilterConfiguration(\n\ttabFilterConfig?: TabFilterConfiguration,\n\turl?: string\n): ParsedTabFilterConfiguration {\n\n\tif (tabFilterConfig === undefined) {\n\n\t\tif (url) {\n\t\t\treturn { include: [ new RegExp(RegExpEscape(urlDirname(url)) + '.*') ], exclude: [] };\n\t\t} else {\n\t\t\treturn { include: [ /.*/ ], exclude: [] };\n\t\t}\n\n\t}\n\n\tif ((typeof tabFilterConfig === 'string') || Array.isArray(tabFilterConfig)) {\n\n\t\treturn { include: parseTabFilter(tabFilterConfig), exclude: [] }\n\n\t} else {\n\n\t\treturn {\n\t\t\tinclude: (tabFilterConfig.include !== undefined) ? parseTabFilter(tabFilterConfig.include) : [ /.*/ ],\n\t\t\texclude: (tabFilterConfig.exclude !== undefined) ? parseTabFilter(tabFilterConfig.exclude) : []\n\t\t}\n\t}\n}\n\nfunction parseTabFilter(tabFilter: string | string[]): RegExp[] {\n\n\tif (typeof tabFilter === 'string') {\n\n\t\tconst parts = tabFilter.split('*').map(part => RegExpEscape(part));\n\t\tconst regExp = new RegExp(`^${parts.join('.*')}$`);\n\t\treturn [ regExp ];\n\n\t} else {\n\n\t\treturn tabFilter.map(f => parseTabFilter(f)[0]);\n\n\t}\n}\n\nasync function parseAddonConfiguration(\n\tconfig: LaunchConfiguration | AttachConfiguration,\n\tpathMappings: PathMappings\n): Promise<ParsedAddonConfiguration> {\n\n\tlet addonPath = config.addonPath!;\n\tconst popupAutohideButton = (config.popupAutohideButton !== false);\n\n\tlet addonId = await findAddonId(addonPath);\n\n\tlet sanitizedAddonPath = addonPath;\n\tif (sanitizedAddonPath[sanitizedAddonPath.length - 1] === '/') {\n\t\tsanitizedAddonPath = sanitizedAddonPath.substr(0, sanitizedAddonPath.length - 1);\n\t}\n\tpathMappings.push({\n\t\turl: new RegExp('^moz-extension://[0-9a-f-]*(/.*)$'),\n\t\tpath: sanitizedAddonPath\n\t});\n\n\tif (addonId) {\n\t\t// this pathMapping may no longer be necessary, I haven't seen this kind of URL recently...\n\t\tlet rewrittenAddonId = addonId.replace('{', '%7B').replace('}', '%7D');\n\t\tpathMappings.push({\n\t\t\turl: new RegExp(`^jar:file:.*/extensions/${rewrittenAddonId}.xpi!(/.*)$`),\n\t\t\tpath: sanitizedAddonPath\n\t\t});\n\t}\n\n\treturn {\n\t\tpath: addonPath, id: addonId, popupAutohideButton\n\t}\n}\n"
  },
  {
    "path": "src/adapter/debugAdapterBase.ts",
    "content": "import process from 'process';\nimport util from 'util';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { DebugSession } from '@vscode/debugadapter';\nimport { Log } from './util/log';\n\nconst log = Log.create('main');\nprocess.on('unhandledRejection', (e: any) => log.error(`Unhandled rejection: ${util.inspect(e)}\\n${e.stack}`));\n\n/**\n * This class extends the base class provided by VS Code for debug adapters,\n * offering a Promise-based API instead of the callback-based API used by VS Code\n */\nexport abstract class DebugAdapterBase extends DebugSession {\n\n\tpublic constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {\n\t\tsuper(debuggerLinesStartAt1, isServer);\n\t}\n\n\tprotected abstract initialize(args: DebugProtocol.InitializeRequestArguments): DebugProtocol.Capabilities | undefined;\n\tprotected abstract launch(args: DebugProtocol.LaunchRequestArguments): Promise<void>;\n\tprotected abstract attach(args: DebugProtocol.AttachRequestArguments): Promise<void>;\n\tprotected abstract disconnect(args: DebugProtocol.DisconnectArguments): Promise<void>;\n\tprotected abstract breakpointLocations(args: DebugProtocol.BreakpointLocationsArguments): Promise<{ breakpoints: DebugProtocol.BreakpointLocation[] }>;\n\tprotected abstract setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): { breakpoints: DebugProtocol.Breakpoint[] };\n\tprotected abstract setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): void;\n\tprotected abstract pause(args: DebugProtocol.PauseArguments): Promise<void>;\n\tprotected abstract next(args: DebugProtocol.NextArguments): Promise<void>;\n\tprotected abstract stepIn(args: DebugProtocol.StepInArguments): Promise<void>;\n\tprotected abstract stepOut(args: DebugProtocol.StepOutArguments): Promise<void>;\n\tprotected abstract continue(args: DebugProtocol.ContinueArguments): Promise<{ allThreadsContinued?: boolean }>;\n\tprotected abstract getSource(args: DebugProtocol.SourceArguments): Promise<{ content: string, mimeType?: string }>;\n\tprotected abstract getThreads(): { threads: DebugProtocol.Thread[] };\n\tprotected abstract getStackTrace(args: DebugProtocol.StackTraceArguments): Promise<{ stackFrames: DebugProtocol.StackFrame[], totalFrames?: number }>;\n\tprotected abstract getScopes(args: DebugProtocol.ScopesArguments): Promise<{ scopes: DebugProtocol.Scope[] }>;\n\tprotected abstract getVariables(args: DebugProtocol.VariablesArguments): Promise<{ variables: DebugProtocol.Variable[] }>;\n\tprotected abstract setVariable(args: DebugProtocol.SetVariableArguments): Promise<{ value: string, variablesReference?: number }>;\n\tprotected abstract evaluate(args: DebugProtocol.EvaluateArguments): Promise<{ result: string, type?: string, variablesReference: number, namedVariables?: number, indexedVariables?: number }>;\n\tprotected abstract getCompletions(args: DebugProtocol.CompletionsArguments): Promise<{ targets: DebugProtocol.CompletionItem[] }>;\n\tprotected abstract dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null, description: string, accessTypes?: DebugProtocol.DataBreakpointAccessType[], canPersist?: boolean }>;\n\tprotected abstract setDataBreakpoints(args: DebugProtocol.SetDataBreakpointsArguments): Promise<{ breakpoints: DebugProtocol.Breakpoint[] }>;\n\tprotected abstract restartFrame(args: DebugProtocol.RestartFrameArguments): Promise<void>;\n\tprotected abstract reloadAddon(): Promise<void>;\n\tprotected abstract toggleSkippingFile(url: string): Promise<void>;\n\tprotected abstract setPopupAutohide(popupAutohide: boolean): Promise<void>;\n\tprotected abstract togglePopupAutohide(): Promise<boolean>;\n\tprotected abstract setActiveEventBreakpoints(args: string[]): Promise<void>;\n\n\tprotected initializeRequest(response: DebugProtocol.InitializeResponse, args: DebugProtocol.InitializeRequestArguments): void {\n\t\tthis.handleRequest(response, () => this.initialize(args));\n\t}\n\n\tprotected disconnectRequest(response: DebugProtocol.DisconnectResponse, args: DebugProtocol.DisconnectArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.disconnect(args));\n\t}\n\n\tprotected launchRequest(response: DebugProtocol.LaunchResponse, args: DebugProtocol.LaunchRequestArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.launch(args));\n\t}\n\n\tprotected attachRequest(response: DebugProtocol.AttachResponse, args: DebugProtocol.AttachRequestArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.attach(args));\n\t}\n\n\tprotected breakpointLocationsRequest(response: DebugProtocol.BreakpointLocationsResponse, args: DebugProtocol.BreakpointLocationsArguments, request?: DebugProtocol.Request): void {\n\t\tthis.handleRequestAsync(response, () => this.breakpointLocations(args));\n\t}\n\n\tprotected setBreakPointsRequest(response: DebugProtocol.SetBreakpointsResponse, args: DebugProtocol.SetBreakpointsArguments): void {\n\t\tthis.handleRequest(response, () => this.setBreakpoints(args));\n\t}\n\n\tprotected setExceptionBreakPointsRequest(response: DebugProtocol.SetExceptionBreakpointsResponse, args: DebugProtocol.SetExceptionBreakpointsArguments): void {\n\t\tthis.handleRequest(response, () => this.setExceptionBreakpoints(args));\n\t}\n\n\tprotected pauseRequest(response: DebugProtocol.PauseResponse, args: DebugProtocol.PauseArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.pause(args));\n\t}\n\n\tprotected nextRequest(response: DebugProtocol.NextResponse, args: DebugProtocol.NextArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.next(args));\n\t}\n\n\tprotected stepInRequest(response: DebugProtocol.StepInResponse, args: DebugProtocol.StepInArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.stepIn(args));\n\t}\n\n\tprotected stepOutRequest(response: DebugProtocol.StepOutResponse, args: DebugProtocol.StepOutArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.stepOut(args));\n\t}\n\n\tprotected continueRequest(response: DebugProtocol.ContinueResponse, args: DebugProtocol.ContinueArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.continue(args));\n\t}\n\n\tprotected sourceRequest(response: DebugProtocol.SourceResponse, args: DebugProtocol.SourceArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.getSource(args));\n\t}\n\n\tprotected threadsRequest(response: DebugProtocol.ThreadsResponse): void {\n\t\tthis.handleRequest(response, () => this.getThreads());\n\t}\n\n\tprotected stackTraceRequest(response: DebugProtocol.StackTraceResponse, args: DebugProtocol.StackTraceArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.getStackTrace(args));\n\t}\n\n\tprotected scopesRequest(response: DebugProtocol.ScopesResponse, args: DebugProtocol.ScopesArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.getScopes(args));\n\t}\n\n\tprotected variablesRequest(response: DebugProtocol.VariablesResponse, args: DebugProtocol.VariablesArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.getVariables(args));\n\t}\n\n\tprotected setVariableRequest(response: DebugProtocol.SetVariableResponse, args: DebugProtocol.SetVariableArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.setVariable(args));\n\t}\n\n\tprotected evaluateRequest(response: DebugProtocol.EvaluateResponse, args: DebugProtocol.EvaluateArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.evaluate(args));\n\t}\n\n\tprotected completionsRequest(response: DebugProtocol.CompletionsResponse, args: DebugProtocol.CompletionsArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.getCompletions(args));\n\t}\n\n\tprotected dataBreakpointInfoRequest(response: DebugProtocol.DataBreakpointInfoResponse, args: DebugProtocol.DataBreakpointInfoArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.dataBreakpointInfo(args));\n\t}\n\n\tprotected setDataBreakpointsRequest(response: DebugProtocol.SetDataBreakpointsResponse, args: DebugProtocol.SetDataBreakpointsArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.setDataBreakpoints(args));\n\t}\n\n\tprotected restartFrameRequest(response: DebugProtocol.RestartFrameResponse, args: DebugProtocol.RestartFrameArguments): void {\n\t\tthis.handleRequestAsync(response, () => this.restartFrame(args));\n\t}\n\n\tprotected customRequest(command: string, response: DebugProtocol.Response, args: any): void {\n\t\tthis.handleRequestAsync(response, async () => {\n\t\t\tswitch(command) {\n\n\t\t\t\tcase 'reloadAddon':\n\t\t\t\treturn await this.reloadAddon();\n\n\t\t\t\tcase 'toggleSkippingFile':\n\t\t\t\treturn await this.toggleSkippingFile(<string>args);\n\n\t\t\t\tcase 'setPopupAutohide':\n\t\t\t\treturn await this.setPopupAutohide(args === 'true');\n\n\t\t\t\tcase 'togglePopupAutohide':\n\t\t\t\treturn await this.togglePopupAutohide();\n\n\t\t\t\tcase 'setActiveEventBreakpoints':\n\t\t\t\treturn await this.setActiveEventBreakpoints(args);\n\t\t\t}\n\t\t});\n\t}\n\n\tprivate handleRequest<TResponse extends DebugProtocol.Response, TResponseBody>(response: TResponse, executeRequest: () => TResponseBody): void {\n\t\ttry {\n\t\t\tresponse.body = executeRequest();\n\t\t} catch (err) {\n\t\t\tresponse.success = false;\n\t\t\tresponse.message = this.errorString(err);\n\t\t}\n\t\tthis.sendResponse(response);\n\t}\n\n\tprivate async handleRequestAsync<TResponse extends DebugProtocol.Response, TResponseBody>(response: TResponse, executeRequest: () => Promise<TResponseBody>): Promise<void> {\n\t\ttry {\n\t\t\tresponse.body = await executeRequest();\n\t\t} catch (err) {\n\t\t\tresponse.success = false;\n\t\t\tresponse.message = this.errorString(err);\n\t\t}\n\t\tthis.sendResponse(response);\n\t}\n\n\tprivate errorString(err: any) {\n\t\tif ((typeof err === 'object') && (err !== null) && (typeof err.message === 'string')) {\n\t\t\treturn err.message;\n\t\t} else {\n\t\t\treturn String(err);\n\t\t}\n\t}\n}"
  },
  {
    "path": "src/adapter/firefox/README.md",
    "content": "This folder contains the code for launching and talking to Firefox.\n\nThe [`launchFirefox()`](./launch.ts) function launches Firefox and establishes the TCP connection for\nthe debugger protocol. The connection's socket is passed to the [`DebugConnection`](./connection.ts)\nclass which implements the \n[Firefox Remote Debugging Protocol](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md)\nusing the [`DebugProtocolTransport`](./transport.ts) class for the\n[transport layer](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#stream-transport)\nof the protocol.\nIt passes the responses and events it receives to [proxy classes](./actorProxy) for the Firefox\n[actors](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#actors).\nThe [`sourceMaps`](./sourceMaps) folder contains the debug adapter's source-map support.\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/README.md",
    "content": "This folder contains proxy classes for the Firefox\n[actors](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#actors).\nThey implement the [`ActorProxy`](./interface.ts) interface and provide a Promise-based API for\nsending requests to and an EventEmitter-based API for receiving events from these actors.\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/addons.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('AddonsActorProxy');\n\n/**\n * Proxy class for an addons actor\n * ([spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/addon/addons.js))\n */\nexport class AddonsActorProxy extends BaseActorProxy {\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic installAddon(addonPath: string): Promise<FirefoxDebugProtocol.InstallAddonResponse> {\n\t\treturn this.sendCachedRequest(\n\t\t\t`installTemporaryAddon:${addonPath}`,\n\t\t\t{ type: 'installTemporaryAddon', addonPath }\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/base.ts",
    "content": "import { EventEmitter } from 'events';\nimport { DebugConnection } from '../connection';\nimport { ActorProxy } from './interface';\nimport { PendingRequests } from '../../util/pendingRequests';\nimport { Log } from '../../util/log';\n\nexport abstract class BaseActorProxy extends EventEmitter implements ActorProxy {\n\n\tprivate readonly pendingRequests = new PendingRequests<any>();\n\tprivate readonly cachedRequestPromises = new Map<string, Promise<any>>();\n\n\tconstructor(\n\t\tpublic readonly name: string,\n\t\tprotected readonly connection: DebugConnection,\n\t\tprivate readonly log: Log\n\t) {\n\t\tsuper();\n\t\tthis.connection.register(this);\n\t}\n\n\tsendRequest<T extends Omit<FirefoxDebugProtocol.Request, 'to'>, S>(request: T): Promise<S> {\n\t\treturn new Promise<S>((resolve, reject) => {\n\t\t\tthis.pendingRequests.enqueue({ resolve, reject });\n\t\t\tthis.connection.sendRequest({ ...request, to: this.name });\n\t\t});\n\t}\n\n\tsendCachedRequest<T extends Omit<FirefoxDebugProtocol.Request, 'to'>, R, S>(key: string, request: T, convert?: (r: R) => S): Promise<S> {\n\t\tif (!this.cachedRequestPromises.has(key)) {\n\t\t\tthis.cachedRequestPromises.set(key, (async () => {\n\t\t\t\tconst response: R = await this.sendRequest(request);\n\t\t\t\treturn convert ? convert(response) : response;\n\t\t\t})());\n\t\t}\n\t\treturn this.cachedRequestPromises.get(key)!;\n\t}\n\n\tsendRequestWithoutResponse<T extends Omit<FirefoxDebugProtocol.Request, 'to'>>(request: T): void {\n\t\tthis.connection.sendRequest({ ...request, to: this.name });\n\t}\n\n\tasync getRequestTypes(): Promise<string[]> {\n\t\treturn (await this.sendRequest<any, FirefoxDebugProtocol.RequestTypesResponse>(\n\t\t\t{ type: 'requestTypes' })\n\t\t).requestTypes;\n\t}\n\n\tisEvent(message: FirefoxDebugProtocol.Response): boolean {\n\t\treturn !!message.type;\n\t}\n\n\thandleEvent(event: FirefoxDebugProtocol.Event): void {\n\t\tthis.log.warn(`Unknown message: ${JSON.stringify(event)}`);\n\t}\n\n\treceiveMessage(message: FirefoxDebugProtocol.Response): void {\n\t\tif (message.error) {\n\t\t\tthis.pendingRequests.rejectOne(message);\n\t\t} else if (this.isEvent(message)) {\n\t\t\tthis.handleEvent(message as FirefoxDebugProtocol.Event);\n\t\t} else {\n\t\t\tthis.pendingRequests.resolveOne(message);\n\t\t}\n\t}\n\n\tdispose(): void {\n\t\tthis.connection.unregister(this);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/breakpointList.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nconst log = Log.create('BreakpointListActorProxy');\n\nexport class BreakpointListActorProxy extends BaseActorProxy {\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic async setBreakpoint(sourceUrl: string, line: number, column: number, condition?: string, logValue?: string) {\n\t\tawait this.sendRequest({\n\t\t\ttype: 'setBreakpoint',\n\t\t\tlocation: { sourceUrl, line, column },\n\t\t\toptions: { condition, logValue }\n\t\t});\n\t}\n\n\tpublic async removeBreakpoint(sourceUrl: string, line: number, column: number) {\n\t\tawait this.sendRequest({ type: 'removeBreakpoint', location: { sourceUrl, line, column } });\n\t}\n\n\tpublic async setActiveEventBreakpoints(ids: string[]) {\n\t\tawait this.sendRequest({ type: 'setActiveEventBreakpoints', ids });\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/console.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { exceptionGripToString } from '../../util/misc';\nimport { BaseActorProxy } from './base';\nimport { DeferredMap } from '../../../common/deferredMap';\n\nlet log = Log.create('ConsoleActorProxy');\n\n/**\n * Proxy class for a console actor\n */\nexport class ConsoleActorProxy extends BaseActorProxy {\n\n\tprivate evaluationResults = new DeferredMap<string, FirefoxDebugProtocol.Grip>();\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic async evaluate(expr: string, disableBreaks: boolean, frameActorName?: string): Promise<FirefoxDebugProtocol.Grip> {\n\t\tconst resultIDResponse: FirefoxDebugProtocol.ResultIDResponse = await this.sendRequest({\n\t\t\ttype: 'evaluateJSAsync',\n\t\t\ttext: expr, frameActor: frameActorName, disableBreaks\n\t\t});\n\t\tconst result = await this.evaluationResults.get(resultIDResponse.resultID);\n\t\tthis.evaluationResults.delete(resultIDResponse.resultID);\n\t\treturn result;\n\t}\n\n\tpublic async autoComplete(text: string, column: number, frameActor?: string) {\n\t\tconst response: FirefoxDebugProtocol.AutoCompleteResponse = await this.sendRequest({ type: 'autocomplete', text, cursor: column, frameActor });\n\t\treturn response.matches;\n\t}\n\n\thandleEvent(event: FirefoxDebugProtocol.EvaluationResultResponse): void {\n\t\tif (event.type === 'evaluationResult') {\n\t\t\tthis.evaluationResults.set(event.resultID, event.exceptionMessage ? exceptionGripToString(event.exception) : event.result);\n\t\t} else {\n\t\t\tlog.warn(`Unknown message: ${JSON.stringify(event)}`);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/descriptor.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\nimport { WatcherActorProxy } from './watcher';\n\nlet log = Log.create('DescriptorActorProxy');\n\nexport type DescriptorType = 'tab' | 'webExtension' | 'process';\n\n/**\n * Proxy class for a TabDescriptor or WebExtensionDescriptor actor\n */\nexport class DescriptorActorProxy extends BaseActorProxy {\n\n\tconstructor(\n\t\tname: string,\n\t\tpublic readonly type: DescriptorType,\n\t\tconnection: DebugConnection\n\t) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic async getWatcher(): Promise<WatcherActorProxy> {\n\t\treturn await this.sendCachedRequest(\n\t\t\t'getWatcher',\n\t\t\t\t{ type: 'getWatcher',\n\t\t\t\t\tenableWindowGlobalThreadActors: this.type === 'process' ? true : undefined,\n\t\t\t\t\tisServerTargetSwitchingEnabled: this.type !== 'process' ? true : undefined,\n\t\t\t\t\tisPopupDebuggingEnabled: this.type === 'tab' ? false : undefined,\n\t\t\t\t},\n\t\t\t(response: FirefoxDebugProtocol.GetWatcherResponse) =>\n\t\t\t\tnew WatcherActorProxy(response.actor, !!response.traits.content_script, this.connection)\n\t\t);\n\t}\n\n\tpublic async reload() {\n\t\tawait this.sendRequest({ type: 'reloadDescriptor' });\n\t}\n\n\tpublic onDestroyed(cb: () => void) {\n\t\tthis.on('destroyed', cb);\n\t}\n\n\thandleEvent(event: FirefoxDebugProtocol.DescriptorDestroyedEvent): void {\n\t\tif (event.type === 'descriptor-destroyed') {\n\t\t\tlog.debug(`Descriptor ${this.name} destroyed`);\n\t\t\tthis.emit('destroyed');\n\t\t} else {\n\t\t\tlog.warn(`Unknown message: ${JSON.stringify(event)}`);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/device.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('DeviceActorProxy');\n\n/**\n * Proxy class for the device actor\n */\nexport class DeviceActorProxy extends BaseActorProxy {\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic getDescription(): Promise<FirefoxDebugProtocol.DeviceDescription> {\n\t\treturn this.sendCachedRequest(\n\t\t\t'getDescription',\n\t\t\t{ type: 'getDescription' },\n\t\t\t(response: FirefoxDebugProtocol.DeviceDescriptionResponse) => response.value\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/frame.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('FrameActorProxy');\n\n/**\n * Proxy class for a frame actor\n * ([docs](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#listing-stack-frames),\n * [spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/frame.js))\n */\nexport class FrameActorProxy extends BaseActorProxy {\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tisEvent(message: FirefoxDebugProtocol.Response): boolean {\n\t\treturn false;\n\t}\n\n\tpublic getEnvironment(): Promise<FirefoxDebugProtocol.Environment> {\n\t\treturn this.sendCachedRequest('getEnvironment', { type: 'getEnvironment' });\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/interface.ts",
    "content": "/**\n * An ActorProxy is a client-side reference to an actor on the server side of the \n * Mozilla Debugging Protocol as defined in\n * https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md\n */\nexport interface ActorProxy {\n\n\t/** the name that is used for the actor in Firefox debug protocol messages */\n\treadonly name: string;\n\n\t/** called by the [DebugConnection](../connection.ts) class to deliver this actor's messages */\n\treceiveMessage(response: FirefoxDebugProtocol.Response): void;\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/longString.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('LongStringGripActorProxy');\n\n/**\n * Proxy class for a long string grip actor\n * ([docs](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#long-strings),\n * [spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/string.js))\n */\nexport class LongStringGripActorProxy extends BaseActorProxy {\n\n\tconstructor(private grip: FirefoxDebugProtocol.LongStringGrip, connection: DebugConnection) {\n\t\tsuper(grip.actor, connection, log);\n\t}\n\n\tpublic fetchContent(): Promise<string> {\n\t\treturn this.sendCachedRequest(\n\t\t\t'content',\n\t\t\t{ type: 'substring', start: 0, end: this.grip.length },\n\t\t\t(response: { substring: string }) => response.substring\n\t\t);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/objectGrip.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('ObjectGripActorProxy');\n\n/**\n * Proxy class for an object grip actor\n * ([docs](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#objects),\n * [spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/object.js))\n */\nexport class ObjectGripActorProxy extends BaseActorProxy {\n\n\tprivate _refCount = 0;\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic get refCount() {\n\t\treturn this._refCount;\n\t}\n\n\tpublic increaseRefCount() {\n\t\tthis._refCount++;\n\t}\n\n\tpublic decreaseRefCount() {\n\t\tthis._refCount--;\n\t\tif (this._refCount === 0) {\n\t\t\tthis.connection.unregister(this);\n\t\t}\n\t}\n\n\tpublic fetchPrototypeAndProperties(): Promise<FirefoxDebugProtocol.PrototypeAndPropertiesResponse> {\n\t\treturn this.sendCachedRequest(\n\t\t\t'prototypeAndProperties',\n\t\t\t{ type: 'prototypeAndProperties' }\n\t\t);\n\t}\n\n\tpublic addWatchpoint(property: string, label: string, watchpointType: 'get' | 'set'): void {\n\t\tthis.sendRequestWithoutResponse({ type: 'addWatchpoint', property, label, watchpointType });\n\t}\n\n\tpublic removeWatchpoint(property: string): void {\n\t\tthis.sendRequestWithoutResponse({ type: 'removeWatchpoint', property });\n\t}\n\n\tpublic threadLifetime(): Promise<void> {\n\t\treturn this.sendCachedRequest('threadGrip', { type: 'threadGrip' }, () => undefined);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/preference.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('PreferenceActorProxy');\n\n/**\n * Proxy class for a preference actor\n * ([spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/preference.js))\n */\nexport class PreferenceActorProxy extends BaseActorProxy {\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic async getBoolPref(pref: string): Promise<boolean> {\n\n\t\tlet prefString = await this.getPref(pref, 'Bool');\n\t\treturn (prefString === 'true');\n\n\t}\n\n\tpublic getCharPref(pref: string): Promise<string> {\n\n\t\treturn this.getPref(pref, 'Char');\n\n\t}\n\n\tpublic async getIntPref(pref: string): Promise<number> {\n\n\t\tlet prefString = await this.getPref(pref, 'Bool');\n\t\treturn parseInt(prefString, 10);\n\n\t}\n\n\tpublic setBoolPref(pref: string, val: boolean): Promise<void> {\n\n\t\treturn this.setPref(pref, val, 'Bool');\n\n\t}\n\n\tpublic setCharPref(pref: string, val: string): Promise<void> {\n\n\t\treturn this.setPref(pref, val, 'Char');\n\n\t}\n\n\tpublic setIntPref(pref: string, val: number): Promise<void> {\n\n\t\treturn this.setPref(pref, val, 'Int');\n\n\t}\n\n\tprivate async getPref(\n\t\tpref: string,\n\t\ttype: 'Bool' | 'Char' | 'Int'\n\t): Promise<string> {\n\t\tconst response: { value: any } = await this.sendRequest({\n\t\t\ttype: `get${type}Pref`,\n\t\t\tvalue: pref\n\t\t});\n\t\treturn response.value.toString();\n\t}\n\n\tprivate setPref(\n\t\tpref: string,\n\t\tval: boolean | string | number,\n\t\ttype: 'Bool' | 'Char' | 'Int'\n\t): Promise<void> {\n\t\treturn this.sendRequest({ \n\t\t\ttype: `set${type}Pref`,\n\t\t\tname: pref,\n\t\t\tvalue: val\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/root.ts",
    "content": "import { Log } from '../../util/log';\nimport { DescriptorActorProxy } from './descriptor';\nimport { PreferenceActorProxy } from './preference';\nimport { AddonsActorProxy } from './addons';\nimport { DeviceActorProxy } from './device';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\nimport { delay } from '../../../common/util';\n\nlet log = Log.create('RootActorProxy');\n\nexport interface FetchRootResult {\n\tpreference: PreferenceActorProxy,\n\taddons: AddonsActorProxy | undefined,\n\tdevice: DeviceActorProxy\n}\n\n/**\n * Proxy class for a root actor\n * ([docs](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#the-root-actor),\n * [spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/root.js))\n */\nexport class RootActorProxy extends BaseActorProxy {\n\n\tprivate tabs = new Map<string, DescriptorActorProxy>();\n\n\tconstructor(connection: DebugConnection) {\n\t\tsuper('root', connection, log);\n\t}\n\n\tisEvent(message: FirefoxDebugProtocol.Response): boolean {\n\t\treturn !!(message.type || message.applicationType);\n\t}\n\n\tpublic async fetchRoot(): Promise<FetchRootResult> {\n\t\tconst rootResponse: FirefoxDebugProtocol.RootResponse = await this.sendCachedRequest(\n\t\t\t'getRoot',\n\t\t\t{ type: 'getRoot' }\n\t\t);\n\n\t\tconst preferenceActor = this.connection.getOrCreate(rootResponse.preferenceActor,\n\t\t\t() => new PreferenceActorProxy(rootResponse.preferenceActor, this.connection));\n\n\t\tlet addonsActor: AddonsActorProxy | undefined;\n\t\tconst addonsActorName = rootResponse.addonsActor;\n\t\tif (addonsActorName) {\n\t\t\taddonsActor = this.connection.getOrCreate(addonsActorName,\n\t\t\t\t() => new AddonsActorProxy(addonsActorName, this.connection));\n\t\t}\n\n\t\tconst deviceActor = this.connection.getOrCreate(rootResponse.deviceActor,\n\t\t\t() => new DeviceActorProxy(rootResponse.deviceActor, this.connection));\n\n\t\treturn { \n\t\t\tpreference: preferenceActor,\n\t\t\taddons: addonsActor,\n\t\t\tdevice: deviceActor\n\t\t};\n\t}\n\n\tpublic async getProcess(id: number): Promise<DescriptorActorProxy> {\n\t\treturn this.sendCachedRequest(\n\t\t\t`getProcess(${id})`,\n\t\t\t{ type: 'getProcess', id },\n\t\t\t(response: FirefoxDebugProtocol.GetProcessResponse) =>\n\t\t\t\tnew DescriptorActorProxy(response.processDescriptor.actor, 'process', this.connection)\n\t\t)\n\t}\n\n\tpublic async fetchTabs(): Promise<Map<string, DescriptorActorProxy>> {\n\t\tlet tabsResponse: FirefoxDebugProtocol.TabsResponse = await this.sendRequest({ type: 'listTabs' });\n\t\twhile (tabsResponse.tabs.length === 0) {\n\t\t\tawait delay(100);\n\t\t\ttabsResponse = await this.sendRequest({ type: 'listTabs' });\n\t\t}\n\n\t\tlog.debug(`Received ${tabsResponse.tabs.length} tabs`);\n\n\t\t// convert the Tab array into a map of TabDescriptorActorProxies, re-using already \n\t\t// existing proxies and emitting tabOpened events for new ones\n\t\tconst currentTabs = new Map<string, DescriptorActorProxy>();\n\t\tfor (const tab of tabsResponse.tabs) {\n\t\t\tlet tabDescriptorActor: DescriptorActorProxy;\n\t\t\tif (this.tabs.has(tab.actor)) {\n\n\t\t\t\ttabDescriptorActor = this.tabs.get(tab.actor)!;\n\n\t\t\t} else {\n\n\t\t\t\tlog.debug(`Tab ${tab.actor} opened`);\n\n\t\t\t\ttabDescriptorActor = new DescriptorActorProxy(tab.actor, 'tab', this.connection);\n\n\t\t\t\tthis.emit('tabOpened', tabDescriptorActor);\n\t\t\t}\n\n\t\t\tcurrentTabs.set(tab.actor, tabDescriptorActor);\n\t\t}\n\n\t\t// emit tabClosed events for tabs that have disappeared\n\t\tthis.tabs.forEach((actorsForTab, tabActorName) => {\n\t\t\tif (!currentTabs.has(tabActorName)) {\n\t\t\t\tlog.debug(`Tab ${tabActorName} closed`);\n\t\t\t\tthis.emit('tabClosed', actorsForTab);\n\t\t\t}\n\t\t});\n\n\t\tthis.tabs = currentTabs;\n\n\t\treturn currentTabs;\n\t}\n\n\tpublic async fetchAddons(): Promise<FirefoxDebugProtocol.Addon[]> {\n\t\tconst addonsResponse: FirefoxDebugProtocol.AddonsResponse = await this.sendRequest({ type: 'listAddons' });\n\t\treturn addonsResponse.addons;\n\t}\n\n\thandleEvent(event: FirefoxDebugProtocol.Event): void {\n\t\tif (event.applicationType) {\n\t\t\tthis.emit('init', event);\n\t\t} else if (['tabListChanged', 'addonListChanged'].includes(event.type)) {\n\t\t\tthis.emit(event.type);\n\t\t} else if (event.type !== 'forwardingCancelled') {\n\t\t\tlog.warn(`Unknown message: ${JSON.stringify(event)}`);\n\t\t}\n\t}\n\n\tpublic onInit(cb: (response: FirefoxDebugProtocol.InitialResponse) => void) {\n\t\tthis.on('init', cb);\n\t}\n\n\tpublic onTabOpened(cb: (actorsForTab: DescriptorActorProxy) => void) {\n\t\tthis.on('tabOpened', cb);\n\t}\n\n\tpublic onTabClosed(cb: (actorsForTab: DescriptorActorProxy) => void) {\n\t\tthis.on('tabClosed', cb);\n\t}\n\n\tpublic onTabListChanged(cb: () => void) {\n\t\tthis.on('tabListChanged', cb);\n\t}\n\n\tpublic onAddonListChanged(cb: () => void) {\n\t\tthis.on('addonListChanged', cb);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/source.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { MappedLocation, Range } from '../../location';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('SourceActorProxy');\n\nexport interface ISourceActorProxy {\n\tname: string;\n\tsource: FirefoxDebugProtocol.Source;\n\turl: string | null;\n\tgetBreakableLines(): Promise<number[]>;\n\tgetBreakableLocations(line: number): Promise<MappedLocation[]>;\n\tfetchSource(): Promise<FirefoxDebugProtocol.Grip>;\n\tsetBlackbox(blackbox: boolean): Promise<void>;\n\tdispose(): void;\n}\n\n/**\n * Proxy class for a source actor\n * ([docs](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#loading-script-sources),\n * [spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/source.js))\n */\nexport class SourceActorProxy extends BaseActorProxy implements ISourceActorProxy {\n\n\tconstructor(\n\t\tpublic readonly source: FirefoxDebugProtocol.Source,\n\t\tconnection: DebugConnection\n\t) {\n\t\tsuper(source.actor, connection, log);\n\t}\n\n\tpublic get url() {\n\t\treturn this.source.url;\n\t}\n\n\tpublic getBreakableLines(): Promise<number[]> {\n\t\tlog.debug(`Fetching breakableLines of ${this.url}`);\n\n\t\treturn this.sendCachedRequest(\n\t\t\t'getBreakableLines',\n\t\t\t{ type: 'getBreakableLines' },\n\t\t\t(response: FirefoxDebugProtocol.GetBreakableLinesResponse) => response.lines\n\t\t);\n\t}\n\n\tpublic async getBreakableLocations(line: number): Promise<MappedLocation[]> {\n\t\tlog.debug(`Fetching breakpointPositions of line ${line} in ${this.url}`);\n\n\t\tconst positions = await this.getBreakpointPositionsForRange({\n\t\t\tstart: { line, column: 0 },\n\t\t\tend: { line, column: Number.MAX_SAFE_INTEGER }\n\t\t});\n\n\t\tif (positions[line]) {\n\t\t\treturn (positions[line].map(column => ({ line, column })));\n\t\t} else {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tpublic getBreakpointPositionsForRange(range: Range): Promise<FirefoxDebugProtocol.BreakpointPositions> {\n\t\tlog.debug(`Fetching breakpoint positions of ${this.url} for range: ${JSON.stringify(range)}`);\n\n\t\treturn this.sendCachedRequest(\n\t\t\t`getBreakpointPositionsCompressed_${range.start.line}:${range.start.column}-${range.end.line}:${range.end.line}`,\n\t\t\t{\n\t\t\t\ttype: 'getBreakpointPositionsCompressed',\n\t\t\t\tquery: {\n\t\t\t\t\tstart: { line: range.start.line, column: range.start.column },\n\t\t\t\t\tend: { line: range.end.line, column: range.end.column },\n\t\t\t\t},\n\t\t\t},\n\t\t\t(response: FirefoxDebugProtocol.GetBreakpointPositionsCompressedResponse) => response.positions\n\t\t);\n\t}\n\n\tpublic fetchSource(): Promise<FirefoxDebugProtocol.Grip> {\n\t\tlog.debug(`Fetching source of ${this.url}`);\n\n\t\treturn this.sendCachedRequest(\n\t\t\t'source',\n\t\t\t{ type: 'source' },\n\t\t\t(response: FirefoxDebugProtocol.SourceResponse) => response.source\n\t\t);\n\t}\n\n\tpublic async setBlackbox(blackbox: boolean): Promise<void> {\n\t\tlog.debug(`Setting blackboxing of ${this.url} to ${blackbox}`);\n\n\t\tawait this.sendRequest({ type: blackbox ? 'blackbox' : 'unblackbox' });\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/target.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\nimport { ISourceActorProxy, SourceActorProxy } from './source';\n\nconst log = Log.create('TargetActorProxy');\n\nexport class TargetActorProxy extends BaseActorProxy {\n\n\tpublic destroyed = false;\n\n\tconstructor(\n\t\tpublic readonly target: FirefoxDebugProtocol.TargetAvailableEvent['target'],\n\t\tconnection: DebugConnection\n\t) {\n\t\tsuper(target.actor, connection, log);\n\t}\n\n\tpublic reload() {\n\t\treturn this.sendRequest({ type: 'reload' });\n\t}\n\n\tpublic onConsoleMessages(cb: (consoleMessages: FirefoxDebugProtocol.ConsoleMessage[]) => void) {\n\t\tthis.on('console-message', cb);\n\t}\n\n\tpublic onErrorMessages(cb: (errorMessages: { pageError: FirefoxDebugProtocol.PageError }[]) => void) {\n\t\tthis.on('error-message', cb);\n\t}\n\n\tpublic onSources(cb: (sources: ISourceActorProxy[]) => void) {\n\t\tthis.on('source', async underlyingSources => {\n\t\t\tlet allMappedSources: ISourceActorProxy[] = [];\n\t\t\tfor (let underlyingSource of underlyingSources) {\n\t\t\t\tlet info = await this.connection.sourceMaps.getOrCreateSourceMappingInfo(underlyingSource.source);\n\t\t\t\tallMappedSources.push(...info.sources);\n\t\t\t\tif (!info.sources.some(actor => actor === underlyingSource)) {\n\t\t\t\t\tallMappedSources.push(underlyingSource);\n\t\t\t\t}\n\t\t\t}\n\t\t\tif (!this.destroyed) {\n\t\t\t\tcb(allMappedSources);\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic onThreadState(cb: (threadState: FirefoxDebugProtocol.ThreadState) => void) {\n\t\tthis.on('thread-state', cb);\n\t}\n\n\thandleEvent(event: FirefoxDebugProtocol.ResourcesAvailableEvent | FirefoxDebugProtocol.ResourceAvailableForm | FirefoxDebugProtocol.FrameUpdateEvent): void {\n\t\tif (event.type === 'resources-available-array') {\n\t\t\tfor (const resource of event.array) {\n\t\t\t\tif (resource[0] === 'source') {\n\t\t\t\t\tthis.emit('source', resource[1].map(source => new SourceActorProxy(source, this.connection)));\n\t\t\t\t} else if (resource[0] === 'thread-state') {\n\t\t\t\t\tfor (let state of resource[1]) {\n\t\t\t\t\t\tthis.emit('thread-state', state);\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tthis.emit(resource[0], resource[1]);\n\t\t\t\t}\n\t\t\t}\n\t\t} else if (event.type === 'resource-available-form') {\n\t\t\tfor (const resource of event.resources) {\n\t\t\t\tswitch (resource.resourceType) {\n\t\t\t\t\tcase 'source': {\n\t\t\t\t\t\tthis.emit('source', [new SourceActorProxy(resource, this.connection)]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'thread-state': {\n\t\t\t\t\t\tthis.emit('thread-state', resource);\n\t\t\t\t\t\tbreak;\t\t\t\t\t\t\n\t\t\t\t\t}\n\t\t\t\t\tcase 'console-message': {\n\t\t\t\t\t\tthis.emit('console-message', [resource.message]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t\tcase 'error-message': {\n\t\t\t\t\t\tthis.emit('error-message', [resource]);\n\t\t\t\t\t\tbreak;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\t\t\t\n\t\t} else if (event.type === 'frameUpdate') {\n\t\t\tlog.debug(\"frameUpdate from TargetActor: \" + JSON.stringify(event));\n\t\t} else {\n\t\t\tlog.warn(`Unknown message: ${JSON.stringify(event)}`);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/thread.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nlet log = Log.create('ThreadActorProxy');\n\nexport interface IThreadActorProxy {\n\tname: string;\n\tresume(resumeLimitType?: 'next' | 'step' | 'finish' | 'restart', frameActorID?: string): Promise<void>;\n\tinterrupt(immediately?: boolean): Promise<void>;\n\tfetchStackFrames(start?: number, count?: number): Promise<FirefoxDebugProtocol.Frame[]>;\n\tgetAvailableEventBreakpoints() : Promise<FirefoxDebugProtocol.AvailableEventCategory[]>;\n\tdispose(): void;\n}\n\n/**\n * A ThreadActorProxy is a proxy for a \"thread-like actor\" (a Tab, Worker or Addon) in Firefox\n * ([docs](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#interacting-with-thread-like-actors),\n * [spec](https://github.com/mozilla/gecko-dev/blob/master/devtools/shared/specs/thread.js))\n */\nexport class ThreadActorProxy extends BaseActorProxy implements IThreadActorProxy {\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic async resume(resumeLimitType?: 'next' | 'step' | 'finish' | 'restart', frameActorID?: string): Promise<void> {\n\t\tconst resumeLimit = resumeLimitType ? { type: resumeLimitType } : undefined;\n\t\tthis.sendRequest({ type: 'resume', resumeLimit, frameActorID });\n\t}\n\n\tpublic async interrupt(immediately?: boolean): Promise<void> {\n\t\tawait this.sendRequest({ type: 'interrupt', when: immediately ? '' : 'onNext' });\n\t}\n\n\tpublic async fetchStackFrames(start = 0, count = 1000): Promise<FirefoxDebugProtocol.Frame[]> {\n\t\tconst response: { frames: FirefoxDebugProtocol.Frame[] } = await this.sendRequest({ type: 'frames', start, count });\n\t\treturn response.frames;\n\t}\n\n\tpublic async getAvailableEventBreakpoints() : Promise<FirefoxDebugProtocol.AvailableEventCategory[]> {\n\t\treturn this.sendCachedRequest(\n\t\t\t'getAvailableEventBreakpoints',\n\t\t\t{ type: 'getAvailableEventBreakpoints' },\n\t\t\t(response: FirefoxDebugProtocol.GetAvailableEventBreakpointsResponse) => response.value\n\t\t);\n\t}\n\n\thandleEvent(event: FirefoxDebugProtocol.Event): void {\n\t\tif (!['paused', 'resumed'].includes(event.type)) {\n\t\t\tlog.warn(`Unknown message: ${JSON.stringify(event)}`);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/actorProxy/threadConfiguration.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\n\nconst log = Log.create('ThreadConfigurationActorProxy');\n\nexport class ThreadConfigurationActorProxy extends BaseActorProxy {\n\n\tconstructor(name: string, connection: DebugConnection) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic async updateConfiguration(configuration: Partial<FirefoxDebugProtocol.ThreadConfiguration>) {\n\t\tawait this.sendRequest({ type: 'updateConfiguration', configuration });\n\t}\n}"
  },
  {
    "path": "src/adapter/firefox/actorProxy/watcher.ts",
    "content": "import { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { BaseActorProxy } from './base';\nimport { BreakpointListActorProxy } from './breakpointList';\nimport { ConsoleActorProxy } from './console';\nimport { TargetActorProxy } from './target';\nimport { IThreadActorProxy, ThreadActorProxy } from './thread';\nimport { SourceMappingThreadActorProxy } from '../sourceMaps/thread';\nimport { ThreadConfigurationActorProxy } from './threadConfiguration';\n\nexport type ResourceType = 'console-message' | 'error-message' | 'source' | 'thread-state';\nexport type TargetType = 'frame' | 'worker' | 'content_script';\n\nconst log = Log.create('WatcherActorProxy');\n\nexport class WatcherActorProxy extends BaseActorProxy {\n\n\tconstructor(\n\t\tname: string,\n\t\tpublic readonly supportsContentScriptTargets: boolean,\n\t\tconnection: DebugConnection\n\t) {\n\t\tsuper(name, connection, log);\n\t}\n\n\tpublic async getBreakpointList() {\n\t\treturn await this.sendCachedRequest(\n\t\t\t'getBreakpointListActor',\n\t\t\t{ type: 'getBreakpointListActor' },\n\t\t\t(response: FirefoxDebugProtocol.GetBreakpointListResponse) => new BreakpointListActorProxy(response.breakpointList.actor, this.connection)\n\t\t);\n\t}\n\n\tpublic async getThreadConfiguration() {\n\t\treturn await this.sendCachedRequest(\n\t\t\t'getThreadConfigurationActor',\n\t\t\t{ type: 'getThreadConfigurationActor' },\n\t\t\t(response: FirefoxDebugProtocol.GetThreadConfigurationResponse) => new ThreadConfigurationActorProxy(response.configuration.actor, this.connection)\n\t\t);\n\t}\n\n\tpublic async watchResources(resourceTypes: ResourceType[]) {\n\t\tawait this.sendRequest({ type: 'watchResources', resourceTypes });\n\t}\n\n\tpublic async watchTargets(targetType: TargetType) {\n\t\tawait this.sendRequest({ type: 'watchTargets', targetType });\n\t}\n\n\tpublic onTargetAvailable(cb: (target: [TargetActorProxy, IThreadActorProxy, ConsoleActorProxy]) => void) {\n\t\tthis.on('targetAvailable', cb);\n\t}\n\n\tpublic onTargetDestroyed(cb: (targetActorName: string) => void) {\n\t\tthis.on('targetDestroyed', cb);\n\t}\n\n\thandleEvent(event: FirefoxDebugProtocol.TargetAvailableEvent | FirefoxDebugProtocol.TargetDestroyedEvent): void {\n\t\tif (event.type === 'target-available-form') {\n\t\t\tconst targetActorProxy = new TargetActorProxy(event.target, this.connection);\n\t\t\tconst threadActorProxy = new ThreadActorProxy(event.target.threadActor, this.connection);\n\t\t\tconst sourcemappingThreadActorProxy = new SourceMappingThreadActorProxy(threadActorProxy, this.connection);\n\t\t\tconst consoleActorProxy = new ConsoleActorProxy(event.target.consoleActor, this.connection);\n\t\t\tthis.emit('targetAvailable', [targetActorProxy, sourcemappingThreadActorProxy, consoleActorProxy]);\n\t\t} else if (event.type === 'target-destroyed-form') {\n\t\t\tthis.emit('targetDestroyed', event.target.actor);\n\t\t} else {\n\t\t\tlog.warn(`Unknown message: ${JSON.stringify(event)}`);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/connection.ts",
    "content": "import { Log } from '../util/log';\nimport { Socket } from 'net';\nimport { DebugProtocolTransport } from './transport';\nimport { ActorProxy } from './actorProxy/interface';\nimport { RootActorProxy } from './actorProxy/root';\nimport { PathMapper } from '../util/pathMapper';\nimport { SourceMapsManager } from './sourceMaps/manager';\nimport { SourcesManager } from '../adapter/sourcesManager';\n\nlet log = Log.create('DebugConnection');\n\n/**\n * Connects to a target supporting the Firefox Debugging Protocol and sends and receives messages\n */\nexport class DebugConnection {\n\n\tprivate transport: DebugProtocolTransport;\n\tprivate actors: Map<string, ActorProxy>;\n\tpublic readonly sourceMaps: SourceMapsManager;\n\tpublic readonly rootActor: RootActorProxy;\n\n\tconstructor(pathMapper: PathMapper, sources: SourcesManager, socket: Socket) {\n\n\t\tthis.actors = new Map<string, ActorProxy>();\n\t\tthis.sourceMaps = new SourceMapsManager(pathMapper, sources, this);\n\t\tthis.rootActor = new RootActorProxy(this);\n\t\tthis.transport = new DebugProtocolTransport(socket);\n\n\t\tthis.transport.on('message', (message: FirefoxDebugProtocol.Response) => {\n\t\t\tif (this.actors.has(message.from)) {\n\t\t\t\tif (log.isDebugEnabled()) {\n\t\t\t\t\tlog.debug(`Received response/event ${JSON.stringify(message)}`);\n\t\t\t\t}\n\t\t\t\tthis.actors.get(message.from)!.receiveMessage(message);\n\t\t\t} else {\n\t\t\t\tlog.error('Unknown actor: ' + JSON.stringify(message));\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic sendRequest<T extends FirefoxDebugProtocol.Request>(request: T) {\n\t\tif (log.isDebugEnabled()) {\n\t\t\tlog.debug(`Sending request ${JSON.stringify(request)}`);\n\t\t}\n\t\tthis.transport.sendMessage(request);\n\t}\n\n\tpublic register(actor: ActorProxy): void {\n\t\tthis.actors.set(actor.name, actor);\n\t}\n\n\tpublic unregister(actor: ActorProxy): void {\n\t\tthis.actors.delete(actor.name);\n\t}\n\n\tpublic has(actorName: string): boolean {\n\t\treturn this.actors.has(actorName);\n\t}\n\n\tpublic getOrCreate<T extends ActorProxy>(actorName: string, createActor: () => T): T {\n\t\tif (this.actors.has(actorName)) {\n\t\t\treturn <T>this.actors.get(actorName);\n\t\t} else {\n\t\t\treturn createActor();\n\t\t}\n\t}\n\t\n\tpublic disconnect(): Promise<void> {\n\t\treturn this.transport.disconnect();\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/launch.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { spawn, fork, ChildProcess } from 'child_process';\nimport FirefoxProfile from 'firefox-profile';\nimport { ParsedLaunchConfiguration, ParsedAttachConfiguration, getExecutableCandidates } from '../configuration';\nimport { isExecutable } from '../util/fs';\n\n/**\n * Launches Firefox after preparing the debug profile.\n * If Firefox is launched \"detached\" (the default unless we are on MacOS and the `reAttach` flag\n * in the launch configuration is set to `false`), it creates one or even two intermediate\n * child processes for launching Firefox:\n * * one of them will wait for the Firefox process to exit and then remove any temporary directories\n *   created by this debug adapter\n * * the other one is used to work around a bug in the node version that is distributed with VS Code\n *   (and that runs this debug adapter), which fails to properly detach from child processes.\n *   See [this issue](https://github.com/microsoft/vscode/issues/22022) for an explanation of the\n *   bug and how to work around it.\n * \n * The intermediate child processes execute the [forkedLauncher](../util/forkedLauncher.ts) script.\n */\nexport async function launchFirefox(launch: ParsedLaunchConfiguration): Promise<ChildProcess | undefined> {\n\n\tawait prepareDebugProfile(launch);\n\n\t// workaround for an issue with the snap version of VS Code\n\t// (see e.g. https://github.com/microsoft/vscode/issues/85344)\n\tconst env = { ...process.env };\n\tif (env.SNAP) {\n\t\tdelete env['GDK_PIXBUF_MODULE_FILE'];\n\t\tdelete env['GDK_PIXBUF_MODULEDIR'];\n\t}\n\n\tlet childProc: ChildProcess | undefined = undefined;\n\n\tif (launch.detached) {\n\n\t\tlet forkedLauncherPath = path.join(__dirname, './launcher.bundle.js');\n\t\tlet forkArgs: string[];\n\t\tswitch (launch.tmpDirs.length) {\n\t\t\tcase 0:\n\t\t\t\tforkArgs = [\n\t\t\t\t\t'spawnDetached', launch.firefoxExecutable, ...launch.firefoxArgs\n\t\t\t\t];\n\t\t\t\tbreak;\n\n\t\t\tcase 1:\n\t\t\t\tforkArgs = [\n\t\t\t\t\t'forkDetached', forkedLauncherPath,\n\t\t\t\t\t'spawnAndRemove', launch.tmpDirs[0], launch.firefoxExecutable, ...launch.firefoxArgs\n\t\t\t\t];\n\t\t\t\tbreak;\n\n\t\t\tdefault:\n\t\t\t\tforkArgs = [\n\t\t\t\t\t'forkDetached', forkedLauncherPath,\n\t\t\t\t\t'spawnAndRemove2', launch.tmpDirs[0], launch.tmpDirs[1], launch.firefoxExecutable, ...launch.firefoxArgs\n\t\t\t\t];\n\t\t\t\tbreak;\n\t\t}\n\n\t\tfork(forkedLauncherPath, forkArgs, { env, execArgv: [] });\n\n\t} else {\n\n\t\tchildProc = spawn(launch.firefoxExecutable, launch.firefoxArgs, { env, detached: true });\n\n\t\tchildProc.stdout?.on('data', () => undefined);\n\t\tchildProc.stderr?.on('data', () => undefined);\n\n\t\tchildProc.unref();\n\t}\n\n\treturn childProc;\n}\n\nexport async function openNewTab(\n\tconfig: ParsedAttachConfiguration,\n\tdescription: FirefoxDebugProtocol.DeviceDescription\n): Promise<boolean> {\n\n\tif (!config.url) return true;\n\n\tlet firefoxExecutable = config.firefoxExecutable;\n\tif (!firefoxExecutable) {\n\n\t\tlet firefoxEdition: 'stable' | 'developer' | 'nightly' | undefined;\n\t\tif (description.channel === 'release') {\n\t\t\tfirefoxEdition = 'stable';\n\t\t} else if (description.channel === 'aurora') {\n\t\t\tfirefoxEdition = 'developer';\n\t\t} else if (description.channel === 'nightly') {\n\t\t\tfirefoxEdition = 'nightly';\n\t\t}\n\n\t\tif (firefoxEdition) {\n\t\t\tconst candidates = getExecutableCandidates(firefoxEdition);\n\t\t\tfor (let i = 0; i < candidates.length; i++) {\n\t\t\t\tif (await isExecutable(candidates[i])) {\n\t\t\t\t\tfirefoxExecutable = candidates[i];\n\t\t\t\t\tbreak;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (!firefoxExecutable) return false;\n\t}\n\n\tconst firefoxArgs = config.profileDir ? [ '--profile', config.profileDir ] : [ '-P', description.profile ];\n\tfirefoxArgs.push(config.url);\n\n\tspawn(firefoxExecutable, firefoxArgs);\n\n\treturn true;\n}\n\nasync function prepareDebugProfile(config: ParsedLaunchConfiguration): Promise<FirefoxProfile> {\n\n\tvar profile = await createDebugProfile(config);\n\n\tfor (let key in config.preferences) {\n\t\tprofile.setPreference(key, config.preferences[key]);\n\t}\n\n\tprofile.updatePreferences();\n\n\treturn profile;\n}\n\nfunction createDebugProfile(config: ParsedLaunchConfiguration): Promise<FirefoxProfile> {\n\treturn new Promise<FirefoxProfile>(async (resolve, reject) => {\n\n\t\tif (config.srcProfileDir) {\n\n\t\t\tFirefoxProfile.copy({\n\t\t\t\tprofileDirectory: config.srcProfileDir,\n\t\t\t\tdestinationDirectory: config.profileDir\n\t\t\t}, \n\t\t\t(err, profile) => {\n\t\t\t\tif (err || !profile) {\n\t\t\t\t\treject(err);\n\t\t\t\t} else {\n\t\t\t\t\tprofile.shouldDeleteOnExit(false);\n\t\t\t\t\tresolve(profile);\n\t\t\t\t}\n\t\t\t});\n\n\t\t} else {\n\n\t\t\tawait fs.ensureDir(config.profileDir);\n\t\t\tlet profile = new FirefoxProfile({\n\t\t\t\tdestinationDirectory: config.profileDir\n\t\t\t});\n\t\t\tprofile.shouldDeleteOnExit(false);\n\t\t\tresolve(profile);\n\t\t}\n\t});\n}\n"
  },
  {
    "path": "src/adapter/firefox/protocol.d.ts",
    "content": "declare namespace FirefoxDebugProtocol {\n\n\tinterface Request {\n\t\tto: string;\n\t\ttype: string;\n\t}\n\n\tinterface Response {\n\t\t[key: string]: any;\n\t\tfrom: string;\n\t}\n\n\tinterface Event extends Response {\n\t\ttype: string;\n\t}\n\n\tinterface TypedResponse extends Response {\n\t\ttype: string;\n\t}\n\n\tinterface ErrorResponse extends Response {\n\t\terror: string;\n\t\tmessage: string;\n\t}\n\n\tinterface RequestTypesResponse extends Response {\n\t\trequestTypes: string[];\n\t}\n\n\tinterface InitialResponse extends Response {\n\t\tapplicationType: string;\n\t\ttraits: {\n\t\t\tbreakpointWhileRunning?: boolean,\n\t\t\tnativeLogpoints?: boolean,\n\t\t\twatchpoints?: boolean,\n\t\t\twebExtensionAddonConnect?: boolean,\n\t\t\tnoPauseOnThreadActorAttach?: boolean\n\t\t\tsupportsEnableWindowGlobalThreadActors?: boolean\n\t\t};\n\t}\n\n\tinterface RootResponse extends Response {\n\t\tpreferenceActor: string;\n\t\taddonsActor?: string;\n\t\tdeviceActor: string;\n\t}\n\n\tinterface GetProcessResponse {\n\t\tprocessDescriptor: ProcessDescriptor;\n\t}\n\n\tinterface ProcessDescriptor {\n\t\tactor: string;\n\t\tid: number;\n\t\tisParent: boolean;\n\t\tisWindowlessParent: boolean;\n\t\ttraits: {\n\t\t\twatcher: boolean;\n\t\t\tsupportsReloadDescriptor: boolean;\n\t\t}\n\t}\n\n\tinterface TabsResponse extends Response {\n\t\ttabs: (Tab | TabDescriptor)[];\n\t}\n\n\tinterface Tab {\n\t\tactor: string;\n\t\ttitle: string;\n\t\turl: string;\n\t\tconsoleActor: string;\n\t\tthreadActor?: string;\n\t}\n\n\tinterface TabDescriptor {\n\t\tactor: string;\n\t}\n\n\tinterface TabDescriptorTargetResponse extends Response {\n\t\tframe: Tab;\n\t}\n\n\tinterface AddonsResponse extends Response {\n\t\taddons: Addon[];\n\t}\n\n\tinterface Addon {\n\t\tactor: string;\n\t\tid: string;\n\t\tname: string;\n\t\tisWebExtension?: boolean;\n\t\turl: string;\n\t\tconsoleActor?: string;\n\t\ticonURL?: string;\n\t\tdebuggable?: boolean;\n\t\ttemporarilyInstalled?: boolean;\n\t\ttraits?: {\n\t\t\thighlightable: boolean;\n\t\t\tnetworkMonitor: boolean;\n\t\t}\n\t}\n\n\tinterface InstallAddonResponse extends Response {\n\t\taddon: {\n\t\t\tid: string,\n\t\t\tactor: boolean\n\t\t};\n\t}\n\n\tinterface GetTargetResponse extends Response {\n\t\tform: {\n\t\t\tactor: string;\n\t\t\turl: string;\n\t\t\tconsoleActor: string;\n\t\t\tthreadActor: string;\n\t\t}\n\t}\n\n\tinterface DeviceDescriptionResponse extends Response {\n\t\tvalue: DeviceDescription;\n\t}\n\n\tinterface DeviceDescription {\n\t\tprofile: string;\n\t\tchannel: string;\n\t}\n\n\tinterface TabAttachedResponse extends TypedResponse {\n\t\tthreadActor: string;\n\t}\n\n\tinterface TabWillNavigateResponse extends TypedResponse {\n\t\tstate: string;\n\t\turl: string;\n\t}\n\n\tinterface TabDidNavigateResponse extends TypedResponse {\n\t\tstate: string;\n\t\turl: string;\n\t\ttitle: string;\n\t}\n\n\tinterface FramesDestroyedResponse extends TypedResponse {\n\t\tdestroyAll: true;\n\t}\n\n\tinterface FrameUpdateResponse extends TypedResponse {\n\t\tframes: {\n\t\t\tid: string;\n\t\t\tparentID?: string;\n\t\t\turl: string;\n\t\t\ttitle: string;\n\t\t}[];\n\t}\n\n\tinterface WorkersResponse extends Response {\n\t\tworkers: Worker[];\n\t}\n\n\tinterface Worker {\n\t\tactor: string;\n\t\turl: string;\n\t\ttype: number;\n\t}\n\n\tinterface WorkerAttachedResponse extends TypedResponse {\n\t\turl: string;\n\t}\n\n\tinterface WorkerConnectedResponse extends TypedResponse {\n\t\tthreadActor: string;\n\t\tconsoleActor: string;\n\t}\n\n\tinterface LegacyGetCachedMessagesResponse extends Response {\n\t\tmessages: LegacyCachedMessage[];\n\t}\n\n\ttype LegacyCachedMessage =\n\t\t(ConsoleAPICallResponseBody & { _type: 'ConsoleAPI' }) |\n\t\t(PageErrorResponseBody & { _type: 'PageError' });\n\n\tinterface GetCachedMessagesResponse extends Response {\n\t\tmessages: CachedMessage[];\n\t}\n\n\ttype CachedMessage =\n\t\t{ type: 'consoleAPICall', message: ConsoleAPICallResponseBody } |\n\t\t{ type: 'pageError', pageError: PageErrorResponseBody };\n\n\tinterface PageErrorResponse extends TypedResponse {\n\t\tpageError: PageErrorResponseBody;\n\t}\n\n\tinterface PageErrorResponseBody {\n\t\terrorMessage: string;\n\t\tsourceName: string;\n\t\tlineText: string;\n\t\tlineNumber: number;\n\t\tcolumnNumber: number;\n\t\tcategory: string;\n\t\ttimeStamp: number;\n\t\tinfo: boolean;\n\t\twarning: boolean;\n\t\terror: boolean;\n\t\texception: boolean;\n\t\tstrict: boolean;\n\t\tprivate: boolean;\n\t\tstacktrace: {\n\t\t\tfilename: string;\n\t\t\tfunctionname: string;\n\t\t\tline: number;\n\t\t\tcolumn: number;\n\t\t}[] | null;\n\t}\n\n\tinterface ConsoleAPICallResponse extends TypedResponse {\n\t\tmessage: ConsoleAPICallResponseBody;\n\t}\n\n\tinterface ConsoleAPICallResponseBody {\n\t\targuments: Grip[];\n\t\tfilename: string;\n\t\tfunctionName: string;\n\t\tgroupName: string;\n\t\tlineNumber: number;\n\t\tcolumnNumber: number;\n\t\tcategory: string;\n\t\ttimeStamp: number;\n\t\tlevel: string;\n\t\tworkerType: string;\n\t\tprivate: boolean;\n\t\tstyles: any[]; //?\n\t\tcounter: any; //?\n\t\ttimer: any; //?\n\t}\n\n\tinterface ConsoleMessage {\n\t\targuments: Grip[];\n\t\tfilename: string;\n\t\tlevel: string;\n\t\tlineNumber: number;\n\t\tcolumnNumber: number;\n\t\ttimeStamp: number;\n\t\ttimer: {\n\t\t\tname: string;\n\t\t\tduration: number;\n\t\t\terror?: string;\n\t\t};\n\t\tstyles?: any[];\n\t\t// sourceId, innerWindowID\n\t}\n\n\tinterface LogMessageResponse extends TypedResponse {\n\t\tmessage: string;\n\t\ttimeStamp: number;\n\t}\n\n\tinterface ResultIDResponse extends Response {\n\t\tresultID: string;\n\t}\n\n\tinterface EvaluationResultResponse extends TypedResponse {\n\t\tinput: string;\n\t\tresultID: string;\n\t\tresult: Grip;\n\t\texception?: Grip | null;\n\t\texceptionMessage?: string;\n\t\texceptionDocURL?: string;\n\t\ttimestamp: number;\n\t\thelperResult: any; //?\n\t}\n\n\tinterface AutoCompleteResponse extends Response {\n\t\tmatches: string[];\n\t\tmatchProp: string;\n\t}\n\n\tinterface ThreadPausedResponse extends TypedResponse {\n\t\tactor: string;\n\t\tframe: Frame;\n\t\tpoppedFrames: Frame[];\n\t\twhy: ThreadPausedReason;\n\t}\n\n\tinterface ThreadPausedReason {\n\t\ttype: 'attached' | 'interrupted' | 'resumeLimit' | 'debuggerStatement' | 'breakpoint' | 'watchpoint' |\n\t\t\t'getWatchpoint' | 'setWatchpoint' | 'clientEvaluated' | 'pauseOnDOMEvents' | 'alreadyPaused' | 'exception';\n\t\tframeFinished?: CompletionValue; // if type is 'resumeLimit' or 'clientEvaluated'\n\t\texception?: Grip; // if type is 'exception'\n\t\tactors?: string[]; // if type is 'breakpoint' or 'watchpoint'\n\t}\n\n\tinterface GetBreakableLinesResponse extends Response {\n\t\tlines: number[];\n\t}\n\n\tinterface GetBreakpointPositionsCompressedResponse extends Response {\n\t\tpositions: BreakpointPositions;\n\t}\n\n\tinterface BreakpointPositions {\n\t\t[line: string]: number[];\n\t}\n\n\tinterface SourceResponse extends Response {\n\t\tsource: Grip;\n\t}\n\n\tinterface SetBreakpointResponse extends Response {\n\t\tactor: string;\n\t\tisPending: boolean;\n\t\tactualLocation?: SourceLocation;\n\t}\n\n\tinterface PrototypeAndPropertiesResponse extends TypedResponse {\n\t\tprototype: ObjectGrip | { type: 'null' };\n\t\townProperties: PropertyDescriptors;\n\t\tsafeGetterValues?: SafeGetterValueDescriptors;\n\t\townSymbols?: NamedPropertyDescriptor[];\n\t}\n\n\tinterface GetWatcherResponse extends Response {\n\t\tactor: string;\n\t\ttraits: {\n\t\t\tcontent_script?: boolean;\n\t\t};\n\t}\n\n\tinterface GetBreakpointListResponse extends Response {\n\t\tbreakpointList: {\n\t\t\tactor: string;\n\t\t};\n\t}\n\n\tinterface GetThreadConfigurationResponse extends Response {\n\t\tconfiguration: {\n\t\t\tactor: string;\n\t\t};\n\t}\n\n\tinterface ThreadConfiguration {\n\t\tshouldPauseOnDebuggerStatement: boolean;\n\t\tpauseOnExceptions: boolean;\n\t\tignoreCaughtExceptions: boolean;\n\t\tshouldIncludeSavedFrames: boolean;\n\t\tshouldIncludeAsyncLiveFrames: boolean;\n\t\tskipBreakpoints: boolean;\n\t\tlogEventBreakpoints: boolean;\n\t\tobserveAsmJS: boolean;\n\t\tpauseOverlay: boolean;\n\t}\n\n\tinterface GetAvailableEventBreakpointsResponse extends Response {\n\t\tvalue: AvailableEventCategory[];\n\t}\n\n\tinterface AvailableEventCategory {\n\t\tname: string;\n\t\tevents: AvailableEvent[];\n\t}\n\n\tinterface AvailableEvent {\n\t\tid: string;\n\t\tname: string;\n\t\ttype: 'simple' | 'event' | 'script';\n\t\teventType?: string;\n\t\tnotificationType?: string;\n\t\ttargetTypes?: ('global' | 'node' | 'websocket' | 'worker' | 'xhr')[];\n\t}\n\n\tinterface TargetAvailableEvent extends Event {\n\t\ttype: 'target-available-form';\n\t\ttarget: {\n\t\t\turl?: string;\n\t\t\tactor: string;\n\t\t\tconsoleActor: string;\n\t\t\tthreadActor: string;\n\t\t\ttargetType?: 'process' | 'frame' | 'worker' | 'shared_worker' | 'service_worker' | 'content_script';\n\t\t\tisTopLevelTarget?: boolean;\n\t\t\tisFallbackExtensionDocument?: boolean;\n\t\t\t// set for all frame targets\n\t\t\tinnerWindowId?: number;\n\t\t\t// set for iframe targets\n\t\t\tparentInnerWindowId?: number;\n\t\t\t// set for all frame targets\n\t\t\ttopInnerWindowId?: number;\n\t\t\t// set for worker targets\n\t\t\trelatedDocumentInnerWindowId?: number;\n\t\t\taddonId?: string;\n\t\t};\n\t}\n\n\tinterface TargetDestroyedEvent extends Event {\n\t\ttype: 'target-destroyed-form';\n\t\ttarget: {\n\t\t\tactor: string;\n\t\t};\n\t}\n\n\tinterface DescriptorDestroyedEvent extends Event {\n\t\ttype: 'descriptor-destroyed';\n\t}\n\t\n\tinterface ThreadState {\n\t\tstate: 'paused' | 'resumed';\n\t\tframe?: Frame;\n\t\twhy?: ThreadPausedReason;\n\t}\n\n\tinterface CompletionValue {\n\t\treturn?: Grip;\n\t\tthrow?: Grip;\n\t\tterminated?: boolean;\n\t}\n\n\tinterface Frame {\n\t\ttype: 'global' | 'call' | 'eval' | 'clientEvaluate' | 'wasmcall';\n\t\tactor: string;\n\t\tdepth: number;\n\t\tthis?: Grip;\n\t\twhere: SourceLocation;\n\t\tenvironment?: Environment;\n\t}\n\n\tinterface GlobalFrame extends Frame {\n\t\tsource: Source;\n\t}\n\n\tinterface CallFrame extends Frame {\n\t\tdisplayName?: string;\n\t\targuments: Grip[];\n\t}\n\n\tinterface EvalFrame extends Frame {\n\t}\n\n\tinterface ClientEvalFrame extends Frame {\n\t}\n\n\tinterface SourceLocation {\n\t\tactor: string;\n\t\tline?: number;\n\t\tcolumn?: number;\n\t}\n\n\tinterface Source {\n\t\tactor: string;\n\t\turl: string | null;\n\t\tintroductionType?: 'scriptElement' | 'eval' | 'Function' | 'debugger eval' | 'wasm' | null;\n\t\tintroductionUrl: string | null;\n\t\tisBlackBoxed: boolean;\n\t\tisPrettyPrinted: boolean;\n\t\tisSourceMapped: boolean;\n\t\tgeneratedUrl: string | null;\n\t\tsourceMapURL: string | null;\n\t\taddonID?: string;\n\t\taddonPath?: string;\n\t}\n\n\tinterface SourceResource extends Source {\n\t\tresourceType: 'source';\n\t}\n\n\tinterface ConsoleMessageResource {\n\t\tresourceType: 'console-message';\n\t\tmessage: ConsoleMessage;\n\t}\n\n\tinterface ErrorMessageResource {\n\t\tresourceType: 'error-message';\n\t\tpageError: PageError;\n\t}\n\n\tinterface ThreadStateResource extends ThreadState {\n\t\tresourceType: 'thread-state';\n\t}\n\n\tinterface ResourceAvailableForm extends Event {\n\t\ttype: 'resource-available-form';\n\t\tresources: (SourceResource | ConsoleMessageResource | ErrorMessageResource | ThreadStateResource)[];\n\t}\n\n\ttype Sources = ['source', Source[]];\n\ttype ConsoleMessages = ['console-message', ConsoleMessage[]];\n\ttype ErrorMessages = ['error-message', PageError[]];\n\ttype ThreadStates = ['thread-state', ThreadState[]];\n\ttype Resources = (Sources | ConsoleMessages | ErrorMessages | ThreadStates)[];\n\n\tinterface ResourcesAvailableEvent extends Event {\n\t\ttype: 'resources-available-array';\n\t\tarray: Resources;\n\t}\n\n\tinterface FrameUpdateEvent extends Event {\n\t\ttype: 'frameUpdate';\n\t}\n\n\tinterface PageError {\n\t\terrorMessage: string;\n\t\tsourceName: string;\n\t\tlineText: string;\n\t\tlineNumber: number;\n\t\tcolumnNumber: number;\n\t\tcategory: string;\n\t\ttimeStamp: number;\n\t\tinfo: boolean;\n\t\twarning: boolean;\n\t\terror: boolean;\n\t\texception: boolean;\n\t\tstrict: boolean;\n\t\tprivate: boolean;\n\t\tstacktrace: {\n\t\t\tfilename: string;\n\t\t\tfunctionname: string;\n\t\t\tline: number;\n\t\t\tcolumn: number;\n\t\t}[] | null;\n\t}\n\t\n\tinterface Environment {\n\t\ttype?: 'object' | 'function' | 'with' | 'block';\n\t\tactor?: string;\n\t\tparent?: Environment;\n\t}\n\n\tinterface ObjectEnvironment extends Environment {\n\t\tobject: Grip;\n\t}\n\n\tinterface FunctionEnvironment extends Environment {\n\t\tfunction: {\n\t\t\tdisplayName: string;\n\t\t};\n\t\tbindings: FunctionBindings;\n\t}\n\n\tinterface WithEnvironment extends Environment {\n\t\tobject: Grip;\n\t}\n\n\tinterface BlockEnvironment extends Environment {\n\t\tbindings: Bindings;\n\t}\n\n\tinterface Bindings {\n\t\tvariables: PropertyDescriptors;\n\t}\n\n\tinterface FunctionBindings extends Bindings {\n\t\targuments: PropertyDescriptors[];\n\t}\n\n\tinterface PropertyDescriptor {\n\t\tenumerable: boolean;\n\t\tconfigurable: boolean;\n\t}\n\n\tinterface DataPropertyDescriptor extends PropertyDescriptor {\n\t\tvalue: Grip;\n\t\twritable: boolean;\n\t}\n\n\tinterface AccessorPropertyDescriptor extends PropertyDescriptor {\n\t\tget: Grip;\n\t\tset: Grip;\n\t}\n\n\tinterface SafeGetterValueDescriptor {\n\t\tgetterValue: Grip;\n\t\tgetterPrototypeLevel: number;\n\t\tenumerable: boolean;\n\t\twritable: boolean;\n\t}\n\n\tinterface PropertyDescriptors {\n\t\t[name: string]: PropertyDescriptor;\n\t}\n\n\tinterface SafeGetterValueDescriptors {\n\t\t[name: string]: SafeGetterValueDescriptor;\n\t}\n\n\tinterface NamedPropertyDescriptor {\n\t\tname: string;\n\t\tdescriptor: PropertyDescriptor;\n\t}\n\n\tinterface NamedDataPropertyDescriptor {\n\t\tname: string;\n\t\tdescriptor: DataPropertyDescriptor;\n\t}\n\n\ttype Grip = boolean | number | string | ComplexGrip;\n\n\tinterface ComplexGrip {\n\t\ttype: 'null' | 'undefined' | 'Infinity' | '-Infinity' | 'NaN' | '-0' | 'BigInt' | 'longString' | 'symbol' | 'object';\n\t}\n\n\tinterface ObjectGrip extends ComplexGrip {\n\t\ttype: 'object';\n\t\tclass: string;\n\t\tactor: string;\n\t\tpreview?: Preview;\n\t}\n\n\ttype Preview = ObjectPreview | DatePreview | ObjectWithURLPreview | DOMNodePreview |\n\t\tDOMEventPreview | ArrayLikePreview | ErrorPreview;\n\n\tinterface ObjectPreview {\n\t\tkind: 'Object';\n\t\townProperties: { [name: string]: PropertyPreview };\n\t\townPropertiesLength: number;\n\t\townSymbols?: NamedDataPropertyDescriptor[];\n\t\townSymbolsLength?: number;\n\t\tsafeGetterValues?: { [name: string]: SafeGetterValuePreview };\n\t}\n\n\tinterface PropertyPreview {\n\t\tconfigurable: boolean;\n\t\tenumerable: boolean;\n\t\twritable?: boolean;\n\t\tvalue?: Grip;\n\t\tget?: FunctionGrip;\n\t\tset?: FunctionGrip;\n\t}\n\n\tinterface DatePreview {\n\t\tkind: undefined;\n\t\ttimestamp: number;\n\t}\n\n\tinterface ObjectWithURLPreview {\n\t\tkind: 'ObjectWithURL';\n\t\turl: string;\n\t}\n\n\tinterface DOMNodePreview {\n\t\tkind: 'DOMNode';\n\t\tnodeType: number;\n\t\tnodeName: string;\n\t\tisConnected: boolean;\n\t\tlocation?: string;\n\t\tattributes?: { [name: string]: string };\n\t\tattributesLength?: number;\n\t}\n\n\tinterface DOMEventPreview {\n\t\tkind: 'DOMEvent';\n\t\ttype: string;\n\t\tproperties: Object;\n\t\ttarget?: ObjectGrip;\n\t}\n\n\tinterface ArrayLikePreview {\n\t\tkind: 'ArrayLike';\n\t\tlength: number;\n\t\titems?: (Grip | null)[];\n\t}\n\n\tinterface SafeGetterValuePreview {\n\t\tgetterValue: Grip;\n\t\tgetterPrototypeLevel: number;\n\t\tenumerable: boolean;\n\t\twritable: boolean;\n\t}\n\n\tinterface ErrorPreview {\n\t\tkind: 'Error';\n\t\tname: string;\n\t\tmessage: string;\n\t\tfileName: string;\n\t\tlineNumber: number;\n\t\tcolumnNumber: number;\n\t\tstack: string;\n\t}\n\n\tinterface FunctionGrip extends ObjectGrip {\n\t\tname?: string;\n\t\tdisplayName?: string;\n\t\tuserDisplayName?: string;\n\t\tparameterNames?: string[];\n\t\tlocation?: {\n\t\t\turl: string;\n\t\t\tline?: number;\n\t\t\tcolumn?: number;\n\t\t};\n\t}\n\n\tinterface LongStringGrip extends ComplexGrip {\n\t\ttype: 'longString';\n\t\tinitial: string;\n\t\tlength: number;\n\t\tactor: string;\n\t}\n\n\tinterface SymbolGrip extends ComplexGrip {\n\t\ttype: 'symbol';\n\t\tname: string;\n\t}\n\n\tinterface BigIntGrip extends ComplexGrip {\n\t\ttype: 'BigInt';\n\t\ttext: string;\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/sourceMaps/README.md",
    "content": "This folder contains the debug adapter's source-map support.\n\nIt contains the [`SourceMappingSourceActorProxy`](./source.ts), which implements the same interface\nas the [`SourceActorProxy`](../actorProxy/source.ts).\nWhen debugging source-mapped sources, there will be a `SourceActorProxy` for each generated source\nand, on top of that, a `SourceMappingSourceActorProxy` for each original source. Unlike other\n`ActorProxy` implementations, a `SourceMappingSourceActorProxy` does not correspond (and talk)\ndirectly to a Firefox actor. Instead, it forwards all requests, responses and events to/from the\n`SourceActorProxy` for the corresponding generated source, translating between original and\ngenerated locations on-the-fly.\n\nLikewise, there is the [`SourceMappingThreadActorProxy`](./thread.ts) working on top of a\nregular [`ThreadActorProxy`](../actorProxy/thread.ts), fetching and processing source-maps, creating the\n`SourceMappingSourceActorProxy` for each original source and translating between original and\ngenerated locations in the messages forwarded to/from the corresponding `ThreadActorProxy`.\n"
  },
  {
    "path": "src/adapter/firefox/sourceMaps/info.ts",
    "content": "import * as url from 'url';\nimport { Log } from '../../util/log';\nimport { isWindowsPlatform as detectWindowsPlatform } from '../../../common/util';\nimport { ISourceActorProxy, SourceActorProxy } from '../actorProxy/source';\nimport { SourceMapConsumer, BasicSourceMapConsumer, MappingItem } from 'source-map';\nimport { UrlLocation, LocationWithColumn } from '../../location';\n\nlet GREATEST_LOWER_BOUND = SourceMapConsumer.GREATEST_LOWER_BOUND;\nlet LEAST_UPPER_BOUND = SourceMapConsumer.LEAST_UPPER_BOUND;\n\nconst isWindowsPlatform = detectWindowsPlatform();\nconst windowsAbsolutePathRegEx = /^[a-zA-Z]:[\\/\\\\]/;\n\ndeclare module \"source-map\" {\n\tinterface MappingItem {\n\t\tlastGeneratedColumn?: number | null;\n\t}\n}\n\nconst log = Log.create('SourceMappingInfo');\n\nexport class SourceMappingInfo {\n\n\tprivate columnSpansComputed = false;\n\n\tpublic get hasSourceMap(): boolean { return !!this.sourceMapConsumer; }\n\n\tpublic constructor(\n\t\tpublic readonly sources: ISourceActorProxy[],\n\t\tpublic readonly underlyingSource: SourceActorProxy,\n\t\tpublic readonly sourceMapUri?: string,\n\t\tprivate readonly sourceMapConsumer?: BasicSourceMapConsumer,\n\t\tprivate readonly sourceRoot?: string\n\t) {}\n\n\tpublic computeColumnSpans(): void {\n\t\tif (this.sourceMapConsumer && !this.columnSpansComputed) {\n\t\t\tthis.sourceMapConsumer.computeColumnSpans();\n\t\t\tthis.columnSpansComputed = true;\n\t\t}\n\t}\n\n\tpublic originalLocationFor(generatedLocation: LocationWithColumn): UrlLocation | undefined {\n\n\t\tif (!this.sourceMapConsumer) {\n\t\t\treturn { ...generatedLocation, url: this.sources[0].url || undefined };\n\t\t}\n\n\t\tlet consumerArgs = {\n\t\t\tline: generatedLocation.line,\n\t\t\tcolumn: generatedLocation.column || 0,\n\t\t\tbias: GREATEST_LOWER_BOUND\n\t\t};\n\n\t\tif (this.underlyingSource.source.introductionType === 'wasm') {\n\t\t\tconsumerArgs.column = consumerArgs.line;\n\t\t\tconsumerArgs.line = 1;\n\t\t}\n\n\t\tlet originalLocation = this.sourceMapConsumer.originalPositionFor(consumerArgs);\n\n\t\tif (originalLocation.source === null) {\n\t\t\tconsumerArgs.bias = LEAST_UPPER_BOUND;\n\t\t\toriginalLocation = this.sourceMapConsumer.originalPositionFor(consumerArgs);\n\t\t}\n\n\t\tif (originalLocation.source === null) {\n\t\t\tlog.warn(`Got original location ${JSON.stringify(originalLocation)} for generated location ${JSON.stringify(generatedLocation)}`);\n\t\t\treturn undefined;\n\t\t}\n\n\t\toriginalLocation.source = this.resolveSource(originalLocation.source);\n\n\t\tif ((this.underlyingSource.source.introductionType === 'wasm') && originalLocation.line) {\n\t\t\toriginalLocation.line--;\n\t\t}\n\n\t\tif (originalLocation.line !== null) {\n\t\t\treturn {\n\t\t\t\turl: originalLocation.source,\n\t\t\t\tline: originalLocation.line,\n\t\t\t\tcolumn: (originalLocation.column !== null) ? originalLocation.column : undefined\n\t\t\t};\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic eachMapping(callback: (mapping: MappingItem) => void): void {\n\t\tif (this.sourceMapConsumer) {\n\t\t\tthis.sourceMapConsumer.eachMapping(mappingItem => {\n\t\t\t\tconst lastGeneratedColumn = (mappingItem.lastGeneratedColumn !== undefined) ? (mappingItem.lastGeneratedColumn || mappingItem.generatedColumn) : undefined;\n\t\t\t\tcallback({\n\t\t\t\t\t...mappingItem,\n\t\t\t\t\toriginalColumn: mappingItem.originalColumn,\n\t\t\t\t\tgeneratedColumn: mappingItem.generatedColumn,\n\t\t\t\t\tlastGeneratedColumn\n\t\t\t\t});\n\t\t\t}, undefined, SourceMapConsumer.GENERATED_ORDER);\n\t\t}\n\t}\n\n\tpublic sourceContentFor(source: string): string | undefined {\n\t\tif (this.sourceMapConsumer) {\n\t\t\treturn this.sourceMapConsumer.sourceContentFor(source) || undefined;\n\t\t}\n\t\treturn undefined;\n\t}\n\n\tpublic syncBlackboxFlag(): void {\n\n\t\tif ((this.sources.length === 1) && (this.sources[0] === this.underlyingSource)) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet blackboxUnderlyingSource = this.sources.every((source) => source.source.isBlackBoxed);\n\t\tif (this.underlyingSource.source.isBlackBoxed !== blackboxUnderlyingSource) {\n\t\t\tthis.underlyingSource.setBlackbox(blackboxUnderlyingSource);\n\t\t}\n\t}\n\n\tpublic disposeSource(source: ISourceActorProxy): void {\n\n\t\tlet sourceIndex = this.sources.indexOf(source);\n\t\tif (sourceIndex >= 0) {\n\n\t\t\tthis.sources.splice(sourceIndex, 1);\n\n\t\t\tif (this.sources.length === 0) {\n\t\t\t\tthis.underlyingSource.dispose();\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic resolveSource(sourceUrl: string): string {\n\n\t\t\t// some tools (e.g. create-react-app) use absolute _paths_ instead of _urls_ here,\n\t\t\t// we work around this bug by converting anything that looks like an absolute path\n\t\t\t// into a url\n\t\t\tif (isWindowsPlatform)\n\t\t\t{\n\t\t\t\tif (windowsAbsolutePathRegEx.test(sourceUrl)) {\n\t\t\t\t\tsourceUrl = encodeURI('file:///' + sourceUrl.replace(/\\\\/g, '/'));\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tif (sourceUrl.startsWith('/')) {\n\t\t\t\t\tsourceUrl = encodeURI('file://' + sourceUrl);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (this.sourceRoot) {\n\t\t\t\tsourceUrl = url.resolve(this.sourceRoot, sourceUrl);\n\t\t\t}\n\n\t\t\treturn sourceUrl;\n\t}\n\n\tpublic findUnresolvedSource(resolvedSource: string): string | undefined {\n\t\tif (!this.sourceMapConsumer) return undefined;\n\n\t\tfor (const source of this.sourceMapConsumer.sources) {\n\n\t\t\tif ((source === resolvedSource) ||\n\t\t\t\t(this.resolveSource(source) === resolvedSource)) {\n\n\t\t\t\treturn source;\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/sourceMaps/manager.ts",
    "content": "import * as url from 'url';\nimport * as fs from 'fs-extra';\nimport isAbsoluteUrl from 'is-absolute-url';\nimport { SourceMapConsumer, RawSourceMap } from 'source-map';\nimport { Log } from '../../util/log';\nimport { getUri, urlDirname } from '../../util/net';\nimport { PathMapper } from '../../util/pathMapper';\nimport { PendingRequest } from '../../util/pendingRequests';\nimport { DebugConnection } from '../connection';\nimport { SourceActorProxy } from '../actorProxy/source';\nimport { SourceMappingSourceActorProxy } from './source';\nimport { SourceMappingInfo } from './info';\nimport { UrlLocation } from '../../location';\nimport { SourcesManager } from '../../adapter/sourcesManager';\n\nlet log = Log.create('SourceMapsManager');\n\nexport class SourceMapsManager {\n\n\tprivate sourceMappingInfos = new Map<string, Promise<SourceMappingInfo>>();\n\tprivate pendingSources = new Map<string, PendingRequest<SourceMappingInfo>>();\n\n\tpublic constructor(\n\t\tprivate readonly pathMapper: PathMapper,\n\t\tprivate readonly sources: SourcesManager,\n\t\tprivate readonly connection: DebugConnection\n\t) {}\n\n\tpublic getOrCreateSourceMappingInfo(source: FirefoxDebugProtocol.Source): Promise<SourceMappingInfo> {\n\n\t\tif (this.sourceMappingInfos.has(source.actor)) {\n\n\t\t\tif (this.pendingSources.has(source.actor)) {\n\n\t\t\t\tconst pending = this.pendingSources.get(source.actor)!;\n\t\t\t\tthis.pendingSources.delete(source.actor);\n\n\t\t\t\t(async () => {\n\t\t\t\t\ttry {\n\n\t\t\t\t\t\tconst sourceMappingInfos = await this.createSourceMappingInfo(source);\n\t\t\t\t\t\tpending.resolve(sourceMappingInfos);\n\n\t\t\t\t\t} catch(e) {\n\t\t\t\t\t\tpending.reject(e);\n\t\t\t\t\t}\n\t\t\t\t})();\n\t\t\t}\n\n\t\t\treturn this.sourceMappingInfos.get(source.actor)!;\n\n\t\t} else {\n\n\t\t\tlet sourceMappingInfoPromise = this.createSourceMappingInfo(source);\n\t\t\tthis.sourceMappingInfos.set(source.actor, sourceMappingInfoPromise);\n\t\t\treturn sourceMappingInfoPromise;\n\t\t}\n\t}\n\n\tpublic getSourceMappingInfo(actor: string): Promise<SourceMappingInfo> {\n\n\t\tif (this.sourceMappingInfos.has(actor)) {\n\n\t\t\treturn this.sourceMappingInfos.get(actor)!;\n\n\t\t} else {\n\n\t\t\tconst promise = new Promise<SourceMappingInfo>((resolve, reject) => {\n\t\t\t\tthis.pendingSources.set(actor, { resolve, reject });\n\t\t\t});\n\n\t\t\tthis.sourceMappingInfos.set(actor, promise);\n\n\t\t\treturn promise;\n\t\t}\n\t}\n\n\tpublic async findOriginalLocation(\n\t\tgeneratedUrl: string,\n\t\tline: number,\n\t\tcolumn?: number\n\t): Promise<UrlLocation | undefined> {\n\n\t\tif (generatedUrl === 'debugger eval code') {\n\t\t\treturn undefined;\n\t\t}\n\n\t\t// sometimes Firefox sends a location shortly before sending information about\n\t\t// the corresponding source, so we try to wait for it\n\t\tconst adapter = await Promise.race([\n\t\t\tthis.sources.getAdapterForUrl(generatedUrl),\n\t\t\tnew Promise(resolve => setTimeout(resolve, 200))\n\t\t]);\n\t\tif (!adapter) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tfor (const infoPromise of this.sourceMappingInfos.values()) {\n\t\t\tconst info = await infoPromise;\n\t\t\tif (generatedUrl === info.underlyingSource.url) {\n\n\t\t\t\tconst originalLocation = info.originalLocationFor({ line, column: column || 0 });\n\n\t\t\t\tif (originalLocation && originalLocation.url && originalLocation.line) {\n\t\t\t\t\treturn {\n\t\t\t\t\t\turl: originalLocation.url,\n\t\t\t\t\t\tline: originalLocation.line,\n\t\t\t\t\t\tcolumn: originalLocation.column || 0\n\t\t\t\t\t};\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tpublic async applySourceMapToFrame(frame: FirefoxDebugProtocol.Frame): Promise<void> {\n\n\t\tconst sourceMappingInfo = await this.getSourceMappingInfo(frame.where.actor);\n\t\tconst source = sourceMappingInfo.underlyingSource.source;\n\n\t\tif (source && sourceMappingInfo && sourceMappingInfo.hasSourceMap && frame.where.line) {\n\n\t\t\tlet originalLocation = sourceMappingInfo.originalLocationFor({\n\t\t\t\tline: frame.where.line, column: frame.where.column || 0\n\t\t\t});\n\n\t\t\tif (originalLocation && originalLocation.url) {\n\n\t\t\t\tframe.where = {\n\t\t\t\t\tactor: `${source.actor}!${originalLocation.url}`,\n\t\t\t\t\tline: originalLocation.line || undefined,\n\t\t\t\t\tcolumn: originalLocation.column || undefined\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate async createSourceMappingInfo(source: FirefoxDebugProtocol.Source): Promise<SourceMappingInfo> {\n\n\t\tif (log.isDebugEnabled()) {\n\t\t\tlog.debug(`Trying to sourcemap ${JSON.stringify(source)}`);\n\t\t}\n\n\t\tlet sourceActor = this.connection.getOrCreate(\n\t\t\tsource.actor, () => new SourceActorProxy(source, this.connection));\n\n\t\tlet sourceMapUrl = source.sourceMapURL;\n\t\tif (!sourceMapUrl) {\n\t\t\treturn new SourceMappingInfo([sourceActor], sourceActor);\n\t\t}\n\n\t\tif (!isAbsoluteUrl(sourceMapUrl)) {\n\t\t\tif (source.url) {\n\t\t\t\tsourceMapUrl = url.resolve(urlDirname(source.url), sourceMapUrl);\n\t\t\t} else {\n\t\t\t\tlog.warn(`Can't create absolute sourcemap URL from ${sourceMapUrl} - giving up`);\n\t\t\t\treturn new SourceMappingInfo([sourceActor], sourceActor);\n\t\t\t}\n\t\t}\n\n\t\tlet rawSourceMap: RawSourceMap | undefined = undefined;\n\t\ttry {\n\n\t\t\tconst sourceMapPath = this.pathMapper.convertFirefoxUrlToPath(sourceMapUrl);\n\t\t\tif (sourceMapPath && !isAbsoluteUrl(sourceMapPath)) {\n\t\t\t\ttry {\n\t\t\t\t\t// TODO support remote development - this only works for local files\n\t\t\t\t\tconst sourceMapString = await fs.readFile(sourceMapPath, 'utf8');\n\t\t\t\t\tlog.debug('Loaded sourcemap from disk');\n\t\t\t\t\trawSourceMap = JSON.parse(sourceMapString);\n\t\t\t\t\tlog.debug('Parsed sourcemap');\n\t\t\t\t} catch(e) {\n\t\t\t\t\tlog.debug(`Failed reading sourcemap from ${sourceMapPath} - trying to fetch it from ${sourceMapUrl}`);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!rawSourceMap) {\n\t\t\t\tconst sourceMapString = await getUri(sourceMapUrl);\n\t\t\t\tlog.debug('Received sourcemap');\n\t\t\t\trawSourceMap = JSON.parse(sourceMapString);\n\t\t\t\tlog.debug('Parsed sourcemap');\n\t\t\t}\n\n\t\t} catch(e) {\n\t\t\tlog.warn(`Failed fetching sourcemap from ${sourceMapUrl} - giving up`);\n\t\t\treturn new SourceMappingInfo([sourceActor], sourceActor);\n\t\t}\n\n\t\tlet sourceMapConsumer = await new SourceMapConsumer(rawSourceMap!);\n\t\tlet sourceMappingSourceActors: SourceMappingSourceActorProxy[] = [];\n\t\tlet sourceRoot = rawSourceMap!.sourceRoot;\n\t\tif (!sourceRoot && source.url) {\n\t\t\tsourceRoot = urlDirname(source.url);\n\t\t} else if ((sourceRoot !== undefined) && !isAbsoluteUrl(sourceRoot)) {\n\t\t\tsourceRoot = url.resolve(sourceMapUrl, sourceRoot);\n\t\t}\n\t\tlog.debug('Created SourceMapConsumer');\n\n\t\tlet sourceMappingInfo = new SourceMappingInfo(\n\t\t\tsourceMappingSourceActors, sourceActor, sourceMapUrl, sourceMapConsumer, sourceRoot);\n\n\t\tfor (let origSource of sourceMapConsumer.sources) {\n\n\t\t\torigSource = sourceMappingInfo.resolveSource(origSource);\n\n\t\t\tlet sourceMappingSource = this.createOriginalSource(source, origSource, sourceMapUrl);\n\n\t\t\tlet sourceMappingSourceActor = new SourceMappingSourceActorProxy(\n\t\t\t\tsourceMappingSource, sourceMappingInfo);\n\n\t\t\tsourceMappingSourceActors.push(sourceMappingSourceActor);\n\t\t}\n\n\t\treturn sourceMappingInfo;\n\t}\n\n\tprivate createOriginalSource(\n\t\tgeneratedSource: FirefoxDebugProtocol.Source,\n\t\toriginalSourceUrl: string | null,\n\t\tsourceMapUrl: string\n\t): FirefoxDebugProtocol.Source {\n\n\t\treturn <FirefoxDebugProtocol.Source>{\n\t\t\tactor: `${generatedSource.actor}!${originalSourceUrl}`,\n\t\t\turl: originalSourceUrl,\n\t\t\tintroductionUrl: generatedSource.introductionUrl,\n\t\t\tintroductionType: generatedSource.introductionType,\n\t\t\tgeneratedUrl: generatedSource.url,\n\t\t\tisBlackBoxed: false,\n\t\t\tisPrettyPrinted: false,\n\t\t\tisSourceMapped: true,\n\t\t\tsourceMapURL: sourceMapUrl\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/sourceMaps/source.ts",
    "content": "import { Log } from '../../util/log';\nimport { ISourceActorProxy, SourceActorProxy } from '../actorProxy/source';\nimport { SourceMappingInfo } from './info';\nimport { getUri } from '../../util/net';\nimport { MappedLocation, Range } from '../../location';\n\nconst log = Log.create('SourceMappingSourceActorProxy');\n\ninterface Breakables {\n\tlines: number[];\n\tlocations: Map<number, MappedLocation[]>;\n}\n\nexport class SourceMappingSourceActorProxy implements ISourceActorProxy {\n\n\tpublic get name(): string {\n\t\treturn this.source.actor;\n\t}\n\n\tpublic get url(): string {\n\t\treturn this.source.url!;\n\t}\n\n\tpublic get underlyingActor(): SourceActorProxy { return this.sourceMappingInfo.underlyingSource; }\n\n\tprivate allBreakablesPromise?: Promise<Breakables>;\n\n\tpublic constructor(\n\t\tpublic readonly source: FirefoxDebugProtocol.Source,\n\t\tprivate readonly sourceMappingInfo: SourceMappingInfo\n\t) {}\n\n\tpublic async getBreakableLines(): Promise<number[]> {\n\n\t\tif (!this.allBreakablesPromise) {\n\t\t\tthis.allBreakablesPromise = this.getAllBreakables();\n\t\t}\n\n\t\tconst allBreakables = await this.allBreakablesPromise;\n\t\treturn allBreakables.lines;\n\t}\n\n\tpublic async getBreakableLocations(line: number): Promise<MappedLocation[]> {\n\n\t\tif (!this.allBreakablesPromise) {\n\t\t\tthis.allBreakablesPromise = this.getAllBreakables();\n\t\t}\n\n\t\tconst allBreakableLocations = await this.allBreakablesPromise;\n\t\treturn allBreakableLocations.locations.get(line) || [];\n\t}\n\n\tprivate async getAllBreakables(): Promise<Breakables> {\n\n\t\tthis.sourceMappingInfo.computeColumnSpans();\n\n\t\tif (log.isDebugEnabled()) log.debug(`Calculating ranges for ${this.url} within its generated source`);\n\t\tconst unresolvedSource = this.sourceMappingInfo.findUnresolvedSource(this.source.url!);\n\t\tconst generatedRanges: Range[] = [];\n\t\tlet currentRange: Range | undefined = undefined;\n\t\tthis.sourceMappingInfo.eachMapping(mapping => {\n\n\t\t\tif (mapping.source === unresolvedSource) {\n\n\t\t\t\tif (!currentRange) {\n\t\t\t\t\tcurrentRange = {\n\t\t\t\t\t\tstart: {\n\t\t\t\t\t\t\tline: mapping.generatedLine,\n\t\t\t\t\t\t\tcolumn: mapping.generatedColumn\n\t\t\t\t\t\t},\n\t\t\t\t\t\tend: {\n\t\t\t\t\t\t\tline: mapping.generatedLine,\n\t\t\t\t\t\t\tcolumn: mapping.lastGeneratedColumn || 0\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\tconst lastGeneratedColumn = (mapping as any).lastGeneratedColumn || mapping.generatedColumn;\n\t\t\t\t\tcurrentRange.end = {\n\t\t\t\t\t\tline: mapping.generatedLine,\n\t\t\t\t\t\tcolumn: lastGeneratedColumn\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tif (currentRange) {\n\t\t\t\t\tgeneratedRanges.push(currentRange);\n\t\t\t\t\tcurrentRange = undefined;\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t\tif (currentRange) {\n\t\t\tgeneratedRanges.push(currentRange);\n\t\t}\n\n\t\tconst mappedBreakableLocations = new Map<number, MappedLocation[]>();\n\t\tconst originalBreakableColumns = new Map<number, Set<number>>();\n\t\tfor (const range of generatedRanges) {\n\n\t\t\tif (log.isDebugEnabled()) log.debug(`Fetching generated breakpoint locations for ${this.url}, ${range.start.line}:${range.start.column} - ${range.end.line}:${range.end.column}`);\n\t\t\tconst generatedBreakableLocations =\n\t\t\t\tawait this.sourceMappingInfo.underlyingSource.getBreakpointPositionsForRange(range);\n\n\t\t\tif (log.isDebugEnabled()) log.debug(`Computing original breakpoint locations for ${Object.keys(generatedBreakableLocations).length} generated lines`);\n\t\t\tfor (const generatedLineString in generatedBreakableLocations) {\n\t\t\t\tfor (const generatedColumn of generatedBreakableLocations[generatedLineString]) {\n\n\t\t\t\t\tconst generatedLine = +generatedLineString;\n\t\t\t\t\tconst originalLocation = this.sourceMappingInfo.originalLocationFor({\n\t\t\t\t\t\tline: generatedLine,\n\t\t\t\t\t\tcolumn: generatedColumn\n\t\t\t\t\t});\n\t\t\t\t\tif ((originalLocation === undefined) ||\n\t\t\t\t\t\t(originalLocation.line === null) ||\n\t\t\t\t\t\t(originalLocation.url !== this.url)) {\n\t\t\t\t\t\tcontinue;\n\t\t\t\t\t}\n\n\t\t\t\t\tif (!mappedBreakableLocations.has(originalLocation.line)) {\n\t\t\t\t\t\tmappedBreakableLocations.set(originalLocation.line, []);\n\t\t\t\t\t\toriginalBreakableColumns.set(originalLocation.line, new Set<number>());\n\t\t\t\t\t}\n\n\t\t\t\t\tconst originalColumn = originalLocation.column || 0;\n\t\t\t\t\tif (!originalBreakableColumns.get(originalLocation.line)!.has(originalColumn)) {\n\t\t\t\t\t\tmappedBreakableLocations.get(originalLocation.line)!.push({\n\t\t\t\t\t\t\tline: originalLocation.line,\n\t\t\t\t\t\t\tcolumn: originalColumn,\n\t\t\t\t\t\t\tgenerated: {\n\t\t\t\t\t\t\t\tline: generatedLine,\n\t\t\t\t\t\t\t\tcolumn: generatedColumn\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t});\n\t\t\t\t\t\toriginalBreakableColumns.get(originalLocation.line)!.add(originalColumn);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tconst breakableLines: number[] = [];\n\t\tfor (const line of mappedBreakableLocations.keys()) {\n\t\t\tif (mappedBreakableLocations.get(line)!.length > 0) {\n\t\t\t\tbreakableLines.push(+line);\n\t\t\t}\n\t\t}\n\t\tbreakableLines.sort();\n\n\t\treturn {\n\t\t\tlines: breakableLines,\n\t\t\tlocations: mappedBreakableLocations\n\t\t};\n\t}\n\n\tpublic async fetchSource(): Promise<FirefoxDebugProtocol.Grip> {\n\t\tif (log.isDebugEnabled()) log.debug(`Fetching source for ${this.url}`);\n\t\tlet embeddedSource = this.sourceMappingInfo.sourceContentFor(this.url);\n\t\tif (embeddedSource) {\n\t\t\tif (log.isDebugEnabled()) log.debug(`Got embedded source for ${this.url}`);\n\t\t\treturn embeddedSource;\n\t\t} else {\n\t\t\tconst source = await getUri(this.url);\n\t\t\tif (log.isDebugEnabled()) log.debug(`Got non-embedded source for ${this.url}`);\n\t\t\treturn source;\n\t\t}\n\t}\n\n\tpublic async setBlackbox(blackbox: boolean): Promise<void> {\n\t\tthis.source.isBlackBoxed = blackbox;\n\t\tthis.sourceMappingInfo.syncBlackboxFlag();\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.sourceMappingInfo.disposeSource(this);\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/sourceMaps/thread.ts",
    "content": "import { EventEmitter } from 'events';\nimport { Log } from '../../util/log';\nimport { DebugConnection } from '../connection';\nimport { IThreadActorProxy } from '../actorProxy/thread';\n\nlet log = Log.create('SourceMappingThreadActorProxy');\n\nexport class SourceMappingThreadActorProxy extends EventEmitter implements IThreadActorProxy {\n\n\tpublic constructor(\n\t\tprivate readonly underlyingActorProxy: IThreadActorProxy,\n\t\tprivate readonly connection: DebugConnection\n\t) {\n\t\tsuper();\n\t}\n\n\tpublic get name(): string {\n\t\treturn this.underlyingActorProxy.name;\n\t}\n\n\tpublic async fetchStackFrames(\n\t\tstart?: number,\n\t\tcount?: number\n\t): Promise<FirefoxDebugProtocol.Frame[]> {\n\n\t\tlet stackFrames = await this.underlyingActorProxy.fetchStackFrames(start, count);\n\n\t\tawait Promise.all(stackFrames.map((frame) => this.connection.sourceMaps.applySourceMapToFrame(frame)));\n\n\t\treturn stackFrames;\n\t}\n\n\tpublic resume(resumeLimitType?: 'next' | 'step' | 'finish' | 'restart', frameActorID?: string): Promise<void> {\n\t\treturn this.underlyingActorProxy.resume(resumeLimitType, frameActorID);\n\t}\n\n\tpublic interrupt(immediately: boolean = true): Promise<void> {\n\t\treturn this.underlyingActorProxy.interrupt(immediately);\n\t}\n\n\tpublic async getAvailableEventBreakpoints() : Promise<FirefoxDebugProtocol.AvailableEventCategory[]> {\n\t\treturn this.underlyingActorProxy.getAvailableEventBreakpoints();\n\t}\n\n\tpublic dispose(): void {\n\t\tthis.underlyingActorProxy.dispose();\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefox/transport.ts",
    "content": "import { Socket } from 'net';\nimport { Log } from '../util/log';\nimport { EventEmitter } from 'events';\n\nlet log = Log.create('DebugProtocolTransport');\n\n/**\n * Implements the Remote Debugging Protocol Stream Transport as defined in\n * https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#stream-transport\n * Currently bulk data packets are unsupported and error handling is nonexistent\n */\nexport class DebugProtocolTransport extends EventEmitter {\n\n\tprivate static initialBufferLength = 11; // must be large enough to receive a complete header\n\tprivate buffer: Buffer;\n\tprivate bufferedLength: number;\n\tprivate receivingHeader: boolean;\n\n\tconstructor(\n\t\tprivate socket: Socket\n\t) {\n\t\tsuper();\n\n\t\tthis.buffer = Buffer.alloc(DebugProtocolTransport.initialBufferLength);\n\t\tthis.bufferedLength = 0;\n\t\tthis.receivingHeader = true;\n\t\tlet firstChunkReceived = false;\n\n\t\tthis.socket.on('data', (chunk: Buffer) => {\n\n\t\t\tif (!firstChunkReceived) {\n\t\t\t\tfirstChunkReceived = true;\n\t\t\t\tlog.debug(`First chunk received from Firefox: ${chunk.toString('hex')}`);\n\t\t\t}\n\n\t\t\tlet processedLength = 0;\n\t\t\twhile (processedLength < chunk.length) {\n\t\t\t\t// copy the maximum number of bytes possible into this.buffer\n\t\t\t\tlet copyLength = Math.min(chunk.length - processedLength, this.buffer.length - this.bufferedLength);\n\t\t\t\tchunk.copy(this.buffer, this.bufferedLength, processedLength, processedLength + copyLength);\n\t\t\t\tprocessedLength += copyLength;\n\t\t\t\tthis.bufferedLength += copyLength;\n\n\t\t\t\tif (this.receivingHeader) {\n\t\t\t\t\t// did we receive a complete header yet?\n\t\t\t\t\tfor (var i = 0; i < this.bufferedLength; i++) {\n\t\t\t\t\t\tif (this.buffer[i] === 58) {\n\t\t\t\t\t\t\t// header is complete: parse it\n\t\t\t\t\t\t\tlet bodyLength = +this.buffer.toString('ascii', 0, i);\n\t\t\t\t\t\t\tif (bodyLength > 1000000) {\n\t\t\t\t\t\t\t\tlog.debug(`Going to receive message with ${bodyLength} bytes in body (initial chunk contained ${chunk.length} bytes)`);\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t\t// create a buffer for the message body\n\t\t\t\t\t\t\tlet bodyBuffer = Buffer.alloc(bodyLength);\n\t\t\t\t\t\t\t// copy the start of the body from this.buffer\n\t\t\t\t\t\t\tthis.buffer.copy(bodyBuffer, 0, i + 1);\n\t\t\t\t\t\t\t// replace this.buffer with bodyBuffer\n\t\t\t\t\t\t\tthis.buffer = bodyBuffer;\n\t\t\t\t\t\t\tthis.bufferedLength = this.bufferedLength - (i + 1);\n\t\t\t\t\t\t\tthis.receivingHeader = false;\n\t\t\t\t\t\t\tbreak;\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} else {\n\t\t\t\t\t// did we receive the complete body yet?\n\t\t\t\t\tif (this.bufferedLength === this.buffer.length) {\n\t\t\t\t\t\tif (this.bufferedLength > 1000000) {\n\t\t\t\t\t\t\tlog.info(`Received ${this.bufferedLength} bytes`);\n\t\t\t\t\t\t}\n\t\t\t\t\t\t// body is complete: parse and emit it\n\t\t\t\t\t\tlet msgString = this.buffer.toString('utf8');\n\t\t\t\t\t\tthis.emit('message', JSON.parse(msgString));\n\t\t\t\t\t\t// get ready to receive the next header\n\t\t\t\t\t\tthis.buffer = Buffer.alloc(DebugProtocolTransport.initialBufferLength);\n\t\t\t\t\t\tthis.bufferedLength = 0;\n\t\t\t\t\t\tthis.receivingHeader = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\t}\n\n\tpublic sendMessage(msg: any): void {\n\t\tlet msgBuf = Buffer.from(JSON.stringify(msg), 'utf8');\n\t\tthis.socket.write(msgBuf.length + ':', 'ascii');\n\t\tthis.socket.write(msgBuf);\n\t}\n\t\n\tpublic disconnect(): Promise<void> {\n\t\treturn new Promise<void>((resolve, reject) => {\n\t\t\tthis.socket.on('close', () => resolve());\n\t\t\tthis.socket.end();\n\t\t});\n\t}\n}\n"
  },
  {
    "path": "src/adapter/firefoxDebugAdapter.ts",
    "content": "import { URI } from 'vscode-uri';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { DebugSession, StoppedEvent, OutputEvent, Thread, Variable, Breakpoint } from '@vscode/debugadapter';\nimport { Log } from './util/log';\nimport { accessorExpression } from './util/misc';\nimport { DebugAdapterBase } from './debugAdapterBase';\nimport { ThreadAdapter } from './adapter/thread';\nimport { SourceAdapter } from './adapter/source';\nimport { LaunchConfiguration, AttachConfiguration } from '../common/configuration';\nimport { parseConfiguration } from './configuration';\nimport { FirefoxDebugSession, ThreadConfiguration } from './firefoxDebugSession';\nimport { popupAutohidePreferenceKey } from './adapter/addonManager';\nimport { ObjectGripAdapter } from './adapter/objectGrip';\nimport { DataBreakpointsManager } from './adapter/dataBreakpointsManager';\nimport { normalizePath } from './util/fs';\n\nlet log = Log.create('FirefoxDebugAdapter');\n\nexport class FirefoxDebugAdapter extends DebugAdapterBase {\n\n\tprivate session!: FirefoxDebugSession;\n\n\tpublic constructor(debuggerLinesStartAt1: boolean, isServer: boolean = false) {\n\t\tsuper(debuggerLinesStartAt1, isServer);\n\n\t\tif (!isServer) {\n\t\t\tLog.consoleLog = (msg: string) => {\n\t\t\t\tthis.sendEvent(new OutputEvent(msg + '\\n'));\n\t\t\t}\n\t\t}\n\t}\n\n\tprotected initialize(args: DebugProtocol.InitializeRequestArguments): DebugProtocol.Capabilities {\n\t\treturn {\n\t\t\tsupportsConfigurationDoneRequest: false,\n\t\t\tsupportsEvaluateForHovers: false,\n\t\t\tsupportsFunctionBreakpoints: false,\n\t\t\tsupportsConditionalBreakpoints: true,\n\t\t\tsupportsSetVariable: true,\n\t\t\tsupportsCompletionsRequest: true,\n\t\t\tsupportsDelayedStackTraceLoading: true,\n\t\t\tsupportsHitConditionalBreakpoints: true,\n\t\t\tsupportsLogPoints: true,\n\t\t\tsupportsDataBreakpoints: true,\n\t\t\tsupportsBreakpointLocationsRequest: true,\n\t\t\tsupportsRestartFrame: true,\n\t\t\tsupportsANSIStyling: true,\n\t\t\texceptionBreakpointFilters: [\n\t\t\t\t{\n\t\t\t\t\tfilter: 'all',\n\t\t\t\t\tlabel: 'All Exceptions',\n\t\t\t\t\tdefault: false\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfilter: 'uncaught',\n\t\t\t\t\tlabel: 'Uncaught Exceptions',\n\t\t\t\t\tdefault: true\n\t\t\t\t},\n\t\t\t\t{\n\t\t\t\t\tfilter: 'debugger',\n\t\t\t\t\tlabel: 'Debugger Statements',\n\t\t\t\t\tdefault: true\n\t\t\t\t}\n\t\t\t]\n\t\t};\n\t}\n\n\tprotected async launch(args: LaunchConfiguration): Promise<void> {\n\t\tawait this.startSession(args);\n\t}\n\n\tprotected async attach(args: AttachConfiguration): Promise<void> {\n\t\tawait this.startSession(args);\n\t}\n\n\tprivate async startSession(config: LaunchConfiguration | AttachConfiguration): Promise<void> {\n\t\tif (config.log) {\n\t\t\tLog.setConfig(config.log);\n\t\t}\n\t\tlet parsedConfig = await parseConfiguration(config);\n\t\tthis.session = new FirefoxDebugSession(parsedConfig, (ev) => this.sendEvent(ev));\n\t\tawait this.session.start();\n\t}\n\n\tprotected async breakpointLocations(\n\t\targs: DebugProtocol.BreakpointLocationsArguments\n\t): Promise<{ breakpoints: DebugProtocol.BreakpointLocation[]; }> {\n\t\tif (!args.source.path) return { breakpoints: [] };\n\n\t\tconst sourceAdapter = await this.session.sources.getAdapterForPath(normalizePath(args.source.path));\n\t\tconst positions = await sourceAdapter.getBreakableLocations(args.line);\n\t\tconst breakpoints: DebugProtocol.BreakpointLocation[] = [];\n\t\tfor (const position of positions) {\n\t\t\tbreakpoints.push({ line: position.line, column: position.column + 1 });\n\t\t}\n\t\treturn { breakpoints };\n\t}\n\n\tprotected setBreakpoints(args: DebugProtocol.SetBreakpointsArguments): { breakpoints: DebugProtocol.Breakpoint[] } {\n\n\t\tconst requestedBreakpoints = args.breakpoints;\n\t\tif (requestedBreakpoints === undefined) {\n\t\t\tlog.error('setBreakpoints request without any breakpoints');\n\t\t\treturn { breakpoints: [] };\n\t\t}\n\n\t\t// a path for local sources or a url (as seen by either VS Code or Firefox) for remote sources\n\t\tconst sourcePathOrUrl = args.source.path;\n\t\tif (sourcePathOrUrl === undefined) {\n\t\t\tthrow 'Couldn\\'t set breakpoint: unknown source path';\n\t\t}\n\n\t\tconst breakpointInfos = this.session.breakpointsManager.setBreakpoints(requestedBreakpoints, sourcePathOrUrl);\n\n\t\tconst breakpoints = breakpointInfos.map(breakpointInfo => {\n\t\t\tconst breakpoint: DebugProtocol.Breakpoint = new Breakpoint(\n\t\t\t\tbreakpointInfo.verified,\n\t\t\t\tbreakpointInfo.requestedBreakpoint.line,\n\t\t\t\tbreakpointInfo.requestedBreakpoint.column\n\t\t\t);\n\t\t\tbreakpoint.id = breakpointInfo.id;\n\t\t\treturn breakpoint;\n\t\t});\n\n\t\treturn { breakpoints };\n\t}\n\n\tprotected setExceptionBreakpoints(args: DebugProtocol.SetExceptionBreakpointsArguments): void {\n\t\tlog.debug(`Setting exception filters: ${JSON.stringify(args.filters)}`);\n\n\t\tconst threadConfiguration: ThreadConfiguration = {\n\t\t\tpauseOnExceptions: args.filters.includes('all') || args.filters.includes('uncaught'),\n\t\t\tignoreCaughtExceptions: !args.filters.includes('all'),\n\t\t\tshouldPauseOnDebuggerStatement: args.filters.includes('debugger'),\n\t\t};\n\n\t\tthis.session.setThreadConfiguration(threadConfiguration);\n\t}\n\n\tprotected async pause(args: DebugProtocol.PauseArguments): Promise<void> {\n\n\t\tlet threadAdapter = this.getThreadAdapter(args.threadId);\n\t\tthis.session.setActiveThread(threadAdapter);\n\n\t\tawait threadAdapter.interrupt();\n\n\t\tlet stoppedEvent = new StoppedEvent('interrupt', threadAdapter.id);\n\t\t(<DebugProtocol.StoppedEvent>stoppedEvent).body.allThreadsStopped = false;\n\t\tthis.sendEvent(stoppedEvent);\n\t}\n\n\tprotected async next(args: DebugProtocol.NextArguments): Promise<void> {\n\n\t\tlet threadAdapter = this.getThreadAdapter(args.threadId);\n\t\tthis.session.setActiveThread(threadAdapter);\n\n\t\tawait threadAdapter.stepOver();\n\t}\n\n\tprotected async stepIn(args: DebugProtocol.StepInArguments): Promise<void> {\n\n\t\tlet threadAdapter = this.getThreadAdapter(args.threadId);\n\t\tthis.session.setActiveThread(threadAdapter);\n\n\t\tawait threadAdapter.stepIn();\n\t}\n\n\tprotected async stepOut(args: DebugProtocol.StepOutArguments): Promise<void> {\n\n\t\tlet threadAdapter = this.getThreadAdapter(args.threadId);\n\t\tthis.session.setActiveThread(threadAdapter);\n\n\t\tawait threadAdapter.stepOut();\n\t}\n\n\tprotected async continue(args: DebugProtocol.ContinueArguments): Promise<{ allThreadsContinued?: boolean }> {\n\n\t\tlet threadAdapter = this.getThreadAdapter(args.threadId);\n\t\tthis.session.setActiveThread(threadAdapter);\n\n\t\tawait threadAdapter.resume();\n\t\treturn { allThreadsContinued: false };\n\t}\n\n\tprotected async getSource(args: DebugProtocol.SourceArguments): Promise<{ content: string, mimeType?: string }> {\n\n\t\tlet sourceAdapter: SourceAdapter | undefined;\n\t\tif (args.sourceReference !== undefined) {\n\n\t\t\tsourceAdapter = this.session.sources.getAdapterForID(args.sourceReference);\n\n\t\t} else if (args.source?.path) {\n\n\t\t\tsourceAdapter = this.session.sources.findSourceAdaptersForPathOrUrl(args.source.path)[0];\n\t\t\tif (!sourceAdapter && args.source.path.indexOf('?') < 0) {\n\t\t\t\t// workaround for VSCode issue #32845: the url may have contained a query string that got lost,\n\t\t\t\t// in this case we look for a Source whose url is the same if the query string is removed\n\t\t\t\tsourceAdapter = this.session.sources.findSourceAdaptersForUrlWithoutQuery(args.source.path)[0];\n\t\t\t}\n\t\t}\n\n\t\tif (!sourceAdapter) {\n\t\t\tthrow new Error('Failed sourceRequest: the requested source can\\'t be found');\n\t\t}\n\n\t\tlet sourceGrip = await sourceAdapter.fetchSource();\n\n\t\tif (typeof sourceGrip === 'string') {\n\n\t\t\treturn { content: sourceGrip, mimeType: 'text/javascript' };\n\n\t\t} else {\n\n\t\t\tlet longStringGrip = <FirefoxDebugProtocol.LongStringGrip>sourceGrip;\n\t\t\tlet longStringActor = this.session.getOrCreateLongStringGripActorProxy(longStringGrip);\n\t\t\tlet content = await longStringActor.fetchContent();\n\t\t\treturn { content, mimeType: 'text/javascript' };\n\n\t\t}\n\t}\n\n\tprotected getThreads(): { threads: DebugProtocol.Thread[] } {\n\n\t\tlog.debug(`${this.session.threads.count} threads`);\n\n\t\tlet threads = this.session.threads.map(\n\t\t\t(threadAdapter) => new Thread(threadAdapter.id, threadAdapter.name));\n\n\t\treturn { threads };\n\t}\n\n\tprotected async getStackTrace(args: DebugProtocol.StackTraceArguments): Promise<{ stackFrames: DebugProtocol.StackFrame[], totalFrames?: number }> {\n\n\t\tlet threadAdapter = this.getThreadAdapter(args.threadId);\n\t\tthis.session.setActiveThread(threadAdapter);\n\n\t\tlet [frameAdapters, totalFrames] =\n\t\t\tawait threadAdapter.fetchStackFrames(args.startFrame || 0, args.levels || 0);\n\n\t\tlet stackFrames = await Promise.all(\n\t\t\tframeAdapters.map((frameAdapter) => frameAdapter.getStackframe())\n\t\t);\n\n\t\treturn { stackFrames, totalFrames };\n\t}\n\n\tprotected async getScopes(args: DebugProtocol.ScopesArguments): Promise<{ scopes: DebugProtocol.Scope[] }> {\n\n\t\tlet frameAdapter = this.session.frames.find(args.frameId);\n\t\tif (!frameAdapter) {\n\t\t\tthrow new Error('Failed scopesRequest: the requested frame can\\'t be found');\n\t\t}\n\n\t\tthis.session.setActiveThread(frameAdapter.threadAdapter);\n\n\t\tconst scopeAdapters = await frameAdapter.getScopeAdapters();\n\t\tconst scopes = scopeAdapters.map((scopeAdapter) => scopeAdapter.getScope());\n\n\t\treturn { scopes };\n\t}\n\n\tprotected async getVariables(args: DebugProtocol.VariablesArguments): Promise<{ variables: DebugProtocol.Variable[] }> {\n\n\t\tlet variablesProvider = this.session.variablesProviders.find(args.variablesReference);\n\t\tif (!variablesProvider) {\n\t\t\tthrow new Error('Failed variablesRequest: the requested object reference can\\'t be found');\n\t\t}\n\n\t\tthis.session.setActiveThread(variablesProvider.threadAdapter);\n\n\t\ttry {\n\n\t\t\tlet variables = await variablesProvider.threadAdapter.fetchVariables(variablesProvider);\n\n\t\t\treturn { variables };\n\n\t\t} catch(err) {\n\n\t\t\tlet msg: string;\n\t\t\tif (err === 'No such actor') {\n\t\t\t\tmsg = 'Value can\\'t be inspected - this is probably due to Firefox bug #1249962';\n\t\t\t} else {\n\t\t\t\tmsg = String(err);\n\t\t\t}\n\n\t\t\treturn { variables: [ new Variable('Error from debugger', msg) ]};\n\t\t}\n\t}\n\n\tprotected async setVariable(args: DebugProtocol.SetVariableArguments): Promise<{ value: string, variablesReference?: number }> {\n\n\t\tlet variablesProvider = this.session.variablesProviders.find(args.variablesReference);\n\t\tif (variablesProvider === undefined) {\n\t\t\tthrow new Error('Failed setVariableRequest: the requested context can\\'t be found')\n\t\t}\n\t\tif (variablesProvider.referenceFrame === undefined) {\n\t\t\tthrow new Error('Failed setVariableRequest: the requested context has no associated stack frame');\n\t\t}\n\n\t\tlet referenceExpression = accessorExpression(variablesProvider.referenceExpression, args.name);\n\t\tlet setterExpression = `${referenceExpression} = ${args.value}`;\n\t\tlet frameActorName = variablesProvider.referenceFrame.frame.actor;\n\t\tlet result = await variablesProvider.threadAdapter.evaluate(setterExpression, false, frameActorName);\n\n\t\treturn { value: result.value, variablesReference: result.variablesReference };\n\t}\n\n\tprotected async evaluate(args: DebugProtocol.EvaluateArguments): Promise<{ result: string, type?: string, variablesReference: number, namedVariables?: number, indexedVariables?: number }> {\n\n\t\tlet variable: Variable | undefined = undefined;\n\n\t\tif (args.context === 'watch') {\n\n\t\t\tif (args.frameId !== undefined) {\n\n\t\t\t\tlet frameAdapter = this.session.frames.find(args.frameId);\n\t\t\t\tif (frameAdapter !== undefined) {\n\n\t\t\t\t\tthis.session.setActiveThread(frameAdapter.threadAdapter);\n\n\t\t\t\t\tlet threadAdapter = frameAdapter.threadAdapter;\n\t\t\t\t\tlet frameActorName = frameAdapter.frame.actor;\n\n\t\t\t\t\tvariable = await threadAdapter.evaluate(args.expression, true, frameActorName);\n\n\t\t\t\t} else {\n\t\t\t\t\tlog.warn(`Couldn\\'t find specified frame for evaluating ${args.expression}`);\n\t\t\t\t\tthrow 'not available';\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tlet threadAdapter = this.session.getActiveThread();\n\t\t\t\tif (threadAdapter !== undefined) {\n\n\t\t\t\t\tvariable = await threadAdapter.evaluate(args.expression, true);\n\n\t\t\t\t} else {\n\t\t\t\t\tlog.info(`Couldn't find a thread for evaluating watch ${args.expression}`);\n\t\t\t\t\tthrow 'not available';\n\t\t\t\t}\n\t\t\t}\n\n\t\t} else {\n\n\t\t\tlet threadAdapter = this.session.getActiveThread();\n\t\t\tif (threadAdapter !== undefined) {\n\n\t\t\t\tlet frameActorName: string | undefined = undefined;\n\t\t\t\tif (args.frameId !== undefined) {\n\t\t\t\t\tlet frameAdapter = this.session.frames.find(args.frameId);\n\t\t\t\t\tif (frameAdapter !== undefined) {\n\t\t\t\t\t\tframeActorName = frameAdapter.frame.actor;\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tvariable = await threadAdapter.evaluate(args.expression, false, frameActorName);\n\n\t\t\t} else {\n\t\t\t\tlog.info(`Couldn't find a thread for evaluating ${args.expression}`);\n\t\t\t\tthrow 'not available';\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tresult: variable.value,\n\t\t\tvariablesReference: variable.variablesReference\n\t\t};\n\t}\n\n\tprotected async getCompletions(args: DebugProtocol.CompletionsArguments): Promise<{ targets: DebugProtocol.CompletionItem[] }> {\n\n\t\tlet matches: string[];\n\n\t\tif (args.frameId !== undefined) {\n\n\t\t\tlet frameAdapter = this.session.frames.find(args.frameId);\n\n\t\t\tif (frameAdapter === undefined) {\n\t\t\t\tlog.warn(`Couldn\\'t find specified frame for auto-completing ${args.text}`);\n\t\t\t\tthrow 'not available';\n\t\t\t}\n\n\t\t\tthis.session.setActiveThread(frameAdapter.threadAdapter);\n\n\t\t\tlet threadAdapter = frameAdapter.threadAdapter;\n\t\t\tlet frameActorName = frameAdapter.frame.actor;\n\n\t\t\tmatches = await threadAdapter.autoComplete(args.text, args.column - 1, frameActorName);\n\n\t\t} else {\n\n\t\t\tlet threadAdapter = this.session.getActiveThread();\n\n\t\t\tif (threadAdapter === undefined) {\n\t\t\t\tlog.warn(`Couldn't find a thread for auto-completing ${args.text}`);\n\t\t\t\tthrow 'not available';\n\t\t\t}\n\n\t\t\tmatches = await threadAdapter.autoComplete(args.text, args.column - 1);\n\t\t}\n\n\t\treturn {\n\t\t\ttargets: matches.map((match) => <DebugProtocol.CompletionItem>{ label: match })\n\t\t};\n\t}\n\n\tprotected async dataBreakpointInfo(args: DebugProtocol.DataBreakpointInfoArguments): Promise<{ dataId: string | null, description: string, accessTypes?: DebugProtocol.DataBreakpointAccessType[], canPersist?: boolean }> {\n\n\t\tif (!this.session.dataBreakpointsManager) {\n\t\t\treturn {\n\t\t\t\tdataId: null,\n\t\t\t\tdescription: \"Your version of Firefox doesn't support watchpoints / data breakpoints\"\n\t\t\t};\n\t\t}\n\n\t\tif (args.variablesReference !== undefined) {\n\n\t\t\tconst provider = this.session.variablesProviders.find(args.variablesReference);\n\t\t\tif (provider instanceof ObjectGripAdapter) {\n\t\t\t\ttry {\n\t\t\t\t\tawait provider.actor.threadLifetime();\n\t\t\t\t\tprovider.threadAdapter.threadLifetime(provider);\n\t\n\t\t\t\t\treturn {\n\t\t\t\t\t\tdataId: DataBreakpointsManager.encodeDataId(args.variablesReference, args.name),\n\t\t\t\t\t\tdescription: args.name,\n\t\t\t\t\t\taccessTypes: [ 'read', 'write' ]\n\t\t\t\t\t};\n\t\t\t\t} catch {}\n\t\t\t}\n\t\t}\n\n\t\treturn {\n\t\t\tdataId: null,\n\t\t\tdescription: 'Data breakpoints are only supported on object properties'\n\t\t};\n\t}\n\n\tprotected async setDataBreakpoints(args: DebugProtocol.SetDataBreakpointsArguments): Promise<{ breakpoints: DebugProtocol.Breakpoint[] }> {\n\t\tif (!this.session.dataBreakpointsManager) {\n\t\t\tif (args.breakpoints.length === 0) {\n\t\t\t\treturn { breakpoints: [] };\n\t\t\t} else {\n\t\t\t\tthrow \"Your version of Firefox doesn't support watchpoints / data breakpoints\";\n\t\t\t}\n\t\t}\n\n\t\tawait this.session.dataBreakpointsManager.setDataBreakpoints(args.breakpoints);\n\t\treturn { breakpoints: new Array(args.breakpoints.length).fill({ verified: true }) }\n\t}\n\n\tprotected async restartFrame(args: DebugProtocol.RestartFrameArguments): Promise<void> {\n\n\t\tconst frameAdapter = this.session.frames.find(args.frameId);\n\t\tif (!frameAdapter) {\n\t\t\tthrow new Error('Failed restartFrameRequest: the requested frame can\\'t be found');\n\t\t}\n\n\t\tthis.session.setActiveThread(frameAdapter.threadAdapter);\n\n\t\tawait frameAdapter.threadAdapter.restartFrame(frameAdapter.frame.actor);\n\t}\n\n\tprotected async reloadAddon(): Promise<void> {\n\t\tif (!this.session.addonManager) {\n\t\t\tthrow 'This command is only available when debugging an addon'\n\t\t}\n\n\t\tawait this.session.addonManager.reloadAddon();\n\t}\n\n\tprotected async toggleSkippingFile(url: string): Promise<void> {\n\n\t\tif (url.startsWith('file://')) {\n\n\t\t\tconst path = URI.parse(url).fsPath;\n\t\t\tawait this.session.skipFilesManager.toggleSkipping(path);\n\n\t\t} else {\n\n\t\t\tawait this.session.skipFilesManager.toggleSkipping(url);\n\n\t\t}\n\t}\n\n\tprotected async setPopupAutohide(enabled: boolean): Promise<void> {\n\t\tawait this.session.preferenceActor.setBoolPref(popupAutohidePreferenceKey, !enabled);\n\t}\n\n\tprotected async togglePopupAutohide(): Promise<boolean> {\n\t\tconst currentValue = await this.session.preferenceActor.getBoolPref(popupAutohidePreferenceKey);\n\t\tconst newValue = !currentValue;\n\t\tawait this.session.preferenceActor.setBoolPref(popupAutohidePreferenceKey, newValue);\n\t\treturn !newValue;\n\t}\n\n\tprotected setActiveEventBreakpoints(args: string[] | undefined): Promise<void> {\n\t\treturn this.session.eventBreakpointsManager.setActiveEventBreakpoints(args ?? []);\n\t}\n\n\tprotected async disconnect(args: DebugProtocol.DisconnectArguments): Promise<void> {\n\t\tawait this.session.stop();\n\t}\n\n\tprivate getThreadAdapter(threadId: number): ThreadAdapter {\n\t\tlet threadAdapter = this.session.threads.find(threadId);\n\t\tif (!threadAdapter) {\n\t\t\tthrow new Error(`Unknown threadId ${threadId}`);\n\t\t}\n\t\treturn threadAdapter;\n\t}\n}\n\nDebugSession.run(FirefoxDebugAdapter);\n"
  },
  {
    "path": "src/adapter/firefoxDebugSession.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { Socket } from 'net';\nimport { ChildProcess } from 'child_process';\nimport * as chokidar from 'chokidar';\nimport debounce from 'debounce';\nimport isAbsoluteUrl from 'is-absolute-url';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { InitializedEvent, TerminatedEvent, StoppedEvent, OutputEvent, ThreadEvent, ContinuedEvent, Event } from '@vscode/debugadapter';\nimport { Log } from './util/log';\nimport { AddonManager } from './adapter/addonManager';\nimport { launchFirefox, openNewTab } from './firefox/launch';\nimport { DebugConnection } from './firefox/connection';\nimport { ObjectGripActorProxy } from './firefox/actorProxy/objectGrip';\nimport { LongStringGripActorProxy } from './firefox/actorProxy/longString';\nimport { AddonsActorProxy } from './firefox/actorProxy/addons';\nimport { IThreadActorProxy } from './firefox/actorProxy/thread';\nimport { ConsoleActorProxy } from './firefox/actorProxy/console';\nimport { ISourceActorProxy } from './firefox/actorProxy/source';\nimport { FrameAdapter } from './adapter/frame';\nimport { SourceAdapter } from './adapter/source';\nimport { VariablesProvider } from './adapter/variablesProvider';\nimport { VariableAdapter } from './adapter/variable';\nimport { Registry } from './adapter/registry';\nimport { TargetType, ThreadAdapter } from './adapter/thread';\nimport { ConsoleAPICallAdapter } from './adapter/consoleAPICall';\nimport { BreakpointsManager } from './adapter/breakpointsManager';\nimport { DataBreakpointsManager } from './adapter/dataBreakpointsManager';\nimport { SkipFilesManager } from './adapter/skipFilesManager';\nimport { ParsedConfiguration } from './configuration';\nimport { PathMapper } from './util/pathMapper';\nimport { isWindowsPlatform as detectWindowsPlatform, delay } from '../common/util';\nimport { connect, waitForSocket } from './util/net';\nimport { NewSourceEventBody, ThreadStartedEventBody, ThreadExitedEventBody } from '../common/customEvents';\nimport { PreferenceActorProxy } from './firefox/actorProxy/preference';\nimport { DeviceActorProxy } from './firefox/actorProxy/device';\nimport { TargetActorProxy } from './firefox/actorProxy/target';\nimport { BreakpointListActorProxy } from './firefox/actorProxy/breakpointList';\nimport { SourceMapsManager } from './firefox/sourceMaps/manager';\nimport { SourcesManager } from './adapter/sourcesManager';\nimport { ThreadConfigurationActorProxy } from './firefox/actorProxy/threadConfiguration';\nimport { DescriptorAdapter } from './adapter/descriptor';\nimport { DescriptorActorProxy } from './firefox/actorProxy/descriptor';\nimport { EventBreakpointsManager } from './adapter/eventBreakpointsManager';\nimport { renderGrip } from './adapter/preview';\nimport { shortenUrl } from './util/misc';\n\nlet log = Log.create('FirefoxDebugSession');\nlet consoleActorLog = Log.create('ConsoleActor');\n\nexport type ThreadConfiguration = Pick<\n\tFirefoxDebugProtocol.ThreadConfiguration,\n\t'pauseOnExceptions' | 'ignoreCaughtExceptions' | 'shouldPauseOnDebuggerStatement'\n>;\n\nexport class FirefoxDebugSession {\n\n\tpublic readonly isWindowsPlatform = detectWindowsPlatform();\n\tpublic processDescriptorMode!: boolean;\n\tpublic readonly pathMapper: PathMapper;\n\tpublic readonly sources: SourcesManager;\n\tpublic sourceMaps!: SourceMapsManager;\n\tpublic readonly breakpointsManager: BreakpointsManager;\n\tpublic readonly dataBreakpointsManager: DataBreakpointsManager;\n\tpublic readonly eventBreakpointsManager: EventBreakpointsManager;\n\tpublic readonly skipFilesManager: SkipFilesManager;\n\tpublic readonly addonManager?: AddonManager;\n\tprivate reloadWatcher?: chokidar.FSWatcher;\n\n\tprivate firefoxProc?: ChildProcess;\n\tprivate firefoxClosedPromise?: Promise<void>;\n\tpublic firefoxDebugConnection!: DebugConnection;\n\tprivate firefoxDebugSocketClosed = false;\n\tprivate firefoxDebugSocketClosedPromise?: Promise<void>;\n\n\tpublic preferenceActor!: PreferenceActorProxy;\n\tpublic addonsActor?: AddonsActorProxy;\n\tpublic deviceActor!: DeviceActorProxy;\n\n\tpublic readonly descriptors = new Registry<DescriptorAdapter>();\n\tpublic readonly threads = new Registry<ThreadAdapter>();\n\tpublic readonly frames = new Registry<FrameAdapter>();\n\tpublic readonly variablesProviders = new Registry<VariablesProvider>();\n\tpublic readonly breakpointLists = new Registry<BreakpointListActorProxy>();\n\tpublic readonly threadConfigurators = new Registry<ThreadConfigurationActorProxy>();\n\tprivate readonly threadsByTargetActorName = new Map<string, ThreadAdapter>();\n\n\tpublic threadConfiguration: ThreadConfiguration = {\n\t\tpauseOnExceptions: true,\n\t\tignoreCaughtExceptions: true,\n\t\tshouldPauseOnDebuggerStatement: true,\n\t};\n\n\tprivate reloadTabs = false;\n\n\t/**\n\t * The ID of the last thread that the user interacted with. This thread will be used when the\n\t * user wants to evaluate an expression in VS Code's debug console.\n\t */\n\tprivate lastActiveThreadId: number = 0;\n\n\tpublic constructor(\n\t\tpublic readonly config: ParsedConfiguration,\n\t\tpublic readonly sendEvent: (ev: DebugProtocol.Event) => void\n\t) {\n\t\tthis.pathMapper = new PathMapper(this.config.pathMappings, this.config.pathMappingIndex, this.config.addon);\n\t\tthis.sources = new SourcesManager(this.pathMapper);\n\t\tthis.breakpointsManager = new BreakpointsManager(this);\n\t\tthis.dataBreakpointsManager = new DataBreakpointsManager(this.variablesProviders);\n\t\tthis.eventBreakpointsManager = new EventBreakpointsManager(this);\n\t\tthis.skipFilesManager = new SkipFilesManager(this.config.filesToSkip, this.sources, this.threads);\n\t\tif (this.config.addon) {\n\t\t\tthis.addonManager = new AddonManager(this);\n\t\t}\n\t}\n\n\t/**\n\t * Connect to Firefox and start the debug session. Returns a Promise that is resolved when the\n\t * initial response from Firefox was processed.\n\t */\n\tpublic start(): Promise<void> {\n\t\treturn new Promise<void>(async (resolve, reject) => {\n\n\t\t\tlet socket: Socket;\n\t\t\ttry {\n\t\t\t\tlog.debug(\"Connecting to Firefox\");\n\t\t\t\tsocket = await this.connectToFirefox();\n\t\t\t\tlog.debug(\"Connected\");\n\t\t\t} catch(err: any) {\n\t\t\t\tif (!err?.message && this.config.attach) {\n\t\t\t\t\treject(new Error(`Couldn't connect to Firefox - please ensure it is running and listening on port ${this.config.attach.port} for debugger connections`));\n\t\t\t\t} else {\n\t\t\t\t\treject(err);\n\t\t\t\t}\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tthis.firefoxDebugSocketClosedPromise = new Promise(resolve => {\n\t\t\t\tsocket.once('close', () => {\n\t\t\t\t\tlog.info('Connection to Firefox closed - terminating debug session');\n\t\t\t\t\tthis.firefoxDebugSocketClosed = true;\n\t\t\t\t\tresolve();\n\t\t\t\t\tthis.sendEvent(new TerminatedEvent());\n\t\t\t\t});\n\t\t\t});\n\t\t\tthis.firefoxDebugConnection = new DebugConnection(this.pathMapper, this.sources, socket);\n\t\t\tthis.sourceMaps = this.firefoxDebugConnection.sourceMaps;\n\t\t\tlet rootActor = this.firefoxDebugConnection.rootActor;\n\n\t\t\tif (!this.processDescriptorMode) {\n\t\t\t\t// attach to all tabs, register the corresponding threads and inform VSCode about them\n\t\t\t\trootActor.onTabOpened(async (tabDescriptorActor) => {\n\n\t\t\t\t\tif (this.reloadTabs) {\n\t\t\t\t\t\tawait tabDescriptorActor.reload();\n\t\t\t\t\t\tawait delay(200);\n\t\t\t\t\t}\n\n\t\t\t\t\tconst adapter = await this.attachDescriptor(tabDescriptorActor);\n\t\t\t\t\tawait adapter.watcherActor.watchResources(['console-message', 'error-message', 'source', 'thread-state']);\n\t\t\t\t});\n\n\t\t\t\trootActor.onTabListChanged(() => {\n\t\t\t\t\trootActor.fetchTabs();\n\t\t\t\t});\n\t\t\t}\n\n\t\t\trootActor.onInit(async (initialResponse) => {\n\n\t\t\t\tif (initialResponse.traits.webExtensionAddonConnect &&\n\t\t\t\t\t!initialResponse.traits.nativeLogpoints) {\n\t\t\t\t\treject('Your version of Firefox is not supported anymore - please upgrade to Firefox 68 or later');\n\t\t\t\t\treturn;\n\t\t\t\t}\n\n\t\t\t\tthis.processDescriptorMode = !!initialResponse.traits.supportsEnableWindowGlobalThreadActors;\n\n\t\t\t\tconst actors = await rootActor.fetchRoot();\n\n\t\t\t\tthis.preferenceActor = actors.preference;\n\t\t\t\tthis.addonsActor = actors.addons;\n\t\t\t\tthis.deviceActor = actors.device;\n\n\t\t\t\tlet adapter: DescriptorAdapter | undefined;\n\t\t\t\tif (this.processDescriptorMode) {\n\t\t\t\t\tconst parentProcess = await rootActor.getProcess(0);\n\t\t\t\t\tadapter = await this.attachDescriptor(parentProcess);\n\t\t\t\t} else {\n\t\t\t\t\trootActor.fetchTabs().then(() => this.reloadTabs = false);\n\t\t\t\t}\n\n\t\t\t\tif (this.addonManager) {\n\t\t\t\t\tif (actors.addons) {\n\t\t\t\t\t\tawait this.addonManager.sessionStarted(rootActor, actors.addons, actors.preference);\n\t\t\t\t\t} else {\n\t\t\t\t\t\treject('No AddonsActor received from Firefox');\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (this.processDescriptorMode) {\n\t\t\t\t\tawait adapter?.watcherActor.watchResources(['console-message', 'error-message', 'source', 'thread-state']);\n\t\t\t\t}\n\n\t\t\t\tresolve();\n\t\t\t});\n\n\t\t\tif (this.config.reloadOnChange) {\n\n\t\t\t\tthis.reloadWatcher = chokidar.watch(this.config.reloadOnChange.watch, {\n\t\t\t\t\tignored: this.config.reloadOnChange.ignore,\n\t\t\t\t\tignoreInitial: true\n\t\t\t\t});\n\n\t\t\t\tlet reload: () => void;\n\t\t\t\tif (this.config.addon) {\n\n\t\t\t\t\treload = () => {\n\t\t\t\t\t\tif (this.addonManager) {\n\t\t\t\t\t\t\tlog.debug('Reloading add-on');\n\n\t\t\t\t\t\t\tthis.addonManager.reloadAddon();\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\n\t\t\t\t} else {\n\n\t\t\t\t\treload = () => {\n\t\t\t\t\t\tlog.debug('Reloading tabs');\n\n\t\t\t\t\t\tfor (let [, thread] of this.threads) {\n\t\t\t\t\t\t\tif (thread.type === 'tab') {\n\t\t\t\t\t\t\t\tthread.targetActor.reload();\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tif (this.config.reloadOnChange.debounce > 0) {\n\t\t\t\t\treload = debounce(reload, this.config.reloadOnChange.debounce);\n\t\t\t\t}\n\n\t\t\t\tthis.reloadWatcher.on('add', reload);\n\t\t\t\tthis.reloadWatcher.on('change', reload);\n\t\t\t\tthis.reloadWatcher.on('unlink', reload);\n\t\t\t}\n\n\t\t\t// now we are ready to accept breakpoints -> fire the initialized event to give UI a chance to set breakpoints\n\t\t\tthis.sendEvent(new InitializedEvent());\n\t\t});\n\t}\n\n\t/**\n\t * Terminate the debug session\n\t */\n\tpublic async stop(): Promise<void> {\n\t\tawait this.disconnectFirefoxAndCleanup();\n\t}\n\n\tpublic setThreadConfiguration(threadConfiguration: ThreadConfiguration) {\n\n\t\tthis.threadConfiguration = threadConfiguration;\n\n\t\tfor (let [, threadConfigurator] of this.threadConfigurators) {\n\t\t\tthreadConfigurator.updateConfiguration(this.threadConfiguration);\n\t\t}\n\t}\n\n\tpublic setActiveThread(threadAdapter: ThreadAdapter): void {\n\t\tthis.lastActiveThreadId = threadAdapter.id;\n\t}\n\n\tpublic getActiveThread(): ThreadAdapter | undefined {\n\n\t\tlet threadAdapter = this.threads.find(this.lastActiveThreadId);\n\t\tif (threadAdapter !== undefined) {\n\t\t\treturn threadAdapter;\n\t\t}\n\n\t\t// last active thread not found -> we return the first thread we get from the registry\n\t\tfor (let [, threadAdapter] of this.threads) {\n\t\t\tthis.setActiveThread(threadAdapter);\n\t\t\treturn threadAdapter;\n\t\t}\n\n\t\treturn undefined;\n\t}\n\n\tpublic getOrCreateObjectGripActorProxy(objectGrip: FirefoxDebugProtocol.ObjectGrip): ObjectGripActorProxy {\n\t\treturn this.firefoxDebugConnection.getOrCreate(objectGrip.actor, () =>\n\t\t\tnew ObjectGripActorProxy(objectGrip.actor, this.firefoxDebugConnection));\n\t}\n\n\tpublic getOrCreateLongStringGripActorProxy(longStringGrip: FirefoxDebugProtocol.LongStringGrip): LongStringGripActorProxy {\n\t\treturn this.firefoxDebugConnection.getOrCreate(longStringGrip.actor, () =>\n\t\t\tnew LongStringGripActorProxy(longStringGrip, this.firefoxDebugConnection));\n\t}\n\n\tprivate async connectToFirefox(): Promise<Socket> {\n\n\t\tlet socket: Socket | undefined = undefined;\n\n\t\tif (this.config.attach) {\n\t\t\ttry {\n\n\t\t\t\tsocket = await connect(this.config.attach.port, this.config.attach.host);\n\n\t\t\t\tthis.reloadTabs = this.config.attach.reloadTabs;\n\n\t\t\t} catch(err) {\n\t\t\t\tif (!this.config.launch) {\n\t\t\t\t\tthrow err;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (socket === undefined) {\n\n\t\t\tconst firefoxProc = await launchFirefox(this.config.launch!);\n\n\t\t\tif (firefoxProc && !this.config.launch!.detached) {\n\n\t\t\t\t// set everything up so that Firefox can be terminated at the end of this debug session\n\t\t\t\tthis.firefoxProc = firefoxProc;\n\n\t\t\t\t// firefoxProc may be a short-lived startup process - we remove the reference to it\n\t\t\t\t// when it exits so that we don't try to kill it with a SIGTERM signal (which may\n\t\t\t\t// end up killing an unrelated process) at the end of this debug session\n\t\t\t\tthis.firefoxProc.once('exit', () => { this.firefoxProc = undefined; });\n\n\t\t\t\t// the `close` event from firefoxProc seems to be the only reliable notification\n\t\t\t\t// that Firefox is exiting\n\t\t\t\tthis.firefoxClosedPromise = new Promise<void>(resolve => {\n\t\t\t\t\tthis.firefoxProc!.once('close', resolve);\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tsocket = await waitForSocket(this.config.launch!.port, this.config.launch!.timeout);\n\t\t}\n\n\t\treturn socket;\n\t}\n\n\tprivate async disconnectFirefoxAndCleanup(): Promise<void> {\n\n\t\tif (this.reloadWatcher !== undefined) {\n\t\t\tthis.reloadWatcher.close();\n\t\t\tthis.reloadWatcher = undefined;\n\t\t}\n\n\t\tif (!this.config.terminate) {\n\t\t\tawait this.firefoxDebugConnection.disconnect();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.firefoxProc) {\n\n\t\t\tlog.debug('Trying to kill Firefox using a SIGTERM signal');\n\t\t\tthis.firefoxProc.kill('SIGTERM');\n\t\t\tawait Promise.race([ this.firefoxClosedPromise, delay(1000) ]);\n\n\t\t} else if (!this.firefoxDebugSocketClosed && this.addonsActor) {\n\n\t\t\tlog.debug('Trying to close Firefox using the Terminator WebExtension');\n\t\t\tconst terminatorPath = path.join(__dirname, 'terminator');\n\t\t\tawait this.addonsActor.installAddon(terminatorPath);\n\t\t\tawait Promise.race([ this.firefoxDebugSocketClosedPromise, delay(1000) ]);\n\n\t\t}\n\n\t\tif (!this.firefoxDebugSocketClosed) {\n\t\t\tlog.warn(\"Couldn't terminate Firefox\");\n\t\t\tawait this.firefoxDebugConnection.disconnect();\n\t\t\treturn;\n\t\t}\n\n\t\tif (this.config.launch && (this.config.launch.tmpDirs.length > 0)) {\n\n\t\t\t// after closing all connections to this debug adapter Firefox will still be using\n\t\t\t// the temporary profile directory for a short while before exiting\n\t\t\tawait delay(500);\n\n\t\t\tlog.debug(\"Removing \" + this.config.launch.tmpDirs.join(\" , \"));\n\t\t\ttry {\n\t\t\t\tawait Promise.all(this.config.launch.tmpDirs.map(\n\t\t\t\t\t(tmpDir) => fs.remove(tmpDir)));\n\t\t\t} catch (err) {\n\t\t\t\tlog.warn(`Failed to remove temporary directory: ${err}`);\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic async attachDescriptor(descriptorActor: DescriptorActorProxy) {\n\t\tconst watcherActor = await descriptorActor.getWatcher();\n\t\tconst [configurator, breakpointList] = await Promise.all([\n\t\t\twatcherActor.getThreadConfiguration(),\n\t\t\twatcherActor.getBreakpointList()\n\t\t]);\n\n\t\tconst adapter = new DescriptorAdapter(\n\t\t\tthis.descriptors, this.threadConfigurators, this.breakpointLists,\n\t\t\tdescriptorActor, watcherActor, configurator, breakpointList\n\t\t);\n\n\t\tdescriptorActor.onDestroyed(() => {\n\t\t\tfor (const threadAdapter of adapter.threads) {\n\t\t\t\tthis.sendThreadExitedEvent(threadAdapter);\n\t\t\t\tthis.threadsByTargetActorName.delete(threadAdapter.targetActor.name);\n\t\t\t}\n\t\t\tadapter.dispose();\n\t\t});\n\n\t\twatcherActor.onTargetAvailable(async ([targetActor, threadActor, consoleActor]) => {\n\n\t\t\tlet skip = false;\n\t\t\tif (descriptorActor.type === 'webExtension' && targetActor.target.isFallbackExtensionDocument) {\n\t\t\t\tskip = true;\n\t\t\t}\n\t\t\tif (targetActor.target.addonId &&\n\t\t\t\t(!this.addonManager || targetActor.target.addonId !== await this.addonManager.addonId)) {\n\t\t\t\tskip = true;\n\t\t\t}\n\t\t\tconst url = targetActor.target.url;\n\t\t\tif (\n\t\t\t\tdescriptorActor.type === 'process' && !targetActor.target.addonId && url &&\n\t\t\t\t(!this.config.tabFilter.include.some(tabFilter => tabFilter.test(url)) ||\n\t\t\t\tthis.config.tabFilter.exclude.some(tabFilter => tabFilter.test(url)))\n\t\t\t) {\n\t\t\t\tskip = true;\n\t\t\t}\n\t\t\tif (skip) {\n\t\t\t\tlog.warn('Not attaching to this thread');\n\t\t\t\ttargetActor.onThreadState(event => {\n\t\t\t\t\tif (event.state === 'paused') {\n\t\t\t\t\t\tlog.info(\"Detached thread paused, resuming\");\n\t\t\t\t\t\tthreadActor.resume();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t\treturn;\n\t\t\t}\n\n\t\t\tlet type: TargetType;\n\t\t\tlet name: string;\n\t\t\tif (\n\t\t\t\t(descriptorActor.type === 'tab' && targetActor.name.includes(\"contentScriptTarget\")) ||\n\t\t\t\t(descriptorActor.type === 'process' && targetActor.target.targetType === 'content_script')\n\t\t\t) {\n\t\t\t\ttype =  'contentScript';\n\t\t\t\tname = 'Content scripts';\n\t\t\t} else if (\n\t\t\t\tdescriptorActor.type === 'webExtension' ||\n\t\t\t\t(descriptorActor.type === 'process' && targetActor.target.addonId)\n\t\t\t) {\n\t\t\t\ttype = 'backgroundScript';\n\t\t\t\tname = 'Background scripts';\n\t\t\t} else {\n\t\t\t\tconst { parentInnerWindowId, relatedDocumentInnerWindowId, url } = targetActor.target;\n\t\t\t\tif (relatedDocumentInnerWindowId) {\n\t\t\t\t\ttype = 'worker';\n\t\t\t\t\tname = `Worker ${shortenUrl(url ?? '')}`;\n\t\t\t\t} else if (parentInnerWindowId) {\n\t\t\t\t\ttype = 'iframe';\n\t\t\t\t\tname = `IFrame ${shortenUrl(url ?? '')}`;\n\t\t\t\t} else {\n\t\t\t\t\ttype = 'tab';\n\t\t\t\t\tname = `Tab ${shortenUrl(url ?? '')}`;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tconst threadAdapter = await this.attachThread(type, name, targetActor, threadActor, consoleActor);\n\t\t\tadapter.threads.add(threadAdapter);\n\t\t});\n\n\t\twatcherActor.onTargetDestroyed(targetActorName => {\n\t\t\tconst threadAdapter = this.threadsByTargetActorName.get(targetActorName);\n\t\t\tif (!threadAdapter) {\n\t\t\t\tlog.debug(`Unknown target actor ${targetActorName} (already destroyed?)`);\n\t\t\t\treturn;\n\t\t\t}\n\t\n\t\t\tif (threadAdapter.type === 'tab' && this.config.clearConsoleOnReload) {\n\t\t\t\tthis.sendEvent(new OutputEvent('\\x1b[2J'));\n\t\t\t}\n\n\t\t\tthreadAdapter.targetActor.destroyed = true;\n\n\t\t\tthis.sendThreadExitedEvent(threadAdapter);\n\t\t\tthis.threadsByTargetActorName.delete(targetActorName);\n\t\t\tadapter.threads.delete(threadAdapter);\n\t\t\tthreadAdapter.dispose();\n\t\t});\n\n\t\tawait Promise.all([\n\t\t\twatcherActor.watchTargets('frame'),\n\t\t\twatcherActor.watchTargets('worker'),\n\t\t\tthis.config.addon && watcherActor.supportsContentScriptTargets ?\n\t\t\t\twatcherActor.watchTargets('content_script') :\n\t\t\t\tPromise.resolve(),\n\t\t\tconfigurator.updateConfiguration(this.threadConfiguration)\n\t\t]);\n\n\t\treturn adapter;\n\t}\n\n\tprivate async attachThread(\n\t\ttype: TargetType,\n\t\tname: string,\n\t\ttargetActor: TargetActorProxy,\n\t\tthreadActor: IThreadActorProxy,\n\t\tconsoleActor: ConsoleActorProxy,\n\t) {\n\t\tconst threadAdapter = new ThreadAdapter(type, name, threadActor, targetActor, consoleActor, this);\n\t\tlog.info(`Attaching ${name}`);\n\t\tthis.threadsByTargetActorName.set(targetActor.name, threadAdapter);\n\n\t\tthis.sendThreadStartedEvent(threadAdapter);\n\n\t\ttargetActor.onConsoleMessages(async messages => {\n\t\t\tfor (const message of messages) {\n\t\t\t\tawait this.sendConsoleMessage(message, threadAdapter);\n\t\t\t}\n\t\t});\n\n\t\ttargetActor.onErrorMessages(async messages => {\n\t\t\tfor (const { pageError } of messages) {\n\t\t\t\tconsoleActorLog.debug(`Page Error: ${JSON.stringify(pageError)}`);\n\n\t\t\t\tif (pageError.category === 'content javascript') {\n\t\n\t\t\t\t\tlet category = pageError.exception ? 'stderr' : 'stdout';\n\t\t\t\t\tlet outputEvent = new OutputEvent(pageError.errorMessage + '\\n', category);\n\t\t\t\t\tawait this.addLocation(outputEvent, pageError.sourceName, pageError.lineNumber, pageError.columnNumber);\n\t\n\t\t\t\t\tthis.sendEvent(outputEvent);\n\t\t\t\t}\n\t\t\t}\n\t\t});\n\n\t\ttargetActor.onSources(sources => {\n\t\t\tfor (const source of sources) {\n\t\t\t\tthis.attachSource(source, threadAdapter);\n\t\t\t}\n\t\t});\n\n\t\ttargetActor.onThreadState(async event => {\n\t\t\tif (event.state === 'paused') {\n\n\t\t\t\tawait this.sourceMaps.applySourceMapToFrame(event.frame!);\n\t\t\t\tconst sourceLocation = event.frame!.where;\n\n\t\t\t\ttry {\n\t\n\t\t\t\t\tconst sourceAdapter = await this.sources.getAdapterForActor(sourceLocation.actor);\n\n\t\t\t\t\tif (sourceAdapter.isBlackBoxed) {\n\t\n\t\t\t\t\t\t// skipping (or blackboxing) source files is usually done by Firefox itself,\n\t\t\t\t\t\t// but when the debugger hits an exception in a source that was just loaded and\n\t\t\t\t\t\t// should be skipped, we may not have been able to tell Firefox that we want\n\t\t\t\t\t\t// to skip this file, so we have to do it here\n\t\t\t\t\t\tthreadAdapter.resume();\n\t\t\t\t\t\treturn;\n\t\n\t\t\t\t\t}\n\t\n\t\t\t\t\tif ((event.why?.type === 'breakpoint') &&\n\t\t\t\t\t\tevent.why.actors && (event.why.actors.length > 0) &&\n\t\t\t\t\t\tsourceAdapter.path\n\t\t\t\t\t) {\n\t\n\t\t\t\t\t\tconst breakpointInfo = this.breakpointsManager.getBreakpoints(sourceAdapter.path)?.find(bpInfo =>\n\t\t\t\t\t\t\tbpInfo.actualLocation && bpInfo.actualLocation.line === sourceLocation.line && bpInfo.actualLocation.column === sourceLocation.column\n\t\t\t\t\t\t);\n\t\n\t\t\t\t\t\tif (breakpointInfo?.hitLimit) {\n\t\n\t\t\t\t\t\t\t// Firefox doesn't have breakpoints with hit counts, so we have to\n\t\t\t\t\t\t\t// implement this here\n\t\t\t\t\t\t\tbreakpointInfo.hitCount++;\n\t\t\t\t\t\t\tif (breakpointInfo.hitCount < breakpointInfo.hitLimit) {\n\t\n\t\t\t\t\t\t\t\tthreadAdapter.resume();\n\t\t\t\t\t\t\t\treturn;\n\t\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t} catch(err) {\n\t\t\t\t\tlog.warn(String(err));\n\t\t\t\t}\n\t\n\t\t\t\tif (event.why?.type === 'exception') {\n\t\n\t\t\t\t\tlet frames = await threadAdapter.fetchAllStackFrames();\n\t\t\t\t\tlet startFrame = (frames.length > 0) ? frames[frames.length - 1] : undefined;\n\t\t\t\t\tif (startFrame) {\n\t\t\t\t\t\ttry {\n\t\n\t\t\t\t\t\t\tconst sourceAdapter = await this.sources.getAdapterForActor(startFrame.frame.where.actor);\n\t\n\t\t\t\t\t\t\tif (sourceAdapter.introductionType === 'debugger eval') {\n\t\n\t\t\t\t\t\t\t\t// skip exceptions triggered by debugger eval code\n\t\t\t\t\t\t\t\tthreadAdapter.resume();\n\t\t\t\t\t\t\t\treturn;\n\t\t\n\t\t\t\t\t\t\t}\n\t\t\t\t\t\t} catch(err) {\n\t\t\t\t\t\t\tlog.warn(String(err));\n\t\t\t\t\t\t}\n\t\t\t\t\t}\n\t\t\t\t}\n\t\n\t\t\t\tthreadAdapter.threadPausedReason = event.why;\n\t\t\t\t// pre-fetch the stackframes, we're going to need them later\n\t\t\t\tthreadAdapter.fetchAllStackFrames();\n\n\t\t\t\tlog.info(`Thread ${threadActor.name} paused , reason: ${event.why?.type}`);\n\t\t\t\tthis.sendStoppedEvent(threadAdapter, event.why);\n\t\t\t}\n\t\t\tif (event.state === 'resumed') {\n\t\t\t\tlog.info(`Thread ${threadActor.name} resumed`);\n\t\t\t\t// TODO we really want to do this synchronously,\n\t\t\t\t// otherwise we may process the next pause before this has finished\n\t\t\t\tawait threadAdapter.disposePauseLifetimeAdapters();\n\t\t\t\tthis.sendEvent(new ContinuedEvent(threadAdapter.id));\n\t\t\t}\n\t\t});\n\n\t\treturn threadAdapter;\n\t}\n\n\tprivate attachSource(sourceActor: ISourceActorProxy, threadAdapter: ThreadAdapter): void {\n\n\t\tconst sourceAdapter = this.sources.addActor(sourceActor);\n\n\t\t// check if this source should be skipped\n\t\tconst source = sourceActor.source;\n\t\tlet skipThisSource: boolean | undefined = undefined;\n\t\tif (sourceAdapter.path !== undefined) {\n\t\t\tskipThisSource = this.skipFilesManager.shouldSkip(sourceAdapter.path);\n\t\t} else if (source.generatedUrl && (!source.url || !isAbsoluteUrl(source.url))) {\n\t\t\tskipThisSource = this.skipFilesManager.shouldSkip(this.pathMapper.removeQueryString(source.generatedUrl));\n\t\t} else if (source.url) {\n\t\t\tskipThisSource = this.skipFilesManager.shouldSkip(this.pathMapper.removeQueryString(source.url));\n\t\t}\n\n\t\tif (skipThisSource !== undefined) {\n\t\t\tif (skipThisSource !== sourceAdapter.isBlackBoxed) {\n\t\t\t\tsourceAdapter.setBlackBoxed(skipThisSource);\n\t\t\t} else if (skipThisSource) {\n\t\t\t\tsourceActor.setBlackbox(skipThisSource);\n\t\t\t}\n\t\t}\n\n\t\tthreadAdapter.sourceActors.add(sourceActor);\n\n\t\tthis.sendNewSourceEvent(threadAdapter, sourceAdapter);\n\t}\n\n\tprivate async sendConsoleMessage(message: FirefoxDebugProtocol.ConsoleMessage, threadAdapter: ThreadAdapter) {\n\t\tconsoleActorLog.debug(`Console API: ${JSON.stringify(message)}`);\n\n\t\tif (message.level === 'clear') {\n\t\t\tthis.sendEvent(new OutputEvent('\\x1b[2J'));\n\t\t\treturn;\n\t\t}\n\n\t\tif (message.level === 'time' && !message.timer?.error) {\n\t\t\t// Match what is done in Firefox console and don't show anything when the timer starts\n\t\t\treturn;\n\t\t}\n\n\t\tlet category = (message.level === 'error') ? 'stderr' :\n\t\t\t(message.level === 'warn') ? 'console' : 'stdout';\n\n\t\tlet outputEvent: DebugProtocol.OutputEvent;\n\n\t\tif (message.level === 'time' && message.timer?.error === \"timerAlreadyExists\") {\n\n\t\t\toutputEvent = new OutputEvent(`Timer “${message.timer.name}” already exists`, 'console');\n\n\t\t} else if (\n\t\t\t(message.level === 'timeLog' || message.level === 'timeEnd') &&\n\t\t\tmessage.timer?.error === \"timerDoesntExist\"\n\t\t) {\n\n\t\t\toutputEvent = new OutputEvent(`Timer “${message.timer.name}” doesn't exist`, 'console');\n\n\t\t} else {\n\n\t\t\tconst args: VariableAdapter[] = [];\n\t\t\tconst previews = message.arguments.map((grip, index) => {\n\t\t\t\tif (message.timer && index === 0) {\n\t\t\t\t\t// The first argument is the timer name\n\t\t\t\t\tconst renderedTimer = `${message.timer.name}: ${message.timer.duration}ms`;\n\t\t\t\t\treturn message.level === 'timeEnd' ? `${renderedTimer} - timer ended` : renderedTimer;\n\t\t\t\t}\n\t\t\t\tif (typeof grip === 'object' && grip.type === 'object') {\n\t\t\t\t\targs.push(VariableAdapter.fromGrip(`arg${index}`, undefined, undefined, grip, true, threadAdapter));\n\t\t\t\t}\n\t\t\t\treturn typeof grip === 'string' ? grip : renderGrip(grip);\n\t\t\t});\n\t\t\tlet msg = previews.join(' ');\n\n\t\t\tif (this.config.showConsoleCallLocation) {\n\t\t\t\tconst filename = this.pathMapper.convertFirefoxUrlToPath(message.filename);\n\t\t\t\tmsg += ` (${filename}:${message.lineNumber}:${message.columnNumber})`;\n\t\t\t}\n\n\t\t\toutputEvent = new OutputEvent(`${msg}\\n`, category);\n\t\t\tif (args.length > 0) {\n\t\t\t\tconst argsAdapter = new ConsoleAPICallAdapter(args, msg, threadAdapter);\n\t\t\t\toutputEvent.body.variablesReference = argsAdapter.variablesProviderId;\n\t\t\t}\n\t\t}\n\n\t\tawait this.addLocation(outputEvent, message.filename, message.lineNumber, message.columnNumber);\n\n\t\tthis.sendEvent(outputEvent);\n\t}\n\n\tprivate async addLocation(\n\t\toutputEvent: DebugProtocol.OutputEvent,\n\t\turl: string,\n\t\tline: number,\n\t\tcolumn: number\n\t) {\n\t\tconst originalLocation = await this.sourceMaps.findOriginalLocation(url, line, column);\n\t\tif (originalLocation?.url) {\n\t\t\tconst sourceAdapter = await this.sources.getAdapterForUrl(originalLocation.url);\n\t\t\tif (sourceAdapter) {\n\t\t\t\toutputEvent.body.source = sourceAdapter.source;\n\t\t\t\toutputEvent.body.line = originalLocation.line;\n\t\t\t\toutputEvent.body.column = originalLocation.column;\n\t\t\t}\n\t\t}\n\t}\n\n\tpublic sendStoppedEvent(\n\t\tthreadAdapter: ThreadAdapter,\n\t\treason?: FirefoxDebugProtocol.ThreadPausedReason\n\t): void {\n\n\t\tlet pauseType = reason ? reason.type : 'interrupt';\n\t\tlet stoppedEvent: DebugProtocol.StoppedEvent = new StoppedEvent(pauseType, threadAdapter.id);\n\t\tstoppedEvent.body.allThreadsStopped = false;\n\n\t\tif (reason && reason.exception) {\n\n\t\t\tif (typeof reason.exception === 'string') {\n\n\t\t\t\tstoppedEvent.body.text = reason.exception;\n\n\t\t\t} else if ((typeof reason.exception === 'object') && (reason.exception.type === 'object')) {\n\n\t\t\t\tlet exceptionGrip = <FirefoxDebugProtocol.ObjectGrip>reason.exception;\n\t\t\t\tif (exceptionGrip.preview && (exceptionGrip.preview.kind === 'Error')) {\n\t\t\t\t\tstoppedEvent.body.text = `${exceptionGrip.class}: ${exceptionGrip.preview.message}`;\n\t\t\t\t} else {\n\t\t\t\t\tstoppedEvent.body.text = exceptionGrip.class;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tthis.sendEvent(stoppedEvent);\n\t}\n\n\t/** tell VS Code and the [Loaded Scripts Explorer](../extension/loadedScripts) about a new thread */\n\tprivate sendThreadStartedEvent(threadAdapter: ThreadAdapter): void {\n\t\tthis.sendEvent(new ThreadEvent('started', threadAdapter.id));\n\t\tthis.sendEvent(new Event('threadStarted', <ThreadStartedEventBody>{\n\t\t\tname: threadAdapter.name,\n\t\t\tid: threadAdapter.id\n\t\t}));\n\t}\n\n\t/** tell VS Code and the [Loaded Scripts Explorer](../extension/loadedScripts) to remove a thread */\n\tprivate sendThreadExitedEvent(threadAdapter: ThreadAdapter): void {\n\t\tthis.sendEvent(new ThreadEvent('exited', threadAdapter.id));\n\t\tthis.sendEvent(new Event('threadExited', <ThreadExitedEventBody>{\n\t\t\tid: threadAdapter.id\n\t\t}));\n\t}\n\n\t/** tell the [Loaded Scripts Explorer](../extension/loadedScripts) about a new source */\n\tprivate sendNewSourceEvent(threadAdapter: ThreadAdapter, sourceAdapter: SourceAdapter): void {\n\n\t\tconst sourceUrl = sourceAdapter.url;\n\n\t\tif (sourceUrl && !sourceUrl.startsWith('javascript:')) {\n\t\t\tthis.sendEvent(new Event('newSource', <NewSourceEventBody>{\n\t\t\t\tthreadId: threadAdapter.id,\n\t\t\t\tsourceId: sourceAdapter.id,\n\t\t\t\turl: sourceUrl,\n\t\t\t\tpath: sourceAdapter.path\n\t\t\t}));\n\t\t}\n\t}\n\n\tpublic sendCustomEvent(event: string, eventBody: any): void {\n\t\tthis.sendEvent(new Event(event, eventBody));\n\t}\n}\n"
  },
  {
    "path": "src/adapter/location.ts",
    "content": "export interface Location {\n\tline: number;\n\tcolumn?: number;\n}\n\nexport interface UrlLocation extends Location {\n\turl?: string;\n}\n\nexport interface LocationWithColumn extends Location {\n\tcolumn: number;\n}\n\nexport interface MappedLocation extends LocationWithColumn {\n\tgenerated?: LocationWithColumn;\n}\n\nexport interface Range {\n\tstart: LocationWithColumn;\n\tend: LocationWithColumn;\n}\n"
  },
  {
    "path": "src/adapter/util/delayedTask.ts",
    "content": "import { Log } from './log';\n\nlet log = Log.create('DelayedTask');\n\nexport class DelayedTask<T> {\n\n\tprivate state: 'waiting' | 'running' | 'finished';\n\tprivate resolve!: (result: T) => void;\n\tprivate reject!: (reason?: any) => void;\n\n\tpublic readonly promise: Promise<T>;\n\n\tpublic constructor(\n\t\tprivate task: () => Promise<T>,\n\t) {\n\n\t\tthis.promise = new Promise<T>((resolve, reject) => {\n\t\t\tthis.resolve = resolve;\n\t\t\tthis.reject = reject;\n\t\t});\n\n\t\tthis.state = 'waiting';\n\t}\n\n\tpublic async execute(): Promise<void> {\n\n\t\tif (this.state !== 'waiting') {\n\t\t\tlog.error(`Tried to execute DelayedTask, but it is ${this.state}`);\n\t\t\treturn;\n\t\t}\n\n\t\tlet result: T;\n\t\ttry {\n\t\t\tthis.state = 'running';\n\t\t\tresult = await this.task();\n\t\t\tthis.resolve(result);\n\t\t} catch (err) {\n\t\t\tthis.reject(err);\n\t\t\tthrow err;\n\t\t}\n\n\t\tthis.state = 'finished';\n\t}\n\n\tpublic cancel(reason?: any): void {\n\n\t\tif (this.state !== 'waiting') {\n\t\t\tlog.error(`Tried to cancel DelayedTask, but it is ${this.state}`);\n\t\t\treturn;\n\t\t}\n\n\t\tthis.reject(reason);\n\t\tthis.state = 'finished';\n\t}\n}\n"
  },
  {
    "path": "src/adapter/util/forkedLauncher.ts",
    "content": "import { spawn, fork } from 'child_process';\nimport * as fs from 'fs-extra';\n\n/**\n * This script is used by the [launchFirefox()](../firefox/launch.ts) function when `reAttach` is\n * set to true in the launch configuration.\n */\n\nlet args = process.argv.splice(2);\n\nlet cmd = args.shift();\n\nif (cmd === 'spawnDetached') {\n\n\tlet exe = args.shift();\n\n\tlet childProc = spawn(exe!, args, { detached: true, stdio: 'ignore' });\n\n\tchildProc.unref();\n\n} else if (cmd === 'forkDetached') {\n\n\tlet script = args.shift();\n\n\tlet childProc = fork(script!, args, { detached: true, stdio: 'ignore' });\n\n\tchildProc.unref();\n\n} else if (cmd === 'spawnAndRemove') {\n\n\tlet pathToRemove = args.shift();\n\tlet exe = args.shift();\n\n\tlet childProc = spawn(exe!, args);\n\n\tchildProc.stdout.on('data', () => undefined);\n\tchildProc.stderr.on('data', () => undefined);\n\n\tchildProc.once('close', () => setTimeout(() => fs.remove(pathToRemove!), 500));\n\n} else if (cmd === 'spawnAndRemove2') {\n\n\tlet pathToRemove = args.shift();\n\tlet pathToRemove2 = args.shift();\n\tlet exe = args.shift();\n\n\tlet childProc = spawn(exe!, args);\n\n\tchildProc.stdout.on('data', () => undefined);\n\tchildProc.stderr.on('data', () => undefined);\n\n\tchildProc.once('close', () => setTimeout(() => fs.remove(pathToRemove!, () => fs.remove(pathToRemove2!)), 500));\n\n}\n"
  },
  {
    "path": "src/adapter/util/fs.ts",
    "content": "import * as fs from 'fs-extra';\nimport { isWindowsPlatform as detectWindowsPlatform } from '../../common/util';\nimport { Log } from './log';\n\nlet log = Log.create('fs');\n\nexport async function isExecutable(path: string): Promise<boolean> {\n\ttry {\n\t\tawait fs.access(path, fs.constants.X_OK);\n\t\treturn true;\n\t} catch (e) {\n\t\treturn false;\n\t}\n}\n\nconst isWindowsPlatform = detectWindowsPlatform();\nconst windowsAbsolutePathRegEx = /^[a-zA-Z]:\\\\/;\n\nexport function normalizePath(sourcePathOrUrl: string): string {\n\tif (isWindowsPlatform && windowsAbsolutePathRegEx.test(sourcePathOrUrl)) {\n\t\treturn sourcePathOrUrl.toLowerCase();\n\t} else {\n\t\treturn sourcePathOrUrl;\n\t}\n}\n"
  },
  {
    "path": "src/adapter/util/log.ts",
    "content": "import * as fs from 'fs-extra';\nimport { LogConfiguration, LogLevel } from '../../common/configuration';\n\nenum NumericLogLevel { Debug, Info, Warn, Error }\n\n/**\n * The logger used throughout the debug adapter.\n * It is configured using the `log` property in the launch or attach configuration.\n */\nexport class Log {\n\n\tprivate static startTime = Date.now();\n\t\n\tprivate static config: LogConfiguration = {};\n\t\n\tprivate static logs = new Map<string, Log>();\n\tprivate static fileDescriptor?: number;\n\n\tpublic static async setConfig(newConfig: LogConfiguration) {\n\t\tif (Log.fileDescriptor !== undefined) {\n\t\t\tawait fs.close(Log.fileDescriptor);\n\t\t\tLog.fileDescriptor = undefined;\n\t\t}\n\n\t\tLog.config = newConfig;\n\t\tif (Log.config.fileName) {\n\t\t\ttry {\n\t\t\t\tLog.fileDescriptor = await fs.open(Log.config.fileName, 'w');\n\t\t\t} catch(e) {}\n\t\t}\n\n\t\tLog.logs.forEach((log) => log.configure());\n\t}\n\t\n\tpublic static consoleLog: (msg: string) => void = console.log;\n\t\n\tpublic static create(name: string): Log {\n\t\treturn new Log(name);\n\t}\n\n\tprivate fileLevel?: NumericLogLevel;\n\tprivate consoleLevel?: NumericLogLevel;\n\tprivate minLevel?: NumericLogLevel;\n\t\n\tconstructor(private name: string) {\n\t\tthis.configure();\n\t\tLog.logs.set(name, this);\n\t}\n\n\tpublic debug(msg: string): void {\n\t\tthis.log(msg, NumericLogLevel.Debug, 'DEBUG');\n\t}\n\t\n\tpublic info(msg: string): void {\n\t\tthis.log(msg, NumericLogLevel.Info, 'INFO ');\n\t}\n\t\n\tpublic warn(msg: string): void {\n\t\tthis.log(msg, NumericLogLevel.Warn, 'WARN ');\n\t}\n\t\n\tpublic error(msg: string): void {\n\t\tthis.log(msg, NumericLogLevel.Error, 'ERROR');\n\t}\n\n\tpublic isDebugEnabled(): boolean {\n\t\treturn (this.minLevel !== undefined) && (this.minLevel <= NumericLogLevel.Debug);\n\t}\n\n\tpublic isInfoEnabled(): boolean {\n\t\treturn (this.minLevel !== undefined) && (this.minLevel <= NumericLogLevel.Info);\n\t}\n\n\tpublic isWarnEnabled(): boolean {\n\t\treturn (this.minLevel !== undefined) && (this.minLevel <= NumericLogLevel.Warn);\n\t}\n\n\tpublic isErrorEnabled(): boolean {\n\t\treturn (this.minLevel !== undefined) && (this.minLevel <= NumericLogLevel.Error);\n\t}\n\n\tprivate configure() {\n\t\tthis.fileLevel = undefined;\n\t\tif (Log.config.fileName && Log.config.fileLevel) {\n\t\t\tthis.fileLevel = this.convertLogLevel(Log.config.fileLevel[this.name]);\n\t\t\tif (this.fileLevel === undefined) {\n\t\t\t\tthis.fileLevel = this.convertLogLevel(Log.config.fileLevel['default']);\n\t\t\t}\n\t\t}\n\t\tif (Log.config.consoleLevel) {\n\t\t\tthis.consoleLevel = this.convertLogLevel(Log.config.consoleLevel[this.name]);\n\t\t\tif (this.consoleLevel === undefined) {\n\t\t\t\tthis.consoleLevel = this.convertLogLevel(Log.config.consoleLevel['default']);\n\t\t\t}\n\t\t}\n\n\t\tthis.minLevel = this.fileLevel;\n\t\tif ((this.consoleLevel !== undefined) && \n\t\t\t((this.minLevel === undefined) || (this.consoleLevel < this.minLevel))) {\n\t\t\tthis.minLevel = this.consoleLevel;\n\t\t}\n\t}\n\n\tprivate convertLogLevel(logLevel: LogLevel): NumericLogLevel | undefined {\n\t\tif (!logLevel) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\tswitch (logLevel) {\n\t\t\tcase 'Debug':\n\t\t\treturn NumericLogLevel.Debug;\n\n\t\t\tcase 'Info':\n\t\t\treturn NumericLogLevel.Info;\n\n\t\t\tcase 'Warn':\n\t\t\treturn NumericLogLevel.Warn;\n\n\t\t\tcase 'Error':\n\t\t\treturn NumericLogLevel.Error;\n\t\t}\n\t}\n\n\tprivate log(msg: string, level: NumericLogLevel, displayLevel: string) {\n\t\tif ((this.minLevel !== undefined) && (level >= this.minLevel)) {\n\t\t\t\n\t\t\tlet elapsedTime = (Date.now() - Log.startTime) / 1000;\n\t\t\tlet elapsedTimeString = elapsedTime.toFixed(3);\n\t\t\twhile (elapsedTimeString.length < 7) {\n\t\t\t\telapsedTimeString = '0' + elapsedTimeString;\n\t\t\t}\n\t\t\tlet logMsg = displayLevel + '|' + elapsedTimeString + '|' + this.name + ': ' + msg;\n\t\t\t\n\t\t\tif ((Log.fileDescriptor !== undefined) && \n\t\t\t\t(this.fileLevel !== undefined) && (level >= this.fileLevel)) {\n\t\t\t\tfs.write(Log.fileDescriptor, logMsg + '\\n', (err, written, str) => {});\n\t\t\t}\n\t\t\tif ((this.consoleLevel !== undefined) && (level >= this.consoleLevel)) {\n\t\t\t\tLog.consoleLog(logMsg);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/adapter/util/misc.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport stripJsonComments from 'strip-json-comments';\nimport { isWindowsPlatform } from '../../common/util';\nimport isAbsoluteUrl from 'is-absolute-url';\n\n/**\n * compare file paths or urls, taking into account whether filenames are case sensitive on the current platform\n */\nexport function pathsAreEqual(path1: string, path2: string | undefined) {\n\tif (path2 === undefined) return false;\n\tif (isWindowsPlatform() && !isAbsoluteUrl(path1)) {\n\t\treturn path1.toUpperCase() === path2.toUpperCase();\n\t} else {\n\t\treturn path1 === path2;\n\t}\n}\n\n/**\n * replace `\\` with `/` on windows and remove trailing slashes\n */\nexport function normalizePath(rawPath: string) {\n\tlet normalized = path.normalize(rawPath);\n\tif (isWindowsPlatform()) {\n\t\tnormalized = normalized.replace(/\\\\/g, '/');\n\t}\n\tif (normalized[normalized.length - 1] === '/') {\n\t\tnormalized = normalized.substr(0, normalized.length - 1);\n\t}\n\n\treturn normalized;\n}\n\nexport function shortenUrl(url: string) {\n\tif (url.startsWith('file://')) {\n\t\tconst i = url.lastIndexOf('/');\n\t\treturn i >= 0 ? url.substring(i + 1) : url;\n\t}\n\tconst i = url.indexOf('://');\n\treturn i >= 0 ? url.substring(i + 3) : url;\n}\n\n/**\n * extract an error message from an exception\n * [grip](https://github.com/mozilla/gecko-dev/blob/master/devtools/docs/backend/protocol.md#grips)\n */\nexport function exceptionGripToString(grip: FirefoxDebugProtocol.Grip | null | undefined) {\n\n\tif ((typeof grip === 'object') && (grip !== null) && (grip.type === 'object')) {\n\n\t\tlet preview = (<FirefoxDebugProtocol.ObjectGrip>grip).preview;\n\t\tif (preview && (preview.kind === 'Error')) {\n\n\t\t\tif (preview.name === 'ReferenceError') {\n\t\t\t\treturn 'not available';\n\t\t\t}\n\n\t\t\tlet str = (preview.name !== undefined) ? (preview.name + ': ') : '';\n\t\t\tstr += (preview.message !== undefined) ? preview.message : '';\n\t\t\tif (str !== '') {\n\t\t\t\treturn str;\n\t\t\t}\n\t\t}\n\n\t} else if (typeof grip === 'string') {\n\t\treturn grip;\n\t}\n\n\treturn 'unknown error';\n}\n\n\nconst identifierExpression = /^[a-zA-Z_$][a-zA-Z_$]*$/;\n\n/**\n * create a javascript expression for accessing a property of an object\n */\nexport function accessorExpression(objectExpression: string | undefined, propertyName: string): string | undefined {\n\tif (objectExpression === undefined) {\n\t\treturn undefined;\n\t} else if (objectExpression === '') {\n\t\treturn propertyName;\n\t} else if (identifierExpression.test(propertyName)) {\n\t\treturn `${objectExpression}.${propertyName}`;\n\t} else {\n\t\tconst escapedPropertyName = propertyName.replace('\\\\', '\\\\\\\\').replace('\\'', '\\\\\\'');\n\t\treturn `${objectExpression}['${escapedPropertyName}']`;\n\t}\n}\n\n/**\n * extract the addon id from a WebExtension's `manifest.json`\n */\nexport async function findAddonId(addonPath: string): Promise<string | undefined> {\n\ttry {\n\t\tconst rawManifest = await fs.readFile(path.join(addonPath, 'manifest.json'), { encoding: 'utf8' });\n\t\tconst manifest = JSON.parse(stripJsonComments(rawManifest));\n\t\tconst id = ((manifest.applications || {}).gecko || {}).id;\n\t\treturn id;\n\t} catch (err) {\n\t\tthrow `Couldn't parse manifest.json: ${err}`;\n\t}\n}\n\nexport function compareStrings(s1: string, s2: string): number {\n\tif (s1 < s2) {\n\t\treturn -1;\n\t} else if (s1 === s2) {\n\t\treturn 0;\n\t} else {\n\t\treturn 1;\n\t}\n}\n"
  },
  {
    "path": "src/adapter/util/net.ts",
    "content": "import * as url from 'url';\nimport * as fs from 'fs-extra';\nimport * as net from 'net';\nimport * as http from 'http';\nimport * as https from 'https';\nimport { setDefaultResultOrder } from 'dns';\nimport fileUriToPath from 'file-uri-to-path';\nimport dataUriToBuffer from 'data-uri-to-buffer';\nimport { Log } from './log';\nimport { delay } from '../../common/util';\n\nlet log = Log.create('net');\n\n/**\n * connect to a TCP port\n */\nexport function connect(port: number, host?: string): Promise<net.Socket> {\n\tsetDefaultResultOrder(\"ipv4first\");\n\treturn new Promise<net.Socket>((resolve, reject) => {\n\t\tlet socket = net.connect(port, host || 'localhost');\n\t\tsocket.on('connect', () => resolve(socket));\n\t\tsocket.on('error', reject);\n\t});\n}\n\n/**\n * Try to connect to a TCP port and keep retrying for the number of seconds given by `timeout`\n * if the connection is rejected initially.\n * Used to connect to Firefox after launching it.\n */\nexport async function waitForSocket(port: number, timeout: number): Promise<net.Socket> {\n\tconst maxIterations = timeout * 5;\n\tlet lastError: any;\n\tfor (var i = 0; i < maxIterations; i++) {\n\t\ttry {\n\t\t\treturn await connect(port);\n\t\t} catch(err) {\n\t\t\tlastError = err;\n\t\t\tawait delay(200);\n\t\t}\n\t}\n\tthrow lastError;\n}\n\nexport function urlBasename(url: string): string {\n\tlet lastSepIndex = url.lastIndexOf('/');\n\tif (lastSepIndex < 0) {\n\t\treturn url;\n\t} else {\n\t\treturn url.substring(lastSepIndex + 1);\n\t}\n}\n\nexport function urlDirname(url: string): string {\n\tlet lastSepIndex = url.lastIndexOf('/');\n\tif (lastSepIndex < 8) {\n\t\treturn url;\n\t} else {\n\t\treturn url.substring(0, lastSepIndex + 1);\n\t}\n}\n\n/**\n * fetch the document from a URI, with support for the http(s), file and data schemes\n */\nexport async function getUri(uri: string): Promise<string> {\n\n\tif (uri.startsWith('data:')) {\n\t\treturn dataUriToBuffer(uri).toString();\n\t}\n\n\tif (uri.startsWith('file:')) {\n\t\treturn await fs.readFile(fileUriToPath(uri), 'utf8');\n\t}\n\n\tif (!uri.startsWith('http:') && !uri.startsWith('https:')) {\n\t\tthrow new Error(`Fetching ${uri} not supported`);\n\t}\n\n\treturn await new Promise<string>((resolve, reject) => {\n\t\tconst parsedUrl = url.parse(uri);\n\t\tconst get = (parsedUrl.protocol === 'https:') ? https.get : http.get;\n\t\tconst options = Object.assign({ rejectUnauthorized: false }, parsedUrl) as https.RequestOptions;\n\n\t\tget(options, response => {\n\t\t\tlet responseData = '';\n\t\t\tresponse.on('data', chunk => responseData += chunk);\n\t\t\tresponse.on('end', () => {\n\t\t\t\tif (response.statusCode === 200) {\n\t\t\t\t\tresolve(responseData);\n\t\t\t\t} else {\n\t\t\t\t\tlog.error(`HTTP GET failed with: ${response.statusCode} ${response.statusMessage}`);\n\t\t\t\t\treject(new Error(responseData.trim()));\n\t\t\t\t}\n\t\t\t});\n\t\t}).on('error', e => {\n\t\t\tlog.error(`HTTP GET failed: ${e}`);\n\t\t\treject(e);\n\t\t});\n\t});\n}\n"
  },
  {
    "path": "src/adapter/util/pathMapper.ts",
    "content": "import * as path from 'path';\nimport * as url from 'url';\nimport isAbsoluteUrl from 'is-absolute-url';\nimport { Log } from './log';\nimport { PathMappings, ParsedAddonConfiguration } from '../configuration';\nimport { isWindowsPlatform as detectWindowsPlatform } from '../../common/util';\nimport { urlDirname } from './net';\n\nlet log = Log.create('PathConversion');\n\nlet isWindowsPlatform = detectWindowsPlatform();\nconst windowsAbsolutePathRegEx = /^[a-zA-Z]:[\\/\\\\]/;\n\n/**\n * This class is used to map the URLs as seen by Firefox to local paths as seen by VS Code.\n * It is configured using the `pathMappings` property in the launch or attach configuration.\n */\nexport class PathMapper {\n\n\tconstructor(\n\t\tprivate readonly pathMappings: PathMappings,\n\t\tprivate readonly pathMappingIndex: string,\n\t\tprivate readonly addonConfig?: ParsedAddonConfiguration\n\t) {}\n\n\tpublic convertFirefoxSourceToPath(source: FirefoxDebugProtocol.Source): string | undefined {\n\t\tif (!source) return undefined;\n\n\t\tif (source.addonID && this.addonConfig && (source.addonID === this.addonConfig.id)) {\n\n\t\t\tlet sourcePath = this.removeQueryString(path.join(this.addonConfig.path, source.addonPath!));\n\t\t\tlog.debug(`Addon script path: ${sourcePath}`);\n\t\t\treturn sourcePath;\n\n\t\t} else if (source.isSourceMapped && source.generatedUrl && source.url && !isAbsoluteUrl(source.url)) {\n\n\t\t\tlet originalPathOrUrl = source.url;\n\n\t\t\tif (path.isAbsolute(originalPathOrUrl)) {\n\n\t\t\t\tlog.debug(`Sourcemapped absolute path: ${originalPathOrUrl}`);\n\n\t\t\t\tif (isWindowsPlatform) {\n\t\t\t\t\toriginalPathOrUrl = path.normalize(originalPathOrUrl);\n\t\t\t\t}\n\n\t\t\t\treturn originalPathOrUrl;\n\n\t\t\t} else {\n\n\t\t\t\tlet generatedUrl = source.generatedUrl;\n\t\t\t\tif ((source.introductionType === 'wasm') && generatedUrl.startsWith('wasm:')) {\n\t\t\t\t\tgeneratedUrl = generatedUrl.substr(5);\n\t\t\t\t}\n\n\t\t\t\tlet sourcePath: string | undefined;\n\t\t\t\tif (originalPathOrUrl.startsWith('../')) {\n\n\t\t\t\t\tlet generatedPath = this.convertFirefoxUrlToPath(generatedUrl);\n\t\t\t\t\tif (!generatedPath) return undefined;\n\t\t\t\t\tsourcePath = path.join(path.dirname(generatedPath), originalPathOrUrl);\n\n\t\t\t\t} else {\n\n\t\t\t\t\tlet sourceUrl = url.resolve(urlDirname(generatedUrl), originalPathOrUrl);\n\t\t\t\t\tsourcePath = this.convertFirefoxUrlToPath(sourceUrl);\n\t\t\t\t\tif (!sourcePath) return undefined;\n\n\t\t\t\t}\n\n\t\t\t\tsourcePath = this.removeQueryString(sourcePath);\n\n\t\t\t\tlog.debug(`Sourcemapped path: ${sourcePath}`);\n\n\t\t\t\treturn sourcePath;\n\t\t\t}\n\n\t\t} else if (source.url) {\n\t\t\treturn this.convertFirefoxUrlToPath(source.url);\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic convertFirefoxUrlToPath(url: string): string | undefined {\n\n\t\tif (url.endsWith('/')) {\n\t\t\turl += this.pathMappingIndex;\n\t\t}\n\n\t\tfor (var i = 0; i < this.pathMappings.length; i++) {\n\n\t\t\tlet { url: from, path: to } = this.pathMappings[i];\n\n\t\t\tif (typeof from === 'string') {\n\n\t\t\t\tif (url.substr(0, from.length) === from) {\n\n\t\t\t\t\tif (to === null) {\n\t\t\t\t\t\tlog.debug(`Url ${url} not converted to path`);\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet thePath = this.removeQueryString(to + decodeURIComponent(url.substr(from.length)));\n\t\t\t\t\tif (isWindowsPlatform && windowsAbsolutePathRegEx.test(thePath)) {\n\t\t\t\t\t\tthePath = path.normalize(thePath);\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.debug(`Converted url ${url} to path ${thePath}`);\n\t\t\t\t\treturn thePath;\n\t\t\t\t}\n\n\t\t\t} else {\n\n\t\t\t\tlet match = from.exec(url);\n\t\t\t\tif (match) {\n\n\t\t\t\t\tif (to === null) {\n\t\t\t\t\t\tlog.debug(`Url ${url} not converted to path`);\n\t\t\t\t\t\treturn undefined;\n\t\t\t\t\t}\n\n\t\t\t\t\tlet thePath = this.removeQueryString(to + decodeURIComponent(match[1]));\n\t\t\t\t\tif (isWindowsPlatform && windowsAbsolutePathRegEx.test(thePath)) {\n\t\t\t\t\t\tthePath = path.normalize(thePath);\n\t\t\t\t\t}\n\n\t\t\t\t\tlog.debug(`Converted url ${url} to path ${thePath}`);\n\t\t\t\t\treturn thePath;\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tlog.info(`Can't convert url ${url} to path`);\n\n\t\treturn undefined;\n\t}\n\n\tremoveQueryString(path: string): string {\n\t\tlet queryStringIndex = path.indexOf('?');\n\t\tif (queryStringIndex >= 0) {\n\t\t\treturn path.substr(0, queryStringIndex);\n\t\t} else {\n\t\t\treturn path;\n\t\t}\n\t}\n}"
  },
  {
    "path": "src/adapter/util/pendingRequests.ts",
    "content": "import { Log } from './log';\n\nlet log = Log.create('PendingRequests');\n\nexport interface PendingRequest<T> {\n\tresolve: (t: T) => void;\n\treject: (err: any) => void;\n}\n\nexport class PendingRequests<T> {\n\t\n\tprivate pendingRequests: PendingRequest<T>[] = [];\n\t\n\tpublic enqueue(req: PendingRequest<T>) {\n\t\tthis.pendingRequests.push(req);\n\t}\n\t\n\tpublic resolveOne(t: T) {\n\t\tif (this.pendingRequests.length > 0) {\n\t\t\tlet request = this.pendingRequests.shift()!;\n\t\t\trequest.resolve(t);\n\t\t} else {\n\t\t\tlog.error(`Received response without corresponding request: ${JSON.stringify(t)}`);\n\t\t}\n\t}\n\t\n\tpublic rejectOne(err: any) {\n\t\tif (this.pendingRequests.length > 0) {\n\t\t\tlet request = this.pendingRequests.shift()!;\n\t\t\trequest.reject(err);\n\t\t} else {\n\t\t\tlog.error(`Received error response without corresponding request: ${JSON.stringify(err)}`);\n\t\t}\n\t}\n\t\n\tpublic isEmpty(): boolean {\n\t\treturn (this.pendingRequests.length === 0);\n\t}\n\n\tpublic resolveAll(t: T) {\n\t\tthis.pendingRequests.forEach((req) => req.resolve(t));\n\t\tthis.pendingRequests = [];\n\t}\n\t\n\tpublic rejectAll(err: any) {\n\t\tthis.pendingRequests.forEach((req) => req.reject(err));\n\t\tthis.pendingRequests = [];\n\t}\n}\n"
  },
  {
    "path": "src/common/configuration.ts",
    "content": "import { DebugProtocol } from '@vscode/debugprotocol';\n\n/**\n * A launch configuration, as provided by VS Code\n */\nexport interface LaunchConfiguration extends CommonConfiguration, DebugProtocol.LaunchRequestArguments {\n\trequest: 'launch';\n\tfile?: string;\n\ttmpDir?: string;\n\tprofile?: string;\n\tkeepProfileChanges?: boolean;\n\tpreferences?: { [key: string]: boolean | number | string | null };\n\tport?: number;\n\tfirefoxArgs?: string[];\n\ttimeout?: number;\n\treAttach?: boolean;\n}\n\n/**\n * An attach configuration, as provided by VS Code\n */\nexport interface AttachConfiguration extends CommonConfiguration, DebugProtocol.AttachRequestArguments {\n\trequest: 'attach';\n\tport?: number;\n\thost?: string;\n}\n\n/**\n * Common properties of launch and attach configurations\n */\nexport interface CommonConfiguration {\n\trequest: 'launch' | 'attach';\n\turl?: string;\n\twebRoot?: string;\n\tfirefoxExecutable?: string;\n\tprofileDir?: string;\n\treloadOnAttach?: boolean;\n\treloadOnChange?: ReloadConfiguration;\n\ttabFilter?: TabFilterConfiguration;\n\tclearConsoleOnReload?: boolean;\n\tpathMappings?: { url: string, path: string | null }[];\n\tpathMappingIndex?: string;\n\tskipFiles?: string[];\n\tshowConsoleCallLocation?: boolean;\n\tlog?: LogConfiguration;\n\taddonPath?: string;\n\tpopupAutohideButton?: boolean;\n\tliftAccessorsFromPrototypes?: number;\n\tsuggestPathMappingWizard?: boolean;\n\tenableCRAWorkaround?: boolean;\n}\n\nexport type ReloadConfiguration = string | string[] | DetailedReloadConfiguration;\n\nexport interface DetailedReloadConfiguration {\n\twatch: string | string[];\n\tignore?: string | string[];\n\tdebounce?: number | boolean;\n}\n\nexport type TabFilterConfiguration = string | string[] | DetailedTabFilterConfiguration;\n\nexport interface DetailedTabFilterConfiguration {\n\tinclude?: string | string[];\n\texclude?: string | string[];\n}\n\nexport declare type LogLevel = 'Debug' | 'Info' | 'Warn' | 'Error';\n\nexport interface LogConfiguration {\n\tfileName?: string;\n\tfileLevel?: { [logName: string]: LogLevel };\n\tconsoleLevel?: { [logName: string]: LogLevel };\n}\n"
  },
  {
    "path": "src/common/customEvents.ts",
    "content": "export interface ThreadStartedEventBody {\n\tname: string;\n\tid: number;\n}\n\nexport interface ThreadExitedEventBody {\n\tid: number;\n}\n\nexport interface NewSourceEventBody {\n\tthreadId: number;\n\tsourceId: number;\n\t/** as seen by Firefox */\n\turl: string | undefined;\n\t/** path or url as seen by VS Code */\n\tpath: string | undefined;\n}\n\nexport interface RemoveSourcesEventBody {\n\tthreadId: number;\n}\n\nexport interface PopupAutohideEventBody {\n\tpopupAutohide: boolean;\n}\n\nexport interface AvailableEventCategory {\n\tname: string;\n\tevents: AvailableEvent[];\n}\n\nexport interface AvailableEvent {\n\tid: string;\n\tname: string;\n}\n\nexport type AvailableEventsEventBody = AvailableEventCategory[];\n"
  },
  {
    "path": "src/common/deferredMap.ts",
    "content": "export class DeferredMap<S, T> {\n\tprivate readonly pending = new Map<S, (t: T) => void>();\n\tprivate readonly existing = new Map<S, T>();\n\tprivate readonly promises = new Map<S, Promise<T>>();\n\n\tpublic get(key: S): Promise<T> {\n\t\tif (this.promises.has(key)) {\n\t\t\treturn this.promises.get(key)!;\n\t\t}\n\n\t\tconst promise = new Promise<T>(resolve => {\n\t\t\tthis.pending.set(key, resolve);\n\t\t});\n\t\tthis.promises.set(key, promise);\n\n\t\treturn promise;\n\t}\n\n\tpublic getExisting(key: S): T | undefined {\n\t\treturn this.existing.get(key);\n\t}\n\n\tpublic getAllExisting(): T[] {\n\t\treturn [...this.existing.values()];\n\t}\n\n\tpublic set(key: S, value: T): void {\n\t\tif (this.pending.has(key)) {\n\t\t\tthis.pending.get(key)!(value);\n\t\t\tthis.pending.delete(key);\n\t\t}\n\t\tif (!this.promises.has(key)) {\n\t\t\tthis.promises.set(key, Promise.resolve(value));\n\t\t}\n\t\tthis.existing.set(key, value);\n\t}\n\n\tpublic delete(key: S) {\n\t\tthis.pending.delete(key);\n\t\tthis.existing.delete(key);\n\t\tthis.promises.delete(key);\n\t}\n}\n"
  },
  {
    "path": "src/common/util.ts",
    "content": "import * as os from 'os';\n\nexport function delay(timeout: number): Promise<void> {\n\treturn new Promise<void>((resolve) => {\n\t\tsetTimeout(resolve, timeout);\n\t});\n}\n\nexport function isWindowsPlatform(): boolean {\n\treturn (os.platform() === 'win32');\n}\n"
  },
  {
    "path": "src/extension/addPathMapping.ts",
    "content": "import * as path from 'path';\nimport * as vscode from 'vscode';\nimport { TreeNode } from './loadedScripts/treeNode';\n\ninterface LaunchConfig {\n\ttype: string;\n\tname: string;\n}\n\ninterface LaunchConfigReference {\n\tworkspaceFolder: vscode.WorkspaceFolder;\n\tlaunchConfigFile: vscode.WorkspaceConfiguration;\n\tindex: number;\n}\n\nexport async function addPathMapping(treeNode: TreeNode): Promise<void> {\n\n\tconst launchConfigReference = _findLaunchConfig();\n\tif (!launchConfigReference) return;\n\n\tconst openDialogResult = await vscode.window.showOpenDialog({\n\t\tcanSelectFiles: (treeNode.treeItem.contextValue === 'file'),\n\t\tcanSelectFolders: (treeNode.treeItem.contextValue === 'directory'),\n\t\tcanSelectMany: false,\n\t\tdefaultUri: launchConfigReference.workspaceFolder.uri,\n\t\topenLabel: 'Map to this ' + treeNode.treeItem.contextValue\n\t});\n\tif (!openDialogResult || (openDialogResult.length === 0)) {\n\t\treturn;\n\t}\n\n\tlet path = (openDialogResult[0].scheme === 'file') ? openDialogResult[0].fsPath : openDialogResult[0].toString();\n\tif (treeNode.treeItem.contextValue === 'directory') {\n\t\tpath += '/';\n\t}\n\n\tconst success = await addPathMappingToLaunchConfig(launchConfigReference, treeNode.getFullPath(), path);\n\n\tif (success) {\n\t\tawait showLaunchConfig(launchConfigReference.workspaceFolder);\n\t\tvscode.window.showWarningMessage('Configuration was modified - please restart your debug session for the changes to take effect');\n\t}\n}\n\nexport async function addNullPathMapping(treeNode: TreeNode): Promise<void> {\n\n\tconst launchConfigReference = _findLaunchConfig();\n\tif (!launchConfigReference) return;\n\n\tconst success = await addPathMappingToLaunchConfig(launchConfigReference, treeNode.getFullPath(), null);\n\n\tif (success) {\n\t\tawait showLaunchConfig(launchConfigReference.workspaceFolder);\n\t\tvscode.window.showWarningMessage('Configuration was modified - please restart your debug session for the changes to take effect');\n\t}\n}\n\nfunction _findLaunchConfig(): LaunchConfigReference | undefined {\n\n\tconst debugSession = vscode.debug.activeDebugSession;\n\tif (!debugSession) {\n\t\tvscode.window.showErrorMessage('No active debug session');\n\t\treturn undefined;\n\t}\n\n\tconst workspaceFolders = vscode.workspace.workspaceFolders;\n\tif (!workspaceFolders) {\n\t\tvscode.window.showErrorMessage('No open folder');\n\t\treturn undefined;\n\t}\n\n\tconst launchConfigReference = findLaunchConfig(workspaceFolders, debugSession);\n\n\tif (!launchConfigReference) {\n\t\tvscode.window.showErrorMessage(`Couldn't find configuration for active debug session '${debugSession.name}'`);\n\t}\n\n\treturn launchConfigReference;\n}\n\nexport function findLaunchConfig(\n\tworkspaceFolders: readonly vscode.WorkspaceFolder[],\n\tactiveDebugSession: vscode.DebugSession\n): LaunchConfigReference | undefined {\n\n\tfor (const workspaceFolder of workspaceFolders) {\n\t\tconst launchConfigFile = vscode.workspace.getConfiguration('launch', workspaceFolder.uri);\n\t\tconst launchConfigs: LaunchConfig[] | undefined = launchConfigFile.get('configurations');\n\t\tif (launchConfigs) {\n\t\t\tfor (let index = 0; index < launchConfigs.length; index++) {\n\t\t\t\tif ((launchConfigs[index].type === activeDebugSession.type) && \n\t\t\t\t\t(launchConfigs[index].name === activeDebugSession.name)) {\n\t\t\t\t\treturn { workspaceFolder, launchConfigFile, index };\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t}\n\n\treturn undefined;\n}\n\nexport async function addPathMappingToLaunchConfig(\n\tlaunchConfigReference: LaunchConfigReference,\n\turl: string,\n\tpath: string | null\n): Promise<boolean> {\n\n\tconst configurations = <any[]>launchConfigReference.launchConfigFile.get('configurations');\n\tconst configuration = configurations[launchConfigReference.index];\n\n\tif (!configuration.pathMappings) {\n\t\tconfiguration.pathMappings = [];\n\t}\n\n\tconst workspacePath = launchConfigReference.workspaceFolder.uri.fsPath;\n\tif (path && path.startsWith(workspacePath)) {\n\t\tpath = '${workspaceFolder}' + path.substr(workspacePath.length);\n\t}\n\n\tconst pathMappings: any[] = configuration.pathMappings;\n\tpathMappings.unshift({ url, path });\n\n\ttry {\n\t\tawait launchConfigReference.launchConfigFile.update('configurations', configurations, vscode.ConfigurationTarget.WorkspaceFolder);\n\t\treturn true;\n\t} catch(e: any) {\n\t\tvscode.window.showErrorMessage(e.message);\n\t\treturn false;\n\t}\n}\n\nexport async function showLaunchConfig(workspaceFolder: vscode.WorkspaceFolder): Promise<void> {\n\tconst uri = workspaceFolder.uri.with({ path: path.posix.join(workspaceFolder.uri.path, '.vscode/launch.json') });\n\tconst document = await vscode.workspace.openTextDocument(uri);\n\tawait vscode.window.showTextDocument(document);\n}\n"
  },
  {
    "path": "src/extension/debugConfigurationProvider.ts",
    "content": "import path from 'path';\nimport * as vscode from 'vscode';\nimport { LaunchConfiguration, AttachConfiguration } from '../common/configuration';\nimport { vscodeUriToPath } from './pathMappingWizard';\n\nconst folderVar = '${workspaceFolder}';\n\nexport class DebugConfigurationProvider implements vscode.DebugConfigurationProvider {\n\n\t/**\n\t * this method is called by VS Code before a debug session is started and makes modifications\n\t * to the debug configuration:\n\t * - some values can be overridden by corresponding VS Code settings\n\t * - if the configuration contains `url` but neither `webRoot` nor `pathMappings`,\n\t *   `webRoot` is set to the workspace folder\n\t * - when running in a remote workspace, we resolve `${workspaceFolder}` ourselves because\n\t *   VS Code resolves it to a local path in the remote workspace but we need the remote URI instead\n\t * - when running in a remote workspace, we check that configuration values that need to point\n\t *   to local files don't contain `${workspaceFolder}`\n\t */\n\tresolveDebugConfiguration(\n\t\tfolder: vscode.WorkspaceFolder | undefined,\n\t\tdebugConfiguration: vscode.DebugConfiguration & (LaunchConfiguration | AttachConfiguration)\n\t): vscode.DebugConfiguration {\n\n\t\tif (!debugConfiguration.type) {\n\t\t\t// The user wants to debug without a launch configuration - we create one for\n\t\t\t// opening the currently active HTML file in Firefox\n\t\t\tconst document = vscode.window.activeTextEditor?.document;\n\t\t\tif (!document || document.languageId !== 'html') {\n\t\t\t\tthrow new Error(\"Please open an HTML file for debugging\");\n\t\t\t}\n\t\t\tif (document.uri.scheme !== 'file') {\n\t\t\t\tthrow new Error(\"Debugging without a launch configuration is not supported in remote workspaces\");\n\t\t\t}\n\n\t\t\tconst file = document.uri.fsPath;\n\t\t\treturn {\n\t\t\t\tname: `Debug ${path.basename(file)} with Firefox`,\n\t\t\t\ttype: 'firefox',\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile\n\t\t\t};\n\t\t}\n\n\t\tdebugConfiguration = { ...debugConfiguration };\n\n\t\tthis.overrideFromSettings(folder, debugConfiguration);\n\n\t\tif (debugConfiguration.url && !debugConfiguration.webRoot && !debugConfiguration.pathMappings && folder) {\n\t\t\tdebugConfiguration.webRoot = vscodeUriToPath(folder.uri);\n\t\t}\n\n\t\tif (folder && (folder.uri.scheme === 'vscode-remote')) {\n\n\t\t\tthis.resolveWorkspaceFolder(folder, debugConfiguration);\n\n\t\t\tthis.checkLocal(debugConfiguration);\n\n\t\t}\n\n\t\treturn debugConfiguration;\n\t}\n\n\tprivate overrideFromSettings(\n\t\tfolder: vscode.WorkspaceFolder | undefined,\n\t\tdebugConfiguration: vscode.DebugConfiguration & (LaunchConfiguration | AttachConfiguration)\n\t): void {\n\n\t\tconst settings = vscode.workspace.getConfiguration('firefox', folder ? folder.uri : null);\n\n\t\tconst executable = this.getSetting<string>(settings, 'executable');\n\t\tif (executable) {\n\t\t\tdebugConfiguration.firefoxExecutable = executable;\n\t\t}\n\n\t\tconst args = this.getSetting<string[]>(settings, 'args');\n\t\tif (args) {\n\t\t\tdebugConfiguration.firefoxArgs = args;\n\t\t}\n\n\t\tconst profileDir = this.getSetting<string>(settings, 'profileDir');\n\t\tif (profileDir) {\n\t\t\tdebugConfiguration.profileDir = profileDir;\n\t\t}\n\n\t\tconst profile = this.getSetting<string>(settings, 'profile');\n\t\tif (profile) {\n\t\t\tdebugConfiguration.profile = profile;\n\t\t}\n\n\t\tconst keepProfileChanges = this.getSetting<boolean>(settings, 'keepProfileChanges');\n\t\tif (keepProfileChanges !== undefined) {\n\t\t\tdebugConfiguration.keepProfileChanges = keepProfileChanges;\n\t\t}\n\n\t\tconst port = this.getSetting<number>(settings, 'port');\n\t\tif (port !== undefined) {\n\t\t\tdebugConfiguration.port = port;\n\t\t}\n\t}\n\n\t/**\n\t * read a value from the user's VS Code settings. If the user hasn't set a value, this\n\t * method returns `undefined` (instead of the default value for the given key).\n\t */\n\tprivate getSetting<T>(settings: vscode.WorkspaceConfiguration, key: string): T | undefined {\n\n\t\tconst values = settings.inspect<T>(key);\n\t\tif (!values) return undefined;\n\n\t\tif (values.workspaceFolderValue !== undefined) return values.workspaceFolderValue;\n\t\tif (values.workspaceValue !== undefined) return values.workspaceValue;\n\t\tif (values.globalValue !== undefined) return values.globalValue;\n\t\treturn undefined;\n\t}\n\n\tprivate resolveWorkspaceFolder(\n\t\tfolder: vscode.WorkspaceFolder,\n\t\tdebugConfiguration: vscode.DebugConfiguration & (LaunchConfiguration | AttachConfiguration)\n\t): void {\n\n\t\tconst uri = folder.uri.toString();\n\t\tif (debugConfiguration.webRoot) {\n\t\t\tdebugConfiguration.webRoot = debugConfiguration.webRoot.replace(folderVar, uri);\n\t\t}\n\n\t\tif (debugConfiguration.pathMappings) {\n\n\t\t\tconst resolvedPathMappings: { url: string, path: string | null }[] = [];\n\n\t\t\tfor (const pathMapping of debugConfiguration.pathMappings) {\n\t\t\t\tif (pathMapping.path) {\n\n\t\t\t\t\tresolvedPathMappings.push({\n\t\t\t\t\t\turl: pathMapping.url,\n\t\t\t\t\t\tpath: pathMapping.path.replace(folderVar, uri)\n\t\t\t\t\t});\n\n\t\t\t\t} else {\n\t\t\t\t\tresolvedPathMappings.push(pathMapping);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tdebugConfiguration.pathMappings = resolvedPathMappings;\n\t\t}\n\t}\n\n\tprivate checkLocal(\n\t\tdebugConfiguration: vscode.DebugConfiguration & (LaunchConfiguration | AttachConfiguration)\n\t): void {\n\n\t\tfunction check(errorMsg: string) {\n\t\t\treturn function(str: string): void {\n\t\t\t\tif (str.indexOf(folderVar) >= 0) {\n\t\t\t\t\tthrow new Error(errorMsg);\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (debugConfiguration.reloadOnChange) {\n\n\t\t\tconst checkReload = check(\"The debug adapter can't watch files in a remote workspace for changes\");\n\n\t\t\tif (typeof debugConfiguration.reloadOnChange === 'string') {\n\n\t\t\t\tcheckReload(debugConfiguration.reloadOnChange);\n\n\t\t\t} else if (Array.isArray(debugConfiguration.reloadOnChange)) {\n\n\t\t\t\tdebugConfiguration.reloadOnChange.forEach(checkReload);\n\n\t\t\t} else {\n\n\t\t\t\tif (typeof debugConfiguration.reloadOnChange.watch === 'string') {\n\t\t\t\t\tcheckReload(debugConfiguration.reloadOnChange.watch);\n\t\t\t\t} else {\n\t\t\t\t\tdebugConfiguration.reloadOnChange.watch.forEach(checkReload);\n\t\t\t\t}\n\n\t\t\t\tif (debugConfiguration.reloadOnChange.ignore) {\n\t\t\t\t\tif (typeof debugConfiguration.reloadOnChange.ignore === 'string') {\n\t\t\t\t\t\tcheckReload(debugConfiguration.reloadOnChange.ignore);\n\t\t\t\t\t} else {\n\t\t\t\t\t\tdebugConfiguration.reloadOnChange.ignore.forEach(checkReload);\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\n\t\tif (debugConfiguration.log && debugConfiguration.log.fileName) {\n\t\t\tcheck(\"The debug adapter can't write a log file in a remote workspace\")(debugConfiguration.log.fileName);\n\t\t}\n\n\t\tif (debugConfiguration.request === 'launch') {\n\n\t\t\tif (debugConfiguration.file) {\n\t\t\t\tcheck(\"Firefox can't open a file in a remote workspace\")(debugConfiguration.file);\n\t\t\t}\n\n\t\t\tif (debugConfiguration.profileDir) {\n\t\t\t\tcheck(\"Firefox can't have its profile in a remote workspace\")(debugConfiguration.profileDir);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/extension/eventBreakpointsProvider.ts",
    "content": "import * as vscode from 'vscode';\nimport { AvailableEvent, AvailableEventCategory } from '../common/customEvents';\n\nexport class EventBreakpointsProvider implements vscode.TreeDataProvider<TreeNode> {\n\n\tprivate availableEvents: AvailableEventCategory[] = [];\n\tprivate readonly rootNode = new RootNode(this);\n\tprivate readonly treeDataChanged = new vscode.EventEmitter<TreeNode | void>();\n\tpublic readonly onDidChangeTreeData: vscode.Event<TreeNode | void>;\n\tpublic readonly activeEventBreakpoints = new Set<string>();\n\n\tconstructor() {\n\t\tthis.onDidChangeTreeData = this.treeDataChanged.event;\n\t}\n\n\tsetAvailableEvents(availableEvents: AvailableEventCategory[]) {\n\t\tthis.availableEvents = availableEvents;\n\t\tthis.treeDataChanged.fire();\n\t}\n\n\tgetAvailableEvents() {\n\t\treturn this.availableEvents;\n\t}\n\n\tupdateActiveEventBreakpoints(event: vscode.TreeCheckboxChangeEvent<TreeNode>) {\n\t\tfor (const [item, state] of event.items) {\n\t\t\titem.setChecked(state === vscode.TreeItemCheckboxState.Checked);\n\t\t}\n\t\tthis.treeDataChanged.fire();\n\t}\n\n\tgetTreeItem(element: TreeNode): vscode.TreeItem | Thenable<vscode.TreeItem> {\n\t\treturn element.item;\n\t}\n\n\tgetChildren(element?: TreeNode | undefined): vscode.ProviderResult<TreeNode[]> {\n\t\treturn (element ?? this.rootNode).getChildren?.() ?? [];\n\t}\n}\n\ninterface TreeNode {\n\titem: vscode.TreeItem;\n\tsetChecked(checked: boolean): void;\n\tgetChildren?(): TreeNode[];\n}\n\nclass RootNode implements TreeNode {\n\n\tpublic readonly item: vscode.TreeItem;\n\n\tconstructor(private readonly provider: EventBreakpointsProvider) {\n\t\tthis.item = new vscode.TreeItem('', vscode.TreeItemCollapsibleState.Collapsed);\n\t}\n\n\tsetChecked(checked: boolean): void {}\n\n\tgetChildren(): TreeNode[] {\n\t\treturn this.provider.getAvailableEvents()?.map(category => new CategoryNode(category, this.provider)) ?? [];\n\t}\n}\n\nclass CategoryNode implements TreeNode {\n\n\tpublic readonly item: vscode.TreeItem;\n\n\tconstructor(\n\t\tprivate readonly category: AvailableEventCategory,\n\t\tprivate readonly provider: EventBreakpointsProvider\n\t) {\n\t\tthis.item = new vscode.TreeItem(category.name, vscode.TreeItemCollapsibleState.Collapsed);\n\t\tthis.item.checkboxState = this.category.events.every(event => this.provider.activeEventBreakpoints.has(event.id)) ?\n\t\t\tvscode.TreeItemCheckboxState.Checked :\n\t\t\tvscode.TreeItemCheckboxState.Unchecked;\n\t}\n\n\tsetChecked(checked: boolean): void {\n\t\tfor (const event of this.category.events) {\n\t\t\tif (checked) {\n\t\t\t\tthis.provider.activeEventBreakpoints.add(event.id);\n\t\t\t} else {\n\t\t\t\tthis.provider.activeEventBreakpoints.delete(event.id);\n\t\t\t}\n\t\t}\n\t}\n\n\tgetChildren(): TreeNode[] {\n\t\treturn this.category.events.map(event => new EventNode(event, this.provider));\n\t}\n}\n\nclass EventNode implements TreeNode {\n\n\tpublic readonly item: vscode.TreeItem;\n\n\tconstructor(\n\t\tprivate readonly event: AvailableEvent,\n\t\tprivate readonly provider: EventBreakpointsProvider\n\t) {\n\t\tthis.item = new vscode.TreeItem(event.name);\n\t\tthis.item.checkboxState = provider.activeEventBreakpoints.has(event.id) ?\n\t\t\tvscode.TreeItemCheckboxState.Checked :\n\t\t\tvscode.TreeItemCheckboxState.Unchecked;\n\t}\n\n\tsetChecked(checked: boolean): void {\n\t\tif (checked) {\n\t\t\tthis.provider.activeEventBreakpoints.add(this.event.id);\n\t\t} else {\n\t\t\tthis.provider.activeEventBreakpoints.delete(this.event.id);\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/extension/loadedScripts/fileNode.ts",
    "content": "import * as vscode from 'vscode';\nimport { NewSourceEventBody } from '../../common/customEvents';\nimport { TreeNode } from './treeNode';\nimport { NonLeafNode } from './nonLeafNode';\n\nexport class FileNode extends TreeNode {\n\n\tpublic constructor(\n\t\tfilename: string,\n\t\tdescription: string | undefined,\n\t\tsourceInfo: NewSourceEventBody,\n\t\tparent: NonLeafNode,\n\t\tsessionId: string\n\t) {\n\t\tsuper((filename.length > 0) ? filename : '(index)', parent, description, vscode.TreeItemCollapsibleState.None);\n\t\tthis.treeItem.contextValue = 'file';\n\n\t\tlet pathOrUri: string;\n\t\tif (sourceInfo.path) {\n\t\t\tpathOrUri = sourceInfo.path;\n\t\t} else {\n\t\t\tpathOrUri = `debug:${encodeURIComponent(sourceInfo.url!)}?session=${encodeURIComponent(sessionId)}&ref=${sourceInfo.sourceId}`;\n\t\t}\n\n\t\tthis.treeItem.command = {\n\t\t\tcommand: 'extension.firefox.openScript',\n\t\t\targuments: [ pathOrUri ],\n\t\t\ttitle: ''\n\t\t}\n\t}\n\n\tpublic getChildren(): TreeNode[] {\n\t\treturn [];\n\t}\n\n\tpublic getFullPath(): string {\n\t\treturn this.parent!.getFullPath() + this.treeItem.label;\n\t}\n}\n"
  },
  {
    "path": "src/extension/loadedScripts/nonLeafNode.ts",
    "content": "import * as vscode from 'vscode';\nimport { NewSourceEventBody, ThreadStartedEventBody } from '../../common/customEvents';\nimport { TreeNode } from './treeNode';\nimport { FileNode } from './fileNode';\nimport { SessionNode } from './sessionNode';\n\nexport abstract class NonLeafNode extends TreeNode {\n\n\tprotected children: (DirectoryNode | FileNode)[] = [];\n\n\tpublic constructor(label: string, parent: TreeNode) {\n\t\tsuper(label, parent);\n\t}\n\n\tpublic addSource(\n\t\tfilename: string,\n\t\tpath: string[],\n\t\tdescription: string | undefined,\n\t\tsourceInfo: NewSourceEventBody,\n\t\tsessionId: string\n\t): TreeNode | undefined {\n\n\t\tif (path.length === 0) {\n\n\t\t\t// add the source file to this directory (not a subdirectory)\n\t\t\tthis.addChild(new FileNode(filename, description, sourceInfo, this, sessionId));\n\t\t\treturn this;\n\n\t\t}\n\n\t\t// find the index (if it exists) of the child directory item whose path starts\n\t\t// with the same directory name as the path to be added\n\t\tlet itemIndex = this.children.findIndex(\n\t\t\t(item) => ((item instanceof DirectoryNode) && (item.path[0] === path[0]))\n\t\t);\n\n\t\tif (itemIndex < 0) {\n\n\t\t\t// there is no subdirectory that shares an initial path segment with the path to be added,\n\t\t\t// so we create a SourceDirectoryTreeItem for the path and add the source file to it\n\t\t\tlet directoryItem = new DirectoryNode(path, this);\n\t\t\tdirectoryItem.addSource(filename, [], description, sourceInfo, sessionId);\n\t\t\tthis.addChild(directoryItem);\n\t\t\treturn this;\n\n\t\t}\n\n\t\t// the subdirectory item that shares an initial path segment with the path to be added\n\t\tlet item = <DirectoryNode>this.children[itemIndex];\n\n\t\t// the length of the initial path segment that is equal\n\t\tlet pathMatchLength = path.findIndex(\n\t\t\t(pathElement, index) => ((index >= item.path.length) || (item.path[index] !== pathElement))\n\t\t);\n\t\tif (pathMatchLength < 0) pathMatchLength = path.length;\n\n\t\t// the unmatched end segment of the path\n\t\tlet pathRest = path.slice(pathMatchLength);\n\n\t\tif (pathMatchLength === item.path.length) {\n\n\t\t\t// the entire path of the subdirectory item is contained in the path of the file to be\n\t\t\t// added, so we add the file with the pathRest to the subdirectory item\n\t\t\treturn item.addSource(filename, pathRest, description, sourceInfo, sessionId);\n\n\t\t}\n\n\t\t// only a part of the path of the subdirectory item is contained in the path of the file to\n\t\t// be added, so we split the subdirectory item into two and add the file to the first item\n\t\titem.split(pathMatchLength);\n\t\titem.addSource(filename, pathRest, description, sourceInfo, sessionId);\n\t\treturn item;\n\n\t}\n\n\tpublic getChildren(): TreeNode[] {\n\t\tthis.treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Expanded;\n\t\treturn this.children;\n\t}\n\n\t/**\n\t * add a child item, respecting the sort order\n\t */\n\tprivate addChild(newChild: DirectoryNode | FileNode): void {\n\n\t\tlet index: number;\n\n\t\tif (newChild instanceof DirectoryNode) {\n\t\t\tindex = this.children.findIndex(\n\t\t\t\t(child) => !((child instanceof DirectoryNode) && \n\t\t\t\t\t\t\t (child.treeItem.label! < newChild.treeItem.label!))\n\t\t\t);\n\t\t} else {\n\t\t\tindex = this.children.findIndex(\n\t\t\t\t(child) => ((child instanceof FileNode) &&\n\t\t\t\t\t\t\t(child.treeItem.label! >= newChild.treeItem.label!))\n\t\t\t);\n\t\t}\n\n\t\tif (index >= 0) {\n\n\t\t\tif (this.children[index].treeItem.label !== newChild.treeItem.label) {\n\t\t\t\tthis.children.splice(index, 0, newChild);\n\t\t\t}\n\n\t\t} else {\n\n\t\t\tthis.children.push(newChild);\n\n\t\t}\n\t}\n}\n\nexport class ThreadNode extends NonLeafNode {\n\n\tpublic readonly id: number;\n\n\tpublic constructor(threadInfo: ThreadStartedEventBody, parent: SessionNode) {\n\t\tsuper(threadInfo.name, parent);\n\t\tthis.id = threadInfo.id;\n\t\tthis.treeItem.contextValue = 'thread';\n\t}\n\n\tpublic removeSources(): TreeNode | undefined {\n\t\tthis.children = [];\n\t\treturn this;\n\t}\n}\n\nexport class DirectoryNode extends NonLeafNode {\n\n\tpublic constructor(public path: string[], parent: TreeNode) {\n\t\tsuper(path.join('/'), parent);\n\t\tthis.treeItem.contextValue = 'directory';\n\t}\n\n\t/**\n\t * split this item into two items with this item representing the initial path segment of length\n\t * `atIndex` and the new child item representing the rest of the path\n\t */\n\tpublic split(atIndex: number): void {\n\n\t\tlet newChild = new DirectoryNode(this.path.slice(atIndex), this);\n\t\tnewChild.children = this.children;\n\t\tnewChild.children.map(grandChild => grandChild.parent = newChild);\n\n\t\tthis.path.splice(atIndex);\n\t\tthis.children = [ newChild ];\n\t\tthis.treeItem.label = this.path.join('/');\n\t}\n\n\tpublic getFullPath(): string {\n\t\treturn this.parent!.getFullPath() + this.treeItem.label + '/';\n\t}\n}\n"
  },
  {
    "path": "src/extension/loadedScripts/provider.ts",
    "content": "import * as vscode from 'vscode';\nimport { ThreadStartedEventBody, NewSourceEventBody } from '../../common/customEvents';\nimport { TreeNode } from './treeNode';\nimport { RootNode } from './rootNode';\n\nexport class LoadedScriptsProvider implements vscode.TreeDataProvider<TreeNode> {\n\n\tprivate readonly root = new RootNode();\n\n\tprivate readonly treeDataChanged = new vscode.EventEmitter<TreeNode | void>();\n\tpublic readonly onDidChangeTreeData: vscode.Event<TreeNode | void>;\n\n\tpublic constructor() {\n\t\tthis.onDidChangeTreeData = this.treeDataChanged.event;\n\t}\n\n\tpublic getTreeItem(node: TreeNode): vscode.TreeItem {\n\t\treturn node.treeItem;\n\t}\n\n\tpublic getChildren(node?: TreeNode): vscode.ProviderResult<TreeNode[]> {\n\t\tlet parent = (node || this.root);\n\t\treturn parent.getChildren();\n\t}\n\n\tpublic hasSession(sessionId: string) {\n\t\treturn this.root.hasSession(sessionId);\n\t}\n\n\tpublic addSession(session: vscode.DebugSession) {\n\t\tlet changedItem = this.root.addSession(session);\n\t\tthis.sendTreeDataChangedEvent(changedItem);\n\t}\n\n\tpublic removeSession(sessionId: string) {\n\t\tlet changedItem = this.root.removeSession(sessionId);\n\t\tthis.sendTreeDataChangedEvent(changedItem);\n\t}\n\n\tpublic async addThread(threadInfo: ThreadStartedEventBody, sessionId: string) {\n\t\tlet changedItem = await this.root.addThread(threadInfo, sessionId);\n\t\tthis.sendTreeDataChangedEvent(changedItem);\n\t}\n\n\tpublic async removeThread(threadId: number, sessionId: string) {\n\t\tlet changedItem = await this.root.removeThread(threadId, sessionId);\n\t\tthis.sendTreeDataChangedEvent(changedItem);\n\t}\n\n\tpublic async addSource(sourceInfo: NewSourceEventBody, sessionId: string) {\n\t\tlet changedItem = await this.root.addSource(sourceInfo, sessionId);\n\t\tthis.sendTreeDataChangedEvent(changedItem);\n\t}\n\n\tpublic async removeSources(threadId: number, sessionId: string) {\n\t\tlet changedItem = await this.root.removeSources(threadId, sessionId);\t\t\n\t\tthis.sendTreeDataChangedEvent(changedItem);\n\t}\n\n\tpublic async getSourceUrls(sessionId: string): Promise<string[] | undefined> {\n\t\treturn this.root.getSourceUrls(sessionId);\n\t}\n\n\tprivate sendTreeDataChangedEvent(changedItem: TreeNode | undefined) {\n\t\tif (changedItem) {\n\t\t\tif (changedItem === this.root) {\n\t\t\t\tthis.treeDataChanged.fire();\n\t\t\t} else {\n\t\t\t\tthis.treeDataChanged.fire(changedItem);\n\t\t\t}\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/extension/loadedScripts/rootNode.ts",
    "content": "import * as vscode from 'vscode';\nimport { ThreadStartedEventBody, NewSourceEventBody } from '../../common/customEvents';\nimport { TreeNode } from './treeNode';\nimport { SessionNode } from './sessionNode';\nimport { DeferredMap } from '../../common/deferredMap';\n\nexport class RootNode extends TreeNode {\n\n\tprivate children = new DeferredMap<string, SessionNode>();\n\tprivate showSessions = false;\n\n\tpublic constructor() {\n\t\tsuper('');\n\t\tthis.treeItem.contextValue = 'root';\n\t}\n\n\tprivate waitForSession(sessionId: string): Promise<SessionNode> {\n\t\treturn this.children.get(sessionId);\n\t}\n\n\tpublic hasSession(sessionId: string): boolean {\n\t\treturn !!this.children.getExisting(sessionId);\n\t}\n\n\tpublic addSession(session: vscode.DebugSession): TreeNode | undefined {\n\t\tthis.children.set(session.id, new SessionNode(session, this));\n\t\treturn this;\n\t}\n\n\tpublic removeSession(sessionId: string): TreeNode | undefined {\n\t\tthis.children.delete(sessionId);\n\t\treturn this;\n\t}\n\n\tpublic async addThread(\n\t\tthreadInfo: ThreadStartedEventBody,\n\t\tsessionId: string\n\t): Promise<TreeNode | undefined> {\n\n\t\tconst sessionNode = await this.waitForSession(sessionId);\n\t\treturn this.fixChangedItem(sessionNode.addThread(threadInfo));\n\n\t}\n\n\tpublic async removeThread(\n\t\tthreadId: number,\n\t\tsessionId: string\n\t): Promise<TreeNode | undefined> {\n\n\t\tconst sessionItem = await this.waitForSession(sessionId);\n\t\treturn sessionItem ? this.fixChangedItem(sessionItem.removeThread(threadId)) : undefined;\n\n\t}\n\n\tpublic async addSource(\n\t\tsourceInfo: NewSourceEventBody,\n\t\tsessionId: string\n\t): Promise<TreeNode | undefined> {\n\n\t\tconst sessionNode = await this.waitForSession(sessionId);\n\t\treturn this.fixChangedItem(sessionNode.addSource(sourceInfo));\n\n\t}\n\n\tpublic async removeSources(threadId: number, sessionId: string): Promise<TreeNode | undefined> {\n\n\t\tconst sessionItem = await this.waitForSession(sessionId);\n\t\treturn sessionItem ? this.fixChangedItem(sessionItem.removeSources(threadId)) : undefined;\n\n\t}\n\n\tpublic async getSourceUrls(sessionId: string): Promise<string[] | undefined> {\n\n\t\tconst sessionNode = await this.waitForSession(sessionId);\n\t\treturn sessionNode ? sessionNode.getSourceUrls() : undefined;\n\n\t}\n\n\tpublic getChildren(): TreeNode[] {\n\n\t\tthis.treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Expanded;\n\n\t\tconst existingChildren = this.children.getAllExisting();\n\t\n\t\tif (this.showSessions || (existingChildren.length > 1)) {\n\n\t\t\tthis.showSessions = true;\n\t\t\treturn existingChildren;\n\n\t\t} else if (existingChildren.length == 1) {\n\n\t\t\treturn existingChildren[0].getChildren();\n\n\t\t} else {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate fixChangedItem(changedItem: TreeNode | undefined): TreeNode | undefined {\n\n\t\tif (!changedItem) return undefined;\n\n\t\tif (!this.showSessions && (changedItem instanceof SessionNode)) {\n\t\t\treturn this;\n\t\t} else {\n\t\t\treturn changedItem;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/extension/loadedScripts/sessionNode.ts",
    "content": "import * as vscode from 'vscode';\nimport { TreeNode } from './treeNode';\nimport { RootNode } from './rootNode';\nimport { ThreadNode } from './nonLeafNode';\nimport { ThreadStartedEventBody, NewSourceEventBody } from '../../common/customEvents';\n\nexport class SessionNode extends TreeNode {\n\n\tprotected children: ThreadNode[] = [];\n\tprivate showThreads = false;\n\tprivate sourceUrls: string[] = [];\n\n\tpublic get id() {\n\t\treturn this.session.id;\n\t}\n\n\tpublic constructor(private session: vscode.DebugSession, parent: RootNode) {\n\t\tsuper(session.name, parent);\n\t\tthis.treeItem.contextValue = 'session';\n\t}\n\n\tpublic addThread(threadInfo: ThreadStartedEventBody): TreeNode | undefined {\n\n\t\tif (!this.children.some((child) => (child.id === threadInfo.id))) {\n\n\t\t\tlet index = this.children.findIndex((child) => (child.treeItem.label! > threadInfo.name));\n\t\t\tif (index < 0) index = this.children.length;\n\n\t\t\tthis.children.splice(index, 0, new ThreadNode(threadInfo, this));\n\n\t\t\treturn this;\n\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic removeThread(threadId: number): TreeNode | undefined {\n\n\t\tthis.children = this.children.filter((child) => (child.id !== threadId));\n\n\t\treturn this;\n\t}\n\n\tpublic addSource(sourceInfo: NewSourceEventBody): TreeNode | undefined {\n\n\t\tif (!sourceInfo.url) return undefined;\n\n\t\tthis.sourceUrls.push(sourceInfo.url);\n\n\t\tlet threadItem = this.children.find((child) => (child.id === sourceInfo.threadId));\n\n\t\tif (threadItem) {\n\n\t\t\tlet path = splitURL(sourceInfo.url);\n\t\t\tlet filename = path.pop()!;\n\n\t\t\tlet description: string | undefined;\n\t\t\tif (sourceInfo.path) {\n\n\t\t\t\tdescription = sourceInfo.path;\n\n\t\t\t\tif (this.session.workspaceFolder) {\n\t\t\t\t\tconst workspaceUri = this.session.workspaceFolder.uri;\n\t\t\t\t\tlet workspacePath = (workspaceUri.scheme === 'file') ? workspaceUri.fsPath : workspaceUri.toString();\n\t\t\t\t\tworkspacePath += '/';\n\t\t\t\t\tif (description.startsWith(workspacePath)) {\n\t\t\t\t\t\tdescription = description.substring(workspacePath.length);\n\t\t\t\t\t}\n\t\t\t\t}\n\n\t\t\t\tdescription = ` → ${description}`;\n\t\t\t}\n\n\t\t\treturn this.fixChangedItem(threadItem.addSource(filename, path, description, sourceInfo, this.id));\n\n\t\t} else {\n\t\t\treturn undefined;\n\t\t}\n\t}\n\n\tpublic removeSources(threadId: number): TreeNode | undefined {\n\n\t\tthis.sourceUrls = [];\n\n\t\tlet threadItem = this.children.find((child) => (child.id === threadId));\n\t\treturn threadItem ? threadItem.removeSources() : undefined;\n\n\t}\n\n\tpublic getSourceUrls(): string[] {\n\t\treturn this.sourceUrls;\n\t}\n\n\tpublic getChildren(): TreeNode[] {\n\n\t\tthis.treeItem.collapsibleState = vscode.TreeItemCollapsibleState.Expanded;\n\n\t\tif (this.showThreads || (this.children.length > 1)) {\n\n\t\t\tthis.showThreads = true;\n\t\t\treturn this.children;\n\n\t\t} else if (this.children.length == 1) {\n\n\t\t\treturn this.children[0].getChildren();\n\n\t\t} else {\n\t\t\treturn [];\n\t\t}\n\t}\n\n\tprivate fixChangedItem(changedItem: TreeNode | undefined): TreeNode | undefined {\n\n\t\tif (!changedItem) return undefined;\n\n\t\tif (!this.showThreads && (changedItem instanceof ThreadNode)) {\n\t\t\treturn this;\n\t\t} else {\n\t\t\treturn changedItem;\n\t\t}\n\t}\n}\n\n/**\n * Split a URL with '/' as the separator, without splitting the origin or the search portion\n */\nfunction splitURL(urlString: string): string[] {\n\n\tlet originLength: number;\n\tlet i = urlString.indexOf(':');\n\tif (i >= 0) {\n\t\ti++;\n\t\tif (urlString[i] === '/') i++;\n\t\tif (urlString[i] === '/') i++;\n\t\toriginLength = urlString.indexOf('/', i);\n\t} else {\n\t\toriginLength = 0;\n\t}\n\n\tlet searchStartIndex = urlString.indexOf('?', originLength);\n\tif (searchStartIndex < 0) {\n\t\tsearchStartIndex = urlString.length;\n\t}\n\n\tlet origin = urlString.substr(0, originLength);\n\tlet search = urlString.substr(searchStartIndex);\n\tlet path = urlString.substring(originLength, searchStartIndex);\n\n\tlet result = path.split('/');\n\tresult[0] = origin + result[0];\n\tresult[result.length - 1] += search;\n\n\treturn result;\n}\n"
  },
  {
    "path": "src/extension/loadedScripts/treeNode.ts",
    "content": "import * as vscode from 'vscode';\n\nexport abstract class TreeNode {\n\n\tpublic readonly treeItem: vscode.TreeItem;\n\n\tpublic constructor(\n\t\tlabel: string,\n\t\tpublic parent?: TreeNode,\n\t\tdescription?: string,\n\t\tcollapsibleState: vscode.TreeItemCollapsibleState = vscode.TreeItemCollapsibleState.Collapsed\n\t) {\n\t\tthis.treeItem = new vscode.TreeItem(label, collapsibleState);\n\t\tthis.treeItem.description = description;\n\t}\n\n\tpublic getFullPath(): string {\n\t\treturn '';\n\t}\n\n\tpublic abstract getChildren(): TreeNode[];\n}\n"
  },
  {
    "path": "src/extension/main.ts",
    "content": "import * as vscode from 'vscode';\nimport isAbsoluteUrl from 'is-absolute-url';\nimport { LoadedScriptsProvider } from './loadedScripts/provider';\nimport { EventBreakpointsProvider } from './eventBreakpointsProvider';\nimport { ThreadStartedEventBody, ThreadExitedEventBody, NewSourceEventBody, RemoveSourcesEventBody, PopupAutohideEventBody, AvailableEventsEventBody } from '../common/customEvents';\nimport { addPathMapping, addNullPathMapping } from './addPathMapping';\nimport { PopupAutohideManager } from './popupAutohideManager';\nimport { DebugConfigurationProvider } from './debugConfigurationProvider';\nimport { createPathMappingForActiveTextEditor, createPathMappingForPath } from './pathMappingWizard';\n\nexport function activate(context: vscode.ExtensionContext) {\n\n\tconst loadedScriptsProvider = new LoadedScriptsProvider();\n\tconst eventBreakpointsProvider = new EventBreakpointsProvider();\n\tconst popupAutohideManager = new PopupAutohideManager(sendCustomRequest);\n\tconst debugConfigurationProvider = new DebugConfigurationProvider();\n\n\tcontext.subscriptions.push(vscode.window.registerTreeDataProvider(\n\t\t'extension.firefox.loadedScripts', loadedScriptsProvider\n\t));\n\n\tconst eventBreakpointsView = vscode.window.createTreeView(\n\t\t'extension.firefox.eventBreakpoints', {\n\t\t\ttreeDataProvider: eventBreakpointsProvider,\n\t\t\tmanageCheckboxStateManually: true,\n\t\t\tshowCollapseAll: true,\n\t\t}\n\t);\n\tcontext.subscriptions.push(eventBreakpointsView);\n\tcontext.subscriptions.push(eventBreakpointsView.onDidChangeCheckboxState(e => {\n\t\teventBreakpointsProvider.updateActiveEventBreakpoints(e);\n\t\tsendCustomRequest('setActiveEventBreakpoints', [...eventBreakpointsProvider.activeEventBreakpoints]);\n\t}));\n\n\tcontext.subscriptions.push(vscode.debug.registerDebugConfigurationProvider(\n\t\t'firefox', debugConfigurationProvider\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.reloadAddon', () => sendCustomRequest('reloadAddon')\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.toggleSkippingFile', (url) => sendCustomRequest('toggleSkippingFile', url)\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.openScript', openScript\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.addPathMapping', addPathMapping\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.addFilePathMapping', addPathMapping\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.addNullPathMapping', addNullPathMapping\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.addNullFilePathMapping', addNullPathMapping\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.enablePopupAutohide', () => popupAutohideManager.setPopupAutohide(true)\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.disablePopupAutohide', () => popupAutohideManager.setPopupAutohide(false)\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.togglePopupAutohide', () => popupAutohideManager.togglePopupAutohide()\n\t));\n\n\tcontext.subscriptions.push(vscode.commands.registerCommand(\n\t\t'extension.firefox.pathMappingWizard', () => createPathMappingForActiveTextEditor(loadedScriptsProvider)\n\t));\n\n\tcontext.subscriptions.push(vscode.debug.onDidReceiveDebugSessionCustomEvent(\n\t\t(event) => onCustomEvent(event, loadedScriptsProvider, eventBreakpointsProvider, popupAutohideManager)\n\t));\n\n\tcontext.subscriptions.push(vscode.debug.onDidStartDebugSession(\n\t\t(session) => onDidStartSession(session, loadedScriptsProvider)\n\t));\n\n\tcontext.subscriptions.push(vscode.debug.onDidTerminateDebugSession(\n\t\t(session) => onDidTerminateSession(session, loadedScriptsProvider, popupAutohideManager)\n\t));\n}\n\nasync function sendCustomRequest(command: string, args?: any): Promise<any> {\n\tawait Promise.all([...activeFirefoxDebugSessions].map(\n\t\tsession => session.customRequest(command, args)\n\t));\n}\n\nconst activeFirefoxDebugSessions = new Set<vscode.DebugSession>();\n\nfunction onDidStartSession(\n\tsession: vscode.DebugSession,\n\tloadedScriptsProvider: LoadedScriptsProvider\n) {\n\tif (session.type === 'firefox') {\n\t\tloadedScriptsProvider.addSession(session);\n\t\tactiveFirefoxDebugSessions.add(session);\n\t}\n}\n\nfunction onDidTerminateSession(\n\tsession: vscode.DebugSession,\n\tloadedScriptsProvider: LoadedScriptsProvider,\n\tpopupAutohideManager: PopupAutohideManager\n) {\n\tif (session.type === 'firefox') {\n\t\tloadedScriptsProvider.removeSession(session.id);\n\t\tactiveFirefoxDebugSessions.delete(session);\n\t\tif (activeFirefoxDebugSessions.size === 0) {\n\t\t\tpopupAutohideManager.disableButton();\n\t\t}\n\t}\n}\n\nfunction onCustomEvent(\n\tevent: vscode.DebugSessionCustomEvent,\n\tloadedScriptsProvider: LoadedScriptsProvider,\n\teventBreakpointsProvider: EventBreakpointsProvider,\n\tpopupAutohideManager: PopupAutohideManager\n) {\n\tif (event.session.type === 'firefox') {\n\n\t\tswitch (event.event) {\n\n\t\t\tcase 'threadStarted':\n\t\t\t\tloadedScriptsProvider.addThread(<ThreadStartedEventBody>event.body, event.session.id);\n\t\t\t\tbreak;\n\n\t\t\tcase 'threadExited':\n\t\t\t\tloadedScriptsProvider.removeThread((<ThreadExitedEventBody>event.body).id, event.session.id);\n\t\t\t\tbreak;\n\n\t\t\tcase 'newSource':\n\t\t\t\tloadedScriptsProvider.addSource(<NewSourceEventBody>event.body, event.session.id);\n\t\t\t\tbreak;\n\n\t\t\tcase 'removeSources':\n\t\t\t\tloadedScriptsProvider.removeSources((<RemoveSourcesEventBody>event.body).threadId, event.session.id);\n\t\t\t\tbreak;\n\n\t\t\tcase 'popupAutohide':\n\t\t\t\tpopupAutohideManager.enableButton((<PopupAutohideEventBody>event.body).popupAutohide);\n\t\t\t\tbreak;\n\n\t\t\tcase 'unknownSource':\n\t\t\t\tcreatePathMappingForPath(event.body, event.session, loadedScriptsProvider);\n\t\t\t\tbreak;\n\n\t\t\tcase 'availableEvents':\n\t\t\t\teventBreakpointsProvider.setAvailableEvents(<AvailableEventsEventBody>event.body);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\nasync function openScript(pathOrUri: string) {\n\n\tlet uri: vscode.Uri;\n\tif (isAbsoluteUrl(pathOrUri)) {\n\t\turi = vscode.Uri.parse(pathOrUri);\n\t} else {\n\t\turi = vscode.Uri.file(pathOrUri);\n\t}\n\n\tconst doc = await vscode.workspace.openTextDocument(uri);\n\n\tvscode.window.showTextDocument(doc);\n}\n"
  },
  {
    "path": "src/extension/pathMappingWizard.ts",
    "content": "import path from 'path';\nimport vscode from 'vscode';\nimport { URL } from 'url';\nimport { LoadedScriptsProvider } from './loadedScripts/provider';\nimport { findLaunchConfig, addPathMappingToLaunchConfig, showLaunchConfig } from './addPathMapping';\n\ninterface PathMapping {\n\turl: string;\n\tpath: string;\n}\n\nexport async function createPathMappingForActiveTextEditor(loadedScriptsProvider: LoadedScriptsProvider) {\n\n\tconst editor = vscode.window.activeTextEditor;\n\tif (!editor) {\n\t\tvscode.window.showErrorMessage(\"There is no active text editor\");\n\t\treturn;\n\t}\n\n\tconst debugSession = vscode.debug.activeDebugSession;\n\tif (!debugSession) {\n\t\tvscode.window.showErrorMessage(\"There is no active debug session\");\n\t\treturn;\n\t}\n\tif (debugSession.type !== 'firefox') {\n\t\tvscode.window.showErrorMessage(\"The active debug session is not of type \\\"firefox\\\"\");\n\t\treturn;\n\t}\n\n\tconst workspaceFolders = vscode.workspace.workspaceFolders;\n\tif (!workspaceFolders) {\n\t\tvscode.window.showErrorMessage('There is no open folder');\n\t\treturn;\n\t}\n\n\tconst launchConfigReference = findLaunchConfig(workspaceFolders, debugSession);\n\tif (!launchConfigReference) {\n\t\tvscode.window.showErrorMessage(`Couldn't find configuration for active debug session \"${debugSession.name}\"`);\n\t\treturn;\n\t}\n\n\tconst ffUrls = await loadedScriptsProvider.getSourceUrls(debugSession.id);\n\tif (!ffUrls) {\n\t\tvscode.window.showErrorMessage(\"Couldn't load the sources of the active debug session\");\n\t\treturn;\n\t}\n\tif (ffUrls.length === 0) {\n\t\tvscode.window.showWarningMessage(\"Firefox didn't load any sources in the active debug session yet\");\n\t\treturn;\n\t}\n\n\tconst vscPath = vscodeUriToPath(editor.document.uri);\n\tconst pathMapping = await createPathMapping(vscPath, ffUrls, debugSession.workspaceFolder);\n\n\tif (pathMapping) {\n\n\t\tconst success = await addPathMappingToLaunchConfig(launchConfigReference, pathMapping.url, pathMapping.path);\n\n\t\tif (success) {\n\t\t\tawait showLaunchConfig(launchConfigReference.workspaceFolder);\n\t\t\tvscode.window.showWarningMessage('Configuration was modified - please restart your debug session for the changes to take effect');\n\t\t}\n\n\t} else {\n\t\tconst vscFilename = editor.document.uri.path.split('/').pop()!;\n\t\tvscode.window.showWarningMessage(`Firefox hasn't loaded any file named \"${vscFilename}\"`);\n\t}\n}\n\nexport async function createPathMappingForPath(\n\tvscPath: string,\n\tdebugSession: vscode.DebugSession,\n\tloadedScriptsProvider: LoadedScriptsProvider\n) {\n\n\tconst workspaceFolders = vscode.workspace.workspaceFolders;\n\tif (!workspaceFolders) {\n\t\treturn;\n\t}\n\n\tconst launchConfigReference = findLaunchConfig(workspaceFolders, debugSession);\n\tif (!launchConfigReference) {\n\t\treturn;\n\t}\n\n\tif (!loadedScriptsProvider.hasSession(debugSession.id)) {\n\t\treturn;\n\t}\n\n\tconst ffUrls = await loadedScriptsProvider.getSourceUrls(debugSession.id);\n\tif (!ffUrls || (ffUrls.length === 0)) {\n\t\treturn;\n\t}\n\n\tconst pathMapping = await createPathMapping(vscPath, ffUrls, debugSession.workspaceFolder);\n\n\tif (!pathMapping) {\n\t\treturn;\n\t}\n\n\tconst message =\n\t\t`This file's path (${vscPath}) isn't mapped to any url that was loaded by Firefox. ` +\n\t\t\"Either this file hasn't been loaded by Firefox yet or \" +\n\t\t\"your debug configuration needs a pathMapping for this file - \" +\n\t\t\"do you think the file has already been loaded and want to let the \" +\n\t\t\"Path Mapping Wizard try to create a pathMapping for you?\";\n\tconst yesOrNo = await vscode.window.showInformationMessage(message, 'Yes', 'No');\n\n\tif (yesOrNo === 'Yes') {\n\n\t\tconst success = await addPathMappingToLaunchConfig(launchConfigReference, pathMapping.url, pathMapping.path);\n\n\t\tif (success) {\n\t\t\tawait showLaunchConfig(launchConfigReference.workspaceFolder);\n\t\t\tvscode.window.showWarningMessage('Configuration was modified - please restart your debug session for the changes to take effect');\n\t\t}\n\t}\n}\n\nasync function createPathMapping(\n\tvscPath: string,\n\tffUrls: string[],\n\tworkspaceFolder?: vscode.WorkspaceFolder\n): Promise<PathMapping | undefined> {\n\n\tconst parsedFfUrls: URL[] = [];\n\tfor (const ffUrl of ffUrls) {\n\t\ttry {\n\t\t\tparsedFfUrls.push(new URL(ffUrl));\n\t\t} catch {}\n\t}\n\n\tconst bestMatch = findBestMatch(vscPath, parsedFfUrls);\n\tif (!bestMatch) return undefined;\n\n\tconst pathMapping = await createPathMappingForMatch(vscPath, bestMatch, parsedFfUrls);\n\n\tif (workspaceFolder) {\n\t\tconst workspaceFolderPath = vscodeUriToPath(workspaceFolder.uri);\n\t\tif (pathMapping.path.startsWith(workspaceFolderPath)) {\n\t\t\tpathMapping.path = '${workspaceFolder}' + pathMapping.path.substring(workspaceFolderPath.length);\n\t\t}\n\t}\n\n\tpathMapping.path = pathMapping.path.replace(/\\\\/g, '/');\n\n\treturn pathMapping;\n}\n\nfunction findBestMatch(vscPath: string, ffUrls: URL[]): URL | undefined {\n\n\tconst vscPathSegments = vscodePathToUri(vscPath).path.split('/');\n\tconst vscFilename = vscPathSegments.pop()!;\n\n\tlet bestMatch: URL | undefined;\n\tlet bestScore = -1;\n\n\tfor (const ffUrl of ffUrls) {\n\n\t\tconst ffPathSegments = ffUrl.pathname.split('/');\n\t\tconst ffFilename = ffPathSegments.pop()!;\n\n\t\tif (ffFilename !== vscFilename) continue;\n\n\t\tlet score = 0;\n\t\twhile ((vscPathSegments.length > 0) && (ffPathSegments.length > 0)) {\n\t\t\tif (vscPathSegments.pop() === ffPathSegments.pop()) {\n\t\t\t\tscore++;\n\t\t\t} else {\n\t\t\t\tbreak;\n\t\t\t}\n\t\t}\n\n\t\tif (score > bestScore) {\n\t\t\tbestMatch = ffUrl;\n\t\t\tbestScore = score;\n\t\t}\n\t}\n\n\treturn bestMatch;\n}\n\nasync function createPathMappingForMatch(\n\tvscPath: string,\n\tmatchingFfUrl: URL,\n\tallFfUrls: URL[]\n): Promise<PathMapping> {\n\n\tlet pathMapping: PathMapping = {\n\t\turl: matchingFfUrl.protocol + '//' + matchingFfUrl.host + matchingFfUrl.pathname,\n\t\tpath: vscPath\n\t};\n\n\twhile (true) {\n\n\t\tconst generalizedPathMapping = generalizePathMapping(pathMapping);\n\n\t\tif (!generalizedPathMapping || !await checkPathMapping(generalizedPathMapping, allFfUrls)) {\n\t\t\treturn pathMapping;\n\t\t}\n\n\t\tpathMapping = generalizedPathMapping;\n\t}\n}\n\nfunction generalizePathMapping(pathMapping: PathMapping): PathMapping | undefined {\n\n\tconst lastSegment = pathMapping.url.substring(pathMapping.url.lastIndexOf('/') + 1);\n\tconst pathSep = vscodePathSep(pathMapping.path);\n\n\tif ((lastSegment === '') || !pathMapping.path.endsWith(pathSep + lastSegment)) {\n\t\treturn undefined;\n\t}\n\n\treturn {\n\t\turl: pathMapping.url.substring(0, pathMapping.url.length - lastSegment.length - 1),\n\t\tpath: pathMapping.path.substring(0, pathMapping.path.length - lastSegment.length - 1)\n\t}\n}\n\nasync function checkPathMapping(pathMapping: PathMapping, ffUrls: URL[]): Promise<boolean> {\n\n\tfor (let i = 0; i < ffUrls.length;) {\n\n\t\tconst ffUrl = ffUrls[i];\n\t\tconst ffUrlWithoutQuery = ffUrl.protocol + '//' + ffUrl.host + ffUrl.pathname;\n\t\tconst vscPath = applyPathMapping(ffUrlWithoutQuery, pathMapping);\n\n\t\tif (vscPath) {\n\t\t\ttry {\n\n\t\t\t\tawait vscode.workspace.fs.stat(vscodePathToUri(vscPath));\n\t\t\t\tffUrls.splice(i, 1);\n\n\t\t\t} catch {\n\t\t\t\treturn false;\n\t\t\t}\n\t\t} else {\n\t\t\ti++;\n\t\t}\n\t}\n\n\treturn true;\n}\n\nfunction applyPathMapping(ffUrl: string, pathMapping: PathMapping): string | undefined {\n\n\tif (ffUrl.startsWith(pathMapping.url)) {\n\n\t\tlet vscPath = pathMapping.path + ffUrl.substring(pathMapping.url.length);\n\t\treturn isWindowsAbsolutePath(vscPath) ? path.normalize(vscPath) : vscPath;\n\n\t} else {\n\t\treturn undefined;\n\t}\n}\n\nconst windowsAbsolutePathRegEx = /^[a-zA-Z]:\\\\/;\n\nfunction isWindowsAbsolutePath(path: string): boolean {\n\treturn windowsAbsolutePathRegEx.test(path);\n}\n\nfunction vscodePathToUri(path: string): vscode.Uri {\n\tif (isWindowsAbsolutePath(path)) {\n\t\treturn vscode.Uri.file(path);\n\t} else {\n\t\treturn vscode.Uri.parse(path);\n\t}\n}\n\nexport function vscodeUriToPath(uri: vscode.Uri): string {\n\treturn (uri.scheme === 'file') ? uri.fsPath : uri.toString();\n}\n\nfunction vscodePathSep(path: string): string {\n\treturn isWindowsAbsolutePath(path) ? '\\\\' : '/';\n}\n"
  },
  {
    "path": "src/extension/popupAutohideManager.ts",
    "content": "import * as vscode from 'vscode';\n\nexport class PopupAutohideManager {\n\n\tprivate button: vscode.StatusBarItem | undefined;\n\n\tconstructor(\n\t\tprivate readonly sendCustomRequest: (command: string, args?: any) => Promise<any>\n\t) {}\n\n\tpublic async setPopupAutohide(popupAutohide: boolean): Promise<void> {\n\t\tawait this.sendCustomRequest('setPopupAutohide', popupAutohide.toString());\n\t\tthis.setButtonText(popupAutohide);\n\t}\n\n\tpublic async togglePopupAutohide(): Promise<void> {\n\t\tconst popupAutohide = await this.sendCustomRequest('togglePopupAutohide');\n\t\tthis.setButtonText(popupAutohide);\n\t}\n\n\tpublic enableButton(popupAutohide: boolean): void {\n\n\t\tif (!this.button) {\n\t\t\tthis.button = vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Left);\n\t\t\tthis.button.command = 'extension.firefox.togglePopupAutohide';\n\t\t\tthis.button.text = '';\n\t\t\tthis.button.show();\n\t\t}\n\n\t\tthis.setButtonText(popupAutohide);\n\t}\n\n\tpublic disableButton(): void {\n\t\tif (this.button) {\n\t\t\tthis.button.dispose();\n\t\t\tthis.button = undefined;\n\t\t}\n\t}\n\n\tprivate setButtonText(popupAutohide: boolean): void {\n\t\tif (this.button) {\n\t\t\tthis.button.text = `Popup auto-hide ${popupAutohide ? 'enabled' : 'disabled'}`;\n\t\t}\n\t}\n}\n"
  },
  {
    "path": "src/test/setup.ts",
    "content": "import { config } from 'dotenv';\nimport fs from 'fs-extra';\n\nconfig({ path: 'src/test/.env'});\n\nexport const mochaHooks = {\n\tasync beforeAll() {\n\t\tif (process.env['FIREFOX_PROFILE_DIR'] && process.env['KEEP_PROFILE_CHANGES'] === 'true') {\n\t\t\tawait fs.remove(process.env['FIREFOX_PROFILE_DIR']);\n\t\t}\n\t}\n};\n"
  },
  {
    "path": "src/test/sourceMapUtil.ts",
    "content": "import * as path from 'path';\nimport * as fs from 'fs-extra';\nimport { Stream } from 'stream';\nimport * as assert from 'assert';\nimport { DebugClient } from '@vscode/debugadapter-testsupport';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport * as util from './util';\n\nexport async function testSourcemaps(\n\tdc: DebugClient,\n\tsrcDir: string,\n\tbreakpoint: DebugProtocol.SourceBreakpoint = { line: 7 }\n): Promise<void> {\n\n\tlet fPath = path.join(srcDir, 'f.js');\n\tlet gPath = path.join(srcDir, 'g.js');\n\n\tawait util.setBreakpoints(dc, fPath, [breakpoint]);\n\n\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, () =>\n\t\tutil.evaluateDelayed(dc, 'f()', 0));\n\tlet threadId = stoppedEvent.body.threadId!;\n\n\tawait checkDebuggeeState(dc, threadId, fPath, 7, 'x', '2');\n\n\tawait util.runCommandAndReceiveStoppedEvent(dc, () => dc.stepInRequest({ threadId }));\n\n\tawait checkDebuggeeState(dc, threadId, gPath, 5, 'y', '2');\n\n\tawait util.runCommandAndReceiveStoppedEvent(dc, () => dc.stepOutRequest({ threadId }));\n\n\tawait checkDebuggeeState(dc, threadId, fPath, 8, 'x', '4');\n\n\tawait util.setBreakpoints(dc, gPath, [ 5 ]);\n\n\tawait util.runCommandAndReceiveStoppedEvent(dc, () => dc.continueRequest({ threadId }));\n\n\tawait checkDebuggeeState(dc, threadId, gPath, 5, 'y', '4');\n}\n\nasync function checkDebuggeeState(\n\tdc: DebugClient,\n\tthreadId: number,\n\tsourcePath: string,\n\tline: number,\n\tvariable: string,\n\tvalue: string\n): Promise<void> {\n\n\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\tassert.equal(stackTrace.body.stackFrames[0].line, line);\n\n\tlet scopes = await dc.scopesRequest({ \n\t\tframeId: stackTrace.body.stackFrames[0].id\n\t});\n\tlet variables = await dc.variablesRequest({ \n\t\tvariablesReference: scopes.body.scopes[0].variablesReference\n\t});\n\tassert.equal(util.findVariable(variables.body.variables, variable).value, value);\n}\n\nexport async function copyFiles(sourceDir: string, targetDir: string, files: string[]): Promise<void> {\n\tawait Promise.all(files.map(\n\t\t(file) => fs.copy(path.join(sourceDir, file), path.join(targetDir, file))));\n}\n\nexport async function injectScriptTags(targetDir: string, scripts: string[]): Promise<void> {\n\tlet file = path.join(targetDir, 'index.html');\n\tlet content = await fs.readFile(file, 'utf8');\n\tlet scriptTags = scripts.map((script) => `<script src=\"${script}\"></script>`);\n\tcontent = content.replace('__SCRIPTS__', scriptTags.join(''));\n\tawait fs.writeFile(file, content);\n}\n\nexport function waitForStreamEnd(s: Stream): Promise<void> {\n\treturn new Promise<void>((resolve) => {\n\t\ts.on('end', () => resolve());\n\t})\n}\n"
  },
  {
    "path": "src/test/testAccessorProperties.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\n\ndescribe('Accessor properties: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\tconst SOURCE_PATH = path.join(TESTDATA_PATH, 'web/main.js');\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should show accessor properties', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet properties = await startAndGetProperties(dc, 98, 'getterAndSetter()');\n\n\t\tassert.equal(util.findVariable(properties, 'getterProperty').value, 'Getter - expand to execute Getter');\n\t\tassert.equal(util.findVariable(properties, 'setterProperty').value, 'Setter');\n\t\tassert.equal(util.findVariable(properties, 'getterAndSetterProperty').value, 'Getter & Setter - expand to execute Getter');\n\t});\n\n\tit('should execute getters on demand', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet properties = await startAndGetProperties(dc, 98, 'getterAndSetter()');\n\n\t\tlet getterProperty = util.findVariable(properties, 'getterProperty');\n\t\tlet getterPropertyResponse = await dc.variablesRequest({ variablesReference: getterProperty.variablesReference });\n\t\tlet getterValue = util.findVariable(getterPropertyResponse.body.variables, 'Value from Getter').value;\n\t\tassert.equal(getterValue, '17');\n\n\t\tlet getterAndSetterProperty = util.findVariable(properties, 'getterAndSetterProperty');\n\t\tlet getterAndSetterPropertyResponse = await dc.variablesRequest({ variablesReference: getterAndSetterProperty.variablesReference });\n\t\tlet getterAndSetterValue = util.findVariable(getterAndSetterPropertyResponse.body.variables, 'Value from Getter').value;\n\t\tassert.equal(getterAndSetterValue, '23');\n\t});\n\n\tit('should execute nested getters', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet properties1 = await startAndGetProperties(dc, 98, 'getterAndSetter()');\n\n\t\tlet getterProperty1 = util.findVariable(properties1, 'nested');\n\t\tlet getterPropertyResponse1 = await dc.variablesRequest({ variablesReference: getterProperty1.variablesReference });\n\t\tlet getterValue1 = util.findVariable(getterPropertyResponse1.body.variables, 'Value from Getter');\n\n\t\tlet propertiesResponse2 = await dc.variablesRequest({ variablesReference: getterValue1.variablesReference });\n\t\tlet properties2 = propertiesResponse2.body.variables;\n\n\t\tlet getterProperty2 = util.findVariable(properties2, 'z');\n\t\tlet getterPropertyResponse2 = await dc.variablesRequest({ variablesReference: getterProperty2.variablesReference });\n\t\tlet getterValue2 = util.findVariable(getterPropertyResponse2.body.variables, 'Value from Getter').value;\n\n\t\tassert.equal(getterValue2, '\"foo\"');\n\t});\n\n\tit('should show and execute getters lifted from prototypes', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, { liftAccessorsFromPrototypes: 2 });\n\n\t\tlet properties1 = await startAndGetProperties(dc, 116, 'protoGetter()');\n\n\t\tlet getterProperty1 = util.findVariable(properties1, 'y');\n\t\tlet getterPropertyResponse1 = await dc.variablesRequest({ variablesReference: getterProperty1.variablesReference });\n\t\tlet getterValue1 = util.findVariable(getterPropertyResponse1.body.variables, 'Value from Getter').value;\n\t\tassert.equal(getterValue1, '\"foo\"');\n\n\t\tlet getterProperty2 = util.findVariable(properties1, 'z');\n\t\tlet getterPropertyResponse2 = await dc.variablesRequest({ variablesReference: getterProperty2.variablesReference });\n\t\tlet getterValue2 = util.findVariable(getterPropertyResponse2.body.variables, 'Value from Getter').value;\n\t\tassert.equal(getterValue2, '\"bar\"');\n\t});\n\n\tit('should only scan the configured number of prototypes for accessors to lift', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, { liftAccessorsFromPrototypes: 1 });\n\n\t\tlet properties = await startAndGetProperties(dc, 116, 'protoGetter()');\n\n\t\tutil.findVariable(properties, 'y');\n\t\tassert.throws(() => util.findVariable(properties, 'z'));\n\t});\n\n\tasync function startAndGetProperties(dc: DebugClient, bpLine: number, trigger: string): Promise<DebugProtocol.Variable[]> {\n\n\t\tawait util.setBreakpoints(dc, SOURCE_PATH, [ bpLine ]);\n\t\n\t\tutil.evaluate(dc, trigger);\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\t\n\t\tlet variablesResponse = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\tlet variable = util.findVariable(variablesResponse.body.variables, 'x');\n\t\tlet propertiesResponse = await dc.variablesRequest({ variablesReference: variable.variablesReference });\n\t\treturn propertiesResponse.body.variables;\n\t}\n});\n"
  },
  {
    "path": "src/test/testConfigurationParser.ts",
    "content": "import * as os from 'os';\nimport { LaunchConfiguration, AttachConfiguration } from '../common/configuration';\nimport { parseConfiguration, NormalizedReloadConfiguration } from '../adapter/configuration';\nimport * as assert from 'assert';\nimport * as path from 'path';\nimport { isWindowsPlatform } from '../common/util';\n\ndescribe('The configuration parser', function() {\n\n\tit('should create default values for a simple launch configuration', async function() {\n\n\t\tlet filePath: string;\n\t\tlet fileUrl: string;\n\t\tlet tabFilter: string;\n\t\tif (isWindowsPlatform()) {\n\t\t\tfilePath = 'c:\\\\Users\\\\user\\\\project\\\\index.html';\n\t\t\tfileUrl = 'file:///c:/Users/user/project/index.html';\n\t\t\ttabFilter = 'file:\\/\\/\\/c:\\/Users\\/user\\/project\\/.*';\n\t\t} else {\n\t\t\tfilePath = '/home/user/project/index.html';\n\t\t\tfileUrl = 'file:///home/user/project/index.html';\n\t\t\ttabFilter = 'file:\\/\\/\\/home\\/user\\/project\\/.*';\n\t\t}\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: filePath\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.attach, undefined);\n\t\tassert.equal(parsedConfiguration.addon, undefined);\n\t\tassert.deepEqual(parsedConfiguration.filesToSkip, []);\n\t\tassert.equal(parsedConfiguration.reloadOnChange, undefined);\n\t\tassert.deepEqual(parsedConfiguration.tabFilter, { include: [ new RegExp(tabFilter) ], exclude: [] });\n\t\tassert.equal(parsedConfiguration.showConsoleCallLocation, false);\n\t\tassert.equal(parsedConfiguration.liftAccessorsFromPrototypes, 0);\n\t\tassert.equal(parsedConfiguration.suggestPathMappingWizard, true);\n\n\t\tassert.ok(parsedConfiguration.launch!.firefoxExecutable);\n\t\tassert.equal([...parsedConfiguration.launch!.firefoxArgs].pop(), fileUrl);\n\t\tassert.equal(parsedConfiguration.launch!.port, 6000);\n\t\tassert.equal(parsedConfiguration.launch!.timeout, 5);\n\t\tassert.equal(parsedConfiguration.launch!.preferences['devtools.debugger.remote-enabled'], true);\n\t\tassert.ok(parsedConfiguration.launch!.profileDir);\n\t\tassert.equal(parsedConfiguration.launch!.srcProfileDir, undefined);\n\t\tassert.equal(parsedConfiguration.launch!.tmpDirs.length, 1);\n\t\tassert.equal(parsedConfiguration.launch!.tmpDirs[0], parsedConfiguration.launch!.profileDir);\n\t});\n\n\tit('should create default values for a simple attach configuration', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'attach',\n\t\t\turl: 'https://mozilla.org/',\n\t\t\twebRoot: '/home/user/project'\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.launch, undefined);\n\t\tassert.equal(parsedConfiguration.addon, undefined);\n\t\tassert.deepEqual(parsedConfiguration.filesToSkip, []);\n\t\tassert.equal(parsedConfiguration.reloadOnChange, undefined);\n\t\tassert.deepEqual(parsedConfiguration.tabFilter, { include: [ /https:\\/\\/mozilla\\.org\\/.*/ ], exclude: [] });\n\t\tassert.equal(parsedConfiguration.showConsoleCallLocation, false);\n\t\tassert.equal(parsedConfiguration.liftAccessorsFromPrototypes, 0);\n\t\tassert.equal(parsedConfiguration.suggestPathMappingWizard, true);\n\n\t\tassert.equal(parsedConfiguration.attach!.port, 6000);\n\t\tassert.equal(parsedConfiguration.attach!.host, 'localhost');\n\t\tassert.equal(parsedConfiguration.attach!.reloadTabs, false);\n\t});\n\n\tit('should require \"file\" or \"url\" to be set in a launch configuration', async function() {\n\t\tawait assertPromiseRejects(parseConfiguration({\n\t\t\trequest: 'launch'\n\t\t}), 'You need to set either \"file\" or \"url\" in the launch configuration');\n\t});\n\n\tit('should require \"file\" to be an absolute path', async function() {\n\t\tawait assertPromiseRejects(parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: './index.html'\n\t\t}), 'The \"file\" property in the launch configuration has to be an absolute path');\n\t});\n\n\tit(`should require \"webRoot\" or \"pathMappings\" if \"url\" is specified in a launch configuration`, async function() {\n\t\tawait assertPromiseRejects(parseConfiguration(<any>{\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org/'\n\t\t}), `If you set \"url\" you also have to set \"webRoot\" or \"pathMappings\" in the launch configuration`);\n\t});\n\n\tfor (let request of [ 'launch', 'attach' ]) {\n\t\tit(`should require \"webRoot\" to be an absolute path in a ${request} configuration`, async function() {\n\t\t\tawait assertPromiseRejects(parseConfiguration(<any>{\n\t\t\t\trequest,\n\t\t\t\turl: 'https://mozilla.org/',\n\t\t\t\twebRoot: './project'\n\t\t\t}), `The \"webRoot\" property in the ${request} configuration has to be an absolute path`);\n\t\t});\n\n\t\tit(`should allow \"url\" without \"webRoot\" if \"pathMappings\" are specified in a ${request} configuration`, async function() {\n\t\t\tawait parseConfiguration(<any>{\n\t\t\t\trequest,\n\t\t\t\turl: 'https://mozilla.org/',\n\t\t\t\tpathMappings: [{\n\t\t\t\t\turl:'https://mozilla.org/',\n\t\t\t\t\tpath: './project'\n\t\t\t\t}]\n\t\t\t});\n\t\t});\n\t}\n\n\tit('should require \"url\" if \"webRoot\" is specified in an attach configuration', async function() {\n\t\tawait assertPromiseRejects(parseConfiguration(<any>{\n\t\t\trequest: 'attach',\n\t\t\twebRoot: '/home/user/project'\n\t\t}), 'If you set \"webRoot\" you also have to set \"url\" in the attach configuration');\n\t});\n\n\tit('should create a pathMapping for mapping \"url\" to \"webRoot\"', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org/',\n\t\t\twebRoot: '/home/user/project'\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://mozilla.org')!.path, '/home/user/project');\n\t});\n\n\tit('should strip a filename from the url and a trailing slash from the webRoot in the pathMapping', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org/index.html',\n\t\t\twebRoot: '/home/user/project/'\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://mozilla.org')!.path, '/home/user/project');\n\t});\n\n\tit('should not strip the hostname from the webRoot in the pathMapping', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org',\n\t\t\twebRoot: '/home/user/project'\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://mozilla.org')!.path, '/home/user/project');\n\t});\n\n\tit('should include a user-specified pathMapping in a launch configuration', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org/index.html',\n\t\t\twebRoot: '/home/user/project/',\n\t\t\tpathMappings: [{\n\t\t\t\turl: 'https://static.mozilla.org',\n\t\t\t\tpath: '/home/user/project/static'\n\t\t\t}]\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://static.mozilla.org')!.path, '/home/user/project/static');\n\t});\n\n\tit('should include a user-specified pathMapping in an attach configuration', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'attach',\n\t\t\tpathMappings: [{\n\t\t\t\turl: 'https://static.mozilla.org',\n\t\t\t\tpath: '/home/user/project/static'\n\t\t\t}]\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://static.mozilla.org')!.path, '/home/user/project/static');\n\t});\n\n\tit('should replace ${webRoot} in a user-specified pathMapping', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org/index.html',\n\t\t\twebRoot: '/home/user/project/',\n\t\t\tpathMappings: [{\n\t\t\t\turl: 'https://static.mozilla.org',\n\t\t\t\tpath: '${webRoot}/static'\n\t\t\t}]\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://static.mozilla.org')!.path, '/home/user/project/static');\n\t});\n\n\tit('should harmonize trailing slashes in user-specified pathMappings', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org/index.html',\n\t\t\twebRoot: '/home/user/project/',\n\t\t\tpathMappings: [{\n\t\t\t\turl: 'https://static.mozilla.org',\n\t\t\t\tpath: '${webRoot}/static/'\n\t\t\t}, {\n\t\t\t\turl: 'https://api.mozilla.org/',\n\t\t\t\tpath: '${webRoot}/api'\n\t\t\t}]\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://static.mozilla.org/')!.path, '/home/user/project/static/');\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'https://api.mozilla.org/')!.path, '/home/user/project/api/');\n\t});\n\n\tit('should add default pathMappings for webpack if webRoot is defined', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org/index.html',\n\t\t\twebRoot: '/home/user/project/'\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'webpack:///~/')!.path, '/home/user/project/node_modules/');\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'webpack:///./~/')!.path, '/home/user/project/node_modules/');\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'webpack:///./')!.path, '/home/user/project/');\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === 'webpack:///src/')!.path, '/home/user/project/src/');\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === (isWindowsPlatform() ? 'webpack:///' : 'webpack://'))!.path, '');\n\t});\n\n\tit('should add only one default pathMapping for webpack if webRoot is not defined', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => mapping.url === (isWindowsPlatform() ? 'webpack:///' : 'webpack://'))!.path, '');\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(mapping) => ((typeof mapping.url === 'string') && mapping.url.startsWith('webpack') && (mapping.url.length > 11))),\n\t\t\tundefined);\n\t});\n\n\tit('should create an attach configuration if \"reAttach\" is set to true', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treAttach: true\n\t\t});\n\n\t\tassert.ok(parsedConfiguration.attach);\n\t});\n\n\t{\n\t\tlet launchConfig: LaunchConfiguration = {\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treAttach: true\n\t\t};\n\t\tlet attachConfig: AttachConfiguration = {\n\t\t\trequest: 'attach',\n\t\t\turl: 'https://mozilla.org/',\n\t\t\twebRoot: '/home/user/project'\n\t\t};\n\n\t\tfor (let config of [ launchConfig, attachConfig ]) {\n\t\tfor (let reloadOnAttach of [ true, false ]) {\n\t\t\tit(`should set \"reloadTabs\" to ${reloadOnAttach} if \"reloadOnAttach\" is set to ${reloadOnAttach} in a ${config.request} configuration`, async function() {\n\n\t\t\t\tlet parsedConfiguration = await parseConfiguration(\n\t\t\t\t\tObject.assign({ reloadOnAttach}, config));\n\n\t\t\t\tassert.equal(parsedConfiguration.attach!.reloadTabs, reloadOnAttach);\n\t\t\t});\n\t\t}}\n\t}\n\n\tit('should set \"reloadTabs\" to true if \"reloadOnAttach\" is not set in a launch configuration with \"reAttach\" set to true', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treAttach: true\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.attach!.reloadTabs, true);\n\t});\n\n\tit('should create a corresponding NormalizedReloadConfiguration if \"reloadOnChange\" is set to a string', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treloadOnChange: '/home/user/project'\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, <NormalizedReloadConfiguration>{\n\t\t\twatch: [ '/home/user/project' ],\n\t\t\tignore: [],\n\t\t\tdebounce: 100\n\t\t});\n\t});\n\n\tit('should create a corresponding NormalizedReloadConfiguration if \"reloadOnChange\" is set to a string array', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treloadOnChange: [ '/home/user/project/js', '/home/user/project/css' ]\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, <NormalizedReloadConfiguration>{\n\t\t\twatch: [ '/home/user/project/js', '/home/user/project/css' ],\n\t\t\tignore: [],\n\t\t\tdebounce: 100\n\t\t});\n\t});\n\n\tit('should convert strings to string arrays and \"debounce\": false to \"debounce\": 0 in a detailed \"reloadOnChange\" configuration', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treloadOnChange: {\n\t\t\t\twatch: '/home/user/project/js',\n\t\t\t\tignore: '/home/user/project/js/dummy.js',\n\t\t\t\tdebounce: false\n\t\t\t}\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, <NormalizedReloadConfiguration>{\n\t\t\twatch: [ '/home/user/project/js' ],\n\t\t\tignore: [ '/home/user/project/js/dummy.js' ],\n\t\t\tdebounce: 0\n\t\t});\n\t});\n\n\tit('should add defaults to a detailed \"reloadOnChange\" configuration', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treloadOnChange: {\n\t\t\t\twatch: [ '/home/user/project/js' ],\n\t\t\t}\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, <NormalizedReloadConfiguration>{\n\t\t\twatch: [ '/home/user/project/js' ],\n\t\t\tignore: [],\n\t\t\tdebounce: 100\n\t\t});\n\t});\n\n\tit('should copy a normalized \"reloadOnChange\" configuration', async function() {\n\n\t\tlet reloadOnChange: NormalizedReloadConfiguration = {\n\t\t\twatch: [ '/home/user/project/js', '/home/user/project/css' ],\n\t\t\tignore: [ '/home/user/project/css/dummy.css' ],\n\t\t\tdebounce: 200\n\t\t}\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treloadOnChange\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, reloadOnChange);\n\t});\n\n\tit('should convert windows-style directory separators for all globs provided to \"reloadOnChange\"', async function () {\n\t\tif (isWindowsPlatform()) {\n\t\t\tlet reloadOnChangeNormalized: NormalizedReloadConfiguration = {\n\t\t\t\twatch: ['C:/Users/WinUser/Projects/project/scripts/**/*.js', '!C:/Users/WinUser/Projects/project/scripts/composer.js'],\n\t\t\t\tignore: ['C:/Users/WinUser/Projects/project/scripts/cache/**/*.js'],\n\t\t\t\tdebounce: 200\n\t\t\t}\n\n\t\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile: 'C:\\\\Users\\\\WinUser\\\\Projects\\\\project/index.html',\n\t\t\t\treloadOnChange: {\n\t\t\t\t\twatch: ['C:\\\\Users\\\\WinUser\\\\Projects\\\\project/scripts/**/*.js', '!C:\\\\Users\\\\WinUser\\\\Projects\\\\project/scripts/composer.js'],\n\t\t\t\t\tignore: ['C:\\\\Users\\\\WinUser\\\\Projects\\\\project/scripts/cache/**/*.js'],\n\t\t\t\t\tdebounce: 200\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, reloadOnChangeNormalized);\n\t\t} else {\n\t\t\tlet reloadOnChangeNormalized: NormalizedReloadConfiguration = {\n\t\t\t\twatch: ['/home/user/project/scripts/**/*.js', '!/home/user/project/scripts/composer.js'],\n\t\t\t\tignore: ['/home/user/project/scripts/cache/**/*.js'],\n\t\t\t\tdebounce: 200\n\t\t\t}\n\n\t\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile: '/home/user/project/index.html',\n\t\t\t\treloadOnChange: {\n\t\t\t\t\twatch: ['/home/user/project/scripts/**/*.js', '!/home/user/project/scripts/composer.js'],\n\t\t\t\t\tignore: ['/home/user/project/scripts/cache/**/*.js'],\n\t\t\t\t\tdebounce: 200\n\t\t\t\t}\n\t\t\t});\n\n\t\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, reloadOnChangeNormalized);\n\t\t}\n\t});\n\n\tit('should convert windows-style directory separators for all globs provided to \"reloadOnChange\" in an array', async function () {\n\t\tif (isWindowsPlatform()) {\n\t\t\tlet reloadOnChangeNormalized: NormalizedReloadConfiguration = {\n\t\t\t\twatch: ['C:/Users/WinUser/Projects/project/scripts/**/*.js', '!C:/Users/WinUser/Projects/project/scripts/composer.js'],\n\t\t\t\tignore: [],\n\t\t\t\tdebounce: 100\n\t\t\t}\n\n\t\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile: 'C:\\\\Users\\\\WinUser\\\\Projects\\\\project/index.html',\n\t\t\t\treloadOnChange: ['C:\\\\Users\\\\WinUser\\\\Projects\\\\project/scripts/**/*.js', '!C:\\\\Users\\\\WinUser\\\\Projects\\\\project/scripts/composer.js']\n\t\t\t});\n\n\t\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, reloadOnChangeNormalized);\n\t\t} else {\n\t\t\tlet reloadOnChangeNormalized: NormalizedReloadConfiguration = {\n\t\t\t\twatch: ['/home/user/project/scripts/**/*.js', '!/home/user/project/scripts/composer.js'],\n\t\t\t\tignore: [],\n\t\t\t\tdebounce: 100\n\t\t\t}\n\n\t\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile: '/home/user/project/index.html',\n\t\t\t\treloadOnChange: ['/home/user/project/scripts/**/*.js', '!/home/user/project/scripts/composer.js']\n\t\t\t});\n\n\t\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, reloadOnChangeNormalized);\n\t\t}\n\t});\n\n\tit('should convert windows-style directory separators for the single glob provided to \"reloadOnChange\"', async function () {\n\t\tif (isWindowsPlatform()) {\n\t\t\tlet reloadOnChangeNormalized: NormalizedReloadConfiguration = {\n\t\t\t\twatch: ['C:/Users/WinUser/Projects/project/scripts/**/*.js'],\n\t\t\t\tignore: [],\n\t\t\t\tdebounce: 100\n\t\t\t}\n\n\t\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile: 'C:\\\\Users\\\\WinUser\\\\Projects\\\\project/index.html',\n\t\t\t\treloadOnChange: 'C:\\\\Users\\\\WinUser\\\\Projects\\\\project/scripts/**/*.js'\n\t\t\t});\n\n\t\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, reloadOnChangeNormalized);\n\t\t} else {\n\t\t\tlet reloadOnChangeNormalized: NormalizedReloadConfiguration = {\n\t\t\t\twatch: ['/home/user/project/scripts/**/*.js'],\n\t\t\t\tignore: [],\n\t\t\t\tdebounce: 100\n\t\t\t}\n\n\t\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile: '/home/user/project/index.html',\n\t\t\t\treloadOnChange: '/home/user/project/scripts/**/*.js'\n\t\t\t});\n\n\t\t\tassert.deepEqual(parsedConfiguration.reloadOnChange, reloadOnChangeNormalized);\n\t\t}\n\t});\n\n\tit('should parse \"skipFiles\"', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tskipFiles: [ '/home/user/project/libs/**/*' ]\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.filesToSkip.length, 1);\n\t});\n\n\tit('should create a corresponding ParsedTabFilterConfiguration if \"tabFilter\" is set to a string', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\ttabFilter: 'http://localhost:3000'\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.tabFilter, {\n\t\t\tinclude: [ /^http:\\/\\/localhost:3000$/ ],\n\t\t\texclude: []\n\t\t});\n\t});\n\n\tit('should create a corresponding ParsedTabFilterConfiguration if \"tabFilter\" is set to a string array', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\ttabFilter: [ 'http://localhost:3000', 'about:newtab' ]\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.tabFilter, {\n\t\t\tinclude: [ /^http:\\/\\/localhost:3000$/, /^about:newtab$/ ],\n\t\t\texclude: []\n\t\t});\n\t});\n\n\tit('should add \"exclude\" to a detailed \"tabFilter\" containing only \"include\"', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\ttabFilter: { include: '*localhost*' }\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.tabFilter, {\n\t\t\tinclude: [ /^.*localhost.*$/ ],\n\t\t\texclude: []\n\t\t});\n\t});\n\n\tit('should add \"include\" to a detailed \"tabFilter\" containing only \"exclude\"', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\ttabFilter: { exclude: 'https://developer.mozilla.org/*' }\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.tabFilter, {\n\t\t\tinclude: [ /.*/ ],\n\t\t\texclude: [ /^https:\\/\\/developer\\.mozilla\\.org\\/.*$/ ]\n\t\t});\n\t});\n\n\tit('should copy a detailed \"tabFilter\"', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\ttabFilter: {\n\t\t\t\tinclude: [ '*localhost*' ],\n\t\t\t\texclude: [ 'https://developer.mozilla.org/*' ]\n\t\t\t}\n\t\t});\n\n\t\tassert.deepEqual(parsedConfiguration.tabFilter, {\n\t\t\tinclude: [ /^.*localhost.*$/ ],\n\t\t\texclude: [ /^https:\\/\\/developer\\.mozilla\\.org\\/.*$/ ]\n\t\t});\n\t});\n\n\tit('should copy the \"showConsoleCallLocation\" value', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tshowConsoleCallLocation: true\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.showConsoleCallLocation, true);\n\t});\n\n\tit('should copy the \"liftAccessorsFromPrototypes\" value', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tliftAccessorsFromPrototypes: 1\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.liftAccessorsFromPrototypes, 1);\n\t});\n\n\tit('should copy the \"suggestPathMappingWizard\" value', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tsuggestPathMappingWizard: false\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.suggestPathMappingWizard, false);\n\t});\n\n\tit('should not allow both \"profile\" and \"profileDir\" to be specified', async function() {\n\t\tawait assertPromiseRejects(parseConfiguration(<any>{\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tprofile: 'default',\n\t\t\tprofileDir: '/home/user/firefoxProfile'\n\t\t}), 'You can set either \"profile\" or \"profileDir\", but not both');\n\t});\n\n\tit('should not allow \"keepProfileChanges\" if neither \"profile\" nor \"profileDir\" is set', async function() {\n\t\tawait assertPromiseRejects(parseConfiguration(<any>{\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tkeepProfileChanges: true,\n\t\t}), 'To enable \"keepProfileChanges\" you need to set either \"profile\" or \"profileDir\"');\n\t});\n\n\tfor (let keepProfileChanges of [ undefined, false ]) {\n\t\tit(`should copy \"profileDir\" to \"srcProfileDir\" if \"keepProfileChanges\" is ${keepProfileChanges}`, async function() {\n\n\t\t\tlet profileDir = '/home/user/project/ff-profile';\n\t\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\t\trequest: 'launch',\n\t\t\t\tfile: '/home/user/project/index.html',\n\t\t\t\tprofileDir,\n\t\t\t\tkeepProfileChanges\n\t\t\t});\n\n\t\t\tassert.equal(parsedConfiguration.launch!.srcProfileDir, profileDir);\n\t\t\tassert.ok(parsedConfiguration.launch!.profileDir);\n\t\t\tassert.notEqual(parsedConfiguration.launch!.profileDir, profileDir);\n\t\t});\n\t}\n\n\tit('should copy \"profileDir\" to \"profileDir\" if \"keepProfileChanges\" is true', async function() {\n\n\t\tlet profileDir = '/home/user/project/ff-profile';\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treAttach: true,\n\t\t\tprofileDir,\n\t\t\tkeepProfileChanges: true\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.launch!.profileDir, profileDir);\n\t\tassert.equal(parsedConfiguration.launch!.srcProfileDir, undefined);\n\t});\n\n\tit('should use the specified tmpDir', async function() {\n\n\t\tconst tmpDir = '/home/user/tmp';\n\t\tconst parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\ttmpDir\n\t\t});\n\n\t\tassert.ok(parsedConfiguration.launch!.profileDir.startsWith(path.join(tmpDir + '/vscode-firefox-debug-profile-')));\n\t});\n\n\tit('should parse user-specified Firefox preferences', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tpreferences: {\n\t\t\t\t'my.boolean': true,\n\t\t\t\t'my.number': 17,\n\t\t\t\t'my.string': 'foo',\n\t\t\t\t'devtools.debugger.remote-enabled': null\n\t\t\t}\n\t\t});\n\t\tlet parsedPreferences = parsedConfiguration.launch!.preferences;\n\n\t\tassert.equal(parsedPreferences['my.boolean'], true);\n\t\tassert.equal(parsedPreferences['my.number'], 17);\n\t\tassert.equal(parsedPreferences['my.string'], 'foo');\n\t\tassert.equal(parsedPreferences['devtools.debugger.remote-enabled'], undefined);\n\t});\n\n\tit('should copy \"port\" from a launch configuration', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\treAttach: true,\n\t\t\tport: 7000\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.attach!.port, 7000);\n\t\tassert.equal(parsedConfiguration.launch!.port, 7000);\n\t\tassert.ok(parsedConfiguration.launch!.firefoxArgs.indexOf('6000') < 0);\n\t\tassert.ok(parsedConfiguration.launch!.firefoxArgs.indexOf('7000') >= 0);\n\t});\n\n\tit('should copy \"timeout\" from a launch configuration', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\ttimeout: 10\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.launch!.timeout, 10);\n\t});\n\n\tit('should add user-specified \"firefoxArgs\"', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: '/home/user/project/index.html',\n\t\t\tfirefoxArgs: [ '-private' ]\n\t\t});\n\n\t\tassert.ok(parsedConfiguration.launch!.firefoxArgs.indexOf('-private') >= 0);\n\t});\n\n\tit('should allow installing WebExtensions that don\\'t specify an ID in their manifest', async function() {\n\t\tawait parseConfiguration(<any>{\n\t\t\trequest: 'launch',\n\t\t\taddonPath: path.join(__dirname, '../../testdata/webExtension2/addOn')\n\t\t});\n\t});\n\n\tit('should add pathMappings for WebExtension debugging', async function() {\n\n\t\tlet addonPath = path.join(__dirname, '../../testdata/webExtension/addOn');\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\taddonPath\n\t\t});\n\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(pathMapping) => (typeof pathMapping.url === 'object') &&\n\t\t\t(pathMapping.url.source === '^moz-extension:\\\\/\\\\/[0-9a-f-]*(\\\\/.*)$'))!.path,\n\t\t\taddonPath);\n\t\tassert.equal(parsedConfiguration.pathMappings.find(\n\t\t\t(pathMapping) => (typeof pathMapping.url === 'object') &&\n\t\t\t(pathMapping.url.source === '^jar:file:.*\\\\/extensions\\\\/%7B12345678-1234-1234-1234-123456781234%7D.xpi!(\\\\/.*)$'))!.path,\n\t\t\taddonPath);\n\t});\n\n\tit('should default to \"about:blank\" as the start page for addon debugging', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\taddonPath: path.join(__dirname, '../../testdata/webExtension/addOn')\n\t\t});\n\n\t\tassert.equal([...parsedConfiguration.launch!.firefoxArgs].pop(), 'about:blank');\n\t});\n\n\tit('should allow setting \"file\" to define the start page for addon debugging', async function() {\n\n\t\tlet filePath: string;\n\t\tlet fileUrl: string;\n\t\tif (isWindowsPlatform()) {\n\t\t\tfilePath = 'c:\\\\Users\\\\user\\\\project\\\\index.html';\n\t\t\tfileUrl = 'file:///c:/Users/user/project/index.html';\n\t\t} else {\n\t\t\tfilePath = '/home/user/project/index.html';\n\t\t\tfileUrl = 'file:///home/user/project/index.html';\n\t\t}\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\tfile: filePath,\n\t\t\taddonPath: path.join(__dirname, '../../testdata/webExtension/addOn')\n\t\t});\n\n\t\tassert.equal([...parsedConfiguration.launch!.firefoxArgs].pop(), fileUrl);\n\t});\n\n\tit('should allow setting \"url\" to define the start page for addon debugging', async function() {\n\n\t\tlet parsedConfiguration = await parseConfiguration({\n\t\t\trequest: 'launch',\n\t\t\turl: 'https://mozilla.org',\n\t\t\twebRoot: '/home/user/project',\n\t\t\taddonPath: path.join(__dirname, '../../testdata/webExtension/addOn')\n\t\t});\n\n\t\tassert.equal([...parsedConfiguration.launch!.firefoxArgs].pop(), 'https://mozilla.org');\n\t});\n});\n\nasync function assertPromiseRejects(promise: Promise<any>, reason: string): Promise<void> {\n\ttry {\n\t\tawait promise;\n\t} catch(err) {\n\t\tassert.equal(err, reason);\n\t\treturn;\n\t}\n\tthrow new Error('The promise was resolved but should have been rejected');\n}\n"
  },
  {
    "path": "src/test/testConsole.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\nimport { isWindowsPlatform } from '../common/util';\n\ndescribe('Debug console: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should forward messages from the browser console to vscode', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tutil.evaluate(dc, 'console.log(\"log\")');\n\t\tlet outputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'stdout');\n\t\tassert.equal(outputEvent.body.output.trim(), 'log');\n\n\t\tutil.evaluate(dc, 'console.debug(\"debug\")');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'stdout');\n\t\tassert.equal(outputEvent.body.output.trim(), 'debug');\n\n\t\tutil.evaluate(dc, 'console.info(\"info\")');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'stdout');\n\t\tassert.equal(outputEvent.body.output.trim(), 'info');\n\n\t\tutil.evaluate(dc, 'console.warn(\"warn\")');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'console');\n\t\tassert.equal(outputEvent.body.output.trim(), 'warn');\n\n\t\tutil.evaluate(dc, 'console.error(\"error\")');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'stderr');\n\t\tassert.equal(outputEvent.body.output.trim(), 'error');\n\n\t\tutil.evaluate(dc, 'console.log(\"foo\",2)');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'stdout');\n\t\tassert.equal(outputEvent.body.output.trim(), 'foo 2');\n\n\t\tutil.evaluate(dc, 'console.log({\"foo\":\"bar\"})');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'stdout');\n\t\tassert.notEqual(outputEvent.body.variablesReference, undefined);\n\n\t\tlet vars = await dc.variablesRequest({ variablesReference: outputEvent.body.variablesReference! });\n\n\t\tassert.equal(vars.body.variables.length, 1);\n\t\tassert.equal(vars.body.variables[0].name, 'arguments');\n\n\t\tvars = await dc.variablesRequest({ variablesReference: vars.body.variables[0].variablesReference });\n\n\t\tassert.equal(vars.body.variables.length, 1);\n\t\tassert.equal(vars.body.variables[0].name, 'arg0');\n\n\t\tvars = await dc.variablesRequest({ variablesReference: vars.body.variables[0].variablesReference });\n\n\t\tassert.equal(vars.body.variables.length, 2);\n\t\tassert.equal(util.findVariable(vars.body.variables, 'foo').value, '\"bar\"');\n\t});\n\n\tit('should send error messages from the browser to vscode', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tdc.setExceptionBreakpointsRequest({ filters: [] });\n\n\t\tutil.evaluateDelayed(dc, 'foo.bar', 0);\n\t\tlet outputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tlet category = outputEvent.body.category;\n\t\tassert.equal((category === 'stdout') || (category === 'stderr'), true);\n\t\tassert.equal(outputEvent.body.output.trim(), 'ReferenceError: foo is not defined');\n\n\t\tutil.evaluateDelayed(dc, 'eval(\"foo(\")', 0);\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tcategory = outputEvent.body.category;\n\t\tassert.equal((category === 'stdout') || (category === 'stderr'), true);\n\t\tassert.equal(outputEvent.body.output.trim(), 'SyntaxError: expected expression, got end of script');\n\n\t\tutil.evaluateDelayed(dc, 'throw new Error(\"Something went wrong\")', 0);\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tcategory = outputEvent.body.category;\n\t\tassert.equal((category === 'stdout') || (category === 'stderr'), true);\n\t\tassert.equal(outputEvent.body.output.trim(), 'Error: Something went wrong');\n\t});\n\n\tit('should append the console call location to console messages', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, {\n\t\t\tshowConsoleCallLocation: true\n\t\t});\n\n\t\tlet expectedMessageEnding = 'testdata/web/main.js:80:10)';\n\t\tif (isWindowsPlatform()) {\n\t\t\texpectedMessageEnding = expectedMessageEnding.replace(/\\//g, '\\\\');\n\t\t}\n\n\t\tutil.evaluate(dc, 'log(\"foo\")');\n\t\tlet outputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.output.substr(0, 5), 'foo (');\n\t\tassert.ok(outputEvent.body.output.endsWith(expectedMessageEnding + '\\n'));\n\n\t\tutil.evaluate(dc, 'log(\"foo\",\"bar\")');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.output.substr(0, 9), 'foo bar (');\n\t\tassert.ok(outputEvent.body.output.endsWith(expectedMessageEnding + '\\n'));\n\n\t\tutil.evaluate(dc, 'log({\"foo\":\"bar\"})');\n\t\toutputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.notEqual(outputEvent.body.variablesReference, undefined);\n\n\t\tassert.equal(outputEvent.body.output.substr(0, 14), '{foo: \"bar\"} (');\n\t\tassert.ok(outputEvent.body.output.endsWith(expectedMessageEnding + '\\n'));\n\t});\n\n\tit('should offer code completions in the debugging console', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet completionsResult = await dc.completionsRequest({ text: 'v', column: 2 });\n\t\tlet completions = completionsResult.body.targets.map(item => item.label);\n\n\t\tassert.ok(completions.length >= 3);\n\t\tassert.ok(completions.indexOf('valueOf') >= 0);\n\t\tassert.ok(completions.indexOf('values') >= 0);\n\t\tassert.ok(completions.indexOf('vars') >= 0);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 12 ]);\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, () => util.evaluate(dc, 'vars()'));\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId });\n\t\tlet frameId = stackTrace.body.stackFrames[0].id;\n\t\tcompletionsResult = await dc.completionsRequest({ frameId, text: 'n', column: 2 });\n\t\tcompletions = completionsResult.body.targets.map(item => item.label);\n\n\t\tassert.ok(completions.length >= 6);\n\t\tassert.ok(completions.indexOf('name') >= 0);\n\t\tassert.ok(completions.indexOf('navigator') >= 0);\n\t\tassert.ok(completions.indexOf('netscape') >= 0);\n\t\tassert.ok(completions.indexOf('noop') >= 0);\n\t\tassert.ok(completions.indexOf('num1') >= 0);\n\t\tassert.ok(completions.indexOf('num2') >= 0);\n\t});\n});\n"
  },
  {
    "path": "src/test/testDataBreakpoints.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport * as path from 'path';\nimport * as assert from 'assert';\nimport * as util from './util';\n\ndescribe('Data breakpoints: The debug adapter', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should add a data breakpoint and hit it', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\t\tconst sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait setupDataBreakpoint(sourcePath);\n\n\t\t// check that we hit the data breakpoint\n\t\tconst stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tconst stackTraceResponse = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tassert.strictEqual(stackTraceResponse.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.strictEqual(stackTraceResponse.body.stackFrames[0].line, 121);\n\t});\n\n\tit('should remove and re-add a data breakpoint and hit it', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\t\tconst sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tconst dataId = await setupDataBreakpoint(sourcePath);\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tawait dc.continueRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\t// set a regular breakpoint after the data breakpoint and remove the data breakpoint\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 122 ], true);\n\t\tawait dc.setDataBreakpointsRequest({ breakpoints: [] });\n\n\t\t// check that we don't hit the data breakpoint anymore\n\t\tutil.evaluate(dc, 'inc(obj)');\n\t\tstoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTraceResponse = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tassert.strictEqual(stackTraceResponse.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.strictEqual(stackTraceResponse.body.stackFrames[0].line, 122);\n\n\t\tawait dc.continueRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\t// re-add the data breakpoint\n\t\tawait dc.setDataBreakpointsRequest({ breakpoints: [ { dataId } ] });\n\n\t\t// check that we hit the data breakpoint again\n\t\tutil.evaluate(dc, 'inc(obj)');\n\t\tstoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tstackTraceResponse = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tassert.strictEqual(stackTraceResponse.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.strictEqual(stackTraceResponse.body.stackFrames[0].line, 121);\n\t});\n\n\tasync function setupDataBreakpoint(sourcePath: string): Promise<string> {\n\n\t\t// set a regular breakpoint and hit it\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 120 ], true);\n\t\tutil.evaluate(dc, 'inc(obj)');\n\t\tconst stoppedEvent = await util.receiveStoppedEvent(dc);\n\n\t\t// find the object in the top scope\n\t\tconst stackTraceResponse = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tconst frameId = stackTraceResponse.body.stackFrames[0].id;\n\t\tconst scopes = await dc.scopesRequest({ frameId });\n\t\tconst scopeVariablesReference = scopes.body.scopes[0].variablesReference;\n\t\tconst variablesResponse = await dc.variablesRequest({ variablesReference: scopeVariablesReference });\n\t\tconst variable = util.findVariable(variablesResponse.body.variables, 'o');\n\n\t\t// set a data breakpoint on the object's `x` property\n\t\tconst dbpInfoResponse = await dc.dataBreakpointInfoRequest({\n\t\t\tvariablesReference: variable.variablesReference,\n\t\t\tname: 'x'\n\t\t});\n\t\tassert.strictEqual(dbpInfoResponse.body.description, 'x');\n\t\tassert.deepStrictEqual(dbpInfoResponse.body.accessTypes, [ 'read', 'write' ]);\n\t\tconst dataId = dbpInfoResponse.body.dataId!;\n\t\tassert.strictEqual(!!dataId, true);\n\n\t\tawait dc.setDataBreakpointsRequest({ breakpoints: [ { dataId, accessType: 'write' } ] });\n\n\t\tdc.continueRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\treturn dataId;\n\t}\n});\n"
  },
  {
    "path": "src/test/testDebugAddons.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\nimport { delay } from '../common/util';\n\nconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\ndescribe('Addons: The debugger', function() {\n\n\tlet dc: DebugClient;\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit(`should debug a WebExtension`, async function() {\n\n\t\tdc = await util.initDebugClientForAddon(TESTDATA_PATH);\n\n\t\tawait debugWebExtension(dc);\n\t});\n\n\tit(`should show log messages from WebExtensions`, async function() {\n\n\t\tdc = await util.initDebugClientForAddon(TESTDATA_PATH);\n\n\t\tawait util.setConsoleThread(dc, await util.findTabThread(dc));\n\t\tutil.evaluate(dc, 'putMessage(\"bar\")');\n\n\t\tlet outputEvent = <DebugProtocol.OutputEvent> await dc.waitForEvent('output');\n\n\t\tassert.equal(outputEvent.body.category, 'stdout');\n\t\tassert.equal(outputEvent.body.output.trim(), 'foo: bar');\n\t});\n\n\tit(`should debug a WebExtension without an ID if it is installed using RDP`, async function() {\n\n\t\tdc = await util.initDebugClientForAddon(TESTDATA_PATH, { addonDirectory: 'webExtension2' });\n\n\t\tawait debugWebExtension(dc, 'webExtension2');\n\t});\n});\n\nasync function debugWebExtension(dc: DebugClient, addonDirectory = 'webExtension') {\n\n\tlet backgroundScriptPath = path.join(TESTDATA_PATH, addonDirectory, 'addOn/backgroundscript.js');\n\tawait util.setBreakpoints(dc, backgroundScriptPath, [ 2 ]);\n\n\tlet contentScriptPath = path.join(TESTDATA_PATH, addonDirectory, 'addOn/contentscript.js');\n\tawait util.setBreakpoints(dc, contentScriptPath,  [ 6 ]);\n\tawait delay(500);\n\n\tawait util.setConsoleThread(dc, await util.findTabThread(dc));\n\tutil.evaluate(dc, 'putMessage(\"bar\")');\n\n\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\tlet contentThreadId = stoppedEvent.body.threadId!;\n\tlet stackTrace = await dc.stackTraceRequest({ threadId: contentThreadId });\n\n\tassert.equal(stackTrace.body.stackFrames[0].source!.path, contentScriptPath);\n\n\tdc.continueRequest({ threadId: contentThreadId });\n\tstoppedEvent = await util.receiveStoppedEvent(dc);\n\tlet addOnThreadId = stoppedEvent.body.threadId!;\n\tstackTrace = await dc.stackTraceRequest({ threadId: addOnThreadId });\n\n\tassert.notEqual(contentThreadId, addOnThreadId);\n}\n"
  },
  {
    "path": "src/test/testDebugWebWorkers.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\n\ndescribe('Webworkers: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tbeforeEach(async function() {\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\t});\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should debug a WebWorker', async function() {\n\n\t\tlet mainSourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, mainSourcePath, [ 55 ]);\n\n\t\tlet workerSourcePath = path.join(TESTDATA_PATH, 'web/worker.js');\n\t\tlet workerBreakpointsResponse = await util.setBreakpoints(dc, workerSourcePath,  [ 2 ], false);\n\t\tlet workerBreakpoint = workerBreakpointsResponse.body.breakpoints[0];\n\n\t\tassert.equal(workerBreakpoint.verified, false);\n\n\t\tutil.evaluateDelayed(dc, 'startWorker()', 0);\n\t\tlet breakpointEvent = await util.receiveBreakpointEvent(dc);\n\n\t\tassert.equal(breakpointEvent.body.breakpoint.id, workerBreakpoint.id);\n\t\tassert.equal(breakpointEvent.body.breakpoint.verified, true);\n\t\tassert.equal(breakpointEvent.body.breakpoint.line, 2);\n\n\t\tutil.evaluateDelayed(dc, 'callWorker()', 0);\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet workerThreadId = stoppedEvent.body.threadId!;\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: workerThreadId });\n\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, workerSourcePath);\n\n\t\tdc.continueRequest({ threadId: workerThreadId });\n\t\tstoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet mainThreadId = stoppedEvent.body.threadId!;\n\t\tstackTrace = await dc.stackTraceRequest({ threadId: mainThreadId });\n\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\t\tlet variables = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\n\t\tassert.notEqual(mainThreadId, workerThreadId);\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, mainSourcePath);\n\t\tassert.equal(util.findVariable(variables.body.variables, 'received').value, '\"bar\"');\n\t});\n\n});\n"
  },
  {
    "path": "src/test/testEvaluate.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\n\ndescribe('Evaluate: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tbeforeEach(async function() {\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\t});\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should evaluate watches while running', async function() {\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj.x + 7', context: 'watch' });\n\t\tassert.equal(evalResult.body.result, '24');\n\t});\n\n\tit('should evaluate watches on different stackframes', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'n*2', context: 'watch',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\t\tassert.equal(evalResult.body.result, '2');\n\n\t\tevalResult = await dc.evaluateRequest({ expression: 'n*2', context: 'watch',\n\t\t\tframeId: stackTrace.body.stackFrames[1].id });\n\t\tassert.equal(evalResult.body.result, '4');\n\t});\n\n\tit('should skip over breakpoints when evaluating watches while running', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 8, 25 ]);\n\n\t\tdc.evaluateRequest({ expression: 'factorial(3)', context: 'watch' });\n\t\tawait util.assertPromiseTimeout(util.receiveStoppedEvent(dc), 200);\n\t});\n\n\tit('should skip over breakpoints when evaluating watches while paused', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 8, 25 ]);\n\n\t\tutil.evaluate(dc, 'vars()');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tdc.evaluateRequest({ expression: 'factorial(3)', context: 'watch',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\t\tawait util.assertPromiseTimeout(util.receiveStoppedEvent(dc), 200);\n\t});\n\n\tit('should inspect watches evaluated while running', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'watch' });\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n\n\tit('should inspect watches evaluated while paused', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'watch',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n\n\tit('should inspect watches (evaluated while running) after running other evaluations', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'watch' });\n\n\t\tawait dc.evaluateRequest({ expression: 'obj.x', context: 'watch' });\n\t\tawait dc.evaluateRequest({ expression: 'obj.y', context: 'repl' });\n\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n\n\tit('should inspect watches (evaluated while paused) after running other evaluations', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'watch',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\n\t\tawait dc.evaluateRequest({ expression: 'n', context: 'watch',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\t\tawait dc.evaluateRequest({ expression: 'obj.y', context: 'repl',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n\n\tit('should evaluate console expressions on different stackframes', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'n*2', context: 'repl',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\t\tassert.equal(evalResult.body.result, '2');\n\n\t\tevalResult = await dc.evaluateRequest({ expression: 'n*2', context: 'repl',\n\t\t\tframeId: stackTrace.body.stackFrames[1].id });\n\t\tassert.equal(evalResult.body.result, '4');\n\t});\n\n\tit('should evaluate console expressions while the thread is running', async function() {\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj.x', context: 'repl' });\n\t\tassert.equal(evalResult.body.result, '17');\n\t});\n\n\tit('should hit breakpoints when evaluating console expressions while running', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 8, 25 ]);\n\n\t\tdc.evaluateRequest({ expression: 'factorial(3)', context: 'repl' });\n\t\tawait util.receiveStoppedEvent(dc);\n\t});\n\n\tit('should inspect console evaluation results after breaking', async function() {\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'repl' });\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n\n\tit('should inspect console evaluation results after running other evaluations', async function() {\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'repl' });\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tawait dc.evaluateRequest({ expression: 'n*2', context: 'watch',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\t\tawait dc.evaluateRequest({ expression: 'obj.y', context: 'repl',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n\n\tit('should inspect console evaluation results after stepping', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'repl',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\n\t\tdc.stepOutRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tawait util.receiveStoppedEvent(dc);\n\t\tdc.stepOutRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n\n\tit('should inspect console evaluation results after resuming', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tlet evalResult = await dc.evaluateRequest({ expression: 'obj', context: 'repl',\n\t\t\tframeId: stackTrace.body.stackFrames[0].id });\n\n\t\tawait dc.continueRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tlet inspectResult = await dc.variablesRequest({ \n\t\t\tvariablesReference: evalResult.body.variablesReference\n\t\t});\n\t\tassert.equal(util.findVariable(inspectResult.body.variables, 'x').value, '17');\n\t});\n});\n"
  },
  {
    "path": "src/test/testGulpSourceMaps.ts",
    "content": "import * as os from 'os';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport * as uuid from 'uuid';\nimport * as util from './util';\nimport * as sourceMapUtil from './sourceMapUtil';\nimport gulp from 'gulp';\nimport nop from 'gulp-nop';\nimport sourcemaps from 'gulp-sourcemaps';\nimport uglify from 'gulp-uglify';\nimport rename from 'gulp-rename';\nimport concat from 'gulp-concat';\nimport mapSources from '@gulp-sourcemaps/map-sources';\nimport { DebugClient } from '@vscode/debugadapter-testsupport';\n\nconst TESTDATA_PATH = path.join(__dirname, '../../testdata/web/sourceMaps/scripts');\n\ndescribe('Gulp sourcemaps: The debugger', function() {\n\n\tlet dc: DebugClient | undefined;\n\tlet targetDir: string | undefined;\n\n\tafterEach(async function() {\n\t\tif (dc) {\n\t\t\tawait dc.stop();\n\t\t\tdc = undefined;\n\t\t}\n\t\tif (targetDir) {\n\t\t\tawait fs.remove(targetDir);\n\t\t\ttargetDir = undefined;\n\t\t}\n\t});\n\n\tfor (let minifyScripts of [false, true]) {\n\tfor (let bundleScripts of [false, true]) {\n\tfor (let embedSourceMap of [false, true]) {\n\tfor (let separateBuildDir of [false, true]) {\n\n\t\t// we need to apply at least one transformation, otherwise no sourcemap will be generated\n\t\tif (!minifyScripts && !bundleScripts) continue;\n\n\t\tconst transformations: string[] = [];\n\t\tif (minifyScripts) transformations.push('minified');\n\t\tif (bundleScripts) transformations.push('bundled');\n\t\tlet descr = \n\t\t\t`should map ${transformations.join(', ')} scripts ` +\n\t\t\t`to their original sources in ${separateBuildDir ? 'a different' : 'the same'} directory ` +\n\t\t\t`using an ${embedSourceMap ? 'embedded' : 'external'} source-map`;\n\n\t\tit(descr, async function() {\n\n\t\t\tconst targetPaths = await prepareTargetDir(bundleScripts, separateBuildDir);\n\t\t\ttargetDir = targetPaths.targetDir;\n\n\t\t\tawait build(targetPaths.buildDir, minifyScripts, bundleScripts, embedSourceMap, separateBuildDir);\n\n\t\t\tdc = await util.initDebugClient('', true, {\n\t\t\t\tfile: path.join(targetPaths.buildDir, 'index.html')\n\t\t\t});\n\n\t\t\tawait sourceMapUtil.testSourcemaps(dc, targetPaths.srcDir);\n\t\t});\n\t}}}}\n});\n\ninterface TargetPaths {\n\ttargetDir: string;\n\tsrcDir: string;\n\tbuildDir: string;\n}\n\nasync function prepareTargetDir(\n\tbundle: boolean,\n\tseparateBuildDir: boolean\n): Promise<TargetPaths> {\n\n\tlet targetDir = path.join(os.tmpdir(), `vscode-firefox-debug-test-${uuid.v4()}`);\n\tawait fs.mkdir(targetDir);\n\tlet scriptTags = bundle ? ['bundle.js'] : ['f.min.js', 'g.min.js'];\n\n\tif (!separateBuildDir) {\n\n\t\tawait sourceMapUtil.copyFiles(TESTDATA_PATH, targetDir, ['index.html', 'f.js', 'g.js']);\n\t\tawait sourceMapUtil.injectScriptTags(targetDir, scriptTags);\n\n\t\treturn { targetDir, srcDir: targetDir, buildDir: targetDir };\n\n\t} else {\n\n\t\tlet srcDir = path.join(targetDir, 'src');\n\t\tawait fs.mkdir(srcDir);\n\t\tlet buildDir = path.join(targetDir, 'build');\n\t\tawait fs.mkdir(buildDir);\n\n\t\tawait sourceMapUtil.copyFiles(TESTDATA_PATH, srcDir, ['f.js', 'g.js']);\n\t\tawait sourceMapUtil.copyFiles(TESTDATA_PATH, buildDir, ['index.html']);\n\t\tawait sourceMapUtil.injectScriptTags(buildDir, scriptTags);\n\n\t\treturn { targetDir, srcDir, buildDir };\n\t}\n}\n\nfunction build(\n\tbuildDir: string,\n\tminifyScripts: boolean,\n\tbundleScripts: boolean,\n\tembedSourceMap: boolean,\n\tseparateBuildDir: boolean\n): Promise<void> {\n\n\treturn sourceMapUtil.waitForStreamEnd(\n\t\tgulp.src(path.join(buildDir, separateBuildDir ? '../src/*.js' : '*.js'))\n\t\t.pipe(sourcemaps.init())\n\t\t.pipe(minifyScripts ? uglify({ mangle: false, compress: { sequences: false } }) : nop())\n\t\t.pipe(bundleScripts ? concat('bundle.js') : rename((path) => { path.basename += '.min'; }))\n\t\t.pipe(mapSources((srcPath) => separateBuildDir ? '../src/' + srcPath : srcPath))\n\t\t.pipe(sourcemaps.write(embedSourceMap ? undefined : '.', { includeContent: false }))\n\t\t.pipe(gulp.dest(buildDir)));\n}\n"
  },
  {
    "path": "src/test/testHitBreakpoints.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { delay } from '../common/util';\n\ndescribe('Hitting breakpoints: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tbeforeEach(async function() {\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\t});\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should hit a breakpoint', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 3 ]);\n\n\t\tutil.evaluateCloaked(dc, 'noop()');\n\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tassert.equal(stoppedEvent.body.allThreadsStopped, false);\n\t\tassert.equal(stoppedEvent.body.reason, 'breakpoint');\n\t});\n\n\tit('should hit a breakpoint in an evaluateRequest', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 3 ]);\n\n\t\tutil.evaluate(dc, 'noop()');\n\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tassert.equal(stoppedEvent.body.allThreadsStopped, false);\n\t\tassert.equal(stoppedEvent.body.reason, 'breakpoint');\n\t});\n\n\tit('should hit an uncaught exception breakpoint', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'uncaught' ]});\n\n\t\tutil.evaluateCloaked(dc, 'throwUncaughtException()');\n\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tassert.equal(stoppedEvent.body.allThreadsStopped, false);\n\t\tassert.equal(stoppedEvent.body.reason, 'exception');\n\t});\n\n\tit('should not hit an uncaught exception breakpoint triggered by a debugger eval', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'uncaught' ]});\n\t\t\n\t\tutil.evaluate(dc, 'throwUncaughtException()');\n\n\t\tawait util.assertPromiseTimeout(util.receiveStoppedEvent(dc), 1000);\n\t});\n\n\tit('should not hit an uncaught exception breakpoint when those are disabled', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: []});\n\n\t\tutil.evaluateCloaked(dc, 'throwUncaughtException()');\n\n\t\tawait util.assertPromiseTimeout(util.receiveStoppedEvent(dc), 1000);\n\t});\n\n\tit('should hit a caught exception breakpoint', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'all' ]});\n\n\t\tutil.evaluateCloaked(dc, 'throwAndCatchException()');\n\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tassert.equal(stoppedEvent.body.allThreadsStopped, false);\n\t\tassert.equal(stoppedEvent.body.reason, 'exception');\n\t});\n\n\tit('should not hit a caught exception breakpoint triggered by a debugger eval', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'all' ]});\n\t\t\n\t\tutil.evaluate(dc, 'throwAndCatchException()');\n\n\t\tawait util.assertPromiseTimeout(util.receiveStoppedEvent(dc), 1000);\n\t});\n\n\tit('should not hit a caught exception breakpoint when those are disabled', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'uncaught' ]});\n\n\t\tutil.evaluateCloaked(dc, 'throwAndCatchException()');\n\n\t\tawait util.assertPromiseTimeout(util.receiveStoppedEvent(dc), 1000);\n\t});\n\n\tit('should break on a debugger statement', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'debugger' ]});\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'loadScript(\"debuggerStatement.js\")'));\n\n\t\tassert.equal(stoppedEvent.body.allThreadsStopped, false);\n\t\tassert.equal(stoppedEvent.body.reason, 'debuggerStatement');\n\n\t\tawait dc.continueRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\tstoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'debuggerStatement()'));\n\n\t\tassert.equal(stoppedEvent.body.allThreadsStopped, false);\n\t\tassert.equal(stoppedEvent.body.reason, 'debuggerStatement');\n\t});\n\n\tit('should not break on a debugger statement when those are disabled', async function() {\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: []});\n\n\t\tlet stoppedPromise = util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'loadScript(\"debuggerStatement.js\")'));\n\t\t\n\t\tawait util.assertPromiseTimeout(stoppedPromise, 500);\n\n\t\tstoppedPromise = util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'debuggerStatement()'));\n\t\t\n\t\tawait util.assertPromiseTimeout(stoppedPromise, 500);\n\t});\n\n\tit('should not hit a breakpoint after it has been removed', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 8, 10 ]);\n\n\t\tutil.evaluateCloaked(dc, 'vars()');\n\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 8);\n\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 12 ]);\n\t\tawait delay(100);\n\t\tawait util.runCommandAndReceiveStoppedEvent(dc, () => dc.continueRequest({ threadId }));\n\t\tstackTrace = await dc.stackTraceRequest({ threadId });\n\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 12);\n\n\t});\n\n\tit('should skip a breakpoint until its hit count is reached', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ { line: 24, hitCondition: '4' } ]);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'factorial(5)')\n\t\t);\n\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\n\t\tlet variablesResponse = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\tlet variables = variablesResponse.body.variables;\n\t\tassert.equal(util.findVariable(variables, 'n').value, '2');\n\t});\n\n\tit('should show the output from logpoints', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ { line: 24, logMessage: 'factorial({n})' } ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tconst outputEvents = await util.collectOutputEvents(dc, 3);\n\n\t\tassert.equal(outputEvents[0].body.output.trimRight(), 'factorial(3)');\n\t\tassert.equal(outputEvents[1].body.output.trimRight(), 'factorial(2)');\n\t\tassert.equal(outputEvents[2].body.output.trimRight(), 'factorial(1)');\n\t});\n\n\tit('should support objects in the output from logpoints', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ { line: 24, logMessage: 'obj is {obj}' } ]);\n\n\t\tutil.evaluate(dc, 'factorial(1)');\n\t\tconst outputEvent = await dc.waitForEvent('output') as DebugProtocol.OutputEvent;\n\t\tassert.equal(outputEvent.body.output.trim(), 'obj is  {x: 17, y: Object}');\n\n\t\tassert.notEqual(outputEvent.body.variablesReference, undefined);\n\t\tconst args = await dc.variablesRequest({ variablesReference: outputEvent.body.variablesReference! });\n\n\t\tassert.equal(args.body.variables.length, 1);\n\t\tconst argsVar = util.findVariable(args.body.variables, 'arguments');\n\t\tassert.equal(argsVar.value.trim(), 'obj is  {x: 17, y: Object}');\n\n\t\tlet vars = await dc.variablesRequest({ variablesReference: argsVar.variablesReference });\n\t\tconst objVar = util.findVariable(vars.body.variables, 'arg1');\n\t\tassert.notEqual(objVar.variablesReference, undefined);\n\t\tvars = await dc.variablesRequest({ variablesReference: objVar.variablesReference! });\n\n\t\tassert.equal(util.findVariable(vars.body.variables, 'x').value, '17');\n\t\tassert.equal(util.findVariable(vars.body.variables, 'y').value, '{z: \"xyz\"}');\n\t});\n});\n"
  },
  {
    "path": "src/test/testInspectVariables.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\nimport { delay } from '../common/util';\n\ndescribe('Inspecting variables: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tbeforeEach(async function() {\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\t});\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should inspect variables of different types in different scopes', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 19 ]);\n\n\t\tutil.evaluate(dc, 'vars({ key: \"value\" })');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\n\t\tlet variablesResponse = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\tlet variables = variablesResponse.body.variables;\n\t\tassert.equal(util.findVariable(variables, 'str2').value, '\"foo\"');\n\t\tassert.equal(util.findVariable(variables, 'undef').value, 'undefined');\n\t\tassert.equal(util.findVariable(variables, 'nul').value, 'null');\n\t\tassert.equal(util.findVariable(variables, 'sym1').value, 'Symbol(Local Symbol)');\n\t\tassert.equal(util.findVariable(variables, 'sym2').value, 'Symbol(Global Symbol)');\n\t\tassert.equal(util.findVariable(variables, 'sym3').value, 'Symbol(Symbol.iterator)');\n\t\t\n\t\tvariablesResponse = await dc.variablesRequest({\n\t\t\tvariablesReference: util.findVariable(variables, 'this').variablesReference\n\t\t});\n\t\tvariables = variablesResponse.body.variables;\n\t\tassert.equal(util.findVariable(variables, 'scrollX').value, '0');\n\n\t\tvariablesResponse = await dc.variablesRequest({ variablesReference: scopes.body.scopes[1].variablesReference });\n\t\tvariables = variablesResponse.body.variables;\n\t\tassert.equal(util.findVariable(variables, 'bool1').value, 'false');\n\t\tassert.equal(util.findVariable(variables, 'bool2').value, 'true');\n\t\tassert.equal(util.findVariable(variables, 'num1').value, '0');\n\t\tassert.equal(util.findVariable(variables, 'num2').value, '120');\n\t\tassert.equal(util.findVariable(variables, 'str1').value, '\"\"');\n\n\t\tvariablesResponse = await dc.variablesRequest({ variablesReference: scopes.body.scopes[2].variablesReference });\n\t\tlet variable = util.findVariable(variablesResponse.body.variables, 'arg')!;\n\t\tassert.equal(variable.value, '{key: \"value\", Symbol(Local Symbol): \"Symbol-keyed 1\", Symbol(Symbol.iterator): \"Symbol-keyed 2\"}');\n\t\tvariablesResponse = await dc.variablesRequest({ variablesReference: variable.variablesReference });\n\t\tassert.equal(util.findVariable(variablesResponse.body.variables, 'key').value, '\"value\"');\n\t\tassert.equal(util.findVariable(variablesResponse.body.variables, 'Symbol(Local Symbol)').value, '\"Symbol-keyed 1\"');\n\t\tassert.equal(util.findVariable(variablesResponse.body.variables, 'Symbol(Symbol.iterator)').value, '\"Symbol-keyed 2\"');\n\t});\n\n\tit('should inspect variables in different stackframes', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluateDelayed(dc, 'factorial(4)', 0);\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tfor (let i = 0; i < 4; i++) {\n\t\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[i].id });\n\t\t\tlet variables = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\t\tassert.equal(util.findVariable(variables.body.variables, 'n').value, i + 1);\n\t\t}\n\t});\n\n\tit('should inspect return values on stepping out', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 71 ]);\n\n\t\tutil.evaluate(dc, 'doEval(17)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\n\t\tawait delay(100); // TODO\n\n\t\tawait dc.stepOutRequest({ threadId });\n\t\tawait util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\t\tlet variables = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\tassert.equal(util.findVariable(variables.body.variables, 'Return value').value, 17);\n\t});\n\n\tit.skip('should inspect return values on stepping out of recursive functions', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 25 ]);\n\n\t\tutil.evaluate(dc, 'factorial(4)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\n\t\tfor (let i = 0; i < 4; i++) {\n\t\t\tawait dc.stepOutRequest({ threadId });\n\t\t\tawait util.receiveStoppedEvent(dc);\n\t\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\t\t\tlet variables = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\t\tassert.equal(util.findVariable(variables.body.variables, 'Return value').value, factorial(i + 1));\n\t\t}\n\t});\n\n\tit('should set variables', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 11 ]);\n\n\t\tutil.evaluate(dc, 'vars()');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tlet frameId = stackTrace.body.stackFrames[0].id;\n\t\tlet scopes = await dc.scopesRequest({ frameId });\n\t\tlet variablesReference = scopes.body.scopes[0].variablesReference;\n\t\tlet variables = await dc.variablesRequest({ variablesReference });\n\n\t\tassert.equal(util.findVariable(variables.body.variables, 'num1').value, '0');\n\n\t\tlet result = await dc.evaluateRequest({ context: 'repl', frameId, expression: 'num1' });\n\t\tassert.equal(result.body.result, '0');\n\n\t\tawait dc.setVariableRequest({ variablesReference, name: 'num1', value: '7' });\n\n\t\tresult = await dc.evaluateRequest({ context: 'repl', frameId, expression: 'num1' });\n\t\tassert.equal(result.body.result, '7');\n\t});\n\n\tit('should inspect variables from the stack after running evaluations', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 8 ]);\n\n\t\tutil.evaluate(dc, 'vars({foo:{bar:\"baz\"}})');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tlet frameId = stackTrace.body.stackFrames[0].id;\n\t\tlet scopes = await dc.scopesRequest({ frameId });\n\n\t\tawait dc.evaluateRequest({ expression: 'obj.x', context: 'watch', frameId });\n\t\tawait dc.evaluateRequest({ expression: 'obj.y', context: 'repl', frameId });\n\n\t\tlet variables = await dc.variablesRequest({ variablesReference: scopes.body.scopes[1].variablesReference });\n\t\tlet arg = util.findVariable(variables.body.variables, 'arg');\n\t\tvariables = await dc.variablesRequest({ variablesReference: arg.variablesReference });\n\t\tlet foo = util.findVariable(variables.body.variables, 'foo');\n\t\tvariables = await dc.variablesRequest({ variablesReference: foo.variablesReference });\n\t\tlet bar = util.findVariable(variables.body.variables, 'bar');\n\t\tassert.equal(bar.value, '\"baz\"');\n\t});\n\n\tit('should return the same variables if the variablesRequest is issued twice', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 112 ]);\n\n\t\tutil.evaluate(dc, 'protoGetter().y');\n\t\tconst stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tconst stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tconst frameId = stackTrace.body.stackFrames[0].id;\n\t\tconst scopes = await dc.scopesRequest({ frameId });\n\t\tconst variablesReference = scopes.body.scopes[0].variablesReference;\n\n\t\tconst variables1 = await dc.variablesRequest({ variablesReference });\n\t\tconst variables2 = await dc.variablesRequest({ variablesReference });\n\t\tassert.deepStrictEqual(variables1.body.variables, variables2.body.variables);\n\t});\n});\n\nfunction factorial(n: number): number {\n\tif (n <= 1) {\n\t\treturn 1;\n\t} else {\n\t\treturn n * factorial(n - 1);\n\t}\n}\n"
  },
  {
    "path": "src/test/testSetBreakpoints.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\nimport { delay } from '../common/util';\n\ndescribe('Setting breakpoints: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should provide breakpoint locations in sources without sourcemaps', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tconst sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tconst locations = await dc.customRequest('breakpointLocations', {\n\t\t\tsource: { path: sourcePath },\n\t\t\tline: 17\n\t\t});\n\n\t\tassert.deepStrictEqual(locations.body.breakpoints, [\n\t\t\t{ line: 17, column: 14 },\n\t\t\t{ line: 17, column: 20 },\n\t\t\t{ line: 17, column: 49 },\n\t\t\t{ line: 17, column: 56 },\n\t\t\t{ line: 17, column: 89 }\n\t\t]);\n\t});\n\n\tit('should eventually verify a breakpoint set on a loaded file', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 3 ], false);\n\t\tlet breakpointId = setBreakpointsResponse.body.breakpoints[0].id;\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 1);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, false);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].line, 3);\n\n\t\tlet ev = await util.receiveBreakpointEvent(dc);\n\t\tassert.equal(ev.body.reason, 'changed');\n\t\tassert.equal(ev.body.breakpoint.id, breakpointId);\n\t\tassert.equal(ev.body.breakpoint.verified, true);\n\t\tassert.equal(ev.body.breakpoint.line, 3);\n\t});\n\n\tit('should eventually move and verify a breakpoint set on a loaded file', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 2 ], false);\n\t\tlet breakpointId = setBreakpointsResponse.body.breakpoints[0].id;\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 1);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, false);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].line, 2);\n\n\t\tlet ev = await util.receiveBreakpointEvent(dc);\n\t\tassert.equal(ev.body.reason, 'changed');\n\t\tassert.equal(ev.body.breakpoint.id, breakpointId);\n\t\tassert.equal(ev.body.breakpoint.verified, true);\n\t\tassert.equal(ev.body.breakpoint.line, 3);\n\t});\n\n\tit('should eventually verify a breakpoint set before the page is loaded', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, false);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 3 ], false);\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 1);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, false);\n\t\tlet breakpointId = setBreakpointsResponse.body.breakpoints[0].id;\n\n\t\tlet ev = await util.receiveBreakpointEvent(dc);\n\t\tassert.equal(ev.body.reason, 'changed');\n\t\tassert.equal(ev.body.breakpoint.id, breakpointId);\n\t\tassert.equal(ev.body.breakpoint.verified, true);\n\t\tassert.equal(ev.body.breakpoint.line, 3);\n\t});\n\n\tit('should eventually move and verify a breakpoint set before the page is loaded', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, false);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 2 ], false);\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 1);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, false);\n\t\tlet breakpointId = setBreakpointsResponse.body.breakpoints[0].id;\n\n\t\tlet ev = await util.receiveBreakpointEvent(dc);\n\t\tassert.equal(ev.body.reason, 'changed');\n\t\tassert.equal(ev.body.breakpoint.id, breakpointId);\n\t\tassert.equal(ev.body.breakpoint.verified, true);\n\t\tassert.equal(ev.body.breakpoint.line, 3);\n\t});\n\n\tit('should eventually verify a breakpoint set on a dynamically loaded script', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/dlscript.js');\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 3 ], false);\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 1);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, false);\n\t\tlet breakpointId = setBreakpointsResponse.body.breakpoints[0].id;\n\n\t\tutil.evaluate(dc, 'loadScript(\"dlscript.js\")');\n\n\t\tlet ev = await util.receiveBreakpointEvent(dc);\n\t\tassert.equal(ev.body.reason, 'changed');\n\t\tassert.equal(ev.body.breakpoint.id, breakpointId);\n\t\tassert.equal(ev.body.breakpoint.verified, true);\n\t\tassert.equal(ev.body.breakpoint.line, 3);\n\t});\n\n\tit('should keep old breakpoints verified when setting new ones', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 3 ]);\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 3, 8 ], false);\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 2);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].line, 3);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, true);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[1].line, 8);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[1].verified, false);\n\t});\n\n\tit('should handle multiple setBreakpointsRequests in quick succession', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tutil.setBreakpoints(dc, sourcePath, [ 11 ], false);\n\t\tutil.setBreakpoints(dc, sourcePath, [ 10, 8 ], false);\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 9, 10 ], false);\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 2);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, false);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[1].verified, false);\n\n\t\tawait delay(200);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'vars()')\n\t\t);\n\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 9);\n\t});\n\n\tit('should remove a breakpoint', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tlet setBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [ 3 ], false);\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 1);\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints[0].verified, false);\n\n\t\tsetBreakpointsResponse = await util.setBreakpoints(dc, sourcePath, [], false);\n\n\t\tassert.equal(setBreakpointsResponse.body.breakpoints.length, 0);\n\t});\n\n\tit('should add a condition to an already set breakpoint', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ { line: 24 } ]);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'factorial(5)')\n\t\t);\n\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\t\tlet scopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\n\t\tlet variablesResponse = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\tlet variables = variablesResponse.body.variables;\n\t\tassert.equal(util.findVariable(variables, 'n').value, '5');\n\n\t\tawait util.setBreakpoints(dc, sourcePath, [ { line: 24, condition: 'n === 2' } ]);\n\t\tawait dc.continueRequest({ threadId });\n\n\t\tawait util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'factorial(5)')\n\t\t);\n\n\t\tstackTrace = await dc.stackTraceRequest({ threadId });\n\t\tscopes = await dc.scopesRequest({ frameId: stackTrace.body.stackFrames[0].id });\n\n\t\tvariablesResponse = await dc.variablesRequest({ variablesReference: scopes.body.scopes[0].variablesReference });\n\t\tvariables = variablesResponse.body.variables;\n\t\tassert.equal(util.findVariable(variables, 'n').value, '2');\n\t});\n\n\tit('should add a logMessage to an already set breakpoint', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ { line: 24 } ]);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc, \n\t\t\t() => util.evaluate(dc, 'factorial(0)')\n\t\t);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\t\tawait dc.continueRequest({ threadId });\n\n\t\tawait util.setBreakpoints(dc, sourcePath, [ { line: 24, logMessage: 'factorial({n})' } ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tconst outputEvents = await util.collectOutputEvents(dc, 3);\n\n\t\tassert.equal(outputEvents[0].body.output.trimRight(), 'factorial(3)');\n\t\tassert.equal(outputEvents[1].body.output.trimRight(), 'factorial(2)');\n\t\tassert.equal(outputEvents[2].body.output.trimRight(), 'factorial(1)');\n\t});\n});\n"
  },
  {
    "path": "src/test/testSkipFiles.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\nimport { delay, isWindowsPlatform } from '../common/util';\n\ndescribe('Skipping files: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should skip exceptions in blackboxed files', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, {\n\t\t\tskipFiles: [ '**/skip.js' ]\n\t\t});\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'all' ]});\n\t\tawait delay(100);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'try{ testSkipFiles() }catch(e){}'));\n\t\tlet stacktrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, path.join(TESTDATA_PATH, 'web/main.js'));\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 76);\n\t});\n\n\tit('should skip exceptions in blackboxed files thrown immediately after loading', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, {\n\t\t\tskipFiles: [ '**/exception.js' ]\n\t\t});\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'all' ]});\n\t\tlet mainFilePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, mainFilePath, [ 3 ]);\n\t\tawait delay(100);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'loadScript(\"exception.js\")'));\n\t\tlet stacktrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, path.join(TESTDATA_PATH, 'web/main.js'));\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 3);\n\t});\n\n\tit(`should skip exceptions in blackboxed source-mapped files thrown immediately after loading`, async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, {\n\t\t\tskipFiles: [ '**/exception-sourcemap.ts' ]\n\t\t});\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: [ 'all' ]});\n\t\tlet mainFilePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, mainFilePath, [ 3 ]);\n\t\tawait delay(100);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'loadScript(\"exception-sourcemap.js\")'));\n\t\tlet stacktrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, path.join(TESTDATA_PATH, 'web/main.js'));\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 3);\n\t});\n\n\tit('should skip breakpoints in blackboxed files', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, {\n\t\t\tskipFiles: [ '**/skip.js' ]\n\t\t});\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: []});\n\t\tlet skipFilePath = path.join(TESTDATA_PATH, 'web/skip.js');\n\t\tawait util.setBreakpoints(dc, skipFilePath, [ 2 ]);\n\t\tlet mainFilePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, mainFilePath, [ 76 ]);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'try{ testSkipFiles() }catch(e){}'));\n\t\tlet stacktrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, mainFilePath);\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 76);\n\t});\n\n\tit('should toggle skipping files that were not skipped by the configuration', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: []});\n\t\tlet skipFilePath = path.join(TESTDATA_PATH, 'web/skip.js');\n\t\tawait util.setBreakpoints(dc, skipFilePath, [ 2 ]);\n\t\tlet mainFilePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, mainFilePath, [ 76 ]);\n\n\t\tlet skipFileUrl = isWindowsPlatform() ? \n\t\t\t'file:///' + skipFilePath.replace(/\\\\/g, '/') :\n\t\t\t'file://' + skipFilePath;\n\t\tawait dc.customRequest('toggleSkippingFile', skipFileUrl);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'try{ testSkipFiles() }catch(e){}'));\n\t\tlet threadId = stoppedEvent.body.threadId;\n\t\tlet stacktrace = await dc.stackTraceRequest({ threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, mainFilePath);\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 76);\n\n\t\tawait dc.customRequest('toggleSkippingFile', skipFileUrl);\n\t\tawait dc.continueRequest({ threadId });\n\n\t\tstoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'try{ testSkipFiles() }catch(e){}'));\n\t\tstacktrace = await dc.stackTraceRequest({ threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, skipFilePath);\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 2);\n\t});\n\n\tit('should toggle skipping files that were skipped by the configuration', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true, {\n\t\t\tskipFiles: [ '**/skip.js' ]\n\t\t});\n\n\t\tawait dc.setExceptionBreakpointsRequest({filters: []});\n\t\tlet skipFilePath = path.join(TESTDATA_PATH, 'web/skip.js');\n\t\tawait util.setBreakpoints(dc, skipFilePath, [ 2 ]);\n\t\tlet mainFilePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, mainFilePath, [ 76 ]);\n\n\t\tlet skipFileUrl = isWindowsPlatform() ? \n\t\t\t'file:///' + skipFilePath.replace(/\\\\/g, '/') :\n\t\t\t'file://' + skipFilePath;\n\t\tawait dc.customRequest('toggleSkippingFile', skipFileUrl);\n\t\t\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'try{ testSkipFiles() }catch(e){}'));\n\t\tlet threadId = stoppedEvent.body.threadId;\n\t\tlet stacktrace = await dc.stackTraceRequest({ threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, skipFilePath);\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 2);\n\n\t\tawait dc.customRequest('toggleSkippingFile', skipFileUrl);\n\t\tawait dc.continueRequest({ threadId });\n\n\t\tstoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'try{ testSkipFiles() }catch(e){}'));\n\t\tstacktrace = await dc.stackTraceRequest({ threadId });\n\n\t\tassert.equal(stacktrace.body.stackFrames[0].source!.path, mainFilePath);\n\t\tassert.equal(stacktrace.body.stackFrames[0].line, 76);\n\t});\n\n\tit('should send a StoppedEvent with the same reason to VSCode after a skipFile toggle', async function() {\n\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\n\t\tlet skipFilePath = path.join(TESTDATA_PATH, 'web/skip.js');\n\t\tlet mainFilePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, mainFilePath, [ 75 ]);\n\n\t\tlet stoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => util.evaluateCloaked(dc, 'try{ testSkipFiles() }catch(e){}'));\n\n\t\tassert.equal((<DebugProtocol.StoppedEvent>stoppedEvent).body.reason, 'breakpoint');\n\n\t\tlet skipFileUrl = isWindowsPlatform() ? \n\t\t\t'file:///' + skipFilePath.replace(/\\\\/g, '/') :\n\t\t\t'file://' + skipFilePath;\n\t\tstoppedEvent = await util.runCommandAndReceiveStoppedEvent(dc,\n\t\t\t() => dc.customRequest('toggleSkippingFile', skipFileUrl));\n\n\t\tassert.equal((<DebugProtocol.StoppedEvent>stoppedEvent).body.reason, 'breakpoint');\n\t})\n});\n"
  },
  {
    "path": "src/test/testSourceActorCollection.ts",
    "content": "import assert from 'assert';\nimport { SourceActorCollection } from '../adapter/adapter/source';\nimport { ISourceActorProxy } from '../adapter/firefox/actorProxy/source';\nimport { MappedLocation } from '../adapter/location';\nimport { delay } from '../common/util';\n\nclass FakeSourceActorProxy implements ISourceActorProxy {\n\tpublic readonly url = null;\n\tpublic readonly source = {\n\t\tactor: this.name,\n\t\turl: null,\n\t\tgeneratedUrl: null,\n\t\tintroductionUrl: null,\n\t\tisBlackBoxed: false,\n\t\tisPrettyPrinted: false,\n\t\tisSourceMapped: false,\n\t\tsourceMapURL: null,\n\t};\n\tconstructor(public readonly name: string) {}\n\tgetBreakableLines(): Promise<number[]> {\n\t\tthrow new Error('Method not implemented.');\n\t}\n\tgetBreakableLocations(line: number): Promise<MappedLocation[]> {\n\t\tthrow new Error('Method not implemented.');\n\t}\n\tfetchSource(): Promise<FirefoxDebugProtocol.Grip> {\n\t\tthrow new Error('Method not implemented.');\n\t}\n\tsetBlackbox(blackbox: boolean): Promise<void> {\n\t\tthrow new Error('Method not implemented.');\n\t}\n\tdispose(): void {\n\t\tthrow new Error('Method not implemented.');\n\t}\n}\n\nconst a1 = new FakeSourceActorProxy('fakeSourceActor1');\nconst a2 = new FakeSourceActorProxy('fakeSourceActor2');\n\nasync function getName(actor: ISourceActorProxy) {\n\treturn actor.name;\n}\n\nasync function getNameOrFail(actor: ISourceActorProxy) {\n\tif (actor === a1) {\n\t\tthrow { from: actor.name, error: 'noSuchActor' }\n\t}\n\treturn actor.name;\n}\n\ndescribe('SourceActorCollection: runWithSomeActor', function() {\n\n\tit('should work', async function() {\n\t\tconst collection = new SourceActorCollection(a1);\n\t\tconst result = await collection.runWithSomeActor(getName);\n\t\tassert.equal(result, a1.name);\n\t});\n\n\tit('should work after the first actor was replaced', async function() {\n\t\tconst collection = new SourceActorCollection(a1);\n\t\tcollection.remove(a1);\n\t\tcollection.add(a2);\n\t\tconst result = await collection.runWithSomeActor(getName);\n\t\tassert.equal(result, a2.name);\n\t});\n\n\tit('should work while the first actor is being replaced', async function() {\n\t\tconst collection = new SourceActorCollection(a1);\n\t\tcollection.remove(a1);\n\t\tconst resultPromise = collection.runWithSomeActor(getName);\n\t\tawait delay(1);\n\t\tcollection.add(a2);\n\t\tassert.equal(await resultPromise, a2.name);\n\t});\n\n\tit('should work after a second actor was removed', async function() {\n\t\tconst collection = new SourceActorCollection(a1);\n\t\tcollection.add(a2);\n\t\tcollection.remove(a2);\n\t\tconst result = await collection.runWithSomeActor(getName);\n\t\tassert.equal(result, a1.name);\n\t});\n\n\tit('should work when an actor was destroyed before another was added', async function() {\n\t\tconst collection = new SourceActorCollection(a1);\n\t\tconst resultPromise = collection.runWithSomeActor(getNameOrFail);\n\t\tawait delay(1);\n\t\tcollection.add(a2);\n\t\tassert.equal(await resultPromise, a2.name);\n\t});\n\n\tit('should work when an actor was destroyed after another was added', async function() {\n\t\tconst collection = new SourceActorCollection(a1);\n\t\tcollection.add(a2);\n\t\tconst resultPromise = collection.runWithSomeActor(getNameOrFail);\n\t\tassert.equal(await resultPromise, a2.name);\n\t});\n\n\tit('should rethrow actor exceptions', async function() {\n\t\tconst collection = new SourceActorCollection(a1);\n\t\ttry {\n\t\t\tawait collection.runWithSomeActor(actor => actor.fetchSource());\n\t\t} catch (err: any) {\n\t\t\tassert.equal(err.message, \"Method not implemented.\");\n\t\t\treturn;\n\t\t}\n\t\tassert.fail();\n\t});\n});\n"
  },
  {
    "path": "src/test/testStepThrough.ts",
    "content": "import { DebugClient } from '@vscode/debugadapter-testsupport';\nimport * as path from 'path';\nimport * as util from './util';\nimport * as assert from 'assert';\nimport { delay } from '../common/util';\n\ndescribe('Stepping: The debugger', function() {\n\n\tlet dc: DebugClient;\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tbeforeEach(async function() {\n\t\tdc = await util.initDebugClient(TESTDATA_PATH, true);\n\t});\n\n\tafterEach(async function() {\n\t\tawait dc.stop();\n\t});\n\n\tit('should step over', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 11 ]);\n\n\t\tutil.evaluateDelayed(dc, 'vars()', 0);\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 11);\n\n\t\tdc.nextRequest({ threadId });\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tstackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 12);\n\t});\n\n\tit('should step in', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 27 ]);\n\n\t\tutil.evaluate(dc, 'factorial(3)');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 27);\n\n\t\tdc.stepInRequest({ threadId });\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tstackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 24);\n\t});\n\n\tit('should step out', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 3 ]);\n\n\t\tutil.evaluate(dc, 'vars()');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 3);\n\n\t\tdc.stepOutRequest({ threadId });\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tstackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 21);\n\t});\n\n\tit('should restart a frame', async function() {\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 11 ]);\n\n\t\tutil.evaluate(dc, 'vars()');\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\t\tlet threadId = stoppedEvent.body.threadId!;\n\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 11);\n\n\t\tconst frameId = stackTrace.body.stackFrames[0].id;\n\t\tdc.restartFrameRequest({ frameId });\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tstackTrace = await dc.stackTraceRequest({ threadId });\n\t\tassert.equal(stackTrace.body.stackFrames[0].source!.path, sourcePath);\n\t\tassert.equal(stackTrace.body.stackFrames[0].line, 8);\n\t});\n\n\tit('should step into an eval()', async function() {\n\n\t\tlet evalExpr = 'factorial(5)';\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 71 ]);\n\n\t\tdc.evaluateRequest({ expression: `doEval('${evalExpr}')`, context: 'repl' });\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\n\t\tdc.stepInRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tawait delay(100);\n\t\t\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tlet source = await dc.sourceRequest({\n\t\t\tsourceReference: stackTrace.body.stackFrames[0].source!.sourceReference!\n\t\t});\n\t\tassert.equal(source.body.content, evalExpr);\n\t});\n\n\tit('should fetch long eval() expressions', async function() {\n\n\t\tlet evalExpr = 'factorial(1);';\n\t\tfor (let i = 0; i < 12; i++) {\n\t\t\tevalExpr = evalExpr + evalExpr;\n\t\t}\n\n\t\tlet sourcePath = path.join(TESTDATA_PATH, 'web/main.js');\n\t\tawait util.setBreakpoints(dc, sourcePath, [ 71 ]);\n\n\t\tdc.evaluateRequest({ expression: `doEval('${evalExpr}')`, context: 'repl' });\n\t\tlet stoppedEvent = await util.receiveStoppedEvent(dc);\n\n\t\tdc.stepInRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tawait util.receiveStoppedEvent(dc);\n\n\t\tawait delay(100);\n\t\t\n\t\tlet stackTrace = await dc.stackTraceRequest({ threadId: stoppedEvent.body.threadId! });\n\t\tlet source = await dc.sourceRequest({\n\t\t\tsourceReference: stackTrace.body.stackFrames[0].source!.sourceReference!\n\t\t});\n\t\tassert.equal(source.body.content, evalExpr);\n\t});\n});\n"
  },
  {
    "path": "src/test/testTerminateAndCleanup.ts",
    "content": "import * as os from 'os';\nimport * as path from 'path';\nimport * as fs from 'fs-extra';\nimport * as assert from 'assert';\nimport * as uuid from 'uuid';\nimport * as util from './util';\nimport { delay } from '../common/util';\nimport { FirefoxDebugSession } from '../adapter/firefoxDebugSession';\nimport { parseConfiguration } from '../adapter/configuration';\n\ndescribe('Terminate and cleanup: The debugger', function() {\n\n\tconst TESTDATA_PATH = path.join(__dirname, '../../testdata');\n\n\tit('should eventually delete the temporary profile after terminating Firefox', async function() {\n\n\t\tif (process.env['KEEP_PROFILE_CHANGES'] === 'true') {\n\t\t\tthis.skip();\n\t\t\treturn;\n\t\t}\n\n\t\tconst tmpDir = path.join(os.tmpdir(), `vscode-firefox-debug-test-${uuid.v4()}`);\n\t\tconst dc = await util.initDebugClient(TESTDATA_PATH, true, { tmpDir });\n\n\t\t// check that the temporary profile has been created\n\t\tassert.ok((await fs.readdir(tmpDir)).length > 0);\n\n\t\tawait dc.stop();\n\n\t\t// check repeatedly if the temporary profile has been deleted and fail after 5 seconds if it hasn't\n\t\tvar startTime = Date.now();\n\t\twhile ((Date.now() - startTime) < 5000) {\n\t\t\tawait delay(200);\n\t\t\tif ((await fs.readdir(tmpDir)).length === 0) {\n\t\t\t\tawait fs.rmdir(tmpDir);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\"The temporary profile hasn't been deleted after 5 seconds\");\n\t});\n\n\tit('should eventually delete the temporary profile after a detached Firefox process was terminated', async function() {\n\n\t\tif (os.platform() === 'darwin') {\n\t\t\tthis.skip();\n\t\t\treturn;\n\t\t}\n\n\t\tconst tmpDir = path.join(os.tmpdir(), `vscode-firefox-debug-test-${uuid.v4()}`);\n\t\tconst dc = await util.initDebugClient(TESTDATA_PATH, true, { tmpDir, reAttach: true });\n\n\t\t// check that the temporary profile has been created\n\t\tassert.ok((await fs.readdir(tmpDir)).length > 0);\n\n\t\tawait dc.stop();\n\n\t\t// attach to Firefox again and terminate it using the Terminator WebExtension\n\t\tconst parsedConfig = await parseConfiguration({ request: 'attach' });\n\t\tconst ds = new FirefoxDebugSession(parsedConfig, () => undefined);\n\t\tawait ds.start();\n\t\tds.addonsActor!.installAddon(path.resolve(__dirname, '../../dist/terminator'));\n\n\t\t// check repeatedly if the temporary profile has been deleted and fail after 5 seconds if it hasn't\n\t\tvar startTime = Date.now();\n\t\twhile ((Date.now() - startTime) < 5000) {\n\t\t\tawait delay(200);\n\t\t\tif ((await fs.readdir(tmpDir)).length === 0) {\n\t\t\t\tawait fs.rmdir(tmpDir);\n\t\t\t\treturn;\n\t\t\t}\n\t\t}\n\n\t\tthrow new Error(\"The temporary profile hasn't been deleted after 5 seconds\");\n\t});\n});\n"
  },
  {
    "path": "src/test/testWebpackSourceMaps.ts",
    "content": "import * as os from 'os';\nimport * as fs from 'fs-extra';\nimport * as path from 'path';\nimport * as uuid from 'uuid';\nimport * as assert from 'assert';\nimport * as util from './util';\nimport * as sourceMapUtil from './sourceMapUtil';\nimport webpack from 'webpack';\nimport TerserPlugin from 'terser-webpack-plugin';\nimport { DebugClient } from '@vscode/debugadapter-testsupport';\n\nconst TESTDATA_PATH = path.join(__dirname, '../../testdata/web/sourceMaps/modules');\n\ndescribe('Webpack sourcemaps: The debugger', function() {\n\n\tlet dc: DebugClient | undefined;\n\tlet targetDir: string | undefined;\n\n\tafterEach(async function() {\n\t\tif (dc) {\n\t\t\tawait dc.stop();\n\t\t\tdc = undefined;\n\t\t}\n\t\tif (targetDir) {\n\t\t\tawait fs.remove(targetDir);\n\t\t\ttargetDir = undefined;\n\t\t}\n\t});\n\n\tfor (const devtool of [\n\t\t'source-map',\n\t\t'inline-source-map',\n\t\t'nosources-source-map',\n\t\t'inline-nosources-source-map',\n\t\t'eval-source-map',\n\t\t'eval-cheap-source-map',\n\t\t'eval-cheap-module-source-map',\n\t\t'eval-nosources-source-map',\n\t\t'eval-nosources-cheap-source-map',\n\t\t'eval-nosources-cheap-module-source-map',\n\t] satisfies Devtool[]) {\n\n\t\tconst description = `should map webpack-bundled modules with devtool \"${devtool}\" to their original sources`;\n\t\tconst isEvalSourcemap = devtool.includes('eval');\n\t\tconst isCheapSourcemap = devtool.includes('cheap');\n\n\t\tit(description, async function() {\n\n\t\t\tlet targetDir = await prepareTargetDir();\n\n\t\t\tawait build(targetDir, devtool);\n\n\t\t\tdc = await util.initDebugClient('', true, {\n\t\t\t\tfile: path.join(targetDir, 'index.html'),\n\t\t\t\tpathMappings: [{ url: 'webpack:///', path: targetDir + '/' }]\n\t\t\t});\n\n\t\t\t// test breakpoint locations if the devtool provides column breakpoints\n\t\t\tif (!isCheapSourcemap) {\n\t\t\t\tconst breakpointLocations = await dc.customRequest('breakpointLocations', {\n\t\t\t\t\tsource: { path: path.join(targetDir, 'f.js') },\n\t\t\t\t\tline: 7\n\t\t\t\t});\n\t\t\t\tassert.deepStrictEqual(breakpointLocations.body.breakpoints, [\n\t\t\t\t\t{ line: 7, column: isEvalSourcemap ? 1 : 2 },\n\t\t\t\t\t{ line: 7, column: 6 }\n\t\t\t\t]);\n\t\t\t}\n\n\t\t\tconst breakpoint = { line: 7, column: isEvalSourcemap ? 1 : 6 };\n\t\t\tawait sourceMapUtil.testSourcemaps(dc, targetDir, breakpoint);\n\t\t});\n\t}\n});\n\nasync function prepareTargetDir(): Promise<string> {\n\n\tlet targetDir = path.join(await fs.realpath(os.tmpdir()), `vscode-firefox-debug-test-${uuid.v4()}`);\n\tawait fs.mkdir(targetDir);\n\tawait sourceMapUtil.copyFiles(TESTDATA_PATH, targetDir, ['index.html', 'f.js', 'g.js']);\n\n\treturn targetDir;\n}\n\ntype Devtool = `${'inline-' | 'hidden-' | 'eval-' | ''}${'nosources-' | ''}${'cheap-module-' | 'cheap-' | ''}source-map`;\n\nfunction build(targetDir: string, devtool: Devtool): Promise<void> {\n\treturn new Promise<void>((resolve, reject) => {\n\t\twebpack({\n\t\t\tcontext: targetDir,\n\t\t\tentry: './f.js',\n\t\t\toutput: {\n\t\t\t\tpath: targetDir,\n\t\t\t\tfilename: 'bundle.js'\n\t\t\t},\n\t\t\tdevtool: devtool,\n\t\t\toptimization: {\n\t\t\t\tminimizer: [new TerserPlugin({ terserOptions: { mangle: false } })],\n\t\t\t}\n\t\t}, (err, stats) => {\n\t\t\tif (err) {\n\t\t\t\treject(err);\n\t\t\t} else {\n\t\t\t\tresolve();\n\t\t\t}\n\t\t});\n\t})\n}"
  },
  {
    "path": "src/test/util.ts",
    "content": "import { delay, isWindowsPlatform } from '../common/util';\nimport { DebugClient } from '@vscode/debugadapter-testsupport';\nimport { DebugProtocol } from '@vscode/debugprotocol';\nimport { LaunchConfiguration } from '../common/configuration';\nimport * as path from 'path';\nimport * as net from 'net';\n\nexport async function initDebugClient(\n\ttestDataPath: string,\n\twaitForPageLoadedEvent: boolean,\n\textraLaunchArgs?: {}\n): Promise<DebugClient> {\n\n\tawait waitForUnoccupiedPort(6000, 1000);\n\n\tlet dc = new DebugClient('node', './dist/adapter.bundle.js', 'firefox');\n\n\tlet launchArgs: LaunchConfiguration = {\n\t\trequest: 'launch',\n\t\tfile: path.join(testDataPath, 'web/index.html')\n\t};\n\n\tprocessEnvironmentVariables(launchArgs);\n\n\tif (extraLaunchArgs !== undefined) {\n\t\tlaunchArgs = Object.assign(launchArgs, extraLaunchArgs);\n\t}\n\n\tconst pageLoadedPromise = receivePageLoadedEvent(dc);\n\n\tawait dc.start();\n\tawait Promise.all([\n\t\tdc.launch(launchArgs),\n\t\tdc.configurationSequence()\n\t]);\n\n\tif (waitForPageLoadedEvent) {\n\t\tawait pageLoadedPromise;\n\t}\n\n\treturn dc;\n}\n\nexport async function initDebugClientForAddon(\n\ttestDataPath: string,\n\toptions?: {\n\t\tdelayedNavigation?: boolean,\n\t\taddonDirectory?: string\n\t}\n): Promise<DebugClient> {\n\n\tawait waitForUnoccupiedPort(6000, 1000);\n\n\tlet addonPath: string;\n\tif (options && options.addonDirectory) {\n\t\taddonPath = path.join(testDataPath, `${options.addonDirectory}/addOn`);\n\t} else {\n\t\taddonPath = path.join(testDataPath, `webExtension/addOn`);\n\t}\n\n\tlet dcArgs: LaunchConfiguration = { \n\t\trequest: 'launch',\n\t\taddonPath\n\t};\n\n\tif (options && options.delayedNavigation) {\n\t\tdcArgs.file = path.join(testDataPath, `web/index.html`);\n\t} else {\n\t\tdcArgs.file = path.join(testDataPath, `webExtension/index.html`);\n\t}\n\n\tprocessEnvironmentVariables(dcArgs);\n\n\tlet dc = new DebugClient('node', './dist/adapter.bundle.js', 'firefox');\n\n\tconst pageLoadedPromise = receivePageLoadedEvent(dc);\n\n\tawait dc.start();\n\tawait Promise.all([\n\t\tdc.launch(dcArgs),\n\t\tdc.waitForEvent('initialized', 20000)\n\t]);\n\tdc.setExceptionBreakpointsRequest({ filters: [] });\n\n\tawait pageLoadedPromise;\n\n\tif (options && options.delayedNavigation) {\n\t\tawait setConsoleThread(dc, await findTabThread(dc));\n\t\tlet filePath = path.join(testDataPath, `webExtension/index.html`);\n\t\tlet fileUrl = isWindowsPlatform() ? \n\t\t\t'file:///' + filePath.replace(/\\\\/g, '/') :\n\t\t\t'file://' + filePath;\n\t\tawait evaluate(dc, `location=\"${fileUrl}\"`);\n\t\tawait receivePageLoadedEvent(dc);\n\t}\n\n\treturn dc;\n}\n\nfunction processEnvironmentVariables(launchArgs: LaunchConfiguration) {\n\tif (process.env['FIREFOX_EXECUTABLE']) {\n\t\tlaunchArgs.firefoxExecutable = process.env['FIREFOX_EXECUTABLE'];\n\t}\n\n\tif (process.env['FIREFOX_PROFILE']) {\n\t\tlaunchArgs.profile = process.env['FIREFOX_PROFILE'];\n\t}\n\n\tif (process.env['FIREFOX_PROFILE_DIR']) {\n\t\tlaunchArgs.profileDir = process.env['FIREFOX_PROFILE_DIR'];\n\t}\n\n\tif (process.env['KEEP_PROFILE_CHANGES'] === 'true') {\n\t\tlaunchArgs.keepProfileChanges = true;\n\t}\n\n\tif (process.env['HEADLESS'] === 'true') {\n\t\tlaunchArgs.firefoxArgs = ['-headless'];\n\t}\n}\n\nasync function waitForUnoccupiedPort(port: number, timeout: number) {\n\tif (!await isPortOccupied(port)) {\n\t\treturn;\n\t}\n\tfor (let i = 0; i < timeout / 100; i++) {\n\t\tawait delay(100);\n\t\tif (!await isPortOccupied(port)) {\n\t\t\treturn;\n\t\t}\n\t}\n\tthrow new Error(`Port ${port} is occupied`);\n}\n\nasync function isPortOccupied(port: number): Promise<boolean> {\n\tconst server = net.createServer();\n\tconst occupied = await new Promise<boolean>((resolve, reject) => {\n\t\tserver.on('error', (err: any) => {\n\t\t\tif (err.code === 'EADDRINUSE') {\n\t\t\t\tresolve(true);\n\t\t\t} else {\n\t\t\t\treject(err);\n\t\t\t}\n\t\t});\n\t\tserver.on('listening', () => resolve(false));\n\t\tserver.listen(port);\n\t});\n\tawait new Promise<void>(resolve => server.close(() => resolve()));\n\treturn occupied;\n}\n\nexport async function receivePageLoadedEvent(dc: DebugClient, lenient: boolean = false): Promise<void> {\n\tlet ev = await dc.waitForEvent('output', 10000);\n\tlet outputMsg = ev.body.output.trim();\n\tif (outputMsg.substr(0, 6) !== 'Loaded') {\n\t\tif (lenient) {\n\t\t\tawait receivePageLoadedEvent(dc, true);\n\t\t} else {\n\t\t\tthrow new Error(`Wrong output message '${outputMsg}'`);\n\t\t}\n\t}\n}\n\nexport async function setBreakpoints(\n\tdc: DebugClient,\n\tsourcePath: string,\n\tbreakpoints: number[] | DebugProtocol.SourceBreakpoint[],\n\twaitForVerification = true\n): Promise<DebugProtocol.SetBreakpointsResponse> {\n\n\tlet sourceBreakpoints: DebugProtocol.SourceBreakpoint[];\n\tif ((breakpoints.length > 0) && (typeof breakpoints[0] === 'number')) {\n\t\tsourceBreakpoints = (<number[]>breakpoints).map(line => { return { line }; })\n\t} else {\n\t\tsourceBreakpoints = <DebugProtocol.SourceBreakpoint[]>breakpoints;\n\t}\n\n\tconst result = await dc.setBreakpointsRequest({\n\t\tsource: { path: sourcePath },\n\t\tbreakpoints: sourceBreakpoints\n\t});\n\n\tif (waitForVerification) {\n\t\tlet unverified = result.body.breakpoints.filter(breakpoint => !breakpoint.verified).length;\n\t\tif (unverified > 0) {\n\t\t\tawait new Promise<void>(resolve => {\n\t\t\t\tdc.on('breakpoint', () => {\n\t\t\t\t\tunverified--;\n\t\t\t\t\tif (unverified === 0) {\n\t\t\t\t\t\tresolve();\n\t\t\t\t\t}\n\t\t\t\t});\n\t\t\t});\n\t\t}\n\t}\n\n\treturn result;\n}\n\nexport function receiveBreakpointEvent(dc: DebugClient): Promise<DebugProtocol.Event> {\n\treturn dc.waitForEvent('breakpoint', 10000);\n}\n\nexport function receiveStoppedEvent(dc: DebugClient): Promise<DebugProtocol.Event> {\n\treturn dc.waitForEvent('stopped', 10000);\n}\n\nexport function collectOutputEvents(dc: DebugClient, count: number): Promise<DebugProtocol.OutputEvent[]> {\n\treturn new Promise<DebugProtocol.OutputEvent[]>(resolve => {\n\n\t\tconst outputEvents: DebugProtocol.OutputEvent[] = [];\n\n\t\tfunction listener(event: DebugProtocol.OutputEvent) {\n\t\t\toutputEvents.push(event);\n\t\t\tif (outputEvents.length >= count) {\n\t\t\t\tdc.removeListener('output', listener);\n\t\t\t\tresolve(outputEvents);\n\t\t\t}\n\t\t}\n\n\t\tdc.addListener('output', listener);\n\t});\n}\n\nexport async function runCommandAndReceiveStoppedEvent(dc: DebugClient, command: () => void): Promise<DebugProtocol.Event> {\n\tlet stoppedEventPromise = dc.waitForEvent('stopped', 10000);\n\tcommand();\n\treturn await stoppedEventPromise;\n}\n\nexport function evaluate(dc: DebugClient, js: string): Promise<DebugProtocol.EvaluateResponse> {\n\treturn dc.evaluateRequest({ context: 'repl', expression: js });\n}\n\nexport function evaluateDelayed(dc: DebugClient, js: string, delay: number): Promise<DebugProtocol.EvaluateResponse> {\n\tjs = `setTimeout(function() { ${js} }, ${delay})`;\n\treturn evaluate(dc, js);\n}\n\nexport function evaluateCloaked(dc: DebugClient, js: string): Promise<DebugProtocol.EvaluateResponse> {\n\tjs = js.replace(\"'\", \"\\\\'\");\n\tjs = `eval('setTimeout(function() { ${js} }, 0)')`;\n\treturn dc.evaluateRequest({ context: 'repl', expression: js });\n}\n\nexport async function assertPromiseTimeout(promise: Promise<any>, timeout: number): Promise<void> {\n\tlet promiseResolved = await Promise.race([\n\t\tpromise.then(() => true),\n\t\tdelay(timeout).then(() => false)\n\t]);\n\tif (promiseResolved) {\n\t\tthrow new Error(`The Promise was resolved within ${timeout}ms`);\n\t}\n}\n\nexport function findVariable(variables: DebugProtocol.Variable[], varName: string): DebugProtocol.Variable {\n\tfor (var i = 0; i < variables.length; i++) {\n\t\tif (variables[i].name === varName) {\n\t\t\treturn variables[i];\n\t\t}\n\t}\n\tthrow new Error(`Variable '${varName}' not found`);\n}\n\nexport async function findTabThread(dc: DebugClient): Promise<number> {\n\tlet threadsPresponse = await dc.threadsRequest();\n\tfor (let thread of threadsPresponse.body.threads) {\n\t\tif (thread.name.startsWith('Tab')) {\n\t\t\treturn thread.id;\n\t\t}\n\t}\n\tthrow new Error('Couldn\\'t find a tab thread');\n}\n\nexport async function setConsoleThread(dc: DebugClient, threadId: number): Promise<void> {\n\ttry {\n\t\t// TODO explain (only used for side effect that the \"active\" thread is set)\n\t\t// await dc.stackTraceRequest({ threadId });\n\t\t// await dc.pauseRequest({ threadId });\n\t\tawait dc.continueRequest({ threadId });\n\t} catch(e) {}\n}\n"
  },
  {
    "path": "src/typings/gulp-nop.d.ts",
    "content": "declare module \"gulp-nop\" {\n\tfunction nop(): NodeJS.ReadWriteStream;\n\tnamespace nop {}\n\texport = nop;\n}\n\t"
  },
  {
    "path": "src/typings/map-sources.d.ts",
    "content": "declare module \"@gulp-sourcemaps/map-sources\" {\n\tfunction mapSources(mapFn: (sourcePath: string, file: any) => string): NodeJS.ReadWriteStream;\n\tnamespace mapSources {}\n\texport = mapSources;\n}\n"
  },
  {
    "path": "testdata/web/debuggerStatement.js",
    "content": "debugger;\n\nfunction debuggerStatement() {\n\tdebugger;\n}\n"
  },
  {
    "path": "testdata/web/dlscript.js",
    "content": "function dynTest() {\n\n\tconsole.log(\"Test\");\n\n}\n"
  },
  {
    "path": "testdata/web/exception-sourcemap.js",
    "content": "try {\n    throw new Error();\n}\ncatch (e) {\n    noop();\n}\n//# sourceMappingURL=exception-sourcemap.js.map"
  },
  {
    "path": "testdata/web/exception-sourcemap.ts",
    "content": "declare function noop(): any;\ntry {\n\tthrow new Error();\n} catch(e) {\n\tnoop();\n}\n"
  },
  {
    "path": "testdata/web/exception.js",
    "content": "try {\n\tthrow new Error();\n} catch(e) {\n\tnoop();\n}\n"
  },
  {
    "path": "testdata/web/index.html",
    "content": "<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\"/>\n\t\t<script>\n\t\t\twindow.onload = function() {\n\t\t\t\tconsole.log('Loaded');\n\t\t\t}\n\t\t</script>\n\t\t<script src=\"main.js\"></script>\n\t\t<script src=\"skip.js\"></script>\n\t</head>\n\t<body>\n\t\t<h1>Test</h1>\n\t</body>\n</html>\n"
  },
  {
    "path": "testdata/web/main.js",
    "content": "function noop() {\n\n\tlet dummy = 0;\n\n}\n\nfunction vars(arg) {\n\tlet bool1 = false;\n\tlet bool2 = true;\n\tlet num1 = 0;\n\tlet num2 = factorial(5);\n\tlet str1 = '';\n\t{\n\t\tlet str2 = 'foo';\n\t\tlet undef = undefined;\n\t\tlet nul = null;\n\t\tlet sym1 = Symbol('Local Symbol'); let sym2 = Symbol.for('Global Symbol'); let sym3 = Symbol.iterator;\n\t\tif (arg) { arg[sym1] = 'Symbol-keyed 1'; arg[sym3] = 'Symbol-keyed 2'; }\n\t\tnoop();\n\t}\n}\n\nfunction factorial(n) {\n\tif (n <= 1) {\n\t\treturn 1;\n\t} else {\n\t\treturn n * factorial(n - 1);\n\t}\n}\n\nfunction loadScript(url) {\n\tvar head = document.getElementsByTagName('head')[0];\n\tvar script = document.createElement('script');\n\tscript.type = 'text/javascript';\n\tscript.src = url;\n\thead.appendChild(script);\n}\n\nfunction throwUncaughtException() {\n\tthrow new Error('TestException');\n}\n\nfunction throwAndCatchException() {\n\ttry {\n\t\tthrow new Error('TestException');\n\t} catch(e) {}\n}\n\nvar worker;\n\nfunction startWorker() {\n\tworker = new Worker('worker.js');\n\tworker.onmessage = function(e) {\n\t\tlet received = e.data;\n\t\tnoop();\n\t}\n}\n\nfunction callWorker() {\n\tworker.postMessage({ foo: 'bar' });\n}\n\nvar obj = {\n\tx: 17,\n\ty: {\n\t\tz: 'xyz'\n\t}\n}\n\nfunction doEval(expr) {\n\treturn eval(expr);\n}\n\nfunction testSkipFiles() {\n\tdummyFunc();\n\tthrowError();\n}\n\nfunction log(...x) {\n\tconsole.log(...x);\n}\n\nfunction getterAndSetter() {\n\tlet y = {\n\t\t_z: 'foo',\n\t\tget z() { return this._z; }\n\t};\n\tlet x = {\n\t\tgp: 17,\n\t\tgsp: 23,\n\t\t_y: y,\n\t\tget getterProperty() { return this.gp; },\n\t\tset setterProperty(val) {},\n\t\tget getterAndSetterProperty() { return this.gsp; },\n\t\tset getterAndSetterProperty(val) {},\n\t\tget nested() { return this._y; }\n\t};\n\treturn x;\n}\n\nclass ProtoGetterBase {\n\tconstructor() {\n\t\tthis._z = 'bar';\n\t}\n\tget z() { return this._z; }\n}\nclass ProtoGetter extends ProtoGetterBase {\n\tconstructor() {\n\t\tsuper();\n\t\tthis._y = 'foo';\n\t}\n\tget y() { return this._y; }\n}\nfunction protoGetter() {\n\tvar x = new ProtoGetter();\n\treturn x;\n}\n\nfunction inc(o) {\n\tconsole.log(`Old: ${o.x}`);\n\to.x++;\n\tconsole.log(`New: ${o.x}`);\n}\n"
  },
  {
    "path": "testdata/web/skip.js",
    "content": "function dummyFunc() {\n\tlet dummy = 0;\n}\nfunction throwError() {\n\tthrow new Error();\n}\n"
  },
  {
    "path": "testdata/web/sourceMaps/modules/f.js",
    "content": "import { g } from './g';\n\n// Test\nfunction f() {\n\tvar x = 1;\n\tx = 2;\n\tx = g(x);\n\tx = g(x);\n}\n\nwindow.f = f;\n"
  },
  {
    "path": "testdata/web/sourceMaps/modules/g.js",
    "content": "/**\n * Test\n */\nexport function g(y) {\n\treturn y * y;\n}\n"
  },
  {
    "path": "testdata/web/sourceMaps/modules/index.html",
    "content": "<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\"/>\n\t\t<script>\n\t\t\twindow.onload = function() {\n\t\t\t\tconsole.log('Loaded');\n\t\t\t}\n\t\t</script>\n\t\t<script src=\"bundle.js\"></script>\n\t</head>\n\t<body>\n\t\t<h1>Source-map Test</h1>\n\t</body>\n</html>\n"
  },
  {
    "path": "testdata/web/sourceMaps/scripts/f.js",
    "content": "/**\n * Test\n */\nfunction f() {\n\tvar x = 1;\n\tx = 2;\n\tx = g(x);\n\tx = g(x);\n}\n"
  },
  {
    "path": "testdata/web/sourceMaps/scripts/g.js",
    "content": "/**\n * Test\n */\nfunction g(y) {\n\treturn y * y;\n}\n"
  },
  {
    "path": "testdata/web/sourceMaps/scripts/index.html",
    "content": "<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\"/>\n\t\t<script>\n\t\t\twindow.onload = function() {\n\t\t\t\tconsole.log('Loaded');\n\t\t\t}\n\t\t</script>\n\t\t__SCRIPTS__\n\t</head>\n\t<body>\n\t\t<h1>Source-map Test</h1>\n\t</body>\n</html>\n"
  },
  {
    "path": "testdata/web/tsconfig.json",
    "content": "{\n    \"compilerOptions\": {\n        \"sourceMap\": true\n    },\n    \"files\": [\n        \"exception-sourcemap.ts\"\n    ]\n}"
  },
  {
    "path": "testdata/web/worker.js",
    "content": "onmessage = function(e) {\n\tpostMessage(e.data.foo);\n}\n"
  },
  {
    "path": "testdata/webExtension/addOn/backgroundscript.js",
    "content": "chrome.runtime.onMessage.addListener((msg) => {\n\tconsole.log('foo: ' + msg.foo);\n});\n"
  },
  {
    "path": "testdata/webExtension/addOn/contentscript.js",
    "content": "let messageBox = document.getElementById('messageBox');\nsetInterval(() => {\n\tlet message = messageBox.textContent;\n\tif (message.length > 0) {\n\t\tmessageBox.textContent = '';\n\t\tchrome.runtime.sendMessage({ \"foo\": message });\n\t}\n}, 200);\n"
  },
  {
    "path": "testdata/webExtension/addOn/manifest.json",
    "content": "{\n\n\t\"description\": \"A WebExtension used for testing vscode-firefox-debug\",\n\t\"manifest_version\": 2,\n\t\"name\": \"Test\",\n\t\"version\": \"1.0\",\n\t\"homepage_url\": \"https://github.com/firefox-devtools/vscode-firefox-debug\",\n\t\"applications\": {\n\t\t\"gecko\": {\n\t\t\t\"id\": \"{12345678-1234-1234-1234-123456781234}\"\n\t\t}\n\t},\n\t\"background\": {\n\t\t\"scripts\": [ \"backgroundscript.js\" ]\n\t},\n\t\"content_scripts\": [\n\t\t{\n\t\t\t\"matches\": [ \"file:///*\" ], \n\t\t\t\"js\": [ \"contentscript.js\" ]\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "testdata/webExtension/index.html",
    "content": "<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\"/>\n\t\t<script>\n\t\t\twindow.onload = function() {\n\t\t\t\tconsole.log('Loaded');\n\t\t\t}\n\t\t\tfunction putMessage(message) {\n\t\t\t\tdocument.getElementById('messageBox').textContent = message;\n\t\t\t}\n\t\t</script>\n\t</head>\n\t<body>\n\t\t<h1>WebExtension Test</h1>\n\t\t<div id='messageBox' style='display: none'></div>\n\t</body>\n</html>\n"
  },
  {
    "path": "testdata/webExtension2/addOn/backgroundscript.js",
    "content": "chrome.runtime.onMessage.addListener((msg) => {\n\tconsole.log('foo: ' + msg.foo);\n});\n"
  },
  {
    "path": "testdata/webExtension2/addOn/contentscript.js",
    "content": "let messageBox = document.getElementById('messageBox');\nsetInterval(() => {\n\tlet message = messageBox.textContent;\n\tif (message.length > 0) {\n\t\tmessageBox.textContent = '';\n\t\tchrome.runtime.sendMessage({ \"foo\": message });\n\t}\n}, 200);\n"
  },
  {
    "path": "testdata/webExtension2/addOn/manifest.json",
    "content": "{\n\n\t\"description\": \"A WebExtension used for testing vscode-firefox-debug\",\n\t\"manifest_version\": 2,\n\t\"name\": \"Test\",\n\t\"version\": \"1.0\",\n\t\"homepage_url\": \"https://github.com/firefox-devtools/vscode-firefox-debug\",\n\t\"background\": {\n\t\t\"scripts\": [ \"backgroundscript.js\" ]\n\t},\n\t\"content_scripts\": [\n\t\t{\n\t\t\t\"matches\": [ \"file:///*\" ], \n\t\t\t\"js\": [ \"contentscript.js\" ]\n\t\t}\n\t]\n}\n"
  },
  {
    "path": "testdata/webExtension2/index.html",
    "content": "<html>\n\t<head>\n\t\t<meta charset=\"UTF-8\"/>\n\t\t<script>\n\t\t\twindow.onload = function() {\n\t\t\t\tconsole.log('Loaded');\n\t\t\t}\n\t\t\tfunction putMessage(message) {\n\t\t\t\tdocument.getElementById('messageBox').textContent = message;\n\t\t\t}\n\t\t</script>\n\t</head>\n\t<body>\n\t\t<h1>WebExtension Test</h1>\n\t\t<div id='messageBox' style='display: none'></div>\n\t</body>\n</html>\n"
  },
  {
    "path": "tsconfig.json",
    "content": "{\n\t\"compilerOptions\": {\n\t\t\"target\": \"es2015\",\n\t\t\"lib\": [ \"es2015\" ],\n\t\t\"module\": \"commonjs\",\n\t\t\"noEmit\": true,\n\t\t\"strict\": true,\n\t\t\"noImplicitReturns\": true,\n\t\t\"esModuleInterop\": true,\n\t\t\"isolatedModules\": true,\n\t\t\"skipLibCheck\": true\n\t},\n\t\"include\": [\n\t\t\"src/adapter/firefoxDebugAdapter.ts\",\n\t\t\"src/adapter/util/forkedLauncher.ts\",\n\t\t\"src/adapter/firefox/protocol.d.ts\",\n\t\t\"src/extension/main.ts\",\n\t\t\"src/typings/*.d.ts\",\n\t\t\"src/test/test*.ts\"\n\t]\n}"
  },
  {
    "path": "webpack.config.js",
    "content": "const path = require('path');\nconst CopyPlugin = require('copy-webpack-plugin');\n\nmodule.exports = {\n\tcontext: path.resolve(__dirname, 'src'),\n\tentry: {\n\t\textension: './extension/main.ts',\n\t\tadapter: './adapter/firefoxDebugAdapter.ts',\n\t\tlauncher: './adapter/util/forkedLauncher.ts'\n\t},\n\tresolve: {\n\t\textensions: [ '.js', '.ts' ]\n\t},\n\tmodule: {\n\t\trules: [\n\t\t\t{\n\t\t\t\ttest: /\\.ts$/,\n\t\t\t\tloader: 'babel-loader'\n\t\t\t}\n\t\t]\n\t},\n\texternals: {\n\t\tvscode: 'commonjs vscode',\n\t\tfsevents: 'commonjs fsevents'\n\t},\n\toutput: {\n\t\tpath: path.resolve(__dirname, 'dist'),\n\t\tfilename: '[name].bundle.js',\n\t\tlibraryTarget: 'commonjs2',\n\t\tdevtoolModuleFilenameTemplate: '../src/[resource-path]'\n\t},\n\ttarget: 'node',\n\tnode: {\n\t\t__dirname: false\n\t},\n\tplugins: [\n\t\tnew CopyPlugin({\n\t\t\tpatterns: [\n\t\t\t\tpath.resolve(__dirname, 'node_modules/source-map/lib/mappings.wasm'),\n\t\t\t\tpath.resolve(__dirname, 'LICENSE')\n\t\t\t]\n\t\t})\n\t],\n\tdevtool: 'source-map'\n};\n"
  }
]