chromium/third_party/blink/web_tests/external/wpt/html/webappapis/scripting/processing-model-2/unhandled-promise-rejections/support/promise-rejection-events.js

'use strict';

if (self.importScripts) {
  importScripts('/resources/testharness.js');
}

setup({
  allow_uncaught_exception: true
});

//
// Straightforward unhandledrejection tests
//
async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = Promise.reject(e);
}, 'unhandledrejection: from Promise.reject');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = new Promise(function(_, reject) {
    reject(e);
  });
}, 'unhandledrejection: from a synchronous rejection in new Promise');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = new Promise(function(_, reject) {
    queueTask(function() {
      reject(e);
    });
  });
}, 'unhandledrejection: from a task-delayed rejection');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = new Promise(function(_, reject) {
    setTimeout(function() {
      reject(e);
    }, 1);
  });
}, 'unhandledrejection: from a setTimeout-delayed rejection');

async_test(function(t) {
  var e = new Error();
  var e2 = new Error();
  var promise2;

  onUnhandledSucceed(t, e2, function() { return promise2; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  promise2 = Promise.reject(e).then(unreached, function(reason) {
    t.step(function() {
      assert_equals(reason, e);
    });
    throw e2;
  });
}, 'unhandledrejection: from a throw in a rejection handler chained off of Promise.reject');

async_test(function(t) {
  var e = new Error();
  var e2 = new Error();
  var promise2;

  onUnhandledSucceed(t, e2, function() { return promise2; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  promise2 = new Promise(function(_, reject) {
    setTimeout(function() {
      reject(e);
    }, 1);
  }).then(unreached, function(reason) {
    t.step(function() {
      assert_equals(reason, e);
    });
    throw e2;
  });
}, 'unhandledrejection: from a throw in a rejection handler chained off of a setTimeout-delayed rejection');

async_test(function(t) {
  var e = new Error();
  var e2 = new Error();
  var promise2;

  onUnhandledSucceed(t, e2, function() { return promise2; });

  var promise = new Promise(function(_, reject) {
    setTimeout(function() {
      reject(e);
      mutationObserverMicrotask(function() {
        var unreached = t.unreached_func('promise should not be fulfilled');
        promise2 = promise.then(unreached, function(reason) {
          t.step(function() {
            assert_equals(reason, e);
          });
          throw e2;
        });
      });
    }, 1);
  });
}, 'unhandledrejection: from a throw in a rejection handler attached one microtask after a setTimeout-delayed rejection');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = Promise.resolve().then(function() {
    return Promise.reject(e);
  });
}, 'unhandledrejection: from returning a Promise.reject-created rejection in a fulfillment handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = Promise.resolve().then(function() {
    throw e;
  });
}, 'unhandledrejection: from a throw in a fulfillment handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = Promise.resolve().then(function() {
    return new Promise(function(_, reject) {
      setTimeout(function() {
        reject(e);
      }, 1);
    });
  });
}, 'unhandledrejection: from returning a setTimeout-delayed rejection in a fulfillment handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = Promise.all([Promise.reject(e)]);
}, 'unhandledrejection: from Promise.reject, indirected through Promise.all');

async_test(function(t) {
  var p;

  var unhandled = function(ev) {
    if (ev.promise === p) {
      t.step(function() {
        assert_equals(ev.reason.name, 'InvalidStateError');
        assert_equals(ev.promise, p);
      });
      t.done();
    }
  };
  addEventListener('unhandledrejection', unhandled);
  ensureCleanup(t, unhandled);

  p = createImageBitmap(new Blob());
}, 'unhandledrejection: from createImageBitmap which is UA triggered');

