Skip to content

Commit 9463ff2

Browse files
committed
Upgrade to Scala.js 1.0.0-M5.
1 parent 4b9fa51 commit 9463ff2

File tree

8 files changed

+377
-109
lines changed

8 files changed

+377
-109
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: 188 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -8,138 +8,225 @@
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)
2639

27-
protected def executable: String = config.executable
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+
}
2847

29-
override protected def args: immutable.Seq[String] = config.args
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+
}
3065

31-
override protected def env: Map[String, String] = config.env
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+
}
3274

33-
// TODO We might want to make this configurable - not sure why it isn't
34-
override protected def wantSourceMap: Boolean = false
75+
private def initFiles: List[VirtualBinaryFile] =
76+
List(JSDOMNodeJSEnv.runtimeEnv, Support.fixPercentConsole)
3577

36-
override def jsRunner(files: Seq[VirtualJSFile]): JSRunner =
37-
new DOMNodeRunner(files)
78+
private def env: Map[String, String] =
79+
Map("NODE_MODULE_CONTEXTS" -> "0") ++ config.env
3880

39-
override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner =
40-
new AsyncDOMNodeRunner(files)
81+
private def scriptFiles(input: Input): List[VirtualBinaryFile] = input match {
82+
case Input.ScriptsToLoad(scripts) => scripts
83+
case _ => throw new UnsupportedInputException(input)
84+
}
4185

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

45-
protected class DOMNodeRunner(files: Seq[VirtualJSFile])
46-
extends ExtRunner(files) with AbstractDOMNodeRunner
155+
object JSDOMNodeJSEnv {
156+
private lazy val validator = ExternalJSRun.supports(RunConfig.Validator())
47157

48-
protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile])
49-
extends AsyncExtRunner(files) with AbstractDOMNodeRunner
158+
private lazy val runtimeEnv = {
159+
MemVirtualBinaryFile.fromStringUTF8("scalaJSEnvInfo.js",
160+
"""
161+
|__ScalaJSEnv = {
162+
| exitFunction: function(status) { process.exit(status); }
163+
|};
164+
""".stripMargin
165+
)
166+
}
50167

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

54-
protected trait AbstractDOMNodeRunner extends AbstractNodeRunner {
181+
@tailrec
182+
def loop(): Unit = {
183+
val read = in.read(buf)
184+
if (read != -1) {
185+
p.write(buf, 0, read)
186+
loop()
187+
}
188+
}
55189

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
190+
loop()
191+
} finally {
192+
in.close()
193+
}
194+
195+
p.println()
104196
}
105-
Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode))
197+
} finally {
198+
p.close()
106199
}
200+
}
107201

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()
202+
// tmpSuffixRE and tmpFile copied from HTMLRunnerBuilder.scala in Scala.js
121203

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.
204+
private val tmpSuffixRE = """[a-zA-Z0-9-_.]*$""".r
205+
206+
private def tmpFile(path: String, in: InputStream): URI = {
207+
try {
208+
/* - createTempFile requires a prefix of at least 3 chars
209+
* - we use a safe part of the path as suffix so the extension stays (some
210+
* browsers need that) and there is a clue which file it came from.
136211
*/
137-
sendJS(getJSFiles(), out)
212+
val suffix = tmpSuffixRE.findFirstIn(path).orNull
213+
214+
val f = File.createTempFile("tmp-", suffix)
215+
f.deleteOnExit()
216+
Files.copy(in, f.toPath(), StandardCopyOption.REPLACE_EXISTING)
217+
f.toURI()
218+
} finally {
219+
in.close()
220+
}
221+
}
222+
223+
private def materialize(file: VirtualBinaryFile): URI = {
224+
file match {
225+
case file: FileVirtualFile => file.file.toURI
226+
case file => tmpFile(file.path, file.inputStream)
138227
}
139228
}
140-
}
141229

142-
object JSDOMNodeJSEnv {
143230
final class Config private (
144231
val executable: String,
145232
val args: List[String],

jsdom-nodejs-env/src/test/scala/org/scalajs/jsenv/jsdomnodejs/JSDOMNodeJSEnvTest.scala

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,20 @@
11
package org.scalajs.jsenv.jsdomnodejs
22

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

55
import org.junit.Test
66
import org.junit.Assert._
77

8-
class JSDOMNodeJSEnvTest extends TimeoutComTests {
8+
import org.scalajs.io._
9+
10+
import org.scalajs.jsenv._
11+
import org.scalajs.jsenv.test._
12+
13+
class JSDOMNodeJSEnvTest {
914

10-
protected def newJSEnv: JSDOMNodeJSEnv = new JSDOMNodeJSEnv()
15+
private val config = JSDOMNodeJSSuite.Config
16+
private val kit = new TestKit(config, withCom = true)
17+
import kit._
1118

1219
@Test
1320
def historyAPI: Unit = {
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package org.scalajs.jsenv.jsdomnodejs
2+
3+
import org.scalajs.jsenv.test._
4+
5+
import org.junit.runner.RunWith
6+
7+
@RunWith(classOf[JSEnvSuiteRunner])
8+
class JSDOMNodeJSSuite extends JSEnvSuite(JSDOMNodeJSSuite.Config)
9+
10+
object JSDOMNodeJSSuite {
11+
val Config = {
12+
JSEnvSuiteConfig(new JSDOMNodeJSEnv)
13+
.withTerminateVMJSCode("__ScalaJSEnv.exitFunction(0)")
14+
}
15+
}

0 commit comments

Comments
 (0)