Skip to content

Commit 21f3334

Browse files
authored
Merge pull request #12 from sjrd/upgrade-scalajs-1.0.0-M5
Upgrade to Scala.js 1.0.0-M5.
2 parents 4b9fa51 + 9403c96 commit 21f3334

File tree

7 files changed

+236
-118
lines changed

7 files changed

+236
-118
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ language: scala
33
scala:
44
- 2.10.7
55
- 2.11.12
6-
- 2.12.4
6+
- 2.12.6
77
jdk:
88
- oraclejdk8
99
env:

build.sbt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ inThisBuild(Seq(
22
version := "1.0.0-SNAPSHOT",
33
organization := "org.scala-js",
44

5-
crossScalaVersions := Seq("2.12.4", "2.10.7", "2.11.12"),
5+
crossScalaVersions := Seq("2.12.6", "2.10.7", "2.11.12"),
66
scalaVersion := crossScalaVersions.value.head,
77
scalacOptions ++= Seq("-deprecation", "-feature", "-Xfatal-warnings"),
88

jsdom-nodejs-env/src/main/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnv.scala

Lines changed: 185 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -8,138 +8,222 @@
88

99
package org.scalajs.jsenv.jsdomnodejs
1010

11+
import scala.annotation.tailrec
12+
1113
import scala.collection.immutable
14+
import scala.util.control.NonFatal
1215

13-
import java.io.OutputStream
16+
import java.io._
17+
import java.nio.file.{Files, StandardCopyOption}
18+
import java.net.URI
1419

1520
import org.scalajs.io._
1621
import org.scalajs.io.JSUtils.escapeJS
1722

1823
import org.scalajs.jsenv._
19-
import org.scalajs.jsenv.nodejs.AbstractNodeJSEnv
24+
import org.scalajs.jsenv.nodejs._
2025

21-
class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) extends AbstractNodeJSEnv {
26+
class JSDOMNodeJSEnv(config: JSDOMNodeJSEnv.Config) extends JSEnv {
2227

2328
def this() = this(JSDOMNodeJSEnv.Config())
2429

25-
protected def vmName: String = "Node.js with JSDOM"
30+
val name: String = "Node.js with JSDOM"
31+
32+
def start(input: Input, runConfig: RunConfig): JSRun = {
33+
JSDOMNodeJSEnv.validator.validate(runConfig)
34+
try {
35+
internalStart(initFiles ++ codeWithJSDOMContext(input), runConfig)
36+
} catch {
37+
case NonFatal(t) =>
38+
JSRun.failed(t)
39+
40+
case t: NotImplementedError =>
41+
/* In Scala 2.10.x, NotImplementedError was considered fatal.
42+
* We need this case for the conformance tests to pass on 2.10.
43+
*/
44+
JSRun.failed(t)
45+
}
46+
}
2647

27-
protected def executable: String = config.executable
48+
def startWithCom(input: Input, runConfig: RunConfig,
49+
onMessage: String => Unit): JSComRun = {
50+
JSDOMNodeJSEnv.validator.validate(runConfig)
51+
try {
52+
ComRun.start(runConfig, onMessage) { comLoader =>
53+
val files = initFiles ::: (comLoader :: codeWithJSDOMContext(input))
54+
internalStart(files, runConfig)
55+
}
56+
} catch {
57+
case t: NotImplementedError =>
58+
/* In Scala 2.10.x, NotImplementedError was considered fatal.
59+
* We need this case for the conformance tests to pass on 2.10.
60+
* Non-fatal exceptions are already handled by ComRun.start().
61+
*/
62+
JSComRun.failed(t)
63+
}
64+
}
2865

29-
override protected def args: immutable.Seq[String] = config.args
66+
private def internalStart(files: List[VirtualBinaryFile],
67+
runConfig: RunConfig): JSRun = {
68+
val command = config.executable :: config.args
69+
val externalConfig = ExternalJSRun.Config()
70+
.withEnv(env)
71+
.withRunConfig(runConfig)
72+
ExternalJSRun.start(command, externalConfig)(JSDOMNodeJSEnv.write(files))
73+
}
3074

31-
override protected def env: Map[String, String] = config.env
75+
private def initFiles: List[VirtualBinaryFile] =
76+
List(JSDOMNodeJSEnv.runtimeEnv, Support.fixPercentConsole)
3277

33-
// TODO We might want to make this configurable - not sure why it isn't
34-
override protected def wantSourceMap: Boolean = false
78+
private def env: Map[String, String] =
79+
Map("NODE_MODULE_CONTEXTS" -> "0") ++ config.env
3580

36-
override def jsRunner(files: Seq[VirtualJSFile]): JSRunner =
37-
new DOMNodeRunner(files)
81+
private def scriptFiles(input: Input): List[VirtualBinaryFile] = input match {
82+
case Input.ScriptsToLoad(scripts) => scripts
83+
case _ => throw new UnsupportedInputException(input)
84+
}
3885

39-
override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner =
40-
new AsyncDOMNodeRunner(files)
86+
private def codeWithJSDOMContext(input: Input): List[VirtualBinaryFile] = {
87+
val scriptsURIs = scriptFiles(input).map(JSDOMNodeJSEnv.materialize(_))
88+
val scriptsURIsAsJSStrings =
89+
scriptsURIs.map(uri => '"' + escapeJS(uri.toASCIIString) + '"')
90+
val jsDOMCode = {
91+
s"""
92+
|(function () {
93+
| var jsdom;
94+
| try {
95+
| jsdom = require("jsdom/lib/old-api.js"); // jsdom >= 10.x
96+
| } catch (e) {
97+
| jsdom = require("jsdom"); // jsdom <= 9.x
98+
| }
99+
|
100+
| var virtualConsole = jsdom.createVirtualConsole()
101+
| .sendTo(console, { omitJsdomErrors: true });
102+
| virtualConsole.on("jsdomError", function (error) {
103+
| /* This inelegant if + console.error is the only way I found
104+
| * to make sure the stack trace of the original error is
105+
| * printed out.
106+
| */
107+
| if (error.detail && error.detail.stack)
108+
| console.error(error.detail.stack);
109+
|
110+
| // Throw the error anew to make sure the whole execution fails
111+
| throw error;
112+
| });
113+
|
114+
| /* Work around the fast that scalajsCom.init() should delay already
115+
| * received messages to the next tick. Here we cannot tell whether
116+
| * the receive callback is called for already received messages or
117+
| * not, so we dealy *all* messages to the next tick.
118+
| */
119+
| var scalajsCom = global.scalajsCom;
120+
| var scalajsComWrapper = scalajsCom === (void 0) ? scalajsCom : ({
121+
| init: function(recvCB) {
122+
| scalajsCom.init(function(msg) {
123+
| process.nextTick(recvCB, msg);
124+
| });
125+
| },
126+
| send: function(msg) {
127+
| scalajsCom.send(msg);
128+
| }
129+
| });
130+
|
131+
| jsdom.env({
132+
| html: "",
133+
| url: "http://localhost/",
134+
| virtualConsole: virtualConsole,
135+
| created: function (error, window) {
136+
| if (error == null) {
137+
| window["__ScalaJSEnv"] = __ScalaJSEnv;
138+
| window["scalajsCom"] = scalajsComWrapper;
139+
| } else {
140+
| throw error;
141+
| }
142+
| },
143+
| scripts: [${scriptsURIsAsJSStrings.mkString(", ")}]
144+
| });
145+
|})();
146+
|""".stripMargin
147+
}
148+
List(MemVirtualBinaryFile.fromStringUTF8("codeWithJSDOMContext.js", jsDOMCode))
149+
}
150+
}
41151

42-
override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner =
43-
new ComDOMNodeRunner(files)
152+
object JSDOMNodeJSEnv {
153+
private lazy val validator = ExternalJSRun.supports(RunConfig.Validator())
44154

45-
protected class DOMNodeRunner(files: Seq[VirtualJSFile])
46-
extends ExtRunner(files) with AbstractDOMNodeRunner
155+
private lazy val runtimeEnv = {
156+
MemVirtualBinaryFile.fromStringUTF8("scalaJSEnvInfo.js",
157+
"""
158+
|__ScalaJSEnv = {
159+
| exitFunction: function(status) { process.exit(status); }
160+
|};
161+
""".stripMargin
162+
)
163+
}
47164

48-
protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile])
49-
extends AsyncExtRunner(files) with AbstractDOMNodeRunner
165+
// Copied from NodeJSEnv.scala upstream
166+
private def write(files: List[VirtualBinaryFile])(out: OutputStream): Unit = {
167+
val p = new PrintStream(out, false, "UTF8")
168+
try {
169+
files.foreach {
170+
case file: FileVirtualBinaryFile =>
171+
val fname = file.file.getAbsolutePath
172+
p.println(s"""require("${escapeJS(fname)}");""")
173+
case f =>
174+
val in = f.inputStream
175+
try {
176+
val buf = new Array[Byte](4096)
50177

51-
protected class ComDOMNodeRunner(files: Seq[VirtualJSFile])
52-
extends AsyncDOMNodeRunner(files) with NodeComJSRunner
178+
@tailrec
179+
def loop(): Unit = {
180+
val read = in.read(buf)
181+
if (read != -1) {
182+
p.write(buf, 0, read)
183+
loop()
184+
}
185+
}
53186

54-
protected trait AbstractDOMNodeRunner extends AbstractNodeRunner {
187+
loop()
188+
} finally {
189+
in.close()
190+
}
55191

56-
protected def codeWithJSDOMContext(): Seq[VirtualJSFile] = {
57-
val scriptsPaths = getScriptsJSFiles().map {
58-
case file: FileVirtualFile => file.path
59-
case file => libCache.materialize(file).getAbsolutePath
60-
}
61-
val scriptsURIs =
62-
scriptsPaths.map(path => new java.io.File(path).toURI.toASCIIString)
63-
val scriptsURIsAsJSStrings = scriptsURIs.map('"' + escapeJS(_) + '"')
64-
val jsDOMCode = {
65-
s"""
66-
|(function () {
67-
| var jsdom;
68-
| try {
69-
| jsdom = require("jsdom/lib/old-api.js"); // jsdom >= 10.x
70-
| } catch (e) {
71-
| jsdom = require("jsdom"); // jsdom <= 9.x
72-
| }
73-
|
74-
| var virtualConsole = jsdom.createVirtualConsole()
75-
| .sendTo(console, { omitJsdomErrors: true });
76-
| virtualConsole.on("jsdomError", function (error) {
77-
| /* This inelegant if + console.error is the only way I found
78-
| * to make sure the stack trace of the original error is
79-
| * printed out.
80-
| */
81-
| if (error.detail && error.detail.stack)
82-
| console.error(error.detail.stack);
83-
|
84-
| // Throw the error anew to make sure the whole execution fails
85-
| throw error;
86-
| });
87-
|
88-
| jsdom.env({
89-
| html: "",
90-
| url: "http://localhost/",
91-
| virtualConsole: virtualConsole,
92-
| created: function (error, window) {
93-
| if (error == null) {
94-
| window["__ScalaJSEnv"] = __ScalaJSEnv;
95-
| window["scalajsCom"] = global.scalajsCom;
96-
| } else {
97-
| throw error;
98-
| }
99-
| },
100-
| scripts: [${scriptsURIsAsJSStrings.mkString(", ")}]
101-
| });
102-
|})();
103-
|""".stripMargin
192+
p.println()
104193
}
105-
Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode))
194+
} finally {
195+
p.close()
106196
}
197+
}
107198

108-
/** All the JS files that are passed to the VM.
109-
*
110-
* This method can overridden to provide custom behavior in subclasses.
111-
*
112-
* This method is overridden in `JSDOMNodeJSEnv` so that user-provided
113-
* JS files (excluding "init" files) are executed as *scripts* within the
114-
* jsdom environment, rather than being directly executed by the VM.
115-
*
116-
* The value returned by this method in `JSDOMNodeJSEnv` is
117-
* `initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()`.
118-
*/
119-
override protected def getJSFiles(): Seq[VirtualJSFile] =
120-
initFiles() ++ customInitFiles() ++ codeWithJSDOMContext()
199+
// tmpSuffixRE and tmpFile copied from HTMLRunnerBuilder.scala in Scala.js
121200

122-
/** JS files to be loaded via scripts in the jsdom environment.
123-
*
124-
* This method can be overridden to provide a different list of scripts.
125-
*
126-
* The default value in `JSDOMNodeJSEnv` is `files`.
127-
*/
128-
protected def getScriptsJSFiles(): Seq[VirtualJSFile] =
129-
files
130-
131-
// Send code to Stdin
132-
override protected def sendVMStdin(out: OutputStream): Unit = {
133-
/* Do not factor this method out into AbstractNodeRunner or when mixin in
134-
* the traits it would use AbstractExtRunner.sendVMStdin due to
135-
* linearization order.
201+
private val tmpSuffixRE = """[a-zA-Z0-9-_.]*$""".r
202+
203+
private def tmpFile(path: String, in: InputStream): URI = {
204+
try {
205+
/* - createTempFile requires a prefix of at least 3 chars
206+
* - we use a safe part of the path as suffix so the extension stays (some
207+
* browsers need that) and there is a clue which file it came from.
136208
*/
137-
sendJS(getJSFiles(), out)
209+
val suffix = tmpSuffixRE.findFirstIn(path).orNull
210+
211+
val f = File.createTempFile("tmp-", suffix)
212+
f.deleteOnExit()
213+
Files.copy(in, f.toPath(), StandardCopyOption.REPLACE_EXISTING)
214+
f.toURI()
215+
} finally {
216+
in.close()
217+
}
218+
}
219+
220+
private def materialize(file: VirtualBinaryFile): URI = {
221+
file match {
222+
case file: FileVirtualFile => file.file.toURI
223+
case file => tmpFile(file.path, file.inputStream)
138224
}
139225
}
140-
}
141226

