Skip to content

Commit e8020fd

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

File tree

8 files changed

+362
-109
lines changed

8 files changed

+362
-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: 173 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -8,138 +8,210 @@
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"
2631

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

29-
override protected def args: immutable.Seq[String] = config.args
42+
def startWithCom(input: Input, runConfig: RunConfig,
43+
onMessage: String => Unit): JSComRun = {
44+
JSDOMNodeJSEnv.validator.validate(runConfig)
45+
ComRun.start(runConfig, onMessage) { comLoader =>
46+
val files = initFiles ::: (comLoader :: codeWithJSDOMContext(input))
47+
internalStart(files, runConfig)
48+
}
49+
}
3050

31-
override protected def env: Map[String, String] = config.env
51+
private def internalStart(files: List[VirtualBinaryFile],
52+
runConfig: RunConfig): JSRun = {
53+
val command = config.executable :: config.args
54+
val externalConfig = ExternalJSRun.Config()
55+
.withEnv(env)
56+
.withRunConfig(runConfig)
57+
ExternalJSRun.start(command, externalConfig)(JSDOMNodeJSEnv.write(files))
58+
}
3259

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

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

39-
override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner =
40-
new AsyncDOMNodeRunner(files)
66+
private def scriptFiles(input: Input): List[VirtualBinaryFile] = input match {
67+
case Input.ScriptsToLoad(scripts) => scripts
68+
case _ => throw new UnsupportedInputException(input)
69+
}
4170

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

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

48-
protected class AsyncDOMNodeRunner(files: Seq[VirtualJSFile])
49-
extends AsyncExtRunner(files) with AbstractDOMNodeRunner
143+
private lazy val runtimeEnv = {
144+
MemVirtualBinaryFile.fromStringUTF8("scalaJSEnvInfo.js",
145+
"""
146+
|__ScalaJSEnv = {
147+
| exitFunction: function(status) { process.exit(status); }
148+
|};
149+
""".stripMargin
150+
)
151+
}
50152

51-
protected class ComDOMNodeRunner(files: Seq[VirtualJSFile])
52-
extends AsyncDOMNodeRunner(files) with NodeComJSRunner
153+
// Copied from NodeJSEnv.scala upstream
154+
private def write(files: List[VirtualBinaryFile])(out: OutputStream): Unit = {
155+
val p = new PrintStream(out, false, "UTF8")
156+
try {
157+
files.foreach {
158+
case file: FileVirtualBinaryFile =>
159+
val fname = file.file.getAbsolutePath
160+
p.println(s"""require("${escapeJS(fname)}");""")
161+
case f =>
162+
val in = f.inputStream
163+
try {
164+
val buf = new Array[Byte](4096)
53165

54-
protected trait AbstractDOMNodeRunner extends AbstractNodeRunner {
166+
@tailrec
167+
def loop(): Unit = {
168+
val read = in.read(buf)
169+
if (read != -1) {
170+
p.write(buf, 0, read)
171+
loop()
172+
}
173+
}
55174

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
175+
loop()
176+
} finally {
177+
in.close()
178+
}
179+
180+
p.println()
104181
}
105-
Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode))
182+
} finally {
183+
p.close()
106184
}
185+
}
107186

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

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.
189+
private val tmpSuffixRE = """[a-zA-Z0-9-_.]*$""".r
190+
191+
private def tmpFile(path: String, in: InputStream): URI = {
192+
try {
193+
/* - createTempFile requires a prefix of at least 3 chars
194+
* - we use a safe part of the path as suffix so the extension stays (some
195+
* browsers need that) and there is a clue which file it came from.
136196
*/
137-
sendJS(getJSFiles(), out)
197+
val suffix = tmpSuffixRE.findFirstIn(path).orNull
198+
199+
val f = File.createTempFile("tmp-", suffix)
200+
f.deleteOnExit()
201+
Files.copy(in, f.toPath(), StandardCopyOption.REPLACE_EXISTING)
202+
f.toURI()
203+
} finally {
204+
in.close()
205+
}
206+
}
207+
208+
private def materialize(file: VirtualBinaryFile): URI = {
209+
file match {
210+
case file: FileVirtualFile => file.file.toURI
211+
case file => tmpFile(file.path, file.inputStream)
138212
}
139213
}
140-
}
141214

142-
object JSDOMNodeJSEnv {
143215
final class Config private (
144216
val executable: String,
145217
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)