diff options
author | Ryan Dahl <ry@tinyclouds.org> | 2020-03-27 16:09:51 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-03-27 16:09:51 -0400 |
commit | 2874664e9131616b71dd0d7d23750245b023833f (patch) | |
tree | 6782161aa151d00f930bc0157e95b14d895347f0 | |
parent | 8bcdb422e387a88075126d80e1612a30f5a7d89e (diff) |
feat: Support Inspector / Chrome Devtools (#4484)
This is a first pass implementation which is still missing several important
features:
- support for --inspect-brk (#4503)
- support for source maps (#4501)
- support for piping console.log to devtools console (#4502)
Co-authored-by: Bert Belder <bertbelder@gmail.com>
Co-authored-by: Matt Harrison <mt.harrison86@gmail.com>
Co-authored-by: Bartek IwaĆczuk <biwanczuk@gmail.com>
-rw-r--r-- | Cargo.lock | 390 | ||||
-rw-r--r-- | cli/Cargo.toml | 5 | ||||
-rw-r--r-- | cli/flags.rs | 88 | ||||
-rw-r--r-- | cli/global_state.rs | 11 | ||||
-rw-r--r-- | cli/inspector.rs | 549 | ||||
-rw-r--r-- | cli/lib.rs | 1 | ||||
-rw-r--r-- | cli/ops/fs.rs | 8 | ||||
-rw-r--r-- | cli/tests/inspector1.js | 3 | ||||
-rw-r--r-- | cli/tests/integration_tests.rs | 105 | ||||
-rw-r--r-- | cli/worker.rs | 24 | ||||
-rw-r--r-- | core/isolate.rs | 4 | ||||
-rw-r--r-- | core/lib.rs | 2 |
12 files changed, 1153 insertions, 37 deletions
diff --git a/Cargo.lock b/Cargo.lock index cdf06abc5..932fbeeab 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -170,6 +170,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b41b7ea54a0c9d92199de89e20e58d49f02f8e699814ef3fdf266f6f748d15c7" [[package]] +name = "base64" +version = "0.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7d5ca2cd0adc3f48f9e9ea5a6bbdf9ccc0bfade884847e484d452414c7ccffb3" + +[[package]] name = "bitflags" version = "1.2.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -187,6 +193,27 @@ dependencies = [ ] [[package]] +name = "block-buffer" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0940dc441f31689269e10ac70eb1002a3a1d3ad1390e030043662eb7fe4688b" +dependencies = [ + "block-padding", + "byte-tools", + "byteorder", + "generic-array", +] + +[[package]] +name = "block-padding" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fa79dedbb091f449f1f39e53edf88d5dbe95f895dae6135a8d7b881fb5af73f5" +dependencies = [ + "byte-tools", +] + +[[package]] name = "brotli" version = "3.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -208,12 +235,28 @@ dependencies = [ ] [[package]] +name = "buf_redux" +version = "0.8.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b953a6887648bb07a535631f2bc00fbdb2a2216f135552cb3f534ed136b9c07f" +dependencies = [ + "memchr", + "safemem", +] + +[[package]] name = "bumpalo" version = "3.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1f359dc14ff8911330a51ef78022d376f25ed00248912803b58f00cb1c27f742" [[package]] +name = "byte-tools" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3b5ca7a04898ad4bcd41c90c5285445ff5b791899bb1b0abdd2a2aa791211d7" + +[[package]] name = "byteorder" version = "1.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -435,7 +478,7 @@ dependencies = [ "indexmap", "lazy_static", "libc", - "log", + "log 0.4.8", "nix", "notify", "os_pipe", @@ -455,9 +498,12 @@ dependencies = [ "termcolor", "tokio", "tokio-rustls", + "tokio-tungstenite", "url 2.1.1", "utime", + "uuid", "walkdir", + "warp", "webpki", "webpki-roots 0.19.0", "winapi 0.3.8", @@ -472,7 +518,7 @@ dependencies = [ "futures 0.3.4", "lazy_static", "libc", - "log", + "log 0.4.8", "rusty_v8", "serde_json", "tokio", @@ -500,6 +546,15 @@ dependencies = [ ] [[package]] +name = "digest" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3d0c8c8752312f9713efd397ff63acb9f85585afbf179282e720e7704954dd5" +dependencies = [ + "generic-array", +] + +[[package]] name = "dirs" version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -627,6 +682,12 @@ dependencies = [ ] [[package]] +name = "fake-simd" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e88a8acf291dafb59c2d96e8f59828f3838bb1a70398823ade51a84de6a6deed" + +[[package]] name = "filetime" version = "0.2.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -829,6 +890,15 @@ dependencies = [ ] [[package]] +name = "generic-array" +version = "0.12.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c68f0274ae0e023facc3c97b2e00f076be70e254bc851d972503b328db79b2ec" +dependencies = [ + "typenum", +] + +[[package]] name = "getrandom" version = "0.1.14" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -858,7 +928,7 @@ dependencies = [ "futures-util", "http", "indexmap", - "log", + "log 0.4.8", "slab", "tokio", "tokio-util", @@ -875,6 +945,31 @@ dependencies = [ ] [[package]] +name = "headers" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed18eb2459bf1a09ad2d6b1547840c3e5e62882fa09b9a6a20b1de8e3228848f" +dependencies = [ + "base64 0.12.0", + "bitflags", + "bytes 0.5.4", + "headers-core", + "http", + "mime 0.3.16", + "sha-1", + "time", +] + +[[package]] +name = "headers-core" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7f66481bfee273957b1f20485a4ff3362987f85b2c236580d81b4eb7a326429" +dependencies = [ + "http", +] + +[[package]] name = "hermit-abi" version = "0.1.8" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -925,7 +1020,7 @@ dependencies = [ "http-body", "httparse", "itoa", - "log", + "log 0.4.8", "net2", "pin-project", "time", @@ -944,7 +1039,7 @@ dependencies = [ "ct-logs", "futures-util", "hyper", - "log", + "log 0.4.8", "rustls", "rustls-native-certs", "tokio", @@ -1024,6 +1119,15 @@ dependencies = [ ] [[package]] +name = "input_buffer" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19a8a95243d5a0398cae618ec29477c6e3cb631152be5c19481f80bc71559754" +dependencies = [ + "bytes 0.5.4", +] + +[[package]] name = "iovec" version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1087,6 +1191,15 @@ dependencies = [ [[package]] name = "log" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e19e8d5c34a3e0e2223db8e060f9e8264aeeb5c5fc64a4ee9965c062211c024b" +dependencies = [ + "log 0.4.8", +] + +[[package]] +name = "log" version = "0.4.8" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "14b6052be84e6b71ab17edffc2eeabf5c2c3ae1fdb464aae35ac50c67a44e1f7" @@ -1114,18 +1227,39 @@ checksum = "3728d817d99e5ac407411fa471ff9800a778d88a24685968b36824eaf4bee400" [[package]] name = "mime" +version = "0.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba626b8a6de5da682e1caa06bdb42a335aee5a84db8e5046a3e8ab17ba0a3ae0" +dependencies = [ + "log 0.3.9", +] + +[[package]] +name = "mime" version = "0.3.16" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2a60c7ce501c71e03a9c9c0d35b861413ae925bd979cc7a4e30d060069aaac8d" [[package]] name = "mime_guess" +version = "1.8.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "216929a5ee4dd316b1702eedf5e74548c123d370f47841ceaac38ca154690ca3" +dependencies = [ + "mime 0.2.6", + "phf", + "phf_codegen", + "unicase 1.4.2", +] + +[[package]] +name = "mime_guess" version = "2.0.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2684d4c2e97d99848d30b324b00c8fcc7e5c897b7cbb5819b09e7c90e8baf212" dependencies = [ - "mime", - "unicase", + "mime 0.3.16", + "unicase 2.6.0", ] [[package]] @@ -1149,7 +1283,7 @@ dependencies = [ "iovec", "kernel32-sys", "libc", - "log", + "log 0.4.8", "miow 0.2.1", "net2", "slab", @@ -1163,7 +1297,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "52403fe290012ce777c4626790c8951324a2b9e3316b3143779c72b029742f19" dependencies = [ "lazycell", - "log", + "log 0.4.8", "mio", "slab", ] @@ -1174,7 +1308,7 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f5e374eff525ce1c5b7687c4cef63943e7686524a387933ad27ca7ec43779cb3" dependencies = [ - "log", + "log 0.4.8", "mio", "miow 0.3.3", "winapi 0.3.8", @@ -1214,6 +1348,24 @@ dependencies = [ ] [[package]] +name = "multipart" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "136eed74cadb9edd2651ffba732b19a450316b680e4f48d6c79e905799e19d01" +dependencies = [ + "buf_redux", + "httparse", + "log 0.4.8", + "mime 0.2.6", + "mime_guess 1.8.8", + "quick-error", + "rand 0.6.5", + "safemem", + "tempfile", + "twoway", +] + +[[package]] name = "net2" version = "0.2.33" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1312,6 +1464,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b1c601810575c99596d4afc46f78a678c80105117c379eb3650cf99b8a21ce5b" [[package]] +name = "opaque-debug" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2839e79665f131bdb5782e51f2c6c9599c133c6098982a54c794358bf432529c" + +[[package]] name = "openssl-probe" version = "0.1.2" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1403,22 +1561,61 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d4fd5641d01c8f18a23da7b6fe29298ff4b55afcccdf78973b24cf3175fee32e" [[package]] +name = "phf" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b3da44b85f8e8dfaec21adae67f95d93244b2ecf6ad2a692320598dcc8e6dd18" +dependencies = [ + "phf_shared 0.7.24", +] + +[[package]] +name = "phf_codegen" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b03e85129e324ad4166b06b2c7491ae27fe3ec353af72e72cd1654c7225d517e" +dependencies = [ + "phf_generator 0.7.24", + "phf_shared 0.7.24", +] + +[[package]] +name = "phf_generator" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09364cc93c159b8b06b1f4dd8a4398984503483891b0c26b867cf431fb132662" +dependencies = [ + "phf_shared 0.7.24", + "rand 0.6.5", +] + +[[package]] name = "phf_generator" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17367f0cc86f2d25802b2c26ee58a7b23faeccf78a396094c13dced0d0182526" dependencies = [ - "phf_shared", + "phf_shared 0.8.0", "rand 0.7.3", ] [[package]] name = "phf_shared" +version = "0.7.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "234f71a15de2288bcb7e3b6515828d22af7ec8598ee6d24c3b526fa0a80b67a0" +dependencies = [ + "siphasher 0.2.3", + "unicase 1.4.2", +] + +[[package]] +name = "phf_shared" version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c00cf8b9eafe68dde5e9eaa2cef8ee84a9336a47d566ec55ca16589633b65af7" dependencies = [ - "siphasher", + "siphasher 0.3.1", ] [[package]] @@ -1522,6 +1719,12 @@ dependencies = [ ] [[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + +[[package]] name = "quote" version = "0.6.13" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1780,9 +1983,9 @@ dependencies = [ "hyper-rustls", "js-sys", "lazy_static", - "log", - "mime", - "mime_guess", + "log 0.4.8", + "mime 0.3.16", + "mime_guess 2.0.3", "percent-encoding 2.1.0", "pin-project-lite", "rustls", @@ -1848,7 +2051,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c0d4a31f5d68413404705d6982529b0e11a9aacd4839d1d6222ee3b8cb4015e1" dependencies = [ "base64 0.11.0", - "log", + "log 0.4.8", "ring", "sct", "webpki", @@ -1888,7 +2091,7 @@ dependencies = [ "cfg-if", "dirs", "libc", - "log", + "log 0.4.8", "memchr", "nix", "unicode-segmentation", @@ -1904,6 +2107,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "535622e6be132bccd223f4bb2b8ac8d53cda3c7a6394944d3b2b33fb974f9d76" [[package]] +name = "safemem" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ef703b7cb59335eae2eb93ceb664c0eb7ea6bf567079d843e09420219668e072" + +[[package]] name = "same-file" version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2032,6 +2241,18 @@ dependencies = [ ] [[package]] +name = "sha-1" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7d94d0bede923b3cea61f3f1ff57ff8cdfd77b400fb8f9998949e0cf04163df" +dependencies = [ + "block-buffer", + "digest", + "fake-simd", + "opaque-debug", +] + +[[package]] name = "signal-hook-registry" version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2043,6 +2264,12 @@ dependencies = [ [[package]] name = "siphasher" +version = "0.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b8de496cf83d4ed58b6be86c3a275b8602f6ffe98d3024a869e124147a9a3ac" + +[[package]] +name = "siphasher" version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83da420ee8d1a89e640d0948c646c1c088758d3a3c538f943bfa97bdac17929d" @@ -2116,7 +2343,7 @@ checksum = "2940c75beb4e3bf3a494cef919a747a2cb81e52571e212bfbd185074add7208a" dependencies = [ "lazy_static", "new_debug_unreachable", - "phf_shared", + "phf_shared 0.8.0", "precomputed-hash", "serde", ] @@ -2127,8 +2354,8 @@ version = "0.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f24c8e5e19d22a726626f1a5e16fe15b132dcf21d10177fa5a45ce7962996b97" dependencies = [ - "phf_generator", - "phf_shared", + "phf_generator 0.8.0", + "phf_shared 0.8.0", "proc-macro2 1.0.9", "quote 1.0.3", ] @@ -2182,7 +2409,7 @@ dependencies = [ "from_variant", "fxhash", "hashbrown", - "log", + "log 0.4.8", "parking_lot 0.7.1", "scoped-tls", "serde", @@ -2214,7 +2441,7 @@ checksum = "701e681b7783c5b9d3df9e18592494ca3cba7a2fa8fc98d4d4f423ae993df155" dependencies = [ "either", "enum_kind", - "log", + "log 0.4.8", "num-bigint", "once_cell", "regex", @@ -2377,7 +2604,7 @@ checksum = "57fc868aae093479e3131e3d165c93b1c7474109d13c90ec0dda2a1bbfff0674" dependencies = [ "bytes 0.4.12", "futures 0.1.29", - "log", + "log 0.4.8", ] [[package]] @@ -2404,6 +2631,19 @@ dependencies = [ ] [[package]] +name = "tokio-tungstenite" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b8b8fe88007ebc363512449868d7da4389c9400072a3f666f212c7280082882a" +dependencies = [ + "futures 0.3.4", + "log 0.4.8", + "pin-project", + "tokio", + "tungstenite", +] + +[[package]] name = "tokio-util" version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2412,7 +2652,7 @@ dependencies = [ "bytes 0.5.4", "futures-core", "futures-sink", - "log", + "log 0.4.8", "pin-project-lite", "tokio", ] @@ -2430,12 +2670,55 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e604eb7b43c06650e854be16a2a03155743d3752dd1c943f6829e26b7a36e382" [[package]] +name = "tungstenite" +version = "0.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cfea31758bf674f990918962e8e5f07071a3161bd7c4138ed23e416e1ac4264e" +dependencies = [ + "base64 0.11.0", + "byteorder", + "bytes 0.5.4", + "http", + "httparse", + "input_buffer", + "log 0.4.8", + "rand 0.7.3", + "sha-1", + "url 2.1.1", + "utf-8", +] + +[[package]] +name = "twoway" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "59b11b2b5241ba34be09c3cc85a36e56e48f9888862e19cedf23336d35316ed1" +dependencies = [ + "memchr", +] + +[[package]] +name = "typenum" +version = "1.11.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d2783fe2d6b8c1101136184eb41be8b1ad379e4657050b8aaff0c79ee7575f9" + +[[package]] +name = "unicase" +version = "1.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4765f83163b74f957c797ad9253caf97f103fb064d3999aea9568d09fc8a33" +dependencies = [ + "version_check 0.1.5", +] + +[[package]] name = "unicase" version = "2.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "50f37be617794602aabbeee0be4f259dc1778fabe05e2d67ee8f79326d5cb4f6" dependencies = [ - "version_check", + "version_check 0.9.1", ] [[package]] @@ -2509,6 +2792,18 @@ dependencies = [ ] [[package]] +name = "urlencoding" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3df3561629a8bb4c57e5a2e4c43348d9e29c7c29d9b1c4c1f47166deca8f37ed" + +[[package]] +name = "utf-8" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05e42f7c18b8f902290b009cde6d651262f956c98bc51bca4cd1d511c9cd85c7" + +[[package]] name = "utf8parse" version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2526,6 +2821,15 @@ dependencies = [ ] [[package]] +name = "uuid" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9fde2f6a4bea1d6e007c4ad38c6839fa71cbb63b6dbf5b595aa38dc9b1093c11" +dependencies = [ + "rand 0.7.3", +] + +[[package]] name = "vec_map" version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2533,6 +2837,12 @@ checksum = "05c78687fb1a80548ae3250346c3db86a80a7cdd77bda190189f2d0a0987c81a" [[package]] name = "version_check" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "914b1a6776c4c929a602fafd8bc742e06365d4bcbe48c30f9cca5824f70dc9dd" + +[[package]] +name = "version_check" version = "0.9.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "078775d0255232fb988e6fccf26ddc9d1ac274299aaedcedce21c6f72cc533ce" @@ -2560,11 +2870,37 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1ce8a968cb1cd110d136ff8b819a556d6fb6d919363c61534f6860c7eb172ba0" dependencies = [ - "log", + "log 0.4.8", "try-lock", ] [[package]] +name = "warp" +version = "0.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "54cd1e2b3eb3539284d88b76a9afcf5e20f2ef2fab74db5b21a1c30d7d945e82" +dependencies = [ + "bytes 0.5.4", + "futures 0.3.4", + "headers", + "http", + "hyper", + "log 0.4.8", + "mime 0.3.16", + "mime_guess 2.0.3", + "multipart", + "pin-project", + "scoped-tls", + "serde", + "serde_json", + "serde_urlencoded", + "tokio", + "tokio-tungstenite", + "tower-service", + "urlencoding", +] + +[[package]] name = "wasi" version = "0.9.0+wasi-snapshot-preview1" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -2590,7 +2926,7 @@ checksum = "e0da9c9a19850d3af6df1cb9574970b566d617ecfaf36eb0b706b6f3ef9bd2f8" dependencies = [ "bumpalo", "lazy_static", - "log", + "log 0.4.8", "proc-macro2 1.0.9", "quote 1.0.3", "syn 1.0.16", diff --git a/cli/Cargo.toml b/cli/Cargo.toml index 1a7a8db8c..aa4fd15de 100644 --- a/cli/Cargo.toml +++ b/cli/Cargo.toml @@ -61,7 +61,10 @@ utime = "0.2.1" webpki = "0.21.2" webpki-roots = "0.19.0" walkdir = "2.3.1" +warp = "0.2.2" semver-parser = "0.9.0" +uuid = { version = "0.8", features = ["v4"] } + [target.'cfg(windows)'.dependencies] winapi = "0.3.8" @@ -72,6 +75,8 @@ nix = "0.14" # rustyline depends on 0.14, to avoid duplicates we do too. [dev-dependencies] os_pipe = "0.9.1" +# Used for testing inspector. Keep in-sync with warp. +tokio-tungstenite = { version = "0.10", features = ["connect"] } [target.'cfg(unix)'.dev-dependencies] pty = "0.2.2" diff --git a/cli/flags.rs b/cli/flags.rs index 475172f0a..4f55d69ef 100644 --- a/cli/flags.rs +++ b/cli/flags.rs @@ -101,6 +101,8 @@ pub struct Flags { pub no_prompts: bool, pub no_remote: bool, pub cached_only: bool, + pub inspect: Option<String>, + pub inspect_brk: Option<String>, pub seed: Option<u64>, pub v8_flags: Option<Vec<String>>, @@ -474,6 +476,7 @@ fn run_test_args_parse(flags: &mut Flags, matches: &clap::ArgMatches) { no_remote_arg_parse(flags, matches); permission_args_parse(flags, matches); ca_file_arg_parse(flags, matches); + inspect_arg_parse(flags, matches); if matches.is_present("cached-only") { flags.cached_only = true; @@ -825,7 +828,7 @@ fn permission_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { } fn run_test_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { - permission_args(app) + permission_args(inspect_args(app)) .arg(importmap_arg()) .arg(reload_arg()) .arg(config_arg()) @@ -956,6 +959,54 @@ fn ca_file_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { flags.ca_file = matches.value_of("cert").map(ToOwned::to_owned); } +fn inspect_args<'a, 'b>(app: App<'a, 'b>) -> App<'a, 'b> { + app + .arg( + Arg::with_name("inspect") + .long("inspect") + .value_name("HOST:PORT") + .help("activate inspector on host:port (default: 127.0.0.1:9229)") + .min_values(0) + .max_values(1) + .require_equals(true) + .takes_value(true), + ) + .arg( + Arg::with_name("inspect-brk") + .long("inspect-brk") + .value_name("HOST:PORT") + .help( + "activate inspector on host:port and break at start of user script", + ) + .min_values(0) + .max_values(1) + .require_equals(true) + .takes_value(true), + ) +} + +fn inspect_arg_parse(flags: &mut Flags, matches: &clap::ArgMatches) { + const DEFAULT: &str = "127.0.0.1:9229"; + flags.inspect = if matches.is_present("inspect") { + if let Some(host) = matches.value_of("inspect") { + Some(host.to_string()) + } else { + Some(DEFAULT.to_string()) + } + } else { + None + }; + flags.inspect_brk = if matches.is_present("inspect-brk") { + if let Some(host) = matches.value_of("inspect-brk") { + Some(host.to_string()) + } else { + Some(DEFAULT.to_string()) + } + } else { + None + }; +} + fn reload_arg<'a, 'b>() -> Arg<'a, 'b> { Arg::with_name("reload") .short("r") @@ -2327,3 +2378,38 @@ fn repl_with_cafile() { } ); } + +#[test] +fn inspect_default_host() { + let r = flags_from_vec_safe(svec!["deno", "run", "--inspect", "foo.js"]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run { + script: "foo.js".to_string(), + }, + inspect: Some("127.0.0.1:9229".to_string()), + ..Flags::default() + } + ); +} + +#[test] +fn inspect_custom_host() { + let r = flags_from_vec_safe(svec![ + "deno", + "run", + "--inspect=deno.land:80", + "foo.js" + ]); + assert_eq!( + r.unwrap(), + Flags { + subcommand: DenoSubcommand::Run { + script: "foo.js".to_string(), + }, + inspect: Some("deno.land:80".to_string()), + ..Flags::default() + } + ); +} diff --git a/cli/global_state.rs b/cli/global_state.rs index 45a31406c..001c3f55f 100644 --- a/cli/global_state.rs +++ b/cli/global_state.rs @@ -9,6 +9,7 @@ use crate::deno_dir; use crate::file_fetcher::SourceFileFetcher; use crate::flags; use crate::http_cache; +use crate::inspector::InspectorServer; use crate::lockfile::Lockfile; use crate::msg; use crate::permissions::DenoPermissions; @@ -42,6 +43,7 @@ pub struct GlobalStateInner { pub wasm_compiler: WasmCompiler, pub lockfile: Option<Mutex<Lockfile>>, pub compiler_starts: AtomicUsize, + pub inspector_server: Option<InspectorServer>, compile_lock: AsyncMutex<()>, } @@ -82,7 +84,16 @@ impl GlobalState { None }; + let inspector_server = if let Some(ref host) = flags.inspect { + Some(InspectorServer::new(host, false)) + } else if let Some(ref host) = flags.inspect_brk { + Some(InspectorServer::new(host, true)) + } else { + None + }; + let inner = GlobalStateInner { + inspector_server, dir, permissions: DenoPermissions::from_flags(&flags), flags, diff --git a/cli/inspector.rs b/cli/inspector.rs new file mode 100644 index 000000000..a30e5c0d7 --- /dev/null +++ b/cli/inspector.rs @@ -0,0 +1,549 @@ +// Copyright 2018-2020 the Deno authors. All rights reserved. MIT license. + +// The documentation for the inspector API is sparse, but these are helpful: +// https://chromedevtools.github.io/devtools-protocol/ +// https://hyperandroid.com/2020/02/12/v8-inspector-from-an-embedder-standpoint/ + +use deno_core::v8; +use futures; +use futures::executor; +use futures::future; +use futures::FutureExt; +use futures::SinkExt; +use futures::StreamExt; +use std::collections::HashMap; +use std::ffi::c_void; +use std::future::Future; +use std::mem::MaybeUninit; +use std::net::SocketAddrV4; +use std::pin::Pin; +use std::ptr; +use std::sync::atomic::AtomicBool; +use std::sync::atomic::Ordering; +use std::sync::Arc; +use std::task::Context; +use std::task::Poll; +use tokio; +use tokio::sync::mpsc; +use tokio::sync::mpsc::error::TryRecvError; +use uuid::Uuid; +use warp; +use warp::filters::ws; +use warp::Filter; + +const CONTEXT_GROUP_ID: i32 = 1; + +/// Owned by GloalState, this channel end can be used by any isolate thread +/// to register it's inspector with the inspector server. +type ServerMsgTx = mpsc::UnboundedSender<ServerMsg>; +/// Owned by the inspector server thread, used to to receive information about +/// new isolates. +type ServerMsgRx = mpsc::UnboundedReceiver<ServerMsg>; +/// These messages can be sent from any thread to the server thread. +enum ServerMsg { + AddInspector(InspectorInfo), +} + +/// Owned by the web socket server. Relays incoming websocket connections and +/// messages to the isolate/inspector thread. +type FrontendToInspectorTx = mpsc::UnboundedSender<FrontendToInspectorMsg>; +/// Owned by the isolate/worker. Receives incoming websocket connections and +/// messages from the inspector server thread. +type FrontendToInspectorRx = mpsc::UnboundedReceiver<FrontendToInspectorMsg>; +/// Messages sent over the FrontendToInspectorTx/FrontendToInspectorRx channel. +pub enum FrontendToInspectorMsg { + WsConnection { + session_uuid: Uuid, + session_to_frontend_tx: SessionToFrontendTx, + }, + WsIncoming { + session_uuid: Uuid, + msg: ws::Message, + }, +} + +/// Owned by the deno inspector session, used to forward messages from the +/// inspector channel on the isolate thread to the websocket that is owned by +/// the inspector server. +type SessionToFrontendTx = mpsc::UnboundedSender<ws::Message>; +/// Owned by the inspector server. Messages arriving on this channel, coming +/// from the inspector session on the isolate thread are forwarded over the +/// websocket to the devtools frontend. +type SessionToFrontendRx = mpsc::UnboundedReceiver<ws::Message>; + +/// Stored in a UUID hashmap, used by WS server. Clonable. +#[derive(Clone)] +struct InspectorInfo { + uuid: Uuid, + frontend_to_inspector_tx: FrontendToInspectorTx, + inspector_handle: DenoInspectorHandle, +} + +/// Owned by GlobalState. +pub struct InspectorServer { + address: SocketAddrV4, + thread_handle: Option<std::thread::JoinHandle<()>>, + server_msg_tx: Option<ServerMsgTx>, +} + +impl InspectorServer { + pub fn new(host: &str, brk: bool) -> Self { + if brk { + todo!("--inspect-brk not yet supported"); + } + let address = host.parse::<SocketAddrV4>().unwrap(); + let (server_msg_tx, server_msg_rx) = mpsc::unbounded_channel::<ServerMsg>(); + let thread_handle = std::thread::spawn(move || { + crate::tokio_util::run_basic(server(address, server_msg_rx)); + }); + Self { + address, + thread_handle: Some(thread_handle), + server_msg_tx: Some(server_msg_tx), + } + } + + /// Each worker/isolate to be debugged should call this exactly one. + /// Called from worker's thread + pub fn add_inspector( + &self, + isolate: &mut deno_core::Isolate, + ) -> Box<DenoInspector> { + let deno_core::Isolate { + v8_isolate, + global_context, + .. + } = isolate; + let v8_isolate = v8_isolate.as_mut().unwrap(); + + let mut hs = v8::HandleScope::new(v8_isolate); + let scope = hs.enter(); + let context = global_context.get(scope).unwrap(); + + let server_msg_tx = self.server_msg_tx.as_ref().unwrap().clone(); + let address = self.address; + let (frontend_to_inspector_tx, frontend_to_inspector_rx) = + mpsc::unbounded_channel::<FrontendToInspectorMsg>(); + let uuid = Uuid::new_v4(); + + let inspector = crate::inspector::DenoInspector::new( + scope, + context, + frontend_to_inspector_rx, + ); + + info!( + "Debugger listening on {}", + websocket_debugger_url(address, &uuid) + ); + + server_msg_tx + .send(ServerMsg::AddInspector(InspectorInfo { + uuid, + frontend_to_inspector_tx, + inspector_handle: DenoInspectorHandle::new( + &inspector, + v8_isolate.thread_safe_handle(), + ), + })) + .unwrap_or_else(|_| { + panic!("sending message to inspector server thread failed"); + }); + + inspector + } +} + +impl Drop for InspectorServer { + fn drop(&mut self) { + self.server_msg_tx.take(); + self.thread_handle.take().unwrap().join().unwrap(); + panic!("TODO: this drop is never called"); + } +} + +fn websocket_debugger_url(address: SocketAddrV4, uuid: &Uuid) -> String { + format!("ws://{}:{}/ws/{}", address.ip(), address.port(), uuid) +} + +async fn server(address: SocketAddrV4, mut server_msg_rx: ServerMsgRx) { + let inspector_map = HashMap::<Uuid, InspectorInfo>::new(); + let inspector_map = Arc::new(std::sync::Mutex::new(inspector_map)); + + let inspector_map_ = inspector_map.clone(); + let msg_handler = async move { + while let Some(msg) = server_msg_rx.next().await { + match msg { + ServerMsg::AddInspector(inspector_info) => { + let existing = inspector_map_ + .lock() + .unwrap() + .insert(inspector_info.uuid, inspector_info); + if existing.is_some() { + panic!("UUID already in map"); + } + } + }; + } + }; + + let inspector_map_ = inspector_map.clone(); + let websocket = warp::path("ws") + .and(warp::path::param()) + .and(warp::ws()) + .map(move |uuid: String, ws: warp::ws::Ws| { + let inspector_map__ = inspector_map_.clone(); + ws.on_upgrade(move |socket| async move { + let inspector_info = { + if let Ok(uuid) = Uuid::parse_str(&uuid) { + let g = inspector_map__.lock().unwrap(); + if let Some(inspector_info) = g.get(&uuid) { + inspector_info.clone() + } else { + return; + } + } else { + return; + } + }; + + // send a message back so register_worker can return... + let (mut ws_tx, mut ws_rx) = socket.split(); + + let (session_to_frontend_tx, mut session_to_frontend_rx): ( + SessionToFrontendTx, + SessionToFrontendRx, + ) = mpsc::unbounded_channel(); + + // Not to be confused with the WS's uuid... + let session_uuid = Uuid::new_v4(); + + inspector_info + .frontend_to_inspector_tx + .send(FrontendToInspectorMsg::WsConnection { + session_to_frontend_tx, + session_uuid, + }) + .unwrap_or_else(|_| { + panic!("sending message to frontend_to_inspector_tx failed"); + }); + + inspector_info.inspector_handle.interrupt(); + + let pump_to_inspector = async { + while let Some(Ok(msg)) = ws_rx.next().await { + inspector_info + .frontend_to_inspector_tx + .send(FrontendToInspectorMsg::WsIncoming { msg, session_uuid }) + .unwrap_or_else(|_| { + panic!("sending message to frontend_to_inspector_tx failed"); + }); + + inspector_info.inspector_handle.interrupt(); + } + }; + + let pump_from_session = async { + while let Some(msg) = session_to_frontend_rx.next().await { + ws_tx.send(msg).await.ok(); + } + }; + + future::join(pump_to_inspector, pump_from_session).await; + }) + }); + + let inspector_map_ = inspector_map.clone(); + let json_list = + warp::path("json") + .map(move || { + let g = inspector_map_.lock().unwrap(); + let json_values: Vec<serde_json::Value> = g.iter().map(|(uuid, _)| { + let url = websocket_debugger_url(address, uuid); + json!({ + "description": "deno", + "devtoolsFrontendUrl": format!("chrome-devtools://devtools/bundled/js_app.html?experiments=true&v8only=true&ws={}", url), + "faviconUrl": "https://deno.land/favicon.ico", + "id": uuid.to_string(), + "title": format!("deno[{}]", std::process::id()), + "type": "deno", + "url": "file://", + "webSocketDebuggerUrl": url, + }) + }).collect(); + warp::reply::json(&json!(json_values)) + }); + + let version = warp::path!("json" / "version").map(|| { + warp::reply::json(&json!({ + "Browser": format!("Deno/{}", crate::version::DENO), + "Protocol-Version": "1.3", + "V8-Version": crate::version::v8(), + })) + }); + + let routes = websocket.or(version).or(json_list); + let web_handler = warp::serve(routes).bind(address); + + future::join(msg_handler, web_handler).await; +} + +pub struct DenoInspector { + client: v8::inspector::V8InspectorClientBase, + inspector: v8::UniqueRef<v8::inspector::V8Inspector>, + pub sessions: HashMap<Uuid, Box<DenoInspectorSession>>, + frontend_to_inspector_rx: FrontendToInspectorRx, + paused: bool, + interrupted: Arc<AtomicBool>, +} + +impl DenoInspector { + pub fn new<P>( + scope: &mut P, + context: v8::Local<v8::Context>, + frontend_to_inspector_rx: FrontendToInspectorRx, + ) -> Box<Self> + where + P: v8::InIsolate, + { + let mut deno_inspector = new_box_with(|address| Self { + client: v8::inspector::V8InspectorClientBase::new::<Self>(), + // TODO(piscisaureus): V8Inspector::create() should require that + // the 'client' argument cannot move. + inspector: v8::inspector::V8Inspector::create(scope, unsafe { + &mut *address + }), + sessions: HashMap::new(), + frontend_to_inspector_rx, + paused: false, + interrupted: Arc::new(AtomicBool::new(false)), + }); + + let empty_view = v8::inspector::StringView::empty(); + deno_inspector.inspector.context_created( + context, + CONTEXT_GROUP_ID, + &empty_view, + ); + + deno_inspector + } + + pub fn connect( + &mut self, + session_uuid: Uuid, + session_to_frontend_tx: SessionToFrontendTx, + ) { + let session = + DenoInspectorSession::new(&mut self.inspector, session_to_frontend_tx); + self.sessions.insert(session_uuid, session); + } + + fn dispatch_frontend_to_inspector_msg( + &mut self, + msg: FrontendToInspectorMsg, + ) { + match msg { + FrontendToInspectorMsg::WsConnection { + session_uuid, + session_to_frontend_tx, + } => { + self.connect(session_uuid, session_to_frontend_tx); + } + FrontendToInspectorMsg::WsIncoming { session_uuid, msg } => { + if let Some(deno_session) = self.sessions.get_mut(&session_uuid) { + deno_session.dispatch_protocol_message(msg) + } else { + info!("Unknown inspector session {}. msg {:?}", session_uuid, msg); + } + } + }; + } + + extern "C" fn poll_interrupt( + _isolate: &mut v8::Isolate, + self_ptr: *mut c_void, + ) { + let self_ = unsafe { &mut *(self_ptr as *mut Self) }; + let _ = self_.poll_without_waker(); + } + + fn poll_without_waker(&mut self) -> Poll<<Self as Future>::Output> { + loop { + match self.frontend_to_inspector_rx.try_recv() { + Ok(msg) => self.dispatch_frontend_to_inspector_msg(msg), + Err(TryRecvError::Closed) => break Poll::Ready(()), + Err(TryRecvError::Empty) + if self.interrupted.swap(false, Ordering::AcqRel) => {} + Err(TryRecvError::Empty) => break Poll::Pending, + } + } + } +} + +/// DenoInspector implements a Future so that it can poll for incoming messages +/// from the WebSocket server. Since a Worker ownes a DenoInspector, and because +/// a Worker is a Future too, Worker::poll will call this. +impl Future for DenoInspector { + type Output = (); + + fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { + let self_ = self.get_mut(); + loop { + match self_.frontend_to_inspector_rx.poll_recv(cx) { + Poll::Ready(Some(msg)) => self_.dispatch_frontend_to_inspector_msg(msg), + Poll::Ready(None) => break Poll::Ready(()), + Poll::Pending if self_.interrupted.swap(false, Ordering::AcqRel) => {} + Poll::Pending => break Poll::Pending, + } + } + } +} + +impl v8::inspector::V8InspectorClientImpl for DenoInspector { + fn base(&self) -> &v8::inspector::V8InspectorClientBase { + &self.client + } + + fn base_mut(&mut self) -> &mut v8::inspector::V8InspectorClientBase { + &mut self.client + } + + fn run_message_loop_on_pause(&mut self, context_group_id: i32) { + assert_eq!(context_group_id, CONTEXT_GROUP_ID); + assert!(!self.paused); + self.paused = true; + + // Creating a new executor and calling block_on generally causes a panic. + // In this case it works because the outer executor is provided by tokio + // and the one created here comes from the 'futures' crate, and they don't + // see each other. + let dispatch_messages_while_paused = + future::poll_fn(|cx| match self.poll_unpin(cx) { + Poll::Pending if self.paused => Poll::Pending, + _ => Poll::Ready(()), + }); + executor::block_on(dispatch_messages_while_paused); + } + + fn quit_message_loop_on_pause(&mut self) { + self.paused = false; + } + + fn run_if_waiting_for_debugger(&mut self, context_group_id: i32) { + assert_eq!(context_group_id, CONTEXT_GROUP_ID); + } +} + +#[derive(Clone)] +struct DenoInspectorHandle { + deno_inspector_ptr: *mut c_void, + isolate_handle: v8::IsolateHandle, + interrupted: Arc<AtomicBool>, +} + +impl DenoInspectorHandle { + pub fn new( + deno_inspector: &DenoInspector, + isolate_handle: v8::IsolateHandle, + ) -> Self { + Self { + deno_inspector_ptr: deno_inspector as *const DenoInspector + as *const c_void as *mut c_void, + isolate_handle, + interrupted: deno_inspector.interrupted.clone(), + } + } + + pub fn interrupt(&self) { + if !self.interrupted.swap(true, Ordering::AcqRel) { + self.isolate_handle.request_interrupt( + DenoInspector::poll_interrupt, + self.deno_inspector_ptr, + ); + } + } +} + +unsafe impl Send for DenoInspectorHandle {} +unsafe impl Sync for DenoInspectorHandle {} + +/// sub-class of v8::inspector::Channel +pub struct DenoInspectorSession { + channel: v8::inspector::ChannelBase, + session: v8::UniqueRef<v8::inspector::V8InspectorSession>, + session_to_frontend_tx: SessionToFrontendTx, +} + +impl DenoInspectorSession { + pub fn new( + inspector: &mut v8::inspector::V8Inspector, + session_to_frontend_tx: SessionToFrontendTx, + ) -> Box<Self> { + new_box_with(|address| { + let empty_view = v8::inspector::StringView::empty(); + Self { + channel: v8::inspector::ChannelBase::new::<Self>(), + session: inspector.connect( + CONTEXT_GROUP_ID, + // Todo(piscisaureus): V8Inspector::connect() should require that + // the 'channel' argument cannot move. + unsafe { &mut *address }, + &empty_view, + ), + session_to_frontend_tx, + } + }) + } + + pub fn dispatch_protocol_message(&mut self, ws_msg: ws::Message) { + let bytes = ws_msg.as_bytes(); + let string_view = v8::inspector::StringView::from(bytes); + self.session.dispatch_protocol_message(&string_view); + } +} + +impl v8::inspector::ChannelImpl for DenoInspectorSession { + fn base(&self) -> &v8::inspector::ChannelBase { + &self.channel + } + + fn base_mut(&mut self) -> &mut v8::inspector::ChannelBase { + &mut self.channel + } + + fn send_response( + &mut self, + _call_id: i32, + message: v8::UniquePtr<v8::inspector::StringBuffer>, + ) { + let ws_msg = v8_to_ws_msg(message); + self.session_to_frontend_tx.send(ws_msg).unwrap(); + } + + fn send_notification( + &mut self, + message: v8::UniquePtr<v8::inspector::StringBuffer>, + ) { + let ws_msg = v8_to_ws_msg(message); + self.session_to_frontend_tx.send(ws_msg).unwrap(); + } + + fn flush_protocol_notifications(&mut self) {} +} + +// TODO impl From or Into +fn v8_to_ws_msg( + message: v8::UniquePtr<v8::inspector::StringBuffer>, +) -> ws::Message { + let mut x = message.unwrap(); + let s = x.string().to_string(); + ws::Message::text(s) +} + +fn new_box_with<T>(new_fn: impl FnOnce(*mut T) -> T) -> Box<T> { + let b = Box::new(MaybeUninit::<T>::uninit()); + let p = Box::into_raw(b) as *mut T; + unsafe { ptr::write(p, new_fn(p)) }; + unsafe { Box::from_raw(p) } +} diff --git a/cli/lib.rs b/cli/lib.rs index ba5152bd6..7b5b56ba2 100644 --- a/cli/lib.rs +++ b/cli/lib.rs @@ -37,6 +37,7 @@ mod global_timer; pub mod http_cache; mod http_util; mod import_map; +mod inspector; pub mod installer; mod js; mod lockfile; diff --git a/cli/ops/fs.rs b/cli/ops/fs.rs index 7e526d71e..493bd31e4 100644 --- a/cli/ops/fs.rs +++ b/cli/ops/fs.rs @@ -256,7 +256,7 @@ fn op_umask( #[cfg(not(unix))] { let _ = args.mask; // avoid unused warning. - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } #[cfg(unix)] { @@ -360,7 +360,7 @@ fn op_chmod( { // Still check file/dir exists on Windows let _metadata = std::fs::metadata(&path)?; - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } }) } @@ -400,7 +400,7 @@ fn op_chown( { // Still check file/dir exists on Windows let _metadata = std::fs::metadata(&path)?; - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } }) } @@ -731,7 +731,7 @@ fn op_symlink( // Unlike with chmod/chown, here we don't // require `oldpath` to exist on Windows let _ = oldpath; // avoid unused warning - return Err(OpError::not_implemented()); + Err(OpError::not_implemented()) } }) } diff --git a/cli/tests/inspector1.js b/cli/tests/inspector1.js new file mode 100644 index 000000000..5cb059def --- /dev/null +++ b/cli/tests/inspector1.js @@ -0,0 +1,3 @@ +setInterval(() => { + console.log("hello"); +}, 1000); diff --git a/cli/tests/integration_tests.rs b/cli/tests/integration_tests.rs index f8651869b..02cccf9cf 100644 --- a/cli/tests/integration_tests.rs +++ b/cli/tests/integration_tests.rs @@ -1967,6 +1967,111 @@ fn test_permissions_net_listen_allow_localhost() { assert!(!err.contains(util::PERMISSION_DENIED_PATTERN)); } +#[cfg(not(target_os = "linux"))] // TODO(ry) broken on github actions. +fn extract_ws_url_from_stderr( + stderr: &mut std::process::ChildStderr, +) -> url::Url { + use std::io::BufRead; + let mut stderr = std::io::BufReader::new(stderr); + let mut stderr_first_line = String::from(""); + let _ = stderr.read_line(&mut stderr_first_line).unwrap(); + assert!(stderr_first_line.starts_with("Debugger listening on ")); + let v: Vec<_> = stderr_first_line.match_indices("ws:").collect(); + assert_eq!(v.len(), 1); + let ws_url_index = v[0].0; + let ws_url = &stderr_first_line[ws_url_index..]; + url::Url::parse(ws_url).unwrap() +} + +#[cfg(not(target_os = "linux"))] // TODO(ry) broken on github actions. +#[tokio::test] +async fn inspector_connect() { + let script = deno::test_util::root_path() + .join("cli") + .join("tests") + .join("inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + // Warning: each inspector test should be on its own port to avoid + // conflicting with another inspector test. + .arg("--inspect=127.0.0.1:9229") + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let ws_url = extract_ws_url_from_stderr(child.stderr.as_mut().unwrap()); + println!("ws_url {}", ws_url); + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (_socket, response) = tokio_tungstenite::connect_async(ws_url) + .await + .expect("Can't connect"); + assert_eq!("101 Switching Protocols", response.status().to_string()); + child.kill().unwrap(); +} + +#[cfg(not(target_os = "linux"))] // TODO(ry) broken on github actions. +#[tokio::test] +async fn inspector_pause() { + let script = deno::test_util::root_path() + .join("cli") + .join("tests") + .join("inspector1.js"); + let mut child = util::deno_cmd() + .arg("run") + // Warning: each inspector test should be on its own port to avoid + // conflicting with another inspector test. + .arg("--inspect=127.0.0.1:9230") + .arg(script) + .stderr(std::process::Stdio::piped()) + .spawn() + .unwrap(); + let ws_url = extract_ws_url_from_stderr(child.stderr.as_mut().unwrap()); + println!("ws_url {}", ws_url); + // We use tokio_tungstenite as a websocket client because warp (which is + // a dependency of Deno) uses it. + let (mut socket, _) = tokio_tungstenite::connect_async(ws_url) + .await + .expect("Can't connect"); + + /// Returns the next websocket message as a string ignoring + /// Debugger.scriptParsed messages. + async fn ws_read_msg( + socket: &mut tokio_tungstenite::WebSocketStream<tokio::net::TcpStream>, + ) -> String { + use futures::stream::StreamExt; + while let Some(msg) = socket.next().await { + let msg = msg.unwrap().to_string(); + assert!(!msg.contains("error")); + if !msg.contains("Debugger.scriptParsed") { + return msg; + } + } + unreachable!() + } + + use futures::sink::SinkExt; + socket + .send(r#"{"id":6,"method":"Debugger.enable"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 1 {}", msg); + assert!(msg.starts_with(r#"{"id":6,"result":{"debuggerId":"#)); + + socket + .send(r#"{"id":31,"method":"Debugger.pause"}"#.into()) + .await + .unwrap(); + + let msg = ws_read_msg(&mut socket).await; + println!("response msg 2 {}", msg); + assert_eq!(msg, r#"{"id":31,"result":{}}"#); + + child.kill().unwrap(); +} + mod util { use deno::colors::strip_ansi_codes; pub use deno::test_util::*; diff --git a/cli/worker.rs b/cli/worker.rs index 6593ade0b..994f22f04 100644 --- a/cli/worker.rs +++ b/cli/worker.rs @@ -97,6 +97,7 @@ pub struct Worker { pub waker: AtomicWaker, pub(crate) internal_channels: WorkerChannelsInternal, external_channels: WorkerHandle, + inspector: Option<Box<crate::inspector::DenoInspector>>, } impl Worker { @@ -104,9 +105,15 @@ impl Worker { let loader = Rc::new(state.clone()); let mut isolate = deno_core::EsIsolate::new(loader, startup_data, false); - let global_state_ = state.borrow().global_state.clone(); + let global_state = state.borrow().global_state.clone(); + + let inspector = global_state + .inspector_server + .as_ref() + .map(|s| s.add_inspector(&mut *isolate)); + isolate.set_js_error_create_fn(move |core_js_error| { - JSError::create(core_js_error, &global_state_.ts_compiler) + JSError::create(core_js_error, &global_state.ts_compiler) }); let (internal_channels, external_channels) = create_channels(); @@ -118,6 +125,7 @@ impl Worker { waker: AtomicWaker::new(), internal_channels, external_channels, + inspector, } } @@ -175,11 +183,23 @@ impl Worker { } } +impl Drop for Worker { + fn drop(&mut self) { + // The Isolate object must outlive the Inspector object, but this is + // currently not enforced by the type system. + self.inspector.take(); + } +} + impl Future for Worker { type Output = Result<(), ErrBox>; fn poll(self: Pin<&mut Self>, cx: &mut Context) -> Poll<Self::Output> { let inner = self.get_mut(); + if let Some(deno_inspector) = inner.inspector.as_mut() { + // We always poll the inspector if it exists. + let _ = deno_inspector.poll_unpin(cx); + } inner.waker.register(cx.waker()); inner.isolate.poll_unpin(cx) } diff --git a/core/isolate.rs b/core/isolate.rs index 3f4f89796..f876f2452 100644 --- a/core/isolate.rs +++ b/core/isolate.rs @@ -159,11 +159,11 @@ type IsolateErrorHandleFn = dyn FnMut(ErrBox) -> Result<(), ErrBox>; /// as arguments. An async Op corresponds exactly to a Promise in JavaScript. #[allow(unused)] pub struct Isolate { - pub(crate) v8_isolate: Option<v8::OwnedIsolate>, + pub v8_isolate: Option<v8::OwnedIsolate>, snapshot_creator: Option<v8::SnapshotCreator>, has_snapshotted: bool, snapshot: Option<SnapshotConfig>, - pub(crate) global_context: v8::Global<v8::Context>, + pub global_context: v8::Global<v8::Context>, pub(crate) shared_ab: v8::Global<v8::SharedArrayBuffer>, pub(crate) js_recv_cb: v8::Global<v8::Function>, pub(crate) js_macrotask_cb: v8::Global<v8::Function>, diff --git a/core/lib.rs b/core/lib.rs index c4fbb33f4..d1776fb69 100644 --- a/core/lib.rs +++ b/core/lib.rs @@ -21,7 +21,7 @@ mod plugins; mod resources; mod shared_queue; -use rusty_v8 as v8; +pub use rusty_v8 as v8; pub use crate::any_error::*; pub use crate::es_isolate::*; |