//
// Negative unhandledrejection/rejectionhandled tests with immediate attachment
//

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  p = Promise.reject(e).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from Promise.reject');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  p = Promise.all([Promise.reject(e)]).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise from ' +
   'Promise.reject, indirecting through Promise.all');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  p = new Promise(function(_, reject) {
    reject(e);
  }).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a synchronously-rejected ' +
   'promise created with new Promise');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  p = Promise.resolve().then(function() {
    throw e;
  }).then(unreached, function(reason) {
    t.step(function() {
      assert_equals(reason, e);
    });
  });
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
   'throwing in a fulfillment handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  p = Promise.resolve().then(function() {
    return Promise.reject(e);
  }).then(unreached, function(reason) {
    t.step(function() {
      assert_equals(reason, e);
    });
  });
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
   'returning a Promise.reject-created promise in a fulfillment handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  p = Promise.resolve().then(function() {
    return new Promise(function(_, reject) {
      setTimeout(function() {
        reject(e);
      }, 1);
    });
  }).then(unreached, function(reason) {
    t.step(function() {
      assert_equals(reason, e);
    });
  });
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
   'returning a setTimeout-delayed rejection in a fulfillment handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  queueTask(function() {
    p = Promise.resolve().then(function() {
      return Promise.reject(e);
    })
    .catch(function() {});
  });
}, 'no unhandledrejection/rejectionhandled: all inside a queued task, a rejection handler attached synchronously to ' +
   'a promise created from returning a Promise.reject-created promise in a fulfillment handler');

async_test(function(t) {
  var p;

  onUnhandledFail(t, function() { return p; });

  var unreached = t.unreached_func('promise should not be fulfilled');
  p = createImageBitmap(new Blob()).then(unreached, function() {});
}, 'no unhandledrejection/rejectionhandled: rejection handler attached synchronously to a promise created from ' +
   'createImageBitmap');

//
// Negative unhandledrejection/rejectionhandled tests with microtask-delayed attachment
//

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  p = Promise.reject(e);
  mutationObserverMicrotask(function() {
    var unreached = t.unreached_func('promise should not be fulfilled');
    p.then(unreached, function() {});
  });
}, 'delayed handling: a microtask delay before attaching a handler prevents both events (Promise.reject-created ' +
   'promise)');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  p = new Promise(function(_, reject) {
    reject(e);
  });
  mutationObserverMicrotask(function() {
    var unreached = t.unreached_func('promise should not be fulfilled');
    p.then(unreached, function() {});
  });
}, 'delayed handling: a microtask delay before attaching a handler prevents both events (immediately-rejected new ' +
   'Promise-created promise)');

async_test(function(t) {
  var e = new Error();
  var p1;
  var p2;

  onUnhandledFail(t, function() { return p1; });
  onUnhandledFail(t, function() { return p2; });

  p1 = new Promise(function(_, reject) {
    mutationObserverMicrotask(function() {
      reject(e);
    });
  });
  p2 = Promise.all([p1]);
  mutationObserverMicrotask(function() {
    var unreached = t.unreached_func('promise should not be fulfilled');
    p2.then(unreached, function() {});
  });
}, 'delayed handling: a microtask delay before attaching the handler, and before rejecting the promise, indirected ' +
   'through Promise.all');

//
// Negative unhandledrejection/rejectionhandled tests with nested-microtask-delayed attachment
//

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  p = Promise.reject(e);
  mutationObserverMicrotask(function() {
    Promise.resolve().then(function() {
      mutationObserverMicrotask(function() {
        Promise.resolve().then(function() {
          p.catch(function() {});
        });
      });
    });
  });
}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  queueTask(function() {
    p = Promise.reject(e);
    mutationObserverMicrotask(function() {
      Promise.resolve().then(function() {
        mutationObserverMicrotask(function() {
          Promise.resolve().then(function() {
            p.catch(function() {});
          });
        });
      });
    });
  });
}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' +
   'all inside a queueTask');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  setTimeout(function() {
    p = Promise.reject(e);
    mutationObserverMicrotask(function() {
      Promise.resolve().then(function() {
        mutationObserverMicrotask(function() {
          Promise.resolve().then(function() {
            p.catch(function() {});
          });
        });
      });
    });
  }, 0);
}, 'microtask nesting: attaching a handler inside a combination of mutationObserverMicrotask + promise microtasks, ' +
   'all inside a setTimeout');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  p = Promise.reject(e);
  Promise.resolve().then(function() {
    mutationObserverMicrotask(function() {
      Promise.resolve().then(function() {
        mutationObserverMicrotask(function() {
          p.catch(function() {});
        });
      });
    });
  });
}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  queueTask(function() {
    p = Promise.reject(e);
    Promise.resolve().then(function() {
      mutationObserverMicrotask(function() {
        Promise.resolve().then(function() {
          mutationObserverMicrotask(function() {
            p.catch(function() {});
          });
        });
      });
    });
  });
}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' +
   'all inside a queueTask');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledFail(t, function() { return p; });

  setTimeout(function() {
    p = Promise.reject(e);
    Promise.resolve().then(function() {
      mutationObserverMicrotask(function() {
        Promise.resolve().then(function() {
          mutationObserverMicrotask(function() {
            p.catch(function() {});
          });
        });
      });
    });
  }, 0);
}, 'microtask nesting: attaching a handler inside a combination of promise microtasks + mutationObserverMicrotask, ' +
   'all inside a setTimeout');


// For workers, queueTask() involves posting tasks to other threads, so
// the following tests don't work there.

if ('document' in self) {
  //
  // Negative unhandledrejection/rejectionhandled tests with task-delayed attachment
  //

  async_test(function(t) {
    var e = new Error();
    var p;

    onUnhandledFail(t, function() { return p; });

    var _reject;
    p = new Promise(function(_, reject) {
      _reject = reject;
    });
    _reject(e);
    queueTask(function() {
      var unreached = t.unreached_func('promise should not be fulfilled');
      p.then(unreached, function() {});
    });
  }, 'delayed handling: a task delay before attaching a handler prevents unhandledrejection');

  async_test(function(t) {
    var e = new Error();
    var p;

    onUnhandledFail(t, function() { return p; });

    p = Promise.reject(e);
    queueTask(function() {
      Promise.resolve().then(function() {
        p.catch(function() {});
      });
    });
  }, 'delayed handling: queueTask after promise creation/rejection, plus promise microtasks, is not too late to ' +
     'attach a rejection handler');

  async_test(function(t) {
    var e = new Error();
    var p;

    onUnhandledFail(t, function() { return p; });

    queueTask(function() {
      Promise.resolve().then(function() {
        Promise.resolve().then(function() {
          Promise.resolve().then(function() {
            Promise.resolve().then(function() {
              p.catch(function() {});
            });
          });
        });
      });
    });
    p = Promise.reject(e);
  }, 'delayed handling: queueTask before promise creation/rejection, plus many promise microtasks, is not too ' +
     'late to attach a rejection handler');

  async_test(function(t) {
    var e = new Error();
    var p;

    onUnhandledFail(t, function() { return p; });

    p = Promise.reject(e);
    queueTask(function() {
      Promise.resolve().then(function() {
        Promise.resolve().then(function() {
          Promise.resolve().then(function() {
            Promise.resolve().then(function() {
              p.catch(function() {});
            });
          });
        });
      });
    });
  }, 'delayed handling: queueTask after promise creation/rejection, plus many promise microtasks, is not too ' +
     'late to attach a rejection handler');
}

//
// Positive unhandledrejection/rejectionhandled tests with delayed attachment
//

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  var _reject;
  p = new Promise(function(_, reject) {
    _reject = reject;
  });
  _reject(e);
  queueTask(function() {
    queueTask(function() {
      var unreached = t.unreached_func('promise should not be fulfilled');
      p.then(unreached, function() {});
    });
  });
}, 'delayed handling: a nested-task delay before attaching a handler causes unhandledrejection');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = Promise.reject(e);
  queueTask(function() {
    queueTask(function() {
      Promise.resolve().then(function() {
        p.catch(function() {});
      });
    });
  });
}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus promise microtasks, is too ' +
   'late to attach a rejection handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  queueTask(function() {
    queueTask(function() {
      Promise.resolve().then(function() {
        Promise.resolve().then(function() {
          Promise.resolve().then(function() {
            Promise.resolve().then(function() {
              p.catch(function() {});
            });
          });
        });
      });
    });
  });
  p = Promise.reject(e);
}, 'delayed handling: a nested-queueTask before promise creation/rejection, plus many promise microtasks, is ' +
   'too late to attach a rejection handler');

async_test(function(t) {
  var e = new Error();
  var p;

  onUnhandledSucceed(t, e, function() { return p; });

  p = Promise.reject(e);
  queueTask(function() {
    queueTask(function() {
      Promise.resolve().then(function() {
        Promise.resolve().then(function() {
          Promise.resolve().then(function() {
            Promise.resolve().then(function() {
              p.catch(function() {});
            });
          });
        });
      });
    });
  });
}, 'delayed handling: a nested-queueTask after promise creation/rejection, plus many promise microtasks, is ' +
   'too late to attach a rejection handler');

async_test(function(t) {
  var unhandledPromises = [];
  var unhandledReasons = [];
  var e = new Error();
  var p;

  var unhandled = function(ev) {
    if (ev.promise === p) {
      t.step(function() {
        unhandledPromises.push(ev.promise);
        unhandledReasons.push(ev.reason);
      });
    }
  };
  var handled = function(ev) {
    if (ev.promise === p) {
      t.step(function() {
        assert_array_equals(unhandledPromises, [p]);
        assert_array_equals(unhandledReasons, [e]);
        assert_equals(ev.promise, p);
        assert_equals(ev.reason, e);
      });
    }
  };
  addEventListener('unhandledrejection', unhandled);
  addEventListener('rejectionhandled', handled);
  ensureCleanup(t, unhandled, handled);

  p = new Promise(function() {
    throw e;
  });
  setTimeout(function() {
    var unreached = t.unreached_func('promise should not be fulfilled');
    p.then(unreached, function(reason) {
      assert_equals(reason, e);
      setTimeout(function() { t.done(); }, 10);
    });
  }, 10);
}, 'delayed handling: delaying handling by setTimeout(,10) will cause both events to fire');

async_test(function(t) {
  var unhandledPromises = [];
  var unhandledReasons = [];
  var p;

  var unhandled = function(ev) {
    if (ev.promise === p) {
      t.step(function() {
        unhandledPromises.push(ev.promise);
        unhandledReasons.push(ev.reason.name);
      });
    }
  };
  var handled = function(ev) {
    if (ev.promise === p) {
      t.step(function() {
        assert_array_equals(unhandledPromises, [p]);
        assert_array_equals(unhandledReasons, ['InvalidStateError']);
        assert_equals(ev.promise, p);
        assert_equals(ev.reason.name, 'InvalidStateError');
      });
    }
  };
  addEventListener('unhandledrejection', unhandled);
  addEventListener('rejectionhandled', handled);
  ensureCleanup(t, unhandled, handled);

  p = createImageBitmap(new Blob());
  setTimeout(function() {
    var unreached = t.unreached_func('promise should not be fulfilled');
    p.then(unreached, function(reason) {
      assert_equals(reason.name, 'InvalidStateError');
      setTimeout(function() { t.done(); }, 10);
    });
  }, 10);
}, 'delayed handling: delaying handling rejected promise created from createImageBitmap will cause both events to fire');

//
// Miscellaneous tests about integration with the rest of the platform
//

async_test(function(t) {
  var e = new Error();
  var l = function(ev) {
    var order = [];
    mutationObserverMicrotask(function() {
      order.push(1);
    });
    setTimeout(function() {
      order.push(2);
      t.step(function() {
        assert_array_equals(order, [1, 2]);
      });
      t.done();
    }, 1);
  };
  addEventListener('unhandledrejection', l);
  ensureCleanup(t, l);
  Promise.reject(e);
}, 'mutationObserverMicrotask vs. queueTask ordering is not disturbed inside unhandledrejection events');

// For workers, queueTask() involves posting tasks to other threads, so
// the following tests don't work there.

if ('document' in self) {

  // For the next two see https://github.com/domenic/unhandled-rejections-browser-spec/issues/2#issuecomment-121121695
  // and the following comments.

  async_test(function(t) {
    var sequenceOfEvents = [];

    addEventListener('unhandledrejection', l);
    ensureCleanup(t, l);

    var p1 = Promise.reject();
    var p2;
    queueTask(function() {
      p2 = Promise.reject();
      queueTask(function() {
        sequenceOfEvents.push('queueTask');
        checkSequence();
      });
    });

    function l(ev) {
      if (ev.promise === p1 || ev.promise === p2) {
        sequenceOfEvents.push(ev.promise);
        checkSequence();
      }
    }

    function checkSequence() {
      if (sequenceOfEvents.length === 3) {
        t.step(function() {
          assert_array_equals(sequenceOfEvents, [p1, 'queueTask', p2]);
        });
        t.done();
      }
    }
  }, 'queueTask ordering vs. the task queued for unhandled rejection notification (1)');

  async_test(function(t) {
    var sequenceOfEvents = [];

    addEventListener('unhandledrejection', l);
    ensureCleanup(t, l);

    var p2;
    queueTask(function() {
      p2 = Promise.reject();
      queueTask(function() {
        sequenceOfEvents.push('queueTask');
        checkSequence();
      });
    });

    function l(ev) {
      if (ev.promise == p2) {
        sequenceOfEvents.push(ev.promise);
        checkSequence();
      }
    }

    function checkSequence() {
      if (sequenceOfEvents.length === 2) {
        t.step(function() {
          assert_array_equals(sequenceOfEvents, ['queueTask', p2]);
        });
        t.done();
      }
    }
  }, 'queueTask ordering vs. the task queued for unhandled rejection notification (2)');

  async_test(function(t) {
    var sequenceOfEvents = [];


    addEventListener('unhandledrejection', unhandled);
    addEventListener('rejectionhandled', handled);
    ensureCleanup(t, unhandled, handled);

    var p = Promise.reject();

    function unhandled(ev) {
      if (ev.promise === p) {
        sequenceOfEvents.push('unhandled');
        checkSequence();
        setTimeout(function() {
          queueTask(function() {
            sequenceOfEvents.push('task before catch');
            checkSequence();
          });

          p.catch(function() {
            sequenceOfEvents.push('catch');
            checkSequence();
          });

          queueTask(function() {
            sequenceOfEvents.push('task after catch');
           checkSequence();
          });

          sequenceOfEvents.push('after catch');
          checkSequence();
        }, 10);
      }
    }

    function handled(ev) {
      if (ev.promise === p) {
        sequenceOfEvents.push('handled');
        checkSequence();
      }
    }

    function checkSequence() {
      if (sequenceOfEvents.length === 6) {
        t.step(function() {
          assert_array_equals(sequenceOfEvents,
            ['unhandled', 'after catch', 'catch', 'task before catch', 'handled', 'task after catch']);
        });
        t.done();
      }
    }
  }, 'rejectionhandled is dispatched from a queued task, and not immediately');
}

//
// HELPERS
//

// This function queues a task in "DOM manipulation task source" in window
// context, but not in workers.
function queueTask(f) {
  if ('document' in self) {
    var d = document.createElement("details");
    d.ontoggle = function() {
      f();
    };
    d.setAttribute("open", "");
  } else {
    // We need to fix this to use something that can queue tasks in
    // "DOM manipulation task source" to ensure the order is correct
    var channel = new MessageChannel();
    channel.port1.onmessage = function() { channel.port1.close(); f(); };
    channel.port2.postMessage('abusingpostmessageforfunandprofit');
    channel.port2.close();
  }
}

function mutationObserverMicrotask(f) {
  if ('document' in self) {
    var observer = new MutationObserver(function() { f(); });
    var node = document.createTextNode('');
    observer.observe(node, { characterData: true });
    node.data = 'foo';
  } else {
    // We don't have mutation observers on workers, so just post a promise-based
    // microtask.
    Promise.resolve().then(function() { f(); });
  }
}

function onUnhandledSucceed(t, expectedReason, expectedPromiseGetter) {
  var l = function(ev) {
    if (ev.promise === expectedPromiseGetter()) {
      t.step(function() {
        assert_equals(ev.reason, expectedReason);
        assert_equals(ev.promise, expectedPromiseGetter());
      });
      t.done();
    }
  };
  addEventListener('unhandledrejection', l);
  ensureCleanup(t, l);
}

function onUnhandledFail(t, expectedPromiseGetter) {
  var unhandled = function(evt) {
    if (evt.promise === expectedPromiseGetter()) {
      t.step(function() {
        assert_unreached('unhandledrejection event is not supposed to be triggered');
      });
    }
  };
  var handled = function(evt) {
    if (evt.promise === expectedPromiseGetter()) {
      t.step(function() {
        assert_unreached('rejectionhandled event is not supposed to be triggered');
      });
    }
  };
  addEventListener('unhandledrejection', unhandled);
  addEventListener('rejectionhandled', handled);
  ensureCleanup(t, unhandled, handled);
  setTimeout(function() {
    t.done();
  }, 10);
}

function ensureCleanup(t, unhandled, handled) {
  t.add_cleanup(function() {
    if (unhandled)
      removeEventListener('unhandledrejection', unhandled);
    if (handled)
      removeEventListener('rejectionhandled', handled);
  });
}

done();