JS: Introduce Window.asCoroutineDispatcher and use custom queue in
Window.awaitAnimationFrame to align all animations
diff --git a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Window.kt b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Window.kt
index ae990ce..de74844 100644
--- a/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Window.kt
+++ b/js/kotlinx-coroutines-core-js/src/main/kotlin/kotlinx/coroutines/experimental/Window.kt
@@ -19,16 +19,56 @@
 import org.w3c.dom.Window
 
 /**
+ * Converts an instance of [Window] to an implementation of [CoroutineDispatcher].
+ */
+public fun Window.asCoroutineDispatcher(): CoroutineDispatcher =
+    @Suppress("UnsafeCastFromDynamic")
+    asDynamic().coroutineDispatcher ?: WindowDispatcher(this).also {
+        asDynamic().coroutineDispatcher = it
+    }
+
+/**
  * Suspends coroutine until next JS animation frame and returns frame time on resumption.
  * The time is consistent with [window.performance.now()][org.w3c.performance.Performance.now].
  * This function is cancellable. If the [Job] of the current coroutine is completed while this suspending
  * function is waiting, this function immediately resumes with [CancellationException].
  */
 public suspend fun Window.awaitAnimationFrame(): Double = suspendCancellableCoroutine { cont ->
-    val handle = requestAnimationFrame { timestamp ->
-        with(cont) { DefaultDispatcher.resumeUndispatched(timestamp) }
-    }
-    cont.invokeOnCompletion {
-        cancelAnimationFrame(handle)
-    }
+    asWindowAnimationQueue().enqueue(cont)
 }
+
+private fun Window.asWindowAnimationQueue(): WindowAnimationQueue =
+    @Suppress("UnsafeCastFromDynamic")
+    asDynamic().coroutineAnimationQueue ?: WindowAnimationQueue(this).also {
+        asDynamic().coroutineAnimationQueue = it
+    }
+
+private class WindowAnimationQueue(private val window: Window) {
+    private val dispatcher = window.asCoroutineDispatcher()
+    private var scheduled = false
+    private var current = Queue<CancellableContinuation<Double>>()
+    private var next = Queue<CancellableContinuation<Double>>()
+    private var timestamp = 0.0
+
+    fun enqueue(cont: CancellableContinuation<Double>) {
+        next.add(cont)
+        if (!scheduled) {
+            scheduled = true
+            window.requestAnimationFrame { ts ->
+                timestamp = ts
+                val prev = current
+                current = next
+                next = prev
+                scheduled = false
+                process()
+            }
+        }
+    }
+
+    fun process() {
+        while(true) {
+            val element = current.poll() ?: return
+            with(element) { dispatcher.resumeUndispatched(timestamp) }
+        }
+    }
+}
\ No newline at end of file