Skip to content

Commit 21a8746

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

File tree

8 files changed

+379
-109
lines changed

8 files changed

+379
-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: 190 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -8,138 +8,227 @@
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(
62+
installSourceMapIfAvailable,
63+
JSDOMNodeJSEnv.runtimeEnv,
64+
Support.fixPercentConsole
65+
)
66+
}
3567

36-
override def jsRunner(files: Seq[VirtualJSFile]): JSRunner =
37-
new DOMNodeRunner(files)
68+
private lazy val installSourceMapIfAvailable = {
69+
MemVirtualBinaryFile.fromStringUTF8("sourceMapSupport.js",
70+
"""
71+
|Error.stackTraceLimit = Infinity;
72+
|try {
73+
| require('source-map-support').install();
74+
|} catch (e) {
75+
|};
76+
""".stripMargin
77+
)
78+
}
3879

39-
override def asyncRunner(files: Seq[VirtualJSFile]): AsyncJSRunner =
40-
new AsyncDOMNodeRunner(files)
80+
private def env: Map[String, String] =
81+
Map("NODE_MODULE_CONTEXTS" -> "0") ++ config.env
4182

42-
override def comRunner(files: Seq[VirtualJSFile]): ComJSRunner =
43-
new ComDOMNodeRunner(files)
83+
private def scriptFiles(input: Input): List[VirtualBinaryFile] = input match {
84+
case Input.ScriptsToLoad(scripts) => scripts
85+
case _ => throw new UnsupportedInputException(input)
86+
}
4487

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

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

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

54-
protected trait AbstractDOMNodeRunner extends AbstractNodeRunner {
183+
@tailrec
184+
def loop(): Unit = {
185+
val read = in.read(buf)
186+
if (read != -1) {
187+
p.write(buf, 0, read)
188+
loop()
189+
}
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+
loop()
193+
} finally {
194+
in.close()
195+
}
196+
197+
p.println()
104198
}
105-
Seq(new MemVirtualJSFile("codeWithJSDOMContext.js").withContent(jsDOMCode))
199+
} finally {
200+
p.close()
106201
}
202+
}
107203

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

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

142-
object JSDOMNodeJSEnv {
143232
final class Config private (
144233
val executable: String,
145234
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)