text: // Copyright 2018-2021 the Deno authors. All rights reserved. MIT license.
  "use strict";
  
  ((window) => {
    const core = window.Deno.core;
  
    // Shamelessly cribbed from extensions/fetch/11_streams.js
    class AssertionError extends Error {
      constructor(msg) {
        super(msg);
        this.name = "AssertionError";
      }
    }
  
    /**
     * @param {unknown} cond
     * @param {string=} msg
     * @returns {asserts cond}
     */
    function assert(cond, msg = "Assertion failed.") {
      if (!cond) {
        throw new AssertionError(msg);
      }
    }
  
    function opStopGlobalTimer() {
      core.opSync("op_global_timer_stop");
    }
  
    function opStartGlobalTimer(timeout) {
      return core.opSync("op_global_timer_start", timeout);
    }
  
    async function opWaitGlobalTimer() {
      await core.opAsync("op_global_timer");
    }
  
    function opNow() {
      return core.opSync("op_now");
    }
  
    function sleepSync(millis = 0) {
      return core.opSync("op_sleep_sync", millis);
    }
  
    // Derived from https://github.com/vadimg/js_bintrees. MIT Licensed.
  
    class RBNode {
      constructor(data) {
        this.data = data;
        this.left = null;
        this.right = null;
        this.red = true;
      }
  
      getChild(dir) {
        return dir ? this.right : this.left;
      }
  
      setChild(dir, val) {
        if (dir) {
          this.right = val;
        } else {
          this.left = val;
        }
      }
    }
  
    class RBTree {
      #comparator = null;
      #root = null;
  
      constructor(comparator) {
        this.#comparator = comparator;
        this.#root = null;
      }
  
      /** Returns `null` if tree is empty. */
      min() {
        let res = this.#root;
        if (res === null) {
          return null;
        }
        while (res.left !== null) {
          res = res.left;
        }
        return res.data;
      }
  
      /** Returns node `data` if found, `null` otherwise. */
      find(data) {
        let res = this.#root;
        while (res !== null) {
          const c = this.#comparator(data, res.data);
          if (c === 0) {
            return res.data;
          } else {
            res = res.getChild(c > 0);
          }
        }
        return null;
      }
  
      /** returns `true` if inserted, `false` if duplicate. */
      insert(data) {
        let ret = false;
  
        if (this.#root === null) {
          // empty tree
          this.#root = new RBNode(data);
          ret = true;
        } else {
          const head = new RBNode(null); // fake tree root
  
          let dir = 0;
          let last = 0;
  
          // setup
          let gp = null; // grandparent
          let ggp = head; // grand-grand-parent
          let p = null; // parent
          let node = this.#root;
          ggp.right = this.#root;
  
          // search down
          while (true) {
            if (node === null) {
              // insert new node at the bottom
              node = new RBNode(data);
              p.setChild(dir, node);
              ret = true;
            } else if (isRed(node.left) && isRed(node.right)) {
              // color flip
              node.red = true;
              node.left.red = false;
              node.right.red = false;
            }
  
            // fix red violation
            if (isRed(node) && isRed(p)) {
              const dir2 = ggp.right === gp;
  
              assert(gp);
              if (node === p.getChild(last)) {
                ggp.setChild(dir2, singleRotate(gp, !last));
              } else {
                ggp.setChild(dir2, doubleRotate(gp, !last));
              }
            }
  
            const cmp = this.#comparator(node.data, data);
  
            // stop if found
            if (cmp === 0) {
              break;
            }
  
            last = dir;
            dir = Number(cmp < 0); // Fix type
  
            // update helpers
            if (gp !== null) {
              ggp = gp;
            }
            gp = p;
            p = node;
            node = node.getChild(dir);
          }
  
          // update root
          this.#root = head.right;
        }
  
        // make root black
        this.#root.red = false;
  
        return ret;
      }
  
      /** Returns `true` if removed, `false` if not found. */
      remove(data) {
        if (this.#root === null) {
          return false;
        }
  
        const head = new RBNode(null); // fake tree root
        let node = head;
        node.right = this.#root;
        let p = null; // parent
        let gp = null; // grand parent
        let found = null; // found item
        let dir = 1;
  
        while (node.getChild(dir) !== null) {
          const last = dir;
  
          // update helpers
          gp = p;
          p = node;
          node = node.getChild(dir);
  
          const cmp = this.#comparator(data, node.data);
  
          dir = cmp > 0;
  
          // save found node
          if (cmp === 0) {
            found = node;
          }
  
          // push the red node down
          if (!isRed(node) && !isRed(node.getChild(dir))) {
            if (isRed(node.getChild(!dir))) {
              const sr = singleRotate(node, dir);
              p.setChild(last, sr);
              p = sr;
            } else if (!isRed(node.getChild(!dir))) {
              const sibling = p.getChild(!last);
              if (sibling !== null) {
                if (
                  !isRed(sibling.getChild(!last)) &&
                  !isRed(sibling.getChild(last))
                ) {
                  // color flip
                  p.red = false;
                  sibling.red = true;
                  node.red = true;
                } else {
                  assert(gp);
                  const dir2 = gp.right === p;
  
                  if (isRed(sibling.getChild(last))) {
                    gp.setChild(dir2, doubleRotate(p, last));
                  } else if (isRed(sibling.getChild(!last))) {
                    gp.setChild(dir2, singleRotate(p, last));
                  }
  
                  // ensure correct coloring
                  const gpc = gp.getChild(dir2);
                  assert(gpc);
                  gpc.red = true;
                  node.red = true;
                  assert(gpc.left);
                  gpc.left.red = false;
                  assert(gpc.right);
                  gpc.right.red = false;
                }
              }
            }
          }
        }
  
        // replace and remove if found
        if (found !== null) {
          found.data = node.data;
          assert(p);
          p.setChild(p.right === node, node.getChild(node.left === null));
        }
  
        // update root and make it black
        this.#root = head.right;
        if (this.#root !== null) {
          this.#root.red = false;
        }
  
        return found !== null;
      }
    }
  
    function isRed(node) {
      return node !== null && node.red;
    }
  
    function singleRotate(root, dir) {
      const save = root.getChild(!dir);
      assert(save);
  
      root.setChild(!dir, save.getChild(dir));
      save.setChild(dir, root);
  
      root.red = true;
      save.red = false;
  
      return save;
    }
  
    function doubleRotate(root, dir) {
      root.setChild(!dir, singleRotate(root.getChild(!dir), !dir));
      return singleRotate(root, dir);
    }
  
    const { console } = globalThis;
    const OriginalDateNow = Date.now;
  
    // Timeout values > TIMEOUT_MAX are set to 1.
    const TIMEOUT_MAX = 2 ** 31 - 1;
  
    let globalTimeoutDue = null;
  
    let nextTimerId = 1;
    const idMap = new Map();
    const dueTree = new RBTree((a, b) => a.due - b.due);
  
    function clearGlobalTimeout() {
      globalTimeoutDue = null;
      opStopGlobalTimer();
    }
  
    let pendingEvents = 0;
    const pendingFireTimers = [];
  
    /** Process and run a single ready timer macrotask.
   * This function should be registered through Deno.core.setMacrotaskCallback.
   * Returns true when all ready macrotasks have been processed, false if more
   * ready ones are available. The Isolate future would rely on the return value
   * to repeatedly invoke this function until depletion. Multiple invocations
   * of this function one at a time ensures newly ready microtasks are processed
   * before next macrotask timer callback is invoked. */
    function handleTimerMacrotask() {
      if (pendingFireTimers.length > 0) {
        fire(pendingFireTimers.shift());
        return pendingFireTimers.length === 0;
      }
      return true;
    }
  
    async function setGlobalTimeout(due, now) {
      // Since JS and Rust don't use the same clock, pass the time to rust as a
      // relative time value. On the Rust side we'll turn that into an absolute
      // value again.
      const timeout = due - now;
      assert(timeout >= 0);
      // Send message to the backend.
      globalTimeoutDue = due;
      pendingEvents++;
      // FIXME(bartlomieju): this is problematic, because `clearGlobalTimeout`
      // is synchronous. That means that timer is cancelled, but this promise is still pending
      // until next turn of event loop. This leads to "leaking of async ops" in tests;
      // because `clearTimeout/clearInterval` might be the last statement in test function
      // `opSanitizer` will immediately complain that there is pending op going on, unless
      // some timeout/defer is put in place to allow promise resolution.
      // Ideally `clearGlobalTimeout` doesn't return until this op is resolved, but
      // I'm not if that's possible.
      opStartGlobalTimer(timeout);
      await opWaitGlobalTimer();
      pendingEvents--;
      prepareReadyTimers();
    }
  
    function prepareReadyTimers() {
      const now = OriginalDateNow();
      // Bail out if we're not expecting the global timer to fire.
      if (globalTimeoutDue === null || pendingEvents > 0) {
        return;
      }
      // After firing the timers that are due now, this will hold the first timer
      // list that hasn't fired yet.
      let nextDueNode;
      while ((nextDueNode = dueTree.min()) !== null && nextDueNode.due <= now) {
        dueTree.remove(nextDueNode);
        // Fire all the timers in the list.
        for (const timer of nextDueNode.timers) {
          // With the list dropped, the timer is no longer scheduled.
          timer.scheduled = false;
          // Place the callback to pending timers to fire.
          pendingFireTimers.push(timer);
        }
      }
      setOrClearGlobalTimeout(nextDueNode && nextDueNode.due, now);
    }
  
    function setOrClearGlobalTimeout(due, now) {
      if (due == null) {
        clearGlobalTimeout();
      } else {
        setGlobalTimeout(due, now);
      }
    }
  
    function schedule(timer, now) {
      assert(!timer.scheduled);
      assert(now <= timer.due);
      // Find or create the list of timers that will fire at point-in-time `due`.
      const maybeNewDueNode = { due: timer.due, timers: [] };
      let dueNode = dueTree.find(maybeNewDueNode);
      if (dueNode === null) {
        dueTree.insert(maybeNewDueNode);
        dueNode = maybeNewDueNode;
      }
      // Append the newly scheduled timer to the list and mark it as scheduled.
      dueNode.timers.push(timer);
      timer.scheduled = true;
      // If the new timer is scheduled to fire before any timer that existed before,
      // update the global timeout to reflect this.
      if (globalTimeoutDue === null || globalTimeoutDue > timer.due) {
        setOrClearGlobalTimeout(timer.due, now);
      }
    }
  
    function unschedule(timer) {
      // Check if our timer is pending scheduling or pending firing.
      // If either is true, they are not in tree, and their idMap entry
      // will be deleted soon. Remove it from queue.
      let index = -1;
      if ((index = pendingFireTimers.indexOf(timer)) >= 0) {
        pendingFireTimers.splice(index);
        return;
      }
      // If timer is not in the 2 pending queues and is unscheduled,
      // it is not in the tree.
      if (!timer.scheduled) {
        return;
      }
      const searchKey = { due: timer.due, timers: [] };
      // Find the list of timers that will fire at point-in-time `due`.
      const list = dueTree.find(searchKey).timers;
      if (list.length === 1) {
        // Time timer is the only one in the list. Remove the entire list.
        assert(list[0] === timer);
        dueTree.remove(searchKey);
        // If the unscheduled timer was 'next up', find when the next timer that
        // still exists is due, and update the global alarm accordingly.
        if (timer.due === globalTimeoutDue) {
          const nextDueNode = dueTree.min();
          setOrClearGlobalTimeout(
            nextDueNode && nextDueNode.due,
            OriginalDateNow(),
          );
        }
      } else {
        // Multiple timers that are due at the same point in time.
        // Remove this timer from the list.
        const index = list.indexOf(timer);
        assert(index > -1);
        list.splice(index, 1);
      }
    }
  
    function fire(timer) {
      // If the timer isn't found in the ID map, that means it has been cancelled
      // between the timer firing and the promise callback (this function).
      if (!idMap.has(timer.id)) {
        return;
      }
      // Reschedule the timer if it is a repeating one, otherwise drop it.
      if (!timer.repeat) {
        // One-shot timer: remove the timer from this id-to-timer map.
        idMap.delete(timer.id);
      } else {
        // Interval timer: compute when timer was supposed to fire next.
        // However make sure to never schedule the next interval in the past.
        const now = OriginalDateNow();
        timer.due = Math.max(now, timer.due + timer.delay);
        schedule(timer, now);
      }
      // Call the user callback. Intermediate assignment is to avoid leaking `this`
      // to it, while also keeping the stack trace neat when it shows up in there.
      const callback = timer.callback;
      if ("function" === typeof callback) {
        callback();
      } else {
        (0, eval)(callback);
      }
    }
  
    function checkThis(thisArg) {
      if (thisArg !== null && thisArg !== undefined && thisArg !== globalThis) {
        throw new TypeError("Illegal invocation");
      }
    }
  
    function setTimer(
      cb,
      delay,
      args,
      repeat,
    ) {
      // If the callack is a function, bind `args` to the callback and bind `this` to globalThis(global).
      // otherwise call `String` on it, and `eval` it on calls; do not pass variardic args to the string
      let callback;
  
      if ("function" === typeof cb) {
        callback = Function.prototype.bind.call(cb, globalThis, ...args);
      } else {
        callback = String(cb);
        args = []; // args are ignored
      }
      // In the browser, the delay value must be coercible to an integer between 0
      // and INT32_MAX. Any other value will cause the timer to fire immediately.
      // We emulate this behavior.
      const now = OriginalDateNow();
      if (delay > TIMEOUT_MAX) {
        console.warn(
          `${delay} does not fit into` +
            " a 32-bit signed integer." +
            "\nTimeout duration was set to 1.",
        );
        delay = 1;
      }
      delay = Math.max(0, delay | 0);
  
      // Create a new, unscheduled timer object.
      const timer = {
        id: nextTimerId++,
        callback,
        args,
        delay,
        due: now + delay,
        repeat,
        scheduled: false,
      };
      // Register the timer's existence in the id-to-timer map.
      idMap.set(timer.id, timer);
      // Schedule the timer in the due table.
      schedule(timer, now);
      return timer.id;
    }
  
    function setTimeout(
      cb,
      delay = 0,
      ...args
    ) {
      delay >>>= 0;
      checkThis(this);
      return setTimer(cb, delay, args, false);
    }
  
    function setInterval(
      cb,
      delay = 0,
      ...args
    ) {
      delay >>>= 0;
      checkThis(this);
      return setTimer(cb, delay, args, true);
    }
  
    function clearTimer(id) {
      id >>>= 0;
      const timer = idMap.get(id);
      if (timer === undefined) {
        // Timer doesn't exist any more or never existed. This is not an error.
        return;
      }
      // Unschedule the timer if it is currently scheduled, and forget about it.
      unschedule(timer);
      idMap.delete(timer.id);
    }
  
    function clearTimeout(id = 0) {
      id >>>= 0;
      if (id === 0) {
        return;
      }
      clearTimer(id);
    }
  
    function clearInterval(id = 0) {
      id >>>= 0;
      if (id === 0) {
        return;
      }
      clearTimer(id);
    }
  
    window.__bootstrap.timers = {
      clearInterval,
      setInterval,
      clearTimeout,
      setTimeout,
      handleTimerMacrotask,
      opStopGlobalTimer,
      opStartGlobalTimer,
      opNow,
      sleepSync,
    };
  })(this);
  