Skip to content

Commit 87c1e38

Browse files
authored
Merge pull request #7 from armanbilge/topic/port-polyfill
Polyfill copypasta
2 parents ffdbe54 + 27fd85d commit 87c1e38

File tree

4 files changed

+183
-4
lines changed

4 files changed

+183
-4
lines changed

build.sbt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,7 +111,10 @@ lazy val root = project
111111

112112
lazy val core = project
113113
.in(file("core"))
114-
.settings(name := "scala-js-macrotask-executor")
114+
.settings(
115+
name := "scala-js-macrotask-executor",
116+
libraryDependencies += "org.scalameta" %%% "munit" % "0.7.29" % Test,
117+
)
115118
.enablePlugins(ScalaJSPlugin)
116119

117120
// this project solely exists for testing purposes
@@ -121,7 +124,10 @@ lazy val webworker = project
121124
.settings(
122125
name := "scala-js-macrotask-executor-webworker",
123126
scalaJSUseMainModuleInitializer := true,
124-
libraryDependencies += ("org.scala-js" %%% "scalajs-dom" % "1.2.0").cross(CrossVersion.for3Use2_13),
127+
libraryDependencies ++= Seq(
128+
("org.scala-js" %%% "scalajs-dom" % "1.2.0").cross(CrossVersion.for3Use2_13),
129+
"org.scalameta" %%% "munit" % "0.7.29" % Test,
130+
),
125131
(Test / test) := (Test / test).dependsOn(Compile / fastOptJS).value,
126132
buildInfoKeys := Seq[BuildInfoKey](scalaVersion, baseDirectory),
127133
buildInfoPackage := "org.scalajs")

core/src/main/scala/org/scalajs/macrotaskexecutor/MacrotaskExecutor.scala

Lines changed: 131 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,138 @@
1616

1717
package org.scalajs.macrotaskexecutor
1818

19+
import scala.collection.mutable
1920
import scala.concurrent.ExecutionContext
21+
import scala.scalajs.js
22+
import scala.util.Random
23+
import scala.util.control.NonFatal
2024

25+
/**
26+
* Based on https://github.com/YuzuJS/setImmediate
27+
*/
2128
object MacrotaskExecutor extends ExecutionContext {
22-
def execute(runnable: Runnable): Unit = ???
23-
def reportFailure(cause: Throwable): Unit = ???
29+
private[this] val Undefined = "undefined"
30+
31+
def execute(runnable: Runnable): Unit =
32+
setImmediate(() => runnable.run())
33+
34+
def reportFailure(cause: Throwable): Unit =
35+
cause.printStackTrace()
36+
37+
private[this] val setImmediate: (() => Unit) => Unit = {
38+
if (js.typeOf(js.Dynamic.global.setImmediate) == Undefined) {
39+
var nextHandle = 1
40+
val tasksByHandle = mutable.Map[Int, () => Unit]()
41+
var currentlyRunningATask = false
42+
43+
def canUsePostMessage(): Boolean = {
44+
// The test against `importScripts` prevents this implementation from being installed inside a web worker,
45+
// where `global.postMessage` means something completely different and can't be used for this purpose.
46+
if (js.typeOf(js.Dynamic.global.postMessage) != Undefined && js.typeOf(
47+
js.Dynamic.global.importScripts) == Undefined) {
48+
var postMessageIsAsynchronous = true
49+
val oldOnMessage = js.Dynamic.global.onmessage
50+
51+
try {
52+
// This line throws `ReferenceError: onmessage is not defined` in JSDOMNodeJS environment
53+
js.Dynamic.global.onmessage = { () => postMessageIsAsynchronous = false }
54+
js.Dynamic.global.postMessage("", "*")
55+
js.Dynamic.global.onmessage = oldOnMessage
56+
postMessageIsAsynchronous
57+
} catch {
58+
case NonFatal(_) =>
59+
false
60+
}
61+
} else {
62+
false
63+
}
64+
}
65+
66+
def runIfPresent(handle: Int): Unit = {
67+
if (currentlyRunningATask) {
68+
js.Dynamic.global.setTimeout(() => runIfPresent(handle), 0)
69+
} else {
70+
tasksByHandle.get(handle) match {
71+
case Some(task) =>
72+
currentlyRunningATask = true
73+
try {
74+
task()
75+
} finally {
76+
tasksByHandle -= handle
77+
currentlyRunningATask = false
78+
}
79+
80+
case None =>
81+
}
82+
}
83+
84+
()
85+
}
86+
87+
if (canUsePostMessage()) {
88+
// postMessage is what we use for most modern browsers (when not in a webworker)
89+
90+
// generate a unique messagePrefix for everything we do
91+
// collision here is *extremely* unlikely, but the random makes it somewhat less so
92+
// as an example, if end-user code is using the setImmediate.js polyfill, we don't
93+
// want to accidentally collide. Then again, if they *are* using the polyfill, we
94+
// would pick it up above unless they init us first. Either way, the odds of
95+
// collision here are microscopic.
96+
val messagePrefix = "setImmediate$" + Random.nextInt() + "$"
97+
98+
def onGlobalMessage(event: js.Dynamic): Unit = {
99+
if (/*event.source == js.Dynamic.global.global &&*/ js.typeOf(
100+
event.data) == "string" && event
101+
.data
102+
.indexOf(messagePrefix)
103+
.asInstanceOf[Int] == 0) {
104+
runIfPresent(event.data.toString.substring(messagePrefix.length).toInt)
105+
}
106+
}
107+
108+
if (js.typeOf(js.Dynamic.global.addEventListener) != Undefined) {
109+
js.Dynamic.global.addEventListener("message", onGlobalMessage _, false)
110+
} else {
111+
js.Dynamic.global.attachEvent("onmessage", onGlobalMessage _)
112+
}
113+
114+
{ k =>
115+
val handle = nextHandle
116+
nextHandle += 1
117+
118+
tasksByHandle += (handle -> k)
119+
js.Dynamic.global.postMessage(messagePrefix + handle, "*")
120+
()
121+
}
122+
} else if (js.typeOf(js.Dynamic.global.MessageChannel) != Undefined) {
123+
val channel = js.Dynamic.newInstance(js.Dynamic.global.MessageChannel)()
124+
125+
channel.port1.onmessage = { (event: js.Dynamic) =>
126+
runIfPresent(event.data.asInstanceOf[Int])
127+
}
128+
129+
{ k =>
130+
val handle = nextHandle
131+
nextHandle += 1
132+
133+
tasksByHandle += (handle -> k)
134+
channel.port2.postMessage(handle)
135+
()
136+
}
137+
} else {
138+
// we don't try to look for process.nextTick since scalajs doesn't support old node
139+
// we're also not going to bother fast-pathing for IE6; just fall through
140+
141+
{ k =>
142+
js.Dynamic.global.setTimeout(k, 0)
143+
()
144+
}
145+
}
146+
} else {
147+
{ k =>
148+
js.Dynamic.global.setImmediate(k)
149+
()
150+
}
151+
}
152+
}
24153
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
/*
2+
* Copyright 2021 Scala.js (https://www.scala-js.org/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.scalajs.macrotaskexecutor
18+
19+
import munit.FunSuite
20+
21+
class MacrotaskExecutorSuite extends FunSuite {
22+
23+
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/*
2+
* Copyright 2021 Scala.js (https://www.scala-js.org/)
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.scalajs.macrotaskexecutor
18+
19+
object WebworkerSuiteRunner {
20+
def main(args: Array[String]): Unit = ()
21+
}

0 commit comments

Comments
 (0)