diff options
Diffstat (limited to 'examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env')
12 files changed, 0 insertions, 1862 deletions
diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala deleted file mode 100644 index e0aa557..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/ExternalJSEnv.scala +++ /dev/null @@ -1,200 +0,0 @@ -package scala.scalajs.sbtplugin.env - -import scala.scalajs.tools.io._ -import scala.scalajs.tools.classpath._ -import scala.scalajs.tools.env._ -import scala.scalajs.tools.logging._ - -import scala.scalajs.sbtplugin.JSUtils._ - -import java.io.{ Console => _, _ } -import scala.io.Source - -import scala.concurrent.{Future, Promise} -import scala.util.Try - -abstract class ExternalJSEnv( - final protected val additionalArgs: Seq[String], - final protected val additionalEnv: Map[String, String]) extends AsyncJSEnv { - - /** Printable name of this VM */ - protected def vmName: String - - /** Command to execute (on shell) for this VM */ - protected def executable: String - - protected class AbstractExtRunner(protected val classpath: CompleteClasspath, - protected val code: VirtualJSFile, protected val logger: Logger, - protected val console: JSConsole) { - - /** JS files used to setup VM */ - protected def initFiles(): Seq[VirtualJSFile] = Nil - - /** Sends required data to VM Stdin (can throw) */ - protected def sendVMStdin(out: OutputStream): Unit = {} - - /** VM arguments excluding executable. Override to adapt. - * Overrider is responsible to add additionalArgs. - */ - protected def getVMArgs(): Seq[String] = additionalArgs - - /** VM environment. Override to adapt. - * - * Default is `sys.env` and [[additionalEnv]] - */ - protected def getVMEnv(): Map[String, String] = - sys.env ++ additionalEnv - - /** Get files that are a library (i.e. that do not run anything) */ - protected def getLibJSFiles(): Seq[VirtualJSFile] = - initFiles() ++ classpath.allCode - - /** Get all files that are passed to VM (libraries and code) */ - protected def getJSFiles(): Seq[VirtualJSFile] = - getLibJSFiles() :+ code - - /** write a single JS file to a writer using an include fct if appropriate */ - protected def writeJSFile(file: VirtualJSFile, writer: Writer): Unit = { - // The only platform-independent way to do this in JS is to dump the file. - writer.write(file.content) - writer.write('\n') - } - - /** Pipe stdin and stdout from/to VM */ - final protected def pipeVMData(vmInst: Process): Unit = { - // Send stdin to VM. - val out = vmInst.getOutputStream() - try { sendVMStdin(out) } - finally { out.close() } - - // Pipe stdout to console - pipeToConsole(vmInst.getInputStream(), console) - - // We are probably done (stdin is closed). Report any errors - val errSrc = Source.fromInputStream(vmInst.getErrorStream(), "UTF-8") - try { errSrc.getLines.foreach(err => logger.error(err)) } - finally { errSrc.close } - } - - /** Wait for the VM to terminate, verify exit code */ - final protected def waitForVM(vmInst: Process): Unit = { - // Make sure we are done. - vmInst.waitFor() - - // Get return value and return - val retVal = vmInst.exitValue - if (retVal != 0) - sys.error(s"$vmName exited with code $retVal") - } - - protected def startVM(): Process = { - val vmArgs = getVMArgs() - val vmEnv = getVMEnv() - - val allArgs = executable +: vmArgs - val pBuilder = new ProcessBuilder(allArgs: _*) - - pBuilder.environment().clear() - for ((name, value) <- vmEnv) - pBuilder.environment().put(name, value) - - pBuilder.start() - } - - /** send a bunch of JS files to an output stream */ - final protected def sendJS(files: Seq[VirtualJSFile], - out: OutputStream): Unit = { - val writer = new BufferedWriter(new OutputStreamWriter(out, "UTF-8")) - try sendJS(files, writer) - finally writer.close() - } - - /** send a bunch of JS files to a writer */ - final protected def sendJS(files: Seq[VirtualJSFile], out: Writer): Unit = - files.foreach { writeJSFile(_, out) } - - /** pipe lines from input stream to JSConsole */ - final protected def pipeToConsole(in: InputStream, console: JSConsole) = { - val source = Source.fromInputStream(in, "UTF-8") - try { source.getLines.foreach(console.log _) } - finally { source.close() } - } - - } - - protected class ExtRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole - ) extends AbstractExtRunner(classpath, code, logger, console) - with JSRunner { - - def run(): Unit = { - val vmInst = startVM() - - pipeVMData(vmInst) - waitForVM(vmInst) - } - } - - protected class AsyncExtRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends AbstractExtRunner(classpath, code, logger, console) - with AsyncJSRunner { - - private[this] var vmInst: Process = null - private[this] var ioThreadEx: Throwable = null - private[this] val promise = Promise[Unit] - - private[this] val thread = new Thread { - override def run(): Unit = { - // This thread should not be interrupted, so it is safe to use Trys - val pipeResult = Try(pipeVMData(vmInst)) - val vmComplete = Try(waitForVM(vmInst)) - - // Store IO exception - pipeResult recover { - case e => ioThreadEx = e - } - - // Chain Try's the other way: We want VM failure first, then IO failure - promise.complete(pipeResult orElse vmComplete) - } - } - - def start(): Future[Unit] = { - require(vmInst == null, "start() may only be called once") - vmInst = startVM() - thread.start() - promise.future - } - - def stop(): Unit = { - require(vmInst != null, "start() must have been called") - vmInst.destroy() - } - - def isRunning(): Boolean = { - require(vmInst != null, "start() must have been called") - // Emulate JDK 8 Process.isAlive - try { - vmInst.exitValue() - false - } catch { - case e: IllegalThreadStateException => - true - } - } - - def await(): Unit = { - require(vmInst != null, "start() must have been called") - thread.join() - waitForVM(vmInst) - - // At this point, the VM itself didn't fail. We need to check if - // anything bad happened while piping the data from the VM - - if (ioThreadEx != null) - throw ioThreadEx - } - } - -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/VirtualFileMaterializer.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/VirtualFileMaterializer.scala deleted file mode 100644 index fca1c47..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/VirtualFileMaterializer.scala +++ /dev/null @@ -1,67 +0,0 @@ -package scala.scalajs.sbtplugin.env - -import scala.scalajs.tools.io.{IO => _, _} - -import sbt.IO - -import java.io.File - -/** A helper class to temporarily store virtual files to the filesystem. - * - * Can be used with tools that require real files. - * @param singleDir if true, forces files to be copied into - * [[cacheDir]]. Useful to setup include directories for - * example. - */ -final class VirtualFileMaterializer(singleDir: Boolean = false) { - - val cacheDir = { - val dir = IO.createTemporaryDirectory - dir.deleteOnExit() - dir - } - - /** Create a target file to write/copy to. Will also call - * deleteOnExit on the file. - */ - private def trgFile(name: String): File = { - val f = new File(cacheDir, name) - f.deleteOnExit() - f - } - - private def materializeFileVF(vf: FileVirtualFile): File = { - if (!singleDir) vf.file - else { - val trg = trgFile(vf.name) - IO.copyFile(vf.file, trg) - trg - } - } - - def materialize(vf: VirtualTextFile): File = vf match { - case vf: FileVirtualFile => materializeFileVF(vf) - case _ => - val trg = trgFile(vf.name) - IO.write(trg, vf.content) - trg - } - - def materialize(vf: VirtualBinaryFile): File = vf match { - case vf: FileVirtualFile => materializeFileVF(vf) - case _ => - val trg = trgFile(vf.name) - IO.write(trg, vf.content) - trg - } - - /** Removes the cache directory. Any operation on this - * VirtualFileMaterializer is invalid after [[close]] has been - * called. - */ - def close(): Unit = { - cacheDir.listFiles().foreach(_.delete) - cacheDir.delete() - } - -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/nodejs/NodeJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/nodejs/NodeJSEnv.scala deleted file mode 100644 index dfabe23..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/nodejs/NodeJSEnv.scala +++ /dev/null @@ -1,306 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.sbtplugin.env.nodejs - -import scala.scalajs.sbtplugin.env._ -import scala.scalajs.sbtplugin.JSUtils.toJSstr - -import scala.scalajs.tools.io._ -import scala.scalajs.tools.classpath._ -import scala.scalajs.tools.env._ -import scala.scalajs.tools.jsdep._ -import scala.scalajs.tools.logging._ - -import scala.scalajs.sbtplugin.JSUtils._ - -import java.io.{ Console => _, _ } -import java.net._ - -import scala.io.Source - -class NodeJSEnv( - nodejsPath: String = "node", - addArgs: Seq[String] = Seq.empty, - addEnv: Map[String, String] = Map.empty -) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { - - protected def vmName: String = "node.js" - protected def executable: String = nodejsPath - - override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): JSRunner = { - new NodeRunner(classpath, code, logger, console) - } - - override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): AsyncJSRunner = { - new AsyncNodeRunner(classpath, code, logger, console) - } - - override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): ComJSRunner = { - new ComNodeRunner(classpath, code, logger, console) - } - - protected class NodeRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends ExtRunner(classpath, code, logger, console) - with AbstractNodeRunner - - protected class AsyncNodeRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends AsyncExtRunner(classpath, code, logger, console) - with AbstractNodeRunner - - protected class ComNodeRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends AsyncNodeRunner(classpath, code, logger, console) - with ComJSRunner { - - /** Retry-timeout to wait for the JS VM to connect */ - private final val acceptTimeout = 1000 - - private[this] val serverSocket = - new ServerSocket(0, 0, InetAddress.getByName(null)) // Loopback address - private[this] var comSocket: Socket = _ - private[this] var jvm2js: DataOutputStream = _ - private[this] var js2jvm: DataInputStream = _ - - private def comSetup = new MemVirtualJSFile("comSetup.js").withContent( - s""" - (function() { - // The socket for communication - var socket = null; - // The callback where received messages go - var recvCallback = null; - - // Buffers received data - var inBuffer = new Buffer(0); - - function onData(data) { - inBuffer = Buffer.concat([inBuffer, data]); - tryReadMsg(); - } - - function tryReadMsg() { - if (inBuffer.length < 4) return; - var msgLen = inBuffer.readInt32BE(0); - var byteLen = 4 + msgLen * 2; - - if (inBuffer.length < byteLen) return; - var res = ""; - - for (var i = 0; i < msgLen; ++i) - res += String.fromCharCode(inBuffer.readInt16BE(4 + i * 2)); - - inBuffer = inBuffer.slice(byteLen); - - recvCallback(res); - } - - global.scalajsCom = { - init: function(recvCB) { - if (socket !== null) throw new Error("Com already open"); - - var net = require('net'); - recvCallback = recvCB; - socket = net.connect(${serverSocket.getLocalPort}); - socket.on('data', onData); - }, - send: function(msg) { - if (socket === null) throw new Error("Com not open"); - - var len = msg.length; - var buf = new Buffer(4 + len * 2); - buf.writeInt32BE(len, 0); - for (var i = 0; i < len; ++i) - buf.writeInt16BE(msg.charCodeAt(i), 4 + i * 2); - socket.write(buf); - }, - close: function() { - if (socket === null) throw new Error("Com not open"); - socket.end(); - } - } - }).call(this); - """ - ) - - def send(msg: String): Unit = { - if (awaitConnection()) { - jvm2js.writeInt(msg.length) - jvm2js.writeChars(msg) - jvm2js.flush() - } - } - - def receive(): String = { - if (!awaitConnection()) - throw new ComJSEnv.ComClosedException - try { - val len = js2jvm.readInt() - val carr = Array.fill(len)(js2jvm.readChar()) - String.valueOf(carr) - } catch { - case e: EOFException => - throw new ComJSEnv.ComClosedException - } - } - - def close(): Unit = { - serverSocket.close() - if (jvm2js != null) - jvm2js.close() - if (js2jvm != null) - js2jvm.close() - if (comSocket != null) - comSocket.close() - } - - override def stop(): Unit = { - close() - super.stop() - } - - /** Waits until the JS VM has established a connection or terminates - * @return true if the connection was established - */ - private def awaitConnection(): Boolean = { - serverSocket.setSoTimeout(acceptTimeout) - while (comSocket == null && isRunning) { - try { - comSocket = serverSocket.accept() - jvm2js = new DataOutputStream( - new BufferedOutputStream(comSocket.getOutputStream())) - js2jvm = new DataInputStream( - new BufferedInputStream(comSocket.getInputStream())) - } catch { - case to: SocketTimeoutException => - } - } - - comSocket != null - } - - override protected def initFiles(): Seq[VirtualJSFile] = - super.initFiles :+ comSetup - - override protected def finalize(): Unit = close() - } - - protected trait AbstractNodeRunner extends AbstractExtRunner { - - protected[this] val libCache = new VirtualFileMaterializer(true) - - /** File(s) to automatically install source-map-support. - * Is used by [[initFiles]], override to change/disable. - */ - protected def installSourceMap(): Seq[VirtualJSFile] = Seq( - new MemVirtualJSFile("sourceMapSupport.js").withContent( - """ - try { - require('source-map-support').install(); - } catch (e) {} - """ - ) - ) - - /** File(s) to hack console.log to prevent if from changing `%%` to `%`. - * Is used by [[initFiles]], override to change/disable. - */ - protected def fixPercentConsole(): Seq[VirtualJSFile] = Seq( - new MemVirtualJSFile("nodeConsoleHack.js").withContent( - """ - // Hack console log to duplicate double % signs - (function() { - var oldLog = console.log; - var newLog = function() { - var args = arguments; - if (args.length >= 1 && args[0] !== void 0 && args[0] !== null) { - args[0] = args[0].toString().replace(/%/g, "%%"); - } - oldLog.apply(console, args); - }; - console.log = newLog; - })(); - """ - ) - ) - - /** File(s) to define `__ScalaJSEnv`. Defines `exitFunction`. - * Is used by [[initFiles]], override to change/disable. - */ - protected def runtimeEnv(): Seq[VirtualJSFile] = Seq( - new MemVirtualJSFile("scalaJSEnvInfo.js").withContent( - """ - __ScalaJSEnv = { - exitFunction: function(status) { process.exit(status); } - }; - """ - ) - ) - - /** Concatenates results from [[installSourceMap]], [[fixPercentConsole]] and - * [[runtimeEnv]] (in this order). - */ - override protected def initFiles(): Seq[VirtualJSFile] = - installSourceMap() ++ fixPercentConsole() ++ runtimeEnv() - - /** Libraries are loaded via require in Node.js */ - override protected def getLibJSFiles(): Seq[VirtualJSFile] = { - initFiles() ++ - classpath.jsLibs.map(requireLibrary) :+ - classpath.scalaJSCode - } - - /** Rewrites a library virtual file to a require statement if possible */ - protected def requireLibrary(dep: ResolvedJSDependency): VirtualJSFile = { - dep.info.commonJSName.fold(dep.lib) { varname => - val fname = dep.lib.name - libCache.materialize(dep.lib) - new MemVirtualJSFile(s"require-$fname").withContent( - s"""$varname = require(${toJSstr(fname)});""" - ) - } - } - - // Send code to Stdin - override protected def sendVMStdin(out: OutputStream): Unit = { - sendJS(getJSFiles(), out) - } - - /** write a single JS file to a writer using an include fct if appropriate - * uses `require` if the file exists on the filesystem - */ - override protected def writeJSFile(file: VirtualJSFile, - writer: Writer): Unit = { - file match { - case file: FileVirtualJSFile => - val fname = toJSstr(file.file.getAbsolutePath) - writer.write(s"require($fname);\n") - case _ => - super.writeJSFile(file, writer) - } - } - - // Node.js specific (system) environment - override protected def getVMEnv(): Map[String, String] = { - val baseNodePath = sys.env.get("NODE_PATH").filter(_.nonEmpty) - val nodePath = libCache.cacheDir.getAbsolutePath + - baseNodePath.fold("")(p => File.pathSeparator + p) - - sys.env ++ Seq( - "NODE_MODULE_CONTEXTS" -> "0", - "NODE_PATH" -> nodePath - ) ++ additionalEnv - } - } - -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala deleted file mode 100644 index 3dec79c..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/JettyWebsocketManager.scala +++ /dev/null @@ -1,126 +0,0 @@ -package scala.scalajs.sbtplugin.env.phantomjs - -import javax.servlet.http.HttpServletRequest - -import org.eclipse.jetty.server.Server -import org.eclipse.jetty.server.nio.SelectChannelConnector -import org.eclipse.jetty.websocket.{WebSocket, WebSocketHandler} -import org.eclipse.jetty.util.component.{LifeCycle, AbstractLifeCycle} -import org.eclipse.jetty.util.log - -private[phantomjs] final class JettyWebsocketManager( - wsListener: WebsocketListener) extends WebsocketManager { thisMgr => - - private[this] var webSocketConn: WebSocket.Connection = null - private[this] var closed = false - - // We can just set the logger here, since we are supposed to be protected by - // the private ClassLoader that loads us reflectively. - log.Log.setLog(new WSLogger("root")) - - private[this] val connector = new SelectChannelConnector - - connector.setHost("localhost") - connector.setPort(0) - - private[this] val server = new Server() - - server.addConnector(connector) - server.setHandler(new WebSocketHandler { - // Support Hixie 76 for Phantom.js - getWebSocketFactory().setMinVersion(-1) - - override def doWebSocketConnect( - request: HttpServletRequest, protocol: String): WebSocket = - new ComWebSocketListener - }) - - server.addLifeCycleListener(new AbstractLifeCycle.AbstractLifeCycleListener { - override def lifeCycleStarted(event: LifeCycle): Unit = { - if (event.isRunning()) - wsListener.onRunning() - } - }) - - private class ComWebSocketListener extends WebSocket.OnTextMessage { - override def onOpen(connection: WebSocket.Connection): Unit = { - thisMgr.synchronized { - if (isConnected) - throw new IllegalStateException("Client connected twice") - webSocketConn = connection - } - wsListener.onOpen() - } - - override def onClose(statusCode: Int, reason: String): Unit = { - thisMgr.synchronized { - webSocketConn = null - closed = true - } - wsListener.onClose() - server.stop() - - if (statusCode != 1000) { - throw new Exception("Abnormal closing of connection. " + - s"Code: $statusCode, Reason: $reason") - } - } - - override def onMessage(message: String): Unit = - wsListener.onMessage(message) - } - - private class WSLogger(fullName: String) extends log.AbstractLogger { - private[this] var debugEnabled = false - - def debug(msg: String, args: Object*): Unit = - if (debugEnabled) log("DEBUG", msg, args) - - def debug(msg: String, thrown: Throwable): Unit = - if (debugEnabled) log("DEBUG", msg, thrown) - - def debug(thrown: Throwable): Unit = - if (debugEnabled) log("DEBUG", thrown) - - def getName(): String = fullName - - def ignore(ignored: Throwable): Unit = () - - def info(msg: String, args: Object*): Unit = log("INFO", msg, args) - def info(msg: String, thrown: Throwable): Unit = log("INFO", msg, thrown) - def info(thrown: Throwable): Unit = log("INFO", thrown) - - def warn(msg: String, args: Object*): Unit = log("WARN", msg, args) - def warn(msg: String, thrown: Throwable): Unit = log("WARN", msg, thrown) - def warn(thrown: Throwable): Unit = log("WARN", thrown) - - def isDebugEnabled(): Boolean = debugEnabled - def setDebugEnabled(enabled: Boolean): Unit = debugEnabled = enabled - - private def log(lvl: String, msg: String, args: Object*): Unit = - wsListener.log(s"$lvl: $msg " + args.mkString(", ")) - - private def log(lvl: String, msg: String, thrown: Throwable): Unit = - wsListener.log(s"$lvl: $msg $thrown\n{$thrown.getStackStrace}") - - private def log(lvl: String, thrown: Throwable): Unit = - wsListener.log(s"$lvl: $thrown\n{$thrown.getStackStrace}") - - protected def newLogger(fullName: String) = new WSLogger(fullName) - } - - def start(): Unit = server.start() - - def stop(): Unit = server.stop() - - def isConnected: Boolean = webSocketConn != null && !closed - def isClosed: Boolean = closed - - def localPort: Int = connector.getLocalPort() - - def sendMessage(msg: String) = synchronized { - if (webSocketConn != null) - webSocketConn.sendMessage(msg) - } - -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala deleted file mode 100644 index 7bb47d2..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJSEnv.scala +++ /dev/null @@ -1,466 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.sbtplugin.env.phantomjs - -import scala.scalajs.sbtplugin.env._ - -import scala.scalajs.tools.io._ -import scala.scalajs.tools.classpath._ -import scala.scalajs.tools.env._ -import scala.scalajs.tools.logging._ - -import scala.scalajs.sbtplugin.JSUtils._ - -import java.io.{ Console => _, _ } -import java.net._ - -import scala.io.Source -import scala.collection.mutable -import scala.annotation.tailrec - -class PhantomJSEnv( - phantomjsPath: String = "phantomjs", - addArgs: Seq[String] = Seq.empty, - addEnv: Map[String, String] = Map.empty, - val autoExit: Boolean = true, - jettyClassLoader: ClassLoader = getClass().getClassLoader() -) extends ExternalJSEnv(addArgs, addEnv) with ComJSEnv { - - import PhantomJSEnv._ - - protected def vmName: String = "PhantomJS" - protected def executable: String = phantomjsPath - - override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): JSRunner = { - new PhantomRunner(classpath, code, logger, console) - } - - override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): AsyncJSRunner = { - new AsyncPhantomRunner(classpath, code, logger, console) - } - - override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): ComJSRunner = { - new ComPhantomRunner(classpath, code, logger, console) - } - - protected class PhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends ExtRunner(classpath, code, logger, console) - with AbstractPhantomRunner - - protected class AsyncPhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends AsyncExtRunner(classpath, code, logger, console) - with AbstractPhantomRunner - - protected class ComPhantomRunner(classpath: CompleteClasspath, - code: VirtualJSFile, logger: Logger, console: JSConsole - ) extends AsyncPhantomRunner(classpath, code, logger, console) - with ComJSRunner with WebsocketListener { - - private def loadMgr() = { - val clazz = jettyClassLoader.loadClass( - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager") - - val ctors = clazz.getConstructors() - assert(ctors.length == 1, "JettyWebsocketManager may only have one ctor") - - val mgr = ctors.head.newInstance(this) - - mgr.asInstanceOf[WebsocketManager] - } - - val mgr: WebsocketManager = loadMgr() - - def onRunning(): Unit = synchronized(notifyAll()) - def onOpen(): Unit = synchronized(notifyAll()) - def onClose(): Unit = synchronized(notifyAll()) - - def onMessage(msg: String): Unit = synchronized { - recvBuf.enqueue(msg) - notifyAll() - } - - def log(msg: String): Unit = logger.debug(s"PhantomJS WS Jetty: $msg") - - private[this] val recvBuf = mutable.Queue.empty[String] - - mgr.start() - - /** The websocket server starts asynchronously, but we need the port it is - * running on. This method waits until the port is non-negative and - * returns its value. - */ - private def waitForPort(): Int = { - while (mgr.localPort < 0) - wait() - mgr.localPort - } - - private def comSetup = { - def maybeExit(code: Int) = - if (autoExit) - s"window.callPhantom({ action: 'exit', returnValue: $code });" - else - "" - - val serverPort = waitForPort() - - val code = s""" - |(function() { - | var MaxPayloadSize = $MaxCharPayloadSize; - | - | // The socket for communication - | var websocket = null; - | - | // Buffer for messages sent before socket is open - | var outMsgBuf = null; - | - | function sendImpl(msg) { - | var frags = (msg.length / MaxPayloadSize) | 0; - | - | for (var i = 0; i < frags; ++i) { - | var payload = msg.substring( - | i * MaxPayloadSize, (i + 1) * MaxPayloadSize); - | websocket.send("1" + payload); - | } - | - | websocket.send("0" + msg.substring(frags * MaxPayloadSize)); - | } - | - | function recvImpl(recvCB) { - | var recvBuf = ""; - | - | return function(evt) { - | var newData = recvBuf + evt.data.substring(1); - | if (evt.data.charAt(0) == "0") { - | recvBuf = ""; - | recvCB(newData); - | } else if (evt.data.charAt(0) == "1") { - | recvBuf = newData; - | } else { - | throw new Error("Bad fragmentation flag in " + evt.data); - | } - | }; - | } - | - | window.scalajsCom = { - | init: function(recvCB) { - | if (websocket !== null) throw new Error("Com already open"); - | - | outMsgBuf = []; - | - | websocket = new WebSocket("ws://localhost:$serverPort"); - | - | websocket.onopen = function(evt) { - | for (var i = 0; i < outMsgBuf.length; ++i) - | sendImpl(outMsgBuf[i]); - | outMsgBuf = null; - | }; - | websocket.onclose = function(evt) { - | websocket = null; - | ${maybeExit(0)} - | }; - | websocket.onmessage = recvImpl(recvCB); - | websocket.onerror = function(evt) { - | websocket = null; - | throw new Error("Websocket failed: " + evt); - | }; - | - | // Take over responsibility to auto exit - | window.callPhantom({ - | action: 'setAutoExit', - | autoExit: false - | }); - | }, - | send: function(msg) { - | if (websocket === null) - | return; // we are closed already. ignore message - | - | if (outMsgBuf !== null) - | outMsgBuf.push(msg); - | else - | sendImpl(msg); - | }, - | close: function() { - | if (websocket === null) - | return; // we are closed already. all is well. - | - | if (outMsgBuf !== null) - | // Reschedule ourselves to give onopen a chance to kick in - | window.setTimeout(window.scalajsCom.close, 10); - | else - | websocket.close(); - | } - | } - |}).call(this);""".stripMargin - - new MemVirtualJSFile("comSetup.js").withContent(code) - } - - def send(msg: String): Unit = synchronized { - if (awaitConnection()) { - val fragParts = msg.length / MaxCharPayloadSize - - for (i <- 0 until fragParts) { - val payload = msg.substring( - i * MaxCharPayloadSize, (i + 1) * MaxCharPayloadSize) - mgr.sendMessage("1" + payload) - } - - mgr.sendMessage("0" + msg.substring(fragParts * MaxCharPayloadSize)) - } - } - - def receive(): String = synchronized { - if (recvBuf.isEmpty && !awaitConnection()) - throw new ComJSEnv.ComClosedException - - @tailrec - def loop(acc: String): String = { - val frag = receiveFrag() - val newAcc = acc + frag.substring(1) - - if (frag(0) == '0') - newAcc - else if (frag(0) == '1') - loop(newAcc) - else - throw new AssertionError("Bad fragmentation flag in " + frag) - } - - loop("") - } - - private def receiveFrag(): String = { - while (recvBuf.isEmpty && !mgr.isClosed) - wait() - - if (recvBuf.isEmpty) - throw new ComJSEnv.ComClosedException - else - recvBuf.dequeue() - } - - def close(): Unit = mgr.stop() - - override def stop(): Unit = { - close() - super.stop() - } - - /** Waits until the JS VM has established a connection, or the VM - * terminated. Returns true if a connection was established. - */ - private def awaitConnection(): Boolean = { - while (!mgr.isConnected && !mgr.isClosed && isRunning) - wait(200) // We sleep-wait for isRunning - - mgr.isConnected - } - - override protected def initFiles(): Seq[VirtualJSFile] = - super.initFiles :+ comSetup - } - - protected trait AbstractPhantomRunner extends AbstractExtRunner { - - override protected def getVMArgs() = - // Add launcher file to arguments - additionalArgs :+ createTmpLauncherFile().getAbsolutePath - - /** In phantom.js, we include JS using HTML */ - override protected def writeJSFile(file: VirtualJSFile, writer: Writer) = { - file match { - case file: FileVirtualJSFile => - val fname = htmlEscape(file.file.getAbsolutePath) - writer.write( - s"""<script type="text/javascript" src="$fname"></script>""" + "\n") - case _ => - writer.write("""<script type="text/javascript">""" + "\n") - writer.write(s"// Virtual File: ${file.path}\n") - writer.write(file.content) - writer.write("</script>\n") - } - } - - /** - * PhantomJS doesn't support Function.prototype.bind. We polyfill it. - * https://github.com/ariya/phantomjs/issues/10522 - */ - override protected def initFiles(): Seq[VirtualJSFile] = Seq( - new MemVirtualJSFile("bindPolyfill.js").withContent( - """ - |// Polyfill for Function.bind from Facebook react: - |// https://github.com/facebook/react/blob/3dc10749080a460e48bee46d769763ec7191ac76/src/test/phantomjs-shims.js - |// Originally licensed under Apache 2.0 - |(function() { - | - | var Ap = Array.prototype; - | var slice = Ap.slice; - | var Fp = Function.prototype; - | - | if (!Fp.bind) { - | // PhantomJS doesn't support Function.prototype.bind natively, so - | // polyfill it whenever this module is required. - | Fp.bind = function(context) { - | var func = this; - | var args = slice.call(arguments, 1); - | - | function bound() { - | var invokedAsConstructor = func.prototype && (this instanceof func); - | return func.apply( - | // Ignore the context parameter when invoking the bound function - | // as a constructor. Note that this includes not only constructor - | // invocations using the new keyword but also calls to base class - | // constructors such as BaseClass.call(this, ...) or super(...). - | !invokedAsConstructor && context || this, - | args.concat(slice.call(arguments)) - | ); - | } - | - | // The bound function must share the .prototype of the unbound - | // function so that any object created by one constructor will count - | // as an instance of both constructors. - | bound.prototype = func.prototype; - | - | return bound; - | }; - | } - | - |})(); - |""".stripMargin - ), - new MemVirtualJSFile("scalaJSEnvInfo.js").withContent( - """ - |__ScalaJSEnv = { - | exitFunction: function(status) { - | window.callPhantom({ - | action: 'exit', - | returnValue: status | 0 - | }); - | } - |}; - """.stripMargin - ) - ) - - protected def writeWebpageLauncher(out: Writer): Unit = { - out.write("<html>\n<head>\n<title>Phantom.js Launcher</title>\n") - sendJS(getLibJSFiles(), out) - writeCodeLauncher(code, out) - out.write("</head>\n<body></body>\n</html>\n") - } - - protected def createTmpLauncherFile(): File = { - val webF = createTmpWebpage() - - val launcherTmpF = File.createTempFile("phantomjs-launcher", ".js") - launcherTmpF.deleteOnExit() - - val out = new FileWriter(launcherTmpF) - - try { - out.write( - s"""// Scala.js Phantom.js launcher - |var page = require('webpage').create(); - |var url = ${toJSstr(webF.getAbsolutePath)}; - |var autoExit = $autoExit; - |page.onConsoleMessage = function(msg) { - | console.log(msg); - |}; - |page.onError = function(msg, trace) { - | console.error(msg); - | if (trace && trace.length) { - | console.error(''); - | trace.forEach(function(t) { - | console.error(' ' + t.file + ':' + t.line + (t.function ? ' (in function "' + t.function +'")' : '')); - | }); - | } - | - | phantom.exit(2); - |}; - |page.onCallback = function(data) { - | if (!data.action) { - | console.error('Called callback without action'); - | phantom.exit(3); - | } else if (data.action === 'exit') { - | phantom.exit(data.returnValue || 0); - | } else if (data.action === 'setAutoExit') { - | if (typeof(data.autoExit) === 'boolean') - | autoExit = data.autoExit; - | else - | autoExit = true; - | } else { - | console.error('Unknown callback action ' + data.action); - | phantom.exit(4); - | } - |}; - |page.open(url, function (status) { - | if (autoExit || status !== 'success') - | phantom.exit(status !== 'success'); - |}); - |""".stripMargin) - } finally { - out.close() - } - - logger.debug( - "PhantomJS using launcher at: " + launcherTmpF.getAbsolutePath()) - - launcherTmpF - } - - protected def createTmpWebpage(): File = { - val webTmpF = File.createTempFile("phantomjs-launcher-webpage", ".html") - webTmpF.deleteOnExit() - - val out = new BufferedWriter(new FileWriter(webTmpF)) - try { - writeWebpageLauncher(out) - } finally { - out.close() - } - - logger.debug( - "PhantomJS using webpage launcher at: " + webTmpF.getAbsolutePath()) - - webTmpF - } - - protected def writeCodeLauncher(code: VirtualJSFile, out: Writer): Unit = { - out.write("""<script type="text/javascript">""" + "\n") - out.write("// Phantom.js code launcher\n") - out.write(s"// Origin: ${code.path}\n") - out.write("window.addEventListener('load', function() {\n") - out.write(code.content) - out.write("}, false);\n") - out.write("</script>\n") - } - } - - protected def htmlEscape(str: String): String = str.flatMap { - case '<' => "<" - case '>' => ">" - case '"' => """ - case '&' => "&" - case c => c :: Nil - } - -} - -object PhantomJSEnv { - private final val MaxByteMessageSize = 32768 // 32 KB - private final val MaxCharMessageSize = MaxByteMessageSize / 2 // 2B per char - private final val MaxCharPayloadSize = MaxCharMessageSize - 1 // frag flag -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala deleted file mode 100644 index 02c229b..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/PhantomJettyClassLoader.scala +++ /dev/null @@ -1,63 +0,0 @@ -package scala.scalajs.sbtplugin.env.phantomjs - -import scala.scalajs.tools.io.IO - -/** A special [[ClassLoader]] to load the Jetty 8 dependency of [[PhantomJSEnv]] - * in a private space. - * - * It loads everything that belongs to [[JettyWebsocketManager]] itself (while - * retrieving the requested class file from its parent. - * For all other classes, it first tries to load them from [[jettyLoader]], - * which should only contain the Jetty 8 classpath. - * If this fails, it delegates to its parent. - * - * The rationale is, that [[JettyWebsocketManager]] and its dependees can use - * the classes on the Jetty 8 classpath, while they remain hidden from the rest - * of the Java world. This allows to load another version of Jetty in the same - * JVM for the rest of the project. - */ -private[sbtplugin] class PhantomJettyClassLoader(jettyLoader: ClassLoader, - parent: ClassLoader) extends ClassLoader(parent) { - - def this(loader: ClassLoader) = - this(loader, ClassLoader.getSystemClassLoader()) - - /** Classes needed to bridge private jetty classpath and public PhantomJS - * Basically everything defined in JettyWebsocketManager. - */ - private val bridgeClasses = Set( - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$WSLogger", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$ComWebSocketListener", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$1", - "scala.scalajs.sbtplugin.env.phantomjs.JettyWebsocketManager$$anon$2" - ) - - override protected def loadClass(name: String, resolve: Boolean): Class[_] = { - if (bridgeClasses.contains(name)) { - // Load bridgeClasses manually since they must be associated to this - // class loader, rather than the parent class loader in order to find the - // jetty classes - - // First check if we have loaded it already - Option(findLoadedClass(name)) getOrElse { - val wsManager = - parent.getResourceAsStream(name.replace('.', '/') + ".class") - - if (wsManager == null) { - throw new ClassNotFoundException(name) - } else { - val buf = IO.readInputStreamToByteArray(wsManager) - defineClass(name, buf, 0, buf.length) - } - } - } else { - try { - jettyLoader.loadClass(name) - } catch { - case _: ClassNotFoundException => - super.loadClass(name, resolve) - } - } - } -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala deleted file mode 100644 index 4faac64..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketListener.scala +++ /dev/null @@ -1,10 +0,0 @@ -package scala.scalajs.sbtplugin.env.phantomjs - -private[phantomjs] trait WebsocketListener { - def onRunning(): Unit - def onOpen(): Unit - def onClose(): Unit - def onMessage(msg: String): Unit - - def log(msg: String): Unit -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala deleted file mode 100644 index a466841..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/phantomjs/WebsocketManager.scala +++ /dev/null @@ -1,10 +0,0 @@ -package scala.scalajs.sbtplugin.env.phantomjs - -private[phantomjs] trait WebsocketManager { - def start(): Unit - def stop(): Unit - def sendMessage(msg: String): Unit - def localPort: Int - def isConnected: Boolean - def isClosed: Boolean -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/LazyScalaJSScope.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/LazyScalaJSScope.scala deleted file mode 100644 index d4cdaee..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/LazyScalaJSScope.scala +++ /dev/null @@ -1,96 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.sbtplugin.env.rhino - -import scala.collection.mutable - -import org.mozilla.javascript.Scriptable - -/** A proxy for a ScalaJS "scope" field that loads scripts lazily - * - * E.g., ScalaJS.c, which is a scope with the Scala.js classes, can be - * turned to a LazyScalaJSScope. Upon first access to a field of ScalaJS.c, - * say ScalaJS.c.scala_Option, the script defining that particular - * field will be loaded. - * This is possible because the relative path to the script can be derived - * from the name of the property being accessed. - * - * It is immensely useful, because it allows to load lazily only the scripts - * that are actually needed. - */ -class LazyScalaJSScope( - coreLib: ScalaJSCoreLib, - globalScope: Scriptable, - base: Scriptable, - isModule: Boolean = false, - isTraitImpl: Boolean = false) extends Scriptable { - - private val fields = mutable.HashMap.empty[String, Any] - private var prototype: Scriptable = _ - private var parentScope: Scriptable = _ - - { - // Pre-fill fields with the properties of `base` - for (id <- base.getIds()) { - (id.asInstanceOf[Any]: @unchecked) match { - case name: String => put(name, this, base.get(name, base)) - case index: Int => put(index, this, base.get(index, base)) - } - } - } - - private def load(name: String): Unit = - coreLib.load(globalScope, propNameToEncodedName(name)) - - private def propNameToEncodedName(name: String): String = { - if (isTraitImpl) name.split("__")(0) - else if (isModule) name + "$" - else name - } - - override def getClassName() = "LazyScalaJSScope" - - override def get(name: String, start: Scriptable) = { - fields.getOrElse(name, { - load(name) - fields.getOrElse(name, Scriptable.NOT_FOUND) - }).asInstanceOf[AnyRef] - } - override def get(index: Int, start: Scriptable) = - get(index.toString, start) - - override def has(name: String, start: Scriptable) = - fields.contains(name) - override def has(index: Int, start: Scriptable) = - has(index.toString, start) - - override def put(name: String, start: Scriptable, value: Any) = { - fields(name) = value - } - override def put(index: Int, start: Scriptable, value: Any) = - put(index.toString, start, value) - - override def delete(name: String) = () - override def delete(index: Int) = () - - override def getPrototype() = prototype - override def setPrototype(value: Scriptable) = prototype = value - - override def getParentScope() = parentScope - override def setParentScope(value: Scriptable) = parentScope = value - - override def getIds() = fields.keys.toArray - - override def getDefaultValue(hint: java.lang.Class[_]) = { - base.getDefaultValue(hint) - } - - override def hasInstance(instance: Scriptable) = false -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/RhinoJSEnv.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/RhinoJSEnv.scala deleted file mode 100644 index cd35ff6..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/RhinoJSEnv.scala +++ /dev/null @@ -1,303 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.sbtplugin.env.rhino - -import scala.scalajs.tools.sem.Semantics -import scala.scalajs.tools.io._ -import scala.scalajs.tools.classpath._ -import scala.scalajs.tools.env._ -import scala.scalajs.tools.logging._ - -import scala.io.Source - -import scala.collection.mutable - -import scala.concurrent.{Future, Promise, Await} -import scala.concurrent.duration.Duration - -import org.mozilla.javascript._ - -class RhinoJSEnv(semantics: Semantics, - withDOM: Boolean = false) extends ComJSEnv { - - import RhinoJSEnv._ - - /** Executes code in an environment where the Scala.js library is set up to - * load its classes lazily. - * - * Other .js scripts in the inputs are executed eagerly before the provided - * `code` is called. - */ - override def jsRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): JSRunner = { - new Runner(classpath, code, logger, console) - } - - private class Runner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole) extends JSRunner { - def run(): Unit = internalRunJS(classpath, code, logger, console, None) - } - - override def asyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): AsyncJSRunner = { - new AsyncRunner(classpath, code, logger, console) - } - - private class AsyncRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole) extends AsyncJSRunner { - - private[this] val promise = Promise[Unit] - - private[this] val thread = new Thread { - override def run(): Unit = { - try { - internalRunJS(classpath, code, logger, console, optChannel) - promise.success(()) - } catch { - case t: Throwable => - promise.failure(t) - } - } - } - - def start(): Future[Unit] = { - thread.start() - promise.future - } - - def stop(): Unit = thread.interrupt() - - def isRunning(): Boolean = !promise.isCompleted - - def await(): Unit = Await.result(promise.future, Duration.Inf) - - protected def optChannel(): Option[Channel] = None - } - - override def comRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole): ComJSRunner = { - new ComRunner(classpath, code, logger, console) - } - - private class ComRunner(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole) - extends AsyncRunner(classpath, code, logger, console) with ComJSRunner { - - private[this] val channel = new Channel - - override protected def optChannel(): Option[Channel] = Some(channel) - - def send(msg: String): Unit = { - try { - channel.sendToJS(msg) - } catch { - case _: ChannelClosedException => - throw new ComJSEnv.ComClosedException - } - } - - def receive(): String = { - try { - channel.recvJVM() - } catch { - case _: ChannelClosedException => - throw new ComJSEnv.ComClosedException - } - } - - def close(): Unit = channel.close() - - override def stop(): Unit = { - close() - super.stop() - } - - } - - private def internalRunJS(classpath: CompleteClasspath, code: VirtualJSFile, - logger: Logger, console: JSConsole, optChannel: Option[Channel]): Unit = { - - val context = Context.enter() - try { - val scope = context.initStandardObjects() - - if (withDOM) { - // Fetch env.rhino.js from webjar - val name = "env.rhino.js" - val path = "/META-INF/resources/webjars/envjs/1.2/" + name - val resource = getClass.getResource(path) - assert(resource != null, s"need $name as resource") - - // Rhino can't optimize envjs - context.setOptimizationLevel(-1) - - // Don't print envjs header - scope.addFunction("print", args => ()) - - // Pipe file to Rhino - val reader = Source.fromURL(resource).bufferedReader - context.evaluateReader(scope, reader, name, 1, null); - - // No need to actually define print here: It is captured by envjs to - // implement console.log, which we'll override in the next statement - } - - // Make sure Rhino does not do its magic for JVM top-level packages (#364) - val PackagesObject = - ScriptableObject.getProperty(scope, "Packages").asInstanceOf[Scriptable] - val topLevelPackageIds = ScriptableObject.getPropertyIds(PackagesObject) - for (id <- topLevelPackageIds) (id: Any) match { - case name: String => ScriptableObject.deleteProperty(scope, name) - case index: Int => ScriptableObject.deleteProperty(scope, index) - case _ => // should not happen, I think, but with Rhino you never know - } - - // Setup console.log - val jsconsole = context.newObject(scope) - jsconsole.addFunction("log", _.foreach(console.log _)) - ScriptableObject.putProperty(scope, "console", jsconsole) - - // Optionally setup scalaJSCom - var recvCallback: Option[String => Unit] = None - for (channel <- optChannel) { - val comObj = context.newObject(scope) - - comObj.addFunction("send", s => - channel.sendToJVM(Context.toString(s(0)))) - - comObj.addFunction("init", s => s(0) match { - case f: Function => - val cb: String => Unit = - msg => f.call(context, scope, scope, Array(msg)) - recvCallback = Some(cb) - case _ => - sys.error("First argument to init must be a function") - }) - - comObj.addFunction("close", _ => { - // Tell JVM side we won't send anything - channel.close() - // Internally register that we're done - recvCallback = None - }) - - ScriptableObject.putProperty(scope, "scalajsCom", comObj) - } - - try { - // Make the classpath available. Either through lazy loading or by - // simply inserting - classpath match { - case cp: IRClasspath => - // Setup lazy loading classpath and source mapper - val optLoader = if (cp.scalaJSIR.nonEmpty) { - val loader = new ScalaJSCoreLib(semantics, cp) - - // Setup sourceMapper - val scalaJSenv = context.newObject(scope) - - scalaJSenv.addFunction("sourceMapper", args => { - val trace = Context.toObject(args(0), scope) - loader.mapStackTrace(trace, context, scope) - }) - - ScriptableObject.putProperty(scope, "__ScalaJSEnv", scalaJSenv) - - Some(loader) - } else { - None - } - - // Load JS libraries - cp.jsLibs.foreach(dep => context.evaluateFile(scope, dep.lib)) - - optLoader.foreach(_.insertInto(context, scope)) - case cp => - cp.allCode.foreach(context.evaluateFile(scope, _)) - } - - context.evaluateFile(scope, code) - - // Callback the com channel if necessary (if comCallback = None, channel - // wasn't initialized on the client) - for ((channel, callback) <- optChannel zip recvCallback) { - try { - while (recvCallback.isDefined) - callback(channel.recvJS()) - } catch { - case _: ChannelClosedException => - // the JVM side closed the connection - } - } - - // Enusre the channel is closed to release JVM side - optChannel.foreach(_.close) - - } catch { - case e: RhinoException => - // Trace here, since we want to be in the context to trace. - logger.trace(e) - sys.error(s"Exception while running JS code: ${e.getMessage}") - } - } finally { - Context.exit() - } - } - -} - -object RhinoJSEnv { - - /** Communication channel between the Rhino thread and the rest of the JVM */ - private class Channel { - private[this] var _closed = false - private[this] val js2jvm = mutable.Queue.empty[String] - private[this] val jvm2js = mutable.Queue.empty[String] - - def sendToJS(msg: String): Unit = synchronized { - jvm2js.enqueue(msg) - notify() - } - - def sendToJVM(msg: String): Unit = synchronized { - js2jvm.enqueue(msg) - notify() - } - - def recvJVM(): String = synchronized { - while (js2jvm.isEmpty && ensureOpen()) - wait() - - js2jvm.dequeue() - } - - def recvJS(): String = synchronized { - while (jvm2js.isEmpty && ensureOpen()) - wait() - - jvm2js.dequeue() - } - - def close(): Unit = synchronized { - _closed = true - notify() - } - - /** Throws if the channel is closed and returns true */ - private def ensureOpen(): Boolean = { - if (_closed) - throw new ChannelClosedException - true - } - } - - private class ChannelClosedException extends Exception - -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/ScalaJSCoreLib.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/ScalaJSCoreLib.scala deleted file mode 100644 index e937e5b..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/ScalaJSCoreLib.scala +++ /dev/null @@ -1,173 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.sbtplugin.env.rhino - -import scala.collection.mutable - -import org.mozilla.javascript.{Context, Scriptable} - -import scala.scalajs.ir - -import scala.scalajs.tools.sem.Semantics -import scala.scalajs.tools.javascript.{Printers, ScalaJSClassEmitter} -import scala.scalajs.tools.io._ -import scala.scalajs.tools.classpath._ -import scala.scalajs.tools.corelib._ - -class ScalaJSCoreLib(semantics: Semantics, classpath: IRClasspath) { - import ScalaJSCoreLib._ - - private val (providers, exportedSymbols) = { - val providers = mutable.Map.empty[String, VirtualScalaJSIRFile] - val exportedSymbols = mutable.ListBuffer.empty[String] - - for (irFile <- classpath.scalaJSIR) { - val info = irFile.roughInfo - providers += info.encodedName -> irFile - if (info.isExported) - exportedSymbols += info.encodedName - } - - (providers, exportedSymbols) - } - - def insertInto(context: Context, scope: Scriptable) = { - CoreJSLibs.libs(semantics).foreach(context.evaluateFile(scope, _)) - lazifyScalaJSFields(scope) - - // Make sure exported symbols are loaded - val ScalaJS = Context.toObject(scope.get("ScalaJS", scope), scope) - val c = Context.toObject(ScalaJS.get("c", ScalaJS), scope) - for (encodedName <- exportedSymbols) - c.get(encodedName, c) - } - - /** Source maps the given stack trace (where possible) */ - def mapStackTrace(stackTrace: Scriptable, - context: Context, scope: Scriptable): Scriptable = { - val count = Context.toNumber(stackTrace.get("length", stackTrace)).toInt - - // Maps file -> max line (0-based) - val neededMaps = mutable.Map.empty[String, Int] - - // Collect required line counts - for (i <- 0 until count) { - val elem = Context.toObject(stackTrace.get(i, stackTrace), scope) - val fileName = Context.toString(elem.get("fileName", elem)) - - if (fileName.endsWith(PseudoFileSuffix) && - providers.contains(fileName.stripSuffix(PseudoFileSuffix))) { - - val curMaxLine = neededMaps.getOrElse(fileName, -1) - val reqLine = Context.toNumber(elem.get("lineNumber", elem)).toInt - 1 - - if (reqLine > curMaxLine) - neededMaps.put(fileName, reqLine) - } - } - - // Map required files - val maps = - for ((fileName, maxLine) <- neededMaps) - yield (fileName, getSourceMapper(fileName, maxLine)) - - // Create new stack trace to return - val res = context.newArray(scope, count) - - for (i <- 0 until count) { - val elem = Context.toObject(stackTrace.get(i, stackTrace), scope) - val fileName = Context.toString(elem.get("fileName", elem)) - val line = Context.toNumber(elem.get("lineNumber", elem)).toInt - 1 - - val pos = maps.get(fileName).fold(ir.Position.NoPosition)(_(line)) - - val newElem = - if (pos.isDefined) newPosElem(scope, context, elem, pos) - else elem - - res.put(i, res, newElem) - } - - res - } - - private def getSourceMapper(fileName: String, untilLine: Int) = { - val irFile = providers(fileName.stripSuffix(PseudoFileSuffix)) - val mapper = new Printers.ReverseSourceMapPrinter(untilLine) - val classDef = irFile.tree - val desugared = new ScalaJSClassEmitter(semantics).genClassDef(classDef) - mapper.reverseSourceMap(desugared) - mapper - } - - private def newPosElem(scope: Scriptable, context: Context, - origElem: Scriptable, pos: ir.Position): Scriptable = { - assert(pos.isDefined) - - val elem = context.newObject(scope) - - elem.put("declaringClass", elem, origElem.get("declaringClass", origElem)) - elem.put("methodName", elem, origElem.get("methodName", origElem)) - elem.put("fileName", elem, pos.source.toString) - elem.put("lineNumber", elem, pos.line + 1) - elem.put("columnNumber", elem, pos.column + 1) - - elem - } - - private val scalaJSLazyFields = Seq( - Info("d"), - Info("c"), - Info("h"), - Info("i", isTraitImpl = true), - Info("n", isModule = true), - Info("m", isModule = true), - Info("is"), - Info("as"), - Info("isArrayOf"), - Info("asArrayOf")) - - private def lazifyScalaJSFields(scope: Scriptable) = { - val ScalaJS = Context.toObject(scope.get("ScalaJS", scope), scope) - - def makeLazyScalaJSScope(base: Scriptable, isModule: Boolean, isTraitImpl: Boolean) = - new LazyScalaJSScope(this, scope, base, isModule, isTraitImpl) - - for (Info(name, isModule, isTraitImpl) <- scalaJSLazyFields) { - val base = ScalaJS.get(name, ScalaJS).asInstanceOf[Scriptable] - val lazified = makeLazyScalaJSScope(base, isModule, isTraitImpl) - ScalaJS.put(name, ScalaJS, lazified) - } - } - - private[rhino] def load(scope: Scriptable, encodedName: String): Unit = { - providers.get(encodedName) foreach { irFile => - val codeWriter = new java.io.StringWriter - val printer = new Printers.JSTreePrinter(codeWriter) - val classDef = irFile.tree - val desugared = new ScalaJSClassEmitter(semantics).genClassDef(classDef) - printer.printTopLevelTree(desugared) - printer.complete() - val ctx = Context.getCurrentContext() - val fakeFileName = encodedName + PseudoFileSuffix - ctx.evaluateString(scope, codeWriter.toString(), - fakeFileName, 1, null) - } - } -} - -object ScalaJSCoreLib { - private case class Info(name: String, - isModule: Boolean = false, isTraitImpl: Boolean = false) - - private val EncodedNameLine = raw""""encodedName": *"([^"]+)"""".r.unanchored - - private final val PseudoFileSuffix = ".sjsir" -} diff --git a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/package.scala b/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/package.scala deleted file mode 100644 index 926fbb2..0000000 --- a/examples/scala-js/sbt-plugin/src/main/scala/scala/scalajs/sbtplugin/env/rhino/package.scala +++ /dev/null @@ -1,42 +0,0 @@ -/* __ *\ -** ________ ___ / / ___ __ ____ Scala.js sbt plugin ** -** / __/ __// _ | / / / _ | __ / // __/ (c) 2013, LAMP/EPFL ** -** __\ \/ /__/ __ |/ /__/ __ |/_// /_\ \ http://scala-js.org/ ** -** /____/\___/_/ |_/____/_/ | |__/ /____/ ** -** |/____/ ** -\* */ - - -package scala.scalajs.sbtplugin.env - -import org.mozilla.javascript._ - -import scala.scalajs.tools.io._ - -package object rhino { - - implicit class ContextOps(val self: Context) extends AnyVal { - def evaluateFile(scope: Scriptable, file: VirtualJSFile, - securityDomain: AnyRef = null): Any = { - self.evaluateString(scope, file.content, file.path, 1, securityDomain) - } - } - - implicit class ScriptableObjectOps(val self: Scriptable) { - def addFunction(name: String, function: Array[AnyRef] => Any) = { - val rhinoFunction = - new BaseFunction { - ScriptRuntime.setFunctionProtoAndParent(this, self) - override def call(context: Context, scope: Scriptable, - thisObj: Scriptable, args: Array[AnyRef]): AnyRef = { - function(args) match { - case () => Undefined.instance - case r => r.asInstanceOf[AnyRef] - } - } - } - - ScriptableObject.putProperty(self, name, rhinoFunction) - } - } -} |