142-
object JSDOMNodeJSEnv {
143227
final class Config private (
144228
val executable: String,
145229
val args: List[String],
Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,42 @@
11
package org.scalajs.jsenv.jsdomnodejs
22

3-
import org.scalajs.jsenv.test._
3+
import scala.concurrent.Await
44

55
import org.junit.Test
6-
import org.junit.Assert._
76

8-
class JSDOMNodeJSEnvTest extends TimeoutComTests {
7+
import org.scalajs.io._
98

10-
protected def newJSEnv: JSDOMNodeJSEnv = new JSDOMNodeJSEnv()
9+
import org.scalajs.jsenv._
10+
11+
class JSDOMNodeJSEnvTest {
12+
13+
private val TestRunConfig = {
14+
RunConfig()
15+
.withInheritOut(false)
16+
.withOnOutputStream((_, _) => ()) // ignore stdout
17+
}
18+
19+
private val config = JSDOMNodeJSSuite.Config
1120

1221
@Test
13-
def historyAPI: Unit = {
14-
"""|console.log(window.location.href);
15-
|window.history.pushState({}, "", "/foo");
16-
|console.log(window.location.href);
17-
""".stripMargin hasOutput
18-
"""|http://localhost/
19-
|http://localhost/foo
20-
|""".stripMargin
22+
def historyAPIWithoutTestKit: Unit = {
23+
assertRunSucceeds(
24+
"""
25+
|console.log(window.location.href);
26+
|window.history.pushState({}, "", "/foo");
27+
|console.log(window.location.href);
28+
""".stripMargin)
29+
}
30+
31+
private def assertRunSucceeds(inputStr: String): Unit = {
32+
val inputFile = MemVirtualBinaryFile.fromStringUTF8("test.js", inputStr)
33+
val input = Input.ScriptsToLoad(List(inputFile))
34+
val run = config.jsEnv.start(input, TestRunConfig)
35+
try {
36+
Await.result(run.future, config.awaitTimeout)
37+
} finally {
38+
run.close()
39+
}
2140
}
2241

2342
}

0 commit comments

Comments
 (0)