opal 1.6.1 → 1.7.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (212) hide show
  1. checksums.yaml +4 -4
  2. data/.github/workflows/build.yml +17 -0
  3. data/CHANGELOG.md +35 -1
  4. data/Gemfile +1 -0
  5. data/HACKING.md +47 -26
  6. data/benchmark/benchmarks +415 -103
  7. data/benchmark/bm_call_overhead.yml +28 -0
  8. data/benchmark/run.rb +61 -40
  9. data/docs/cdp_common.json +3364 -0
  10. data/docs/cdp_common.md +18 -0
  11. data/docs/{headless_chrome.md → headless_browsers.md} +31 -12
  12. data/lib/opal/ast/builder.rb +1 -1
  13. data/lib/opal/builder.rb +6 -1
  14. data/lib/opal/builder_processors.rb +5 -3
  15. data/lib/opal/cache.rb +1 -7
  16. data/lib/opal/cli_options.rb +72 -58
  17. data/lib/opal/cli_runners/chrome.rb +47 -9
  18. data/lib/opal/cli_runners/chrome_cdp_interface.rb +238 -112
  19. data/lib/opal/cli_runners/compiler.rb +146 -13
  20. data/lib/opal/cli_runners/deno.rb +32 -0
  21. data/lib/opal/cli_runners/firefox.rb +350 -0
  22. data/lib/opal/cli_runners/firefox_cdp_interface.rb +212 -0
  23. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.cmd +17 -0
  24. data/lib/opal/cli_runners/node_modules/.bin/chrome-remote-interface.ps1 +28 -0
  25. data/lib/opal/cli_runners/node_modules/.package-lock.json +41 -0
  26. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/LICENSE +1 -1
  27. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/README.md +322 -182
  28. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/bin/client.js +99 -114
  29. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/chrome-remote-interface.js +1 -11
  30. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/index.js +16 -11
  31. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/api.js +41 -33
  32. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/chrome.js +224 -214
  33. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/devtools.js +71 -191
  34. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/external-request.js +26 -6
  35. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/protocol.json +20788 -9049
  36. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/lib/websocket-wrapper.js +10 -3
  37. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/package.json +59 -123
  38. data/lib/opal/cli_runners/node_modules/chrome-remote-interface/webpack.config.js +25 -32
  39. data/lib/opal/cli_runners/node_modules/commander/History.md +298 -0
  40. data/lib/opal/cli_runners/node_modules/commander/LICENSE +22 -0
  41. data/lib/opal/cli_runners/node_modules/commander/Readme.md +217 -61
  42. data/lib/opal/cli_runners/node_modules/commander/index.js +431 -145
  43. data/lib/opal/cli_runners/node_modules/commander/package.json +16 -79
  44. data/lib/opal/cli_runners/node_modules/ws/README.md +334 -98
  45. data/lib/opal/cli_runners/node_modules/ws/browser.js +8 -0
  46. data/lib/opal/cli_runners/node_modules/ws/index.js +5 -10
  47. data/lib/opal/cli_runners/node_modules/ws/lib/buffer-util.js +129 -0
  48. data/lib/opal/cli_runners/node_modules/ws/lib/constants.js +10 -0
  49. data/lib/opal/cli_runners/node_modules/ws/lib/event-target.js +184 -0
  50. data/lib/opal/cli_runners/node_modules/ws/lib/extension.js +223 -0
  51. data/lib/opal/cli_runners/node_modules/ws/lib/limiter.js +55 -0
  52. data/lib/opal/cli_runners/node_modules/ws/lib/permessage-deflate.js +518 -0
  53. data/lib/opal/cli_runners/node_modules/ws/lib/receiver.js +607 -0
  54. data/lib/opal/cli_runners/node_modules/ws/lib/sender.js +409 -0
  55. data/lib/opal/cli_runners/node_modules/ws/lib/stream.js +180 -0
  56. data/lib/opal/cli_runners/node_modules/ws/lib/validation.js +104 -0
  57. data/lib/opal/cli_runners/node_modules/ws/lib/websocket-server.js +447 -0
  58. data/lib/opal/cli_runners/node_modules/ws/lib/websocket.js +1195 -0
  59. data/lib/opal/cli_runners/node_modules/ws/package.json +40 -106
  60. data/lib/opal/cli_runners/package-lock.json +62 -0
  61. data/lib/opal/cli_runners/package.json +1 -1
  62. data/lib/opal/cli_runners.rb +26 -4
  63. data/lib/opal/nodes/args/prepare_post_args.rb +2 -2
  64. data/lib/opal/nodes/def.rb +8 -8
  65. data/lib/opal/nodes/iter.rb +12 -12
  66. data/lib/opal/nodes/logic.rb +1 -1
  67. data/lib/opal/nodes/masgn.rb +2 -2
  68. data/lib/opal/parser/with_ruby_lexer.rb +1 -1
  69. data/lib/opal/paths.rb +14 -0
  70. data/lib/opal/rewriter.rb +2 -0
  71. data/lib/opal/rewriters/forward_args.rb +52 -4
  72. data/lib/opal/rewriters/targeted_patches.rb +94 -0
  73. data/lib/opal/version.rb +1 -1
  74. data/opal/corelib/basic_object.rb +1 -1
  75. data/opal/corelib/boolean.rb +2 -2
  76. data/opal/corelib/class.rb +11 -0
  77. data/opal/corelib/constants.rb +3 -3
  78. data/opal/corelib/enumerable.rb +4 -0
  79. data/opal/corelib/enumerator.rb +1 -1
  80. data/opal/corelib/hash.rb +2 -2
  81. data/opal/corelib/helpers.rb +1 -1
  82. data/opal/corelib/kernel.rb +3 -3
  83. data/opal/corelib/method.rb +1 -1
  84. data/opal/corelib/module.rb +29 -8
  85. data/opal/corelib/proc.rb +7 -5
  86. data/opal/corelib/runtime.js +141 -78
  87. data/opal/corelib/set.rb +252 -0
  88. data/opal/corelib/string.rb +2 -1
  89. data/opal/corelib/time.rb +2 -2
  90. data/opal/opal.rb +1 -0
  91. data/opal.gemspec +1 -0
  92. data/spec/filters/bugs/array.rb +22 -13
  93. data/spec/filters/bugs/base64.rb +5 -5
  94. data/spec/filters/bugs/basicobject.rb +16 -8
  95. data/spec/filters/bugs/bigdecimal.rb +161 -160
  96. data/spec/filters/bugs/binding.rb +10 -10
  97. data/spec/filters/bugs/class.rb +8 -8
  98. data/spec/filters/bugs/complex.rb +2 -1
  99. data/spec/filters/bugs/date.rb +79 -81
  100. data/spec/filters/bugs/datetime.rb +29 -29
  101. data/spec/filters/bugs/delegate.rb +1 -3
  102. data/spec/filters/bugs/encoding.rb +69 -69
  103. data/spec/filters/bugs/enumerable.rb +22 -20
  104. data/spec/filters/bugs/enumerator.rb +88 -85
  105. data/spec/filters/bugs/exception.rb +46 -40
  106. data/spec/filters/bugs/file.rb +32 -32
  107. data/spec/filters/bugs/float.rb +26 -21
  108. data/spec/filters/bugs/freeze.rb +88 -0
  109. data/spec/filters/bugs/hash.rb +39 -38
  110. data/spec/filters/bugs/integer.rb +57 -44
  111. data/spec/filters/bugs/io.rb +1 -1
  112. data/spec/filters/bugs/kernel.rb +349 -269
  113. data/spec/filters/bugs/language.rb +220 -188
  114. data/spec/filters/bugs/main.rb +5 -3
  115. data/spec/filters/bugs/marshal.rb +38 -38
  116. data/spec/filters/bugs/math.rb +2 -1
  117. data/spec/filters/bugs/method.rb +73 -62
  118. data/spec/filters/bugs/module.rb +163 -143
  119. data/spec/filters/bugs/numeric.rb +6 -6
  120. data/spec/filters/bugs/objectspace.rb +16 -16
  121. data/spec/filters/bugs/openstruct.rb +1 -1
  122. data/spec/filters/bugs/pack_unpack.rb +51 -51
  123. data/spec/filters/bugs/pathname.rb +7 -7
  124. data/spec/filters/bugs/proc.rb +63 -63
  125. data/spec/filters/bugs/random.rb +7 -6
  126. data/spec/filters/bugs/range.rb +12 -9
  127. data/spec/filters/bugs/rational.rb +8 -7
  128. data/spec/filters/bugs/regexp.rb +49 -48
  129. data/spec/filters/bugs/ruby-32.rb +56 -0
  130. data/spec/filters/bugs/set.rb +30 -30
  131. data/spec/filters/bugs/singleton.rb +4 -4
  132. data/spec/filters/bugs/string.rb +187 -99
  133. data/spec/filters/bugs/stringio.rb +7 -0
  134. data/spec/filters/bugs/stringscanner.rb +68 -68
  135. data/spec/filters/bugs/struct.rb +11 -9
  136. data/spec/filters/bugs/symbol.rb +1 -1
  137. data/spec/filters/bugs/time.rb +78 -63
  138. data/spec/filters/bugs/trace_point.rb +4 -4
  139. data/spec/filters/bugs/unboundmethod.rb +32 -17
  140. data/spec/filters/bugs/warnings.rb +8 -12
  141. data/spec/filters/unsupported/array.rb +24 -107
  142. data/spec/filters/unsupported/basicobject.rb +12 -12
  143. data/spec/filters/unsupported/bignum.rb +27 -52
  144. data/spec/filters/unsupported/class.rb +1 -2
  145. data/spec/filters/unsupported/delegator.rb +3 -3
  146. data/spec/filters/unsupported/enumerable.rb +2 -9
  147. data/spec/filters/unsupported/enumerator.rb +2 -11
  148. data/spec/filters/unsupported/file.rb +1 -1
  149. data/spec/filters/unsupported/float.rb +28 -47
  150. data/spec/filters/unsupported/hash.rb +8 -14
  151. data/spec/filters/unsupported/integer.rb +75 -91
  152. data/spec/filters/unsupported/kernel.rb +17 -35
  153. data/spec/filters/unsupported/language.rb +11 -19
  154. data/spec/filters/unsupported/marshal.rb +22 -41
  155. data/spec/filters/unsupported/matchdata.rb +28 -52
  156. data/spec/filters/unsupported/math.rb +1 -1
  157. data/spec/filters/unsupported/privacy.rb +229 -285
  158. data/spec/filters/unsupported/range.rb +1 -5
  159. data/spec/filters/unsupported/regexp.rb +40 -66
  160. data/spec/filters/unsupported/set.rb +2 -2
  161. data/spec/filters/unsupported/singleton.rb +4 -4
  162. data/spec/filters/unsupported/string.rb +305 -508
  163. data/spec/filters/unsupported/struct.rb +3 -4
  164. data/spec/filters/unsupported/symbol.rb +15 -18
  165. data/spec/filters/unsupported/thread.rb +1 -7
  166. data/spec/filters/unsupported/time.rb +159 -202
  167. data/spec/filters/unsupported/usage_of_files.rb +170 -259
  168. data/spec/lib/builder_spec.rb +4 -4
  169. data/spec/lib/rewriters/forward_args_spec.rb +32 -12
  170. data/spec/mspec-opal/runner.rb +2 -0
  171. data/spec/ruby_specs +4 -0
  172. data/stdlib/deno/base.rb +28 -0
  173. data/stdlib/deno/file.rb +340 -0
  174. data/stdlib/{headless_chrome.rb → headless_browser/base.rb} +1 -1
  175. data/stdlib/headless_browser/file.rb +15 -0
  176. data/stdlib/headless_browser.rb +4 -0
  177. data/stdlib/native.rb +1 -1
  178. data/stdlib/nodejs/file.rb +5 -0
  179. data/stdlib/opal/platform.rb +8 -6
  180. data/stdlib/opal-platform.rb +14 -8
  181. data/stdlib/set.rb +1 -258
  182. data/tasks/benchmarking.rake +62 -19
  183. data/tasks/performance.rake +1 -1
  184. data/tasks/testing.rake +5 -3
  185. data/test/nodejs/test_file.rb +29 -10
  186. data/test/opal/http_server.rb +28 -11
  187. data/test/opal/unsupported_and_bugs.rb +2 -1
  188. metadata +89 -50
  189. data/lib/opal/cli_runners/node_modules/ultron/LICENSE +0 -22
  190. data/lib/opal/cli_runners/node_modules/ultron/index.js +0 -138
  191. data/lib/opal/cli_runners/node_modules/ultron/package.json +0 -112
  192. data/lib/opal/cli_runners/node_modules/ws/SECURITY.md +0 -33
  193. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.fallback.js +0 -56
  194. data/lib/opal/cli_runners/node_modules/ws/lib/BufferUtil.js +0 -15
  195. data/lib/opal/cli_runners/node_modules/ws/lib/ErrorCodes.js +0 -28
  196. data/lib/opal/cli_runners/node_modules/ws/lib/EventTarget.js +0 -158
  197. data/lib/opal/cli_runners/node_modules/ws/lib/Extensions.js +0 -69
  198. data/lib/opal/cli_runners/node_modules/ws/lib/PerMessageDeflate.js +0 -339
  199. data/lib/opal/cli_runners/node_modules/ws/lib/Receiver.js +0 -520
  200. data/lib/opal/cli_runners/node_modules/ws/lib/Sender.js +0 -438
  201. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.fallback.js +0 -9
  202. data/lib/opal/cli_runners/node_modules/ws/lib/Validation.js +0 -17
  203. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocket.js +0 -705
  204. data/lib/opal/cli_runners/node_modules/ws/lib/WebSocketServer.js +0 -336
  205. data/spec/filters/bugs/boolean.rb +0 -3
  206. data/spec/filters/bugs/matrix.rb +0 -3
  207. data/spec/filters/unsupported/fixnum.rb +0 -15
  208. data/spec/filters/unsupported/freeze.rb +0 -102
  209. data/spec/filters/unsupported/pathname.rb +0 -4
  210. data/spec/filters/unsupported/proc.rb +0 -4
  211. data/spec/filters/unsupported/random.rb +0 -5
  212. data/spec/filters/unsupported/taint.rb +0 -162
@@ -3,143 +3,269 @@
3
3
  # This script I converted into Opal, so that I don't have to write
4
4
  # buffer handling again. We have gets, Node has nothing close to it,
5
5
  # even async.
6
+ # For CDP see docs/cdp_common.(md|json)
6
7
 
7
8
  require 'opal/platform'
9
+ require 'nodejs/env'
8
10
 
9
11
  %x{
10
12
  var CDP = require("chrome-remote-interface");
11
13
  var fs = require("fs");
14
+ var http = require("http");
12
15
 
13
- var dir = #{ARGV.last}
16
+ var dir = #{ARGV.last};
17
+ var ext = #{ENV['OPAL_CDP_EXT']};
18
+ var port_offset; // port offset for http server, depending on number of targets
19
+ var script_id; // of script which is executed after page is initialized, before page scripts are executed
20
+ var cdp_client; // CDP client
21
+ var target_id; // the used Target
14
22
 
15
23
  var options = {
16
24
  host: #{ENV['CHROME_HOST'] || 'localhost'},
17
- port: #{ENV['CHROME_PORT'] || 9222}
25
+ port: parseInt(#{ENV['CHROME_PORT'] || '9222'})
18
26
  };
19
27
 
20
- CDP(options, function(client) {
21
- var Page = client.Page,
22
- Runtime = client.Runtime,
23
- Console = client.Console;
24
-
25
- Promise.all([
26
- Console.enable(),
27
- Page.enable(),
28
- Runtime.enable(),
29
- ]).then(function() {
30
- // This hook catches only the first argument of `console.log`
31
- // More advanced version Runtime.consoleAPICalled returns all arguments
32
- // but all of them are not formatted, i.e. by calling
33
- // console.log('string', [1, 2, 3], {a: 'b'})
34
- // it returns the following data to the callback:
35
- // {
36
- // "type":"log",
37
- // "args":[
38
- // {
39
- // "type":"string",
40
- // "value":"string"
41
- // },
42
- // {
43
- // "type":"object",
44
- // "subtype":"array",
45
- // "className":"Array",
46
- // "description":"Array(3)",
47
- // "objectId":"{\"injectedScriptId\":11,\"id\":1}",
48
- // "preview":{
49
- // "type":"object",
50
- // "subtype":"array",
51
- // "description":"Array(3)",
52
- // "overflow":false,
53
- // "properties":[
54
- // {"name":"0","type":"number","value":"1"},
55
- // {"name":"1","type":"number","value":"2"},
56
- // {"name":"2","type":"number","value":"3"}
57
- // ]
58
- // }
59
- // },
60
- // {
61
- // "type":"object",
62
- // "className":"Object",
63
- // "description":"Object",
64
- // "objectId":"{\"injectedScriptId\":11,\"id\":2}",
65
- // "preview":{
66
- // "type":"object",
67
- // "description":"Object",
68
- // "overflow":false,
69
- // "properties":[
70
- // {"name":"a","type":"string","value":"b"}
71
- // ]
72
- // }
73
- // }
74
- // ],
75
- // // ...
76
- // }
77
- // Supporting this format for complex data structure is challenging, feel free to contribute!
78
- //
79
- Console.messageAdded(function(console_message) {
80
- process.stdout.write(console_message.message.text);
81
- });
28
+ // shared secret
29
+
30
+ function random_string() {
31
+ let chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
32
+ let str = '';
33
+ for (let i = 0; i < 256; i++) {
34
+ str += chars.charAt(Math.floor(Math.random() * chars.length));
35
+ }
36
+ return str;
37
+ };
38
+
39
+ var shared_secret = random_string(); // secret must be passed with POST requests
40
+
41
+ // support functions
42
+
43
+ function perror(error) { console.error(error); }
44
+
45
+ var exiting = false;
46
+
47
+ function shutdown(exit_code) {
48
+ if (exiting) { return Promise.resolve(); }
49
+ exiting = true;
50
+ var promises = [];
51
+ cdp_client.Page.removeScriptToEvaluateOnNewDocument(script_id).then(function(){}, perror);
52
+ return Promise.all(promises).then(function () {
53
+ target_id ? cdp_client.Target.closeTarget(target_id) : null;
54
+ }).then(function() {
55
+ server.close();
56
+ process.exit(exit_code);
57
+ }, function(error) {
58
+ perror(error);
59
+ process.exit(1)
60
+ });
61
+ };
82
62
 
83
- Runtime.exceptionThrown(function(exception) {
84
- var properties = exception.exceptionDetails.exception.preview.properties,
85
- stack, i;
63
+ // simple HTTP server to deliver page, scripts to, and trigger commands from browser
86
64
 
87
- for (i = 0; i < properties.length; i++) {
88
- var property = properties[i];
65
+ function not_found(res) {
66
+ res.writeHead(404, { "Content-Type": "text/plain" });
67
+ res.end("NOT FOUND");
68
+ }
89
69
 
90
- if (property.name == "stack") {
91
- stack = property.value;
92
- }
70
+ function response_ok(res) {
71
+ res.writeHead(200, { "Content-Type": "text/plain" });
72
+ res.end("OK");
73
+ }
74
+
75
+ function handle_post(req, res, fun) {
76
+ var data = "";
77
+ req.on('data', function(chunk) {
78
+ data += chunk;
79
+ })
80
+ req.on('end', function() {
81
+ var obj = JSON.parse(data);
82
+ if (obj.secret == shared_secret) {
83
+ fun.call(this, obj);
84
+ } else {
85
+ not_found(res);
86
+ }
87
+ });
88
+ }
89
+
90
+ var server = http.createServer(function(req, res) {
91
+ if (req.method === "GET") {
92
+ var path = dir + '/' + req.url.slice(1);
93
+ if (path.includes('..') || !fs.existsSync(path)) {
94
+ not_found(res);
95
+ } else {
96
+ var content_type;
97
+ if (path.endsWith(".html")) {
98
+ content_type = "text/html"
99
+ } else if (path.endsWith(".map")) {
100
+ content_type = "application/json"
101
+ } else {
102
+ content_type = "application/javascript"
93
103
  }
104
+ res.writeHead(200, { "Content-Type": content_type });
105
+ res.end(fs.readFileSync(path));
106
+ }
107
+ } else if (req.method === "POST") {
108
+ if (req.url === "/File.write") {
109
+ // totally insecure on purpose
110
+ handle_post(req, res, function(obj) {
111
+ fs.writeFileSync(obj.filename, obj.data);
112
+ response_ok(res);
113
+ });
114
+ } else {
115
+ not_found(res);
116
+ }
117
+ } else {
118
+ not_found(res);
119
+ }
120
+ });
94
121
 
95
- console.log(stack);
122
+ // actual CDP code
96
123
 
97
- process.exit(1);
98
- });
124
+ CDP.List(options, function(err, targets) {
125
+ // default CDP port is 9222, Firefox runner is at 9333
126
+ // Lets collect clients for
127
+ // Chrome CDP starting at 9273 ...
128
+ // Firefox CDP starting 9334 ...
129
+ port_offset = targets ? targets.length + 51 : 51; // default CDP port is 9222, Node CDP port 9229, Firefox is at 9333
99
130
 
100
- Page.javascriptDialogOpening((dialog) => {
101
- #{
102
- if `dialog.type` == 'prompt'
103
- message = gets&.chomp
104
- if message
105
- `Page.handleJavaScriptDialog({accept: true, promptText: #{message}})`
106
- else
107
- `Page.handleJavaScriptDialog({accept: false})`
108
- end
109
- elsif `dialog.type` == 'alert' && `dialog.message` == 'opalheadlesschromeexit'
110
- # A special case of an alert with a magic string "opalheadlesschromeexit".
111
- # This denotes that `Kernel#exit` has been called. We would have rather used
112
- # an exception here, but they don't bubble sometimes.
113
- %x{
114
- Page.handleJavaScriptDialog({accept: true});
131
+ return CDP(options, function(browser_client) {
132
+
133
+ server.listen({ port: port_offset + options.port, host: options.host });
134
+
135
+ browser_client.Target.createTarget({url: "about:blank"}).then(function(target) {
136
+ target_id = target;
137
+ options.target = target_id.targetId;
138
+
139
+ CDP(options, function(client) {
140
+ cdp_client = client;
141
+
142
+ var Log = client.Log,
143
+ Page = client.Page,
144
+ Runtime = client.Runtime;
145
+
146
+ // enable used CDP domains
147
+ Promise.all([
148
+ Log.enable(),
149
+ Page.enable(),
150
+ Runtime.enable()
151
+ ]).then(function() {
152
+ // add script to set the shared_secret in the browser
153
+ return Page.addScriptToEvaluateOnNewDocument({source: "window.OPAL_CDP_SHARED_SECRET = '" + shared_secret + "';"}).then(function(scrid) {
154
+ script_id = scrid;
155
+ }, perror);
156
+ }, perror).then(function() {
157
+
158
+ // receive and handle all kinds of log and console messages
159
+ Log.entryAdded(function(entry) {
160
+ process.stdout.write(entry.entry.level + ': ' + entry.entry.text + "\n");
161
+ });
162
+
163
+ Runtime.consoleAPICalled(function(entry) {
164
+ var args = entry.args;
165
+ var stack = null;
166
+ var i, arg, frame, value;
167
+
168
+ // output actual message
169
+ for(i = 0; i < args.length; i++) {
170
+ arg = args[i];
171
+ if (arg.type === "string") { value = arg.value; }
172
+ else { value = JSON.stringify(arg); }
173
+ process.stdout.write(value);
174
+ }
175
+
176
+ if (entry.stackTrace && entry.stackTrace.callFrames) {
177
+ stack = entry.stackTrace.callFrames;
178
+ }
179
+
180
+ if (entry.type === "error" && stack) {
181
+ // print full stack for errors
182
+ process.stdout.write("\n");
183
+ for(i = 0; i < stack.length; i++) {
184
+ frame = stack[i];
185
+ if (frame) {
186
+ value = frame.url + ':' + frame.lineNumer + ':' + frame.columnNumber + '\n';
187
+ process.stdout.write(value);
188
+ }
189
+ }
190
+ }
191
+ });
192
+
193
+ // react to exceptions
194
+ Runtime.exceptionThrown(function(exception) {
195
+ var ex = exception.exceptionDetails.exception.preview.properties;
196
+ var stack = [];
197
+ if (exception.exceptionDetails.stackTrace) {
198
+ stack = exception.exceptionDetails.stackTrace.callFrames;
199
+ } else {
200
+ var d = exception.exceptionDetails;
201
+ stack.push({
202
+ url: d.url,
203
+ lineNumber: d.lineNumber,
204
+ columnNumber: d.columnNumber,
205
+ functionName: "(unknown)"
206
+ });
207
+ }
208
+ var fr;
209
+ for (var i = 0; i < ex.length; i++) {
210
+ fr = ex[i];
211
+ if (fr.name === "message") {
212
+ perror(fr.value);
213
+ }
214
+ }
215
+ for (var i = 0; i < stack.length; i++) {
216
+ fr = stack[i];
217
+ perror(fr.url + ':' + fr.lineNumber + ':' + fr.columnNumber + ': in ' + fr.functionName);
218
+ }
219
+ return shutdown(1);
220
+ });
221
+
222
+ // handle input
223
+ Page.javascriptDialogOpening((dialog) => {
224
+ #{
225
+ if `dialog.type` == 'prompt'
226
+ message = gets&.chomp
227
+ if message
228
+ `Page.handleJavaScriptDialog({accept: true, promptText: #{message}})`
229
+ else
230
+ `Page.handleJavaScriptDialog({accept: false})`
231
+ end
232
+ elsif `dialog.type` == 'alert' && `dialog.message` == 'opalheadlessbrowserexit'
233
+ # A special case of an alert with a magic string "opalheadlessbrowserexit".
234
+ # This denotes that `Kernel#exit` has been called. We would have rather used
235
+ # an exception here, but they don't bubble sometimes.
236
+ %x{
237
+ Page.handleJavaScriptDialog({accept: true});
238
+ Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
239
+ var exit_code = 0;
240
+ if (typeof(output.result) !== "undefined" && output.result.type === "number") {
241
+ exit_code = output.result.value;
242
+ }
243
+ return shutdown(exit_code);
244
+ });
245
+ }
246
+ end
247
+ }
248
+ });
249
+
250
+ // page has been loaded, all code has been executed
251
+ Page.loadEventFired(() => {
115
252
  Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
116
- client.close();
117
253
  if (typeof(output.result) !== "undefined" && output.result.type === "number") {
118
- process.exit(output.result.value);
254
+ return shutdown(output.result.value);
255
+ } else if (typeof(output.result) !== "undefined" && output.result.type === "string" && output.result.value === "noexit") {
256
+ // do nothing, we have headless chrome support enabled and there are most probably async events awaiting
119
257
  } else {
120
- process.exit(0);
258
+ return shutdown(0);
121
259
  }
122
- });
123
- }
124
- end
125
- }
126
- });
260
+ })
261
+ });
127
262
 
128
- Page.loadEventFired(() => {
129
- Runtime.evaluate({ expression: "window.OPAL_EXIT_CODE" }).then(function(output) {
130
- if (typeof(output.result) !== "undefined" && output.result.type === "number") {
131
- client.close();
132
- process.exit(output.result.value);
133
- } else if (typeof(output.result) !== "undefined" && output.result.type === "string" && output.result.value === "noexit") {
134
- // do nothing, we have headless chrome support enabled and there are most probably async events awaiting
135
- } else {
136
- client.close();
137
- process.exit(0);
138
- }
139
- })
263
+ // init page load
264
+ Page.navigate({ url: "http://localhost:" + (port_offset + options.port).toString() + "/index.html" })
265
+ }, perror);
266
+ });
140
267
  });
141
-
142
- Page.navigate({ url: "file://"+dir+"/index.html" })
143
268
  });
144
269
  });
145
270
  }
271
+ # end of code (marker to help see if brackets match above)
@@ -3,16 +3,149 @@
3
3
  require 'opal/paths'
4
4
 
5
5
  # The compiler runner will just output the compiled JavaScript
6
- Opal::CliRunners::Compiler = ->(data) {
7
- options = data[:options] || {}
8
- builder = data.fetch(:builder).call
9
- map_file = options[:map_file]
10
- output = data.fetch(:output)
11
-
12
- compiled_source = builder.to_s
13
- compiled_source += "\n" + builder.source_map.to_data_uri_comment unless options[:no_source_map]
14
- output.puts compiled_source
15
- File.write(map_file, builder.source_map.to_json) if map_file
16
-
17
- 0
18
- }
6
+ class Opal::CliRunners::Compiler
7
+ def self.call(data)
8
+ new(data).start
9
+ end
10
+
11
+ def initialize(data)
12
+ @options = data[:options] || {}
13
+ @builder_factory = data.fetch(:builder)
14
+ @map_file = @options[:map_file]
15
+ @output = data.fetch(:output)
16
+ @watch = @options[:watch]
17
+ end
18
+
19
+ def compile
20
+ builder = @builder_factory.call
21
+ compiled_source = builder.to_s
22
+ compiled_source += "\n" + builder.source_map.to_data_uri_comment unless @options[:no_source_map]
23
+
24
+ rewind_output if @watch
25
+
26
+ @output.puts compiled_source
27
+ @output.flush
28
+
29
+ File.write(@map_file, builder.source_map.to_json) if @map_file
30
+
31
+ builder
32
+ end
33
+
34
+ def compile_noraise
35
+ compile
36
+ rescue StandardError, Opal::SyntaxError => e
37
+ $stderr.puts "* Compilation failed: #{e.message}"
38
+ nil
39
+ end
40
+
41
+ def rewind_output
42
+ if !@output.is_a?(File) || @output.tty?
43
+ fail_unrewindable!
44
+ else
45
+ begin
46
+ @output.rewind
47
+ @output.truncate(0)
48
+ rescue Errno::ESPIPE
49
+ fail_unrewindable!
50
+ end
51
+ end
52
+ end
53
+
54
+ def fail_unrewindable!
55
+ warn <<~ERROR
56
+ You have specified --watch, but for watch to work, you must specify an
57
+ --output file.
58
+ ERROR
59
+ exit 1
60
+ end
61
+
62
+ def fail_no_listen!
63
+ warn <<~ERROR
64
+ --watch mode requires the `listen` gem present. Please try to run:
65
+
66
+ gem install listen
67
+
68
+ Or if you are using bundler, add listen to your Gemfile.
69
+ ERROR
70
+ exit 1
71
+ end
72
+
73
+ def watch_compile
74
+ begin
75
+ require 'listen'
76
+ rescue LoadError
77
+ fail_no_listen!
78
+ end
79
+
80
+ @opal_deps = Opal.dependent_files
81
+
82
+ builder = compile
83
+ code_deps = builder.dependent_files
84
+ @files = @opal_deps + code_deps
85
+ @code_listener = watch_files
86
+ @code_listener.start
87
+
88
+ $stderr.puts "* Opal v#{Opal::VERSION} successfully compiled your program in --watch mode"
89
+
90
+ sleep
91
+ end
92
+
93
+ def reexec
94
+ system($0, *OriginalARGV) && exit
95
+ end
96
+
97
+ def on_code_change(modified)
98
+ if !(modified & @opal_deps).empty?
99
+ $stderr.puts "* Modified core Opal files: #{modified.join(', ')}; reexecuting"
100
+ reexec
101
+ elsif !modified.all? { |file| @directories.any? { |dir| file.start_with?(dir + '/') } }
102
+ $stderr.puts "* New unwatched files: #{modified.join(', ')}; reexecuting"
103
+ reexec
104
+ end
105
+
106
+ $stderr.puts "* Modified code: #{modified.join(', ')}; rebuilding"
107
+
108
+ builder = compile_noraise
109
+
110
+ # Ignore the bad compilation
111
+ if builder
112
+ code_deps = builder.dependent_files
113
+ @files = @opal_deps + code_deps
114
+ end
115
+ end
116
+
117
+ def files_to_directories
118
+ directories = @files.map { |file| File.dirname(file) }.uniq
119
+
120
+ previous_dir = nil
121
+ # Only get the topmost directories
122
+ directories = directories.sort.map do |dir|
123
+ if previous_dir && dir.start_with?(previous_dir + '/')
124
+ nil
125
+ else
126
+ previous_dir = dir
127
+ end
128
+ end
129
+
130
+ directories.compact
131
+ end
132
+
133
+ def watch_files
134
+ @directories = files_to_directories
135
+
136
+ Listen.to(*@directories) do |modified, added, removed|
137
+ our_modified = @files & (modified + added + removed)
138
+ on_code_change(our_modified) unless our_modified.empty?
139
+ end
140
+ end
141
+
142
+ def start
143
+ if @watch
144
+ watch_compile
145
+ else
146
+ compile
147
+ end
148
+
149
+ 0
150
+ end
151
+ end
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'shellwords'
4
+ require 'opal/paths'
5
+ require 'opal/cli_runners/system_runner'
6
+ require 'opal/os'
7
+
8
+ module Opal
9
+ module CliRunners
10
+ class Deno
11
+ def self.call(data)
12
+ argv = data[:argv].dup.to_a
13
+
14
+ SystemRunner.call(data) do |tempfile|
15
+ [
16
+ 'deno',
17
+ 'run',
18
+ '--allow-read',
19
+ '--allow-write',
20
+ tempfile.path,
21
+ *argv
22
+ ]
23
+ end
24
+ rescue Errno::ENOENT
25
+ raise MissingDeno, 'Please install Deno to be able to run Opal scripts.'
26
+ end
27
+
28
+ class MissingDeno < RunnerError
29
+ end
30
+ end
31
+ end
32
+ end