|
16 | 16 |
|
17 | 17 | package org.scalajs.macrotaskexecutor
|
18 | 18 |
|
| 19 | +import scala.collection.mutable |
19 | 20 | import scala.concurrent.ExecutionContext
|
| 21 | +import scala.scalajs.js |
| 22 | +import scala.util.Random |
| 23 | +import scala.util.control.NonFatal |
20 | 24 |
|
| 25 | +/** |
| 26 | + * Based on https://github.com/YuzuJS/setImmediate |
| 27 | + */ |
21 | 28 | 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 | + } |
24 | 153 | }
|
0 commit comments