tor-browser

The Tor Browser
git clone https://git.dasho.dev/tor-browser.git
Log | Files | Refs | README | LICENSE

observable-from.any.js (52280B)


      1 // Because we test that the global error handler is called at various times.
      2 setup({allow_uncaught_exception: true});
      3 
      4 test(() => {
      5  assert_equals(typeof Observable.from, "function",
      6      "Observable.from() is a function");
      7 }, "from(): Observable.from() is a function");
      8 
      9 test(() => {
     10  assert_throws_js(TypeError, () => Observable.from(10),
     11      "Number cannot convert to an Observable");
     12  assert_throws_js(TypeError, () => Observable.from(true),
     13      "Boolean cannot convert to an Observable");
     14  assert_throws_js(TypeError, () => Observable.from("String"),
     15      "String cannot convert to an Observable");
     16  assert_throws_js(TypeError, () => Observable.from({a: 10}),
     17      "Object cannot convert to an Observable");
     18  assert_throws_js(TypeError, () => Observable.from(Symbol.iterator),
     19      "Bare Symbol.iterator cannot convert to an Observable");
     20  assert_throws_js(TypeError, () => Observable.from(Promise),
     21      "Promise constructor cannot convert to an Observable");
     22 }, "from(): Failed conversions");
     23 
     24 test(() => {
     25  const target = new EventTarget();
     26  const observable = target.when('custom');
     27  const from_observable = Observable.from(observable);
     28  assert_equals(observable, from_observable);
     29 }, "from(): Given an observable, it returns that exact observable");
     30 
     31 test(() => {
     32  let completeCalled = false;
     33  const results = [];
     34  const array = [1, 2, 3, 'a', new Date(), 15, [12]];
     35  const observable = Observable.from(array);
     36  observable.subscribe({
     37    next: v => results.push(v),
     38    error: e => assert_unreached('error is not called'),
     39    complete: () => completeCalled = true
     40  });
     41 
     42  assert_array_equals(results, array);
     43  assert_true(completeCalled);
     44 }, "from(): Given an array");
     45 
     46 test(() => {
     47  const iterable = {
     48    [Symbol.iterator]() {
     49      let n = 0;
     50      return {
     51        next() {
     52          n++;
     53          if (n <= 3) {
     54            return { value: n, done: false };
     55          }
     56          return { value: undefined, done: true };
     57        },
     58      };
     59    },
     60  };
     61 
     62  const observable = Observable.from(iterable);
     63 
     64  assert_true(observable instanceof Observable, "Observable.from() returns an Observable");
     65 
     66  const results = [];
     67 
     68  observable.subscribe({
     69    next: (value) => results.push(value),
     70    error: () => assert_unreached("should not error"),
     71    complete: () => results.push("complete"),
     72  });
     73 
     74  assert_array_equals(results, [1, 2, 3, "complete"],
     75      "Subscription pushes iterable values out to Observable");
     76 
     77  // A second subscription should restart iteration.
     78  observable.subscribe({
     79    next: (value) => results.push(value),
     80    error: () => assert_unreached("should not error"),
     81    complete: () => results.push("complete2"),
     82  });
     83 
     84  assert_array_equals(results, [1, 2, 3, "complete", 1, 2, 3, "complete2"],
     85      "Subscribing again causes another fresh iteration on an un-exhausted iterable");
     86 }, "from(): Iterable converts to Observable");
     87 
     88 // This test, and the variants below it, test the web-observable side-effects of
     89 // converting an iterable object to an Observable. Specifically, it tracks
     90 // exactly when the %Symbol.iterator% method is *retrieved* from the object,
     91 // invoked, and what its error-throwing side-effects are.
     92 //
     93 // Even more specifically, we assert that the %Symbol.iterator% method is
     94 // retrieved a single time when converting to an Observable, and then again when
     95 // subscribing to the converted Observable. This makes it possible for the
     96 // %Symbol.iterator% method getter to change return values in between conversion
     97 // and subscription. See https://github.com/WICG/observable/issues/127 for
     98 // related discussion.
     99 test(() => {
    100  const results = [];
    101 
    102  const iterable = {
    103    get [Symbol.iterator]() {
    104      results.push("[Symbol.iterator] method GETTER");
    105      return function() {
    106        results.push("[Symbol.iterator implementation]");
    107        return {
    108          get next() {
    109            results.push("next() method GETTER");
    110            return function() {
    111              results.push("next() implementation");
    112              return {value: undefined, done: true};
    113            };
    114          },
    115        };
    116      };
    117    },
    118  };
    119 
    120  const observable = Observable.from(iterable);
    121  assert_array_equals(results, ["[Symbol.iterator] method GETTER"]);
    122 
    123  let thrownError = null;
    124  observable.subscribe();
    125  assert_array_equals(results, [
    126    "[Symbol.iterator] method GETTER",
    127    "[Symbol.iterator] method GETTER",
    128    "[Symbol.iterator implementation]",
    129    "next() method GETTER",
    130    "next() implementation"
    131  ]);
    132 }, "from(): [Symbol.iterator] side-effects (one observable)");
    133 
    134 // This tests that once `Observable.from()` detects a non-null and non-undefined
    135 // `[Symbol.iterator]` property, we've committed to converting as an iterable.
    136 // If the value of that property is then not callable, we don't silently move on
    137 // to the next conversion type — we throw a TypeError.
    138 //
    139 // That's because that's what TC39's `GetMethod()` [1] calls for, which is what
    140 // `Observable.from()` first uses in the iterable conversion branch [2].
    141 //
    142 // [1]: https://tc39.es/ecma262/multipage/abstract-operations.html#sec-getmethod
    143 // [2]: http://wicg.github.io/observable/#from-iterable-conversion
    144 test(() => {
    145  let results = [];
    146  const iterable = {
    147    [Symbol.iterator]: 10,
    148  };
    149 
    150  let errorThrown = null;
    151  try {
    152    Observable.from(iterable);
    153  } catch(e) {
    154    errorThrown = e;
    155  }
    156 
    157  assert_true(errorThrown instanceof TypeError);
    158 }, "from(): [Symbol.iterator] not callable");
    159 
    160 test(() => {
    161  let results = [];
    162  const iterable = {
    163    calledOnce: false,
    164    get [Symbol.iterator]() {
    165      if (this.calledOnce) {
    166        // Return a non-callable primitive the second time `@@iterator` is
    167        // called.
    168        return 10;
    169      }
    170 
    171      this.calledOnce = true;
    172      return this.validImplementation;
    173    },
    174    validImplementation: () => {
    175      return {
    176        next() { return {done: true}; }
    177      }
    178    }
    179  };
    180 
    181  let errorThrown = null;
    182 
    183  const observable = Observable.from(iterable);
    184  observable.subscribe({
    185    next: v => results.push("should not be called"),
    186    error: e => {
    187      errorThrown = e;
    188      results.push(e);
    189    },
    190  });
    191 
    192  assert_array_equals(results, [errorThrown],
    193      "An error was plumbed through the Observable");
    194  assert_true(errorThrown instanceof TypeError);
    195 }, "from(): [Symbol.iterator] not callable AFTER SUBSCRIBE throws");
    196 
    197 test(() => {
    198  let results = [];
    199  const iterable = {
    200    calledOnce: false,
    201    validImplementation: () => {
    202      return {
    203        next() { return {done: true}; }
    204      }
    205    },
    206    get [Symbol.iterator]() {
    207      if (this.calledOnce) {
    208        // Return null the second time `@@iterator` is called.
    209        return null;
    210      }
    211 
    212      this.calledOnce = true;
    213      return this.validImplementation;
    214    }
    215  };
    216 
    217  let errorThrown = null;
    218 
    219  const observable = Observable.from(iterable);
    220  observable.subscribe({
    221    next: v => results.push("should not be called"),
    222    error: e => {
    223      errorThrown = e;
    224      results.push(e);
    225    },
    226  });
    227 
    228  assert_array_equals(results, [errorThrown],
    229      "An error was plumbed through the Observable");
    230  assert_true(errorThrown instanceof TypeError);
    231 }, "from(): [Symbol.iterator] returns null AFTER SUBSCRIBE throws");
    232 
    233 test(() => {
    234  let results = [];
    235  const customError = new Error("@@iterator override error");
    236 
    237  const iterable = {
    238    numTimesCalled: 0,
    239 
    240    // The first time this getter is called, it returns a legitimate function
    241    // that, when called, returns an iterator. Every other time it returns an
    242    // error-throwing function that does not return an iterator.
    243    get [Symbol.iterator]() {
    244      this.numTimesCalled++;
    245      results.push("[Symbol.iterator] method GETTER");
    246 
    247      if (this.numTimesCalled === 1) {
    248        return this.validIteratorImplementation;
    249      } else {
    250        return this.errorThrowingIteratorImplementation;
    251      }
    252    },
    253 
    254    validIteratorImplementation: function() {
    255      results.push("[Symbol.iterator implementation]");
    256      return {
    257        get next() {
    258          results.push("next() method GETTER");
    259          return function() {
    260            results.push("next() implementation");
    261            return {value: undefined, done: true};
    262          }
    263        }
    264      };
    265    },
    266    errorThrowingIteratorImplementation: function() {
    267      results.push("Error-throwing [Symbol.iterator] implementation");
    268      throw customError;
    269    },
    270  };
    271 
    272  const observable = Observable.from(iterable);
    273  assert_array_equals(results, [
    274    "[Symbol.iterator] method GETTER",
    275  ]);
    276 
    277  // Override iterable's `[Symbol.iterator]` protocol with an error-throwing
    278  // function. We assert that on subscription, this method (the new `@@iterator`
    279  // implementation), is called because only the raw JS object gets stored in
    280  // the Observable that results in conversion. This raw value must get
    281  // re-converted to an iterable once iteration is about to start.
    282 
    283  let thrownError = null;
    284  observable.subscribe({
    285    error: e => thrownError = e,
    286  });
    287 
    288  assert_equals(thrownError, customError,
    289      "Error thrown from next() is passed to the error() handler");
    290  assert_array_equals(results, [
    291    // Old:
    292    "[Symbol.iterator] method GETTER",
    293    // New:
    294    "[Symbol.iterator] method GETTER",
    295    "Error-throwing [Symbol.iterator] implementation"
    296  ]);
    297 }, "from(): [Symbol.iterator] is not cached");
    298 
    299 // Similar to the above test, but with more Observables!
    300 test(() => {
    301  const results = [];
    302  let numTimesSymbolIteratorCalled = 0;
    303  let numTimesNextCalled = 0;
    304 
    305  const iterable = {
    306    get [Symbol.iterator]() {
    307      results.push("[Symbol.iterator] method GETTER");
    308      return this.internalIteratorImplementation;
    309    },
    310    set [Symbol.iterator](func) {
    311      this.internalIteratorImplementation = func;
    312    },
    313 
    314    internalIteratorImplementation: function() {
    315      results.push("[Symbol.iterator] implementation");
    316      return {
    317        get next() {
    318          results.push("next() method GETTER");
    319          return function() {
    320            results.push("next() implementation");
    321            return {value: undefined, done: true};
    322          };
    323        },
    324      };
    325    },
    326  };
    327 
    328  const obs1 = Observable.from(iterable);
    329  const obs2 = Observable.from(iterable);
    330  const obs3 = Observable.from(iterable);
    331  const obs4 = Observable.from(obs3);
    332  assert_equals(obs3, obs4);
    333 
    334  assert_array_equals(results, [
    335    "[Symbol.iterator] method GETTER",
    336    "[Symbol.iterator] method GETTER",
    337    "[Symbol.iterator] method GETTER",
    338  ]);
    339 
    340  obs1.subscribe();
    341  assert_array_equals(results, [
    342    // Old:
    343    "[Symbol.iterator] method GETTER",
    344    "[Symbol.iterator] method GETTER",
    345    "[Symbol.iterator] method GETTER",
    346    // New:
    347    "[Symbol.iterator] method GETTER",
    348    "[Symbol.iterator] implementation",
    349    "next() method GETTER",
    350    "next() implementation",
    351  ]);
    352 
    353  iterable[Symbol.iterator] = () => {
    354    results.push("Error-throwing [Symbol.iterator] implementation");
    355    throw new Error('Symbol.iterator override error');
    356  };
    357 
    358  let errorCount = 0;
    359 
    360  const observer = {error: e => errorCount++};
    361  obs2.subscribe(observer);
    362  obs3.subscribe(observer);
    363  obs4.subscribe(observer);
    364  assert_equals(errorCount, 3,
    365      "Error-throwing `@@iterator` implementation is called once per " +
    366      "subscription");
    367 
    368  assert_array_equals(results, [
    369    // Old:
    370    "[Symbol.iterator] method GETTER",
    371    "[Symbol.iterator] method GETTER",
    372    "[Symbol.iterator] method GETTER",
    373    "[Symbol.iterator] method GETTER",
    374    "[Symbol.iterator] implementation",
    375    "next() method GETTER",
    376    "next() implementation",
    377    // New:
    378    "[Symbol.iterator] method GETTER",
    379    "Error-throwing [Symbol.iterator] implementation",
    380    "[Symbol.iterator] method GETTER",
    381    "Error-throwing [Symbol.iterator] implementation",
    382    "[Symbol.iterator] method GETTER",
    383    "Error-throwing [Symbol.iterator] implementation",
    384  ]);
    385 }, "from(): [Symbol.iterator] side-effects (many observables)");
    386 
    387 test(() => {
    388  const customError = new Error('@@iterator next() error');
    389  const iterable = {
    390    [Symbol.iterator]() {
    391      return {
    392        next() {
    393          throw customError;
    394        }
    395      };
    396    }
    397  };
    398 
    399  let thrownError = null;
    400  Observable.from(iterable).subscribe({
    401    error: e => thrownError = e,
    402  });
    403 
    404  assert_equals(thrownError, customError,
    405      "Error thrown from next() is passed to the error() handler");
    406 }, "from(): [Symbol.iterator] next() throws error");
    407 
    408 promise_test(async () => {
    409  const promise = Promise.resolve('value');
    410  const observable = Observable.from(promise);
    411 
    412  assert_true(observable instanceof Observable, "Converts to Observable");
    413 
    414  const results = [];
    415 
    416  observable.subscribe({
    417    next: (value) => results.push(value),
    418    error: () => assert_unreached("error() is not called"),
    419    complete: () => results.push("complete()"),
    420  });
    421 
    422  assert_array_equals(results, [], "Observable does not emit synchronously");
    423 
    424  await promise;
    425 
    426  assert_array_equals(results, ["value", "complete()"],
    427      "Observable emits and completes after Promise resolves");
    428 }, "from(): Converts Promise to Observable");
    429 
    430 promise_test(async t => {
    431  let unhandledRejectionHandlerCalled = false;
    432  const unhandledRejectionHandler = () => {
    433    unhandledRejectionHandlerCalled = true;
    434  };
    435 
    436  self.addEventListener("unhandledrejection", unhandledRejectionHandler);
    437  t.add_cleanup(() => self.removeEventListener("unhandledrejection", unhandledRejectionHandler));
    438 
    439  const promise = Promise.reject("reason");
    440  const observable = Observable.from(promise);
    441 
    442  assert_true(observable instanceof Observable, "Converts to Observable");
    443 
    444  const results = [];
    445 
    446  observable.subscribe({
    447    next: (value) => assert_unreached("next() not called"),
    448    error: (error) => results.push(error),
    449    complete: () => assert_unreached("complete() not called"),
    450  });
    451 
    452  assert_array_equals(results, [], "Observable does not emit synchronously");
    453 
    454  let catchBlockEntered = false;
    455  try {
    456    await promise;
    457  } catch {
    458    catchBlockEntered = true;
    459  }
    460 
    461  assert_true(catchBlockEntered, "Catch block entered");
    462  assert_false(unhandledRejectionHandlerCalled, "No unhandledrejection event");
    463  assert_array_equals(results, ["reason"],
    464      "Observable emits error() after Promise rejects");
    465 }, "from(): Converts rejected Promise to Observable. No " +
    466   "`unhandledrejection` event when error is handled by subscription");
    467 
    468 promise_test(async t => {
    469  let unhandledRejectionHandlerCalled = false;
    470  const unhandledRejectionHandler = () => {
    471    unhandledRejectionHandlerCalled = true;
    472  };
    473 
    474  self.addEventListener("unhandledrejection", unhandledRejectionHandler);
    475  t.add_cleanup(() => self.removeEventListener("unhandledrejection", unhandledRejectionHandler));
    476 
    477  let errorReported = null;
    478  self.addEventListener("error", e => errorReported = e, { once: true });
    479 
    480  let catchBlockEntered = false;
    481  try {
    482    const promise = Promise.reject("custom reason");
    483    const observable = Observable.from(promise);
    484 
    485    observable.subscribe();
    486    await promise;
    487  } catch {
    488    catchBlockEntered = true;
    489  }
    490 
    491  assert_true(catchBlockEntered, "Catch block entered");
    492  assert_false(unhandledRejectionHandlerCalled,
    493      "No unhandledrejection event, because error got reported to global");
    494  assert_not_equals(errorReported, null, "Error was reported to the global");
    495 
    496  assert_true(errorReported.message.includes("custom reason"),
    497      "Error message matches");
    498  assert_equals(errorReported.lineno, 0, "Error lineno is 0");
    499  assert_equals(errorReported.colno, 0, "Error lineno is 0");
    500  assert_equals(errorReported.error, "custom reason",
    501      "Error object is equivalent");
    502 }, "from(): Rejections not handled by subscription are reported to the " +
    503   "global, and still not sent as an unhandledrejection event");
    504 
    505 test(() => {
    506  const results = [];
    507  const observable = new Observable(subscriber => {
    508    subscriber.next('from Observable');
    509    subscriber.complete();
    510  });
    511 
    512  observable[Symbol.iterator] = () => {
    513    results.push('Symbol.iterator() called');
    514    return {
    515      next() {
    516        return {value: 'from @@iterator', done: true};
    517      }
    518    };
    519  };
    520 
    521  Observable.from(observable).subscribe({
    522    next: v => results.push(v),
    523    complete: () => results.push("complete"),
    524  });
    525 
    526  assert_array_equals(results, ["from Observable", "complete"]);
    527 }, "from(): Observable that implements @@iterator protocol gets converted " +
    528   "as an Observable, not iterator");
    529 
    530 test(() => {
    531  const results = [];
    532  const promise = new Promise(resolve => {
    533    resolve('from Promise');
    534  });
    535 
    536  promise[Symbol.iterator] = () => {
    537    let done = false;
    538    return {
    539      next() {
    540        if (!done) {
    541          done = true;
    542          return {value: 'from @@iterator', done: false};
    543        } else {
    544          return {value: undefined, done: true};
    545        }
    546      }
    547    };
    548  };
    549 
    550  Observable.from(promise).subscribe({
    551    next: v => results.push(v),
    552    complete: () => results.push("complete"),
    553  });
    554 
    555  assert_array_equals(results, ["from @@iterator", "complete"]);
    556 }, "from(): Promise that implements @@iterator protocol gets converted as " +
    557   "an iterable, not Promise");
    558 
    559 // When the [Symbol.iterator] method on a given object is undefined, we don't
    560 // try to convert the object to an Observable via the iterable protocol. The
    561 // Observable specification *also* does the same thing if the [Symbol.iterator]
    562 // method is *null*. That is, in that case we also skip the conversion via
    563 // iterable protocol, and continue to try and convert the object as another type
    564 // (in this case, a Promise).
    565 promise_test(async () => {
    566  const promise = new Promise(resolve => resolve('from Promise'));
    567  assert_equals(promise[Symbol.iterator], undefined);
    568  promise[Symbol.iterator] = null;
    569  assert_equals(promise[Symbol.iterator], null);
    570 
    571  const value = await new Promise(resolve => {
    572    Observable.from(promise).subscribe(value => resolve(value));
    573  });
    574 
    575  assert_equals(value, 'from Promise');
    576 }, "from(): Promise whose [Symbol.iterator] returns null converts as Promise");
    577 
    578 // This is a more sensitive test, which asserts that even just trying to reach
    579 // for the [Symbol.iterator] method on an object whose *getter* for the
    580 // [Symbol.iterator] method throws an error, results in `Observable#from()`
    581 // rethrowing that error.
    582 test(() => {
    583  const error = new Error('thrown from @@iterator getter');
    584  const obj = {
    585    get [Symbol.iterator]() {
    586      throw error;
    587    }
    588  }
    589 
    590  try {
    591    Observable.from(obj);
    592    assert_unreached("from() conversion throws");
    593  } catch(e) {
    594    assert_equals(e, error);
    595  }
    596 }, "from(): Rethrows the error when Converting an object whose @@iterator " +
    597   "method *getter* throws an error");
    598 
    599 // This test exercises the line of spec prose that says:
    600 //
    601 // "If |asyncIteratorMethodRecord|'s [[Value]] is undefined or null, then jump
    602 // to the step labeled 'From iterable'."
    603 test(() => {
    604  const sync_iterable = {
    605    [Symbol.asyncIterator]: null,
    606    [Symbol.iterator]() {
    607      return {
    608        value: 0,
    609        next() {
    610          if (this.value === 2)
    611            return {value: undefined, done: true};
    612          else
    613            return {value: this.value++, done: false};
    614        }
    615      }
    616    },
    617  };
    618 
    619  const results = [];
    620  const source = Observable.from(sync_iterable).subscribe(v => results.push(v));
    621  assert_array_equals(results, [0, 1]);
    622 }, "from(): Async iterable protocol null, converts as iterator");
    623 
    624 promise_test(async t => {
    625  const results = [];
    626  const async_iterable = {
    627    [Symbol.asyncIterator]() {
    628      results.push("[Symbol.asyncIterator]() invoked");
    629      return {
    630        val: 0,
    631        next() {
    632          return new Promise(resolve => {
    633            t.step_timeout(() => {
    634              resolve({
    635                value: this.val,
    636                done: this.val++ === 4 ? true : false,
    637              });
    638            }, 400);
    639          });
    640        },
    641      };
    642    },
    643  };
    644 
    645  const source = Observable.from(async_iterable);
    646  assert_array_equals(results, []);
    647 
    648  await new Promise(resolve => {
    649    source.subscribe({
    650      next: v => {
    651        results.push(`Observing ${v}`);
    652        queueMicrotask(() => results.push(`next() microtask interleaving (v=${v})`));
    653      },
    654      complete: () => {
    655        results.push('complete()');
    656        resolve();
    657      },
    658    });
    659  });
    660 
    661  assert_array_equals(results, [
    662    "[Symbol.asyncIterator]() invoked",
    663    "Observing 0",
    664    "next() microtask interleaving (v=0)",
    665    "Observing 1",
    666    "next() microtask interleaving (v=1)",
    667    "Observing 2",
    668    "next() microtask interleaving (v=2)",
    669    "Observing 3",
    670    "next() microtask interleaving (v=3)",
    671    "complete()",
    672  ]);
    673 }, "from(): Asynchronous iterable conversion");
    674 
    675 // This test is a more chaotic version of the above. It ensures that a single
    676 // Observable can handle multiple in-flight subscriptions to the same underlying
    677 // async iterable without the two subscriptions competing. It asserts that the
    678 // asynchronous values are pushed to the observers in the correct order.
    679 promise_test(async t => {
    680  const async_iterable = {
    681    [Symbol.asyncIterator]() {
    682      return {
    683        val: 0,
    684        next() {
    685          // Returns a Promise that resolves in a random amount of time less
    686          // than a second.
    687          return new Promise(resolve => {
    688            t.step_timeout(() => resolve({
    689              value: this.val,
    690              done: this.val++ === 4 ? true : false,
    691            }), 200);
    692          });
    693        },
    694      };
    695    },
    696  };
    697 
    698  const results = [];
    699  const source = Observable.from(async_iterable);
    700 
    701  const promise = new Promise(resolve => {
    702    source.subscribe({
    703      next: v => {
    704        results.push(`${v}-first-sub`);
    705 
    706        // Half-way through the first subscription, start another subscription.
    707        if (v === 0) {
    708          source.subscribe({
    709            next: v => results.push(`${v}-second-sub`),
    710            complete: () => {
    711              results.push('complete-second-sub');
    712              resolve();
    713            }
    714          });
    715        }
    716      },
    717      complete: () => {
    718        results.push('complete-first-sub');
    719        resolve();
    720      }
    721    });
    722  });
    723 
    724  await promise;
    725  assert_array_equals(results, [
    726    '0-first-sub',
    727 
    728    '1-first-sub',
    729    '1-second-sub',
    730 
    731    '2-first-sub',
    732    '2-second-sub',
    733 
    734    '3-first-sub',
    735    '3-second-sub',
    736 
    737    'complete-first-sub',
    738    'complete-second-sub',
    739  ]);
    740 }, "from(): Asynchronous iterable multiple in-flight subscriptions");
    741 // This test is like the above, ensuring that multiple subscriptions to the same
    742 // sync-iterable-converted-Observable can exist at a time. Since sync iterables
    743 // push all of their values to the Observable synchronously, the way to do this
    744 // is subscribe to the sync iterable Observable *inside* the next handler of the
    745 // same Observable.
    746 test(() => {
    747  const results = [];
    748 
    749  const array = [1, 2, 3, 4, 5];
    750  const source = Observable.from(array);
    751  source.subscribe({
    752    next: v => {
    753      results.push(`${v}-first-sub`);
    754      if (v === 3) {
    755        // Pushes all 5 values to `results` right after the first instance of `3`.
    756        source.subscribe({
    757          next: v => results.push(`${v}-second-sub`),
    758          complete: () => results.push('complete-second-sub'),
    759        });
    760      }
    761    },
    762    complete: () => results.push('complete-first-sub'),
    763  });
    764 
    765  assert_array_equals(results, [
    766    // These values are pushed when there is only a single subscription.
    767    '1-first-sub', '2-first-sub', '3-first-sub',
    768    // These values are pushed in the correct order, for two subscriptions.
    769    '4-first-sub', '4-second-sub',
    770    '5-first-sub', '5-second-sub',
    771    'complete-first-sub', 'complete-second-sub',
    772  ]);
    773 }, "from(): Sync iterable multiple in-flight subscriptions");
    774 
    775 promise_test(async () => {
    776  const async_generator = async function*() {
    777    yield 1;
    778    yield 2;
    779    yield 3;
    780  };
    781 
    782  const results = [];
    783  const source = Observable.from(async_generator());
    784 
    785  const subscribeFunction = function(resolve) {
    786    source.subscribe({
    787      next: v => results.push(v),
    788      complete: () => resolve(),
    789    });
    790  }
    791  await new Promise(subscribeFunction);
    792  assert_array_equals(results, [1, 2, 3]);
    793  await new Promise(subscribeFunction);
    794  assert_array_equals(results, [1, 2, 3]);
    795 }, "from(): Asynchronous generator conversion: can only be used once");
    796 
    797 // The value returned by an async iterator object's `next()` method is supposed
    798 // to be a Promise. But this requirement "isn't enforced": see [1]. Therefore,
    799 // the Observable spec unconditionally wraps the return value in a resolved
    800 // Promise, as is standard practice [2].
    801 //
    802 // This test ensures that even if the object returned from an async iterator's
    803 // `next()` method is a synchronously-available object with `done: true`
    804 // (instead of a Promise), the `done` property is STILL not retrieved
    805 // synchronously. In other words, we test that the Promise-wrapping is
    806 // implemented.
    807 //
    808 // [1]: https://tc39.es/ecma262/#table-async-iterator-r
    809 // [2]: https://matrixlogs.bakkot.com/WHATWG/2024-08-30#L30
    810 promise_test(async () => {
    811  const results = [];
    812 
    813  const async_iterable = {
    814    [Symbol.asyncIterator]() {
    815      return {
    816        next() {
    817          return {
    818            value: undefined,
    819            get done() {
    820              results.push('done() GETTER called');
    821              return true;
    822            },
    823          };
    824        },
    825      };
    826    },
    827  };
    828 
    829  const source = Observable.from(async_iterable);
    830  assert_array_equals(results, []);
    831 
    832  queueMicrotask(() => results.push('Microtask queued before subscription'));
    833  source.subscribe();
    834  assert_array_equals(results, []);
    835 
    836  await Promise.resolve();
    837  assert_array_equals(results, [
    838    "Microtask queued before subscription",
    839    "done() GETTER called",
    840  ]);
    841 }, "from(): Promise-wrapping semantics of IteratorResult interface");
    842 
    843 // Errors thrown from [Symbol.asyncIterator] are propagated to the observer
    844 // synchronously. This is because in language constructs (i.e., for-await of
    845 // loops) that invoke [Symbol.asyncIterator]() that throw errors, the errors are
    846 // synchronously propagated to script outside of the loop, and are catchable.
    847 // Observables follow this precedent.
    848 test(() => {
    849  const error = new Error("[Symbol.asyncIterator] error");
    850  const results = [];
    851  const async_iterable = {
    852    [Symbol.asyncIterator]() {
    853      results.push("[Symbol.asyncIterator]() invoked");
    854      throw error;
    855    }
    856  };
    857 
    858  Observable.from(async_iterable).subscribe({
    859    error: e => results.push(e),
    860  });
    861 
    862  assert_array_equals(results, [
    863    "[Symbol.asyncIterator]() invoked",
    864    error,
    865  ]);
    866 }, "from(): Errors thrown in Symbol.asyncIterator() are propagated synchronously");
    867 
    868 // AsyncIterable: next() throws exception instead of return Promise. Any errors
    869 // that occur during the retrieval of `next()` always result in a rejected
    870 // Promise. Therefore, the error makes it to the Observer with microtask timing.
    871 promise_test(async () => {
    872  const nextError = new Error('next error');
    873  const async_iterable = {
    874    [Symbol.asyncIterator]() {
    875      return {
    876        get next() {
    877          throw nextError;
    878        }
    879      };
    880    }
    881  };
    882 
    883  const results = [];
    884  Observable.from(async_iterable).subscribe({
    885    error: e => results.push(e),
    886  });
    887 
    888  assert_array_equals(results, []);
    889  // Wait one microtask since the error will be propagated through a rejected
    890  // Promise managed by the async iterable conversion semantics.
    891  await Promise.resolve();
    892  assert_array_equals(results, [nextError]);
    893 }, "from(): Errors thrown in async iterator's next() GETTER are propagated " +
    894   "in a microtask");
    895 promise_test(async () => {
    896  const nextError = new Error('next error');
    897  const async_iterable = {
    898    [Symbol.asyncIterator]() {
    899      return {
    900        next() {
    901          throw nextError;
    902        }
    903      };
    904    }
    905  };
    906 
    907  const results = [];
    908  Observable.from(async_iterable).subscribe({
    909    error: e => results.push(e),
    910  });
    911 
    912  assert_array_equals(results, []);
    913  await Promise.resolve();
    914  assert_array_equals(results, [nextError]);
    915 }, "from(): Errors thrown in async iterator's next() are propagated in a microtask");
    916 
    917 test(() => {
    918  const results = [];
    919  const iterable = {
    920    [Symbol.iterator]() {
    921      return {
    922        val: 0,
    923        next() {
    924          results.push(`IteratorRecord#next() pushing ${this.val}`);
    925          return {
    926            value: this.val,
    927            done: this.val++ === 10 ? true : false,
    928          };
    929        },
    930        return() {
    931          results.push(`IteratorRecord#return() called with this.val=${this.val}`);
    932        },
    933      };
    934    },
    935  };
    936 
    937  const ac = new AbortController();
    938  Observable.from(iterable).subscribe(v => {
    939    results.push(`Observing ${v}`);
    940    if (v === 3) {
    941      ac.abort();
    942    }
    943  }, {signal: ac.signal});
    944 
    945  assert_array_equals(results, [
    946    "IteratorRecord#next() pushing 0",
    947    "Observing 0",
    948    "IteratorRecord#next() pushing 1",
    949    "Observing 1",
    950    "IteratorRecord#next() pushing 2",
    951    "Observing 2",
    952    "IteratorRecord#next() pushing 3",
    953    "Observing 3",
    954    "IteratorRecord#return() called with this.val=4",
    955  ]);
    956 }, "from(): Aborting sync iterable midway through iteration both stops iteration " +
    957   "and invokes `IteratorRecord#return()");
    958 // Like the above test, but for async iterables.
    959 promise_test(async t => {
    960  const results = [];
    961  const iterable = {
    962    [Symbol.asyncIterator]() {
    963      return {
    964        val: 0,
    965        next() {
    966          results.push(`IteratorRecord#next() pushing ${this.val}`);
    967          return {
    968            value: this.val,
    969            done: this.val++ === 10 ? true : false,
    970          };
    971        },
    972        return(reason) {
    973          results.push(`IteratorRecord#return() called with reason=${reason}`);
    974          return {done: true};
    975        },
    976      };
    977    },
    978  };
    979 
    980  const ac = new AbortController();
    981  await new Promise(resolve => {
    982    Observable.from(iterable).subscribe(v => {
    983      results.push(`Observing ${v}`);
    984      if (v === 3) {
    985        ac.abort(`Aborting because v=${v}`);
    986        resolve();
    987      }
    988    }, {signal: ac.signal});
    989  });
    990 
    991  assert_array_equals(results, [
    992    "IteratorRecord#next() pushing 0",
    993    "Observing 0",
    994    "IteratorRecord#next() pushing 1",
    995    "Observing 1",
    996    "IteratorRecord#next() pushing 2",
    997    "Observing 2",
    998    "IteratorRecord#next() pushing 3",
    999    "Observing 3",
   1000    "IteratorRecord#return() called with reason=Aborting because v=3",
   1001  ]);
   1002 }, "from(): Aborting async iterable midway through iteration both stops iteration " +
   1003   "and invokes `IteratorRecord#return()");
   1004 
   1005 test(() => {
   1006  const iterable = {
   1007    [Symbol.iterator]() {
   1008      return {
   1009        val: 0,
   1010        next() {
   1011          return {value: this.val, done: this.val++ === 10 ? true : false};
   1012        },
   1013        // Not returning an Object results in a TypeError being thrown.
   1014        return(reason) {},
   1015      };
   1016    },
   1017  };
   1018 
   1019  let thrownError = null;
   1020  const ac = new AbortController();
   1021  Observable.from(iterable).subscribe(v => {
   1022    if (v === 3) {
   1023      try {
   1024        ac.abort(`Aborting because v=${v}`);
   1025      } catch (e) {
   1026        thrownError = e;
   1027      }
   1028    }
   1029  }, {signal: ac.signal});
   1030 
   1031  assert_not_equals(thrownError, null, "abort() threw an Error");
   1032  assert_true(thrownError instanceof TypeError);
   1033  assert_true(thrownError.message.includes('return()'));
   1034  assert_true(thrownError.message.includes('Object'));
   1035 }, "from(): Sync iterable: `Iterator#return()` must return an Object, or an " +
   1036   "error is thrown");
   1037 // This test is just like the above but for async iterables. It asserts that a
   1038 // Promise is rejected when `return()` does not return an Object.
   1039 promise_test(async t => {
   1040  const iterable = {
   1041    [Symbol.asyncIterator]() {
   1042      return {
   1043        val: 0,
   1044        next() {
   1045          return {value: this.val, done: this.val++ === 10 ? true : false};
   1046        },
   1047        // Not returning an Object results in a rejected Promise.
   1048        return(reason) {},
   1049      };
   1050    },
   1051  };
   1052 
   1053  const unhandled_rejection_promise = new Promise((resolve, reject) => {
   1054    const unhandled_rejection_handler = e => resolve(e.reason);
   1055    self.addEventListener("unhandledrejection", unhandled_rejection_handler);
   1056    t.add_cleanup(() =>
   1057        self.removeEventListener("unhandledrejection", unhandled_rejection_handler));
   1058 
   1059    t.step_timeout(() => reject('Timeout'), 3000);
   1060  });
   1061 
   1062  const ac = new AbortController();
   1063  await new Promise(resolve => {
   1064    Observable.from(iterable).subscribe(v => {
   1065      if (v === 3) {
   1066        ac.abort(`Aborting because v=${v}`);
   1067        resolve();
   1068      }
   1069    }, {signal: ac.signal});
   1070  });
   1071 
   1072  const reason = await unhandled_rejection_promise;
   1073  assert_true(reason instanceof TypeError);
   1074  assert_true(reason.message.includes('return()'));
   1075  assert_true(reason.message.includes('Object'));
   1076 }, "from(): Async iterable: `Iterator#return()` must return an Object, or a " +
   1077   "Promise rejects asynchronously");
   1078 
   1079 // This test exercises the logic of `GetIterator()` async->sync fallback
   1080 // logic. Specifically, we have an object that is an async iterable — that is,
   1081 // it has a callback [Symbol.asyncIterator] implementation. Observable.from()
   1082 // detects this, and commits to converting the object from the async iterable
   1083 // protocol. Then, after conversion but before subscription, the object is
   1084 // modified such that it no longer implements the async iterable protocol.
   1085 //
   1086 // But since it still implements the *iterable* protocol, ECMAScript's
   1087 // `GetIterator()` abstract algorithm [1] is fully exercised, which is spec'd to
   1088 // fall-back to the synchronous iterable protocol if it exists, and create a
   1089 // fully async iterable out of the synchronous iterable.
   1090 //
   1091 // [1]: https://tc39.es/ecma262/#sec-getiterator
   1092 promise_test(async () => {
   1093  const results = [];
   1094  const async_iterable = {
   1095    asyncIteratorGotten: false,
   1096    get [Symbol.asyncIterator]() {
   1097      results.push("[Symbol.asyncIterator] GETTER");
   1098      if (this.asyncIteratorGotten) {
   1099        return null; // Both null and undefined work here.
   1100      }
   1101 
   1102      this.asyncIteratorGotten = true;
   1103      // The only requirement for `this` to be converted as an async
   1104      // iterable -> Observable is that the return value be callable (i.e., a function).
   1105      return function() {};
   1106    },
   1107 
   1108    [Symbol.iterator]() {
   1109      results.push('[Symbol.iterator]() invoked as fallback');
   1110      return {
   1111        val: 0,
   1112        next() {
   1113          return {
   1114            value: this.val,
   1115            done: this.val++ === 4 ? true : false,
   1116          };
   1117        },
   1118      };
   1119    },
   1120  };
   1121 
   1122  const source = Observable.from(async_iterable);
   1123  assert_array_equals(results, [
   1124    "[Symbol.asyncIterator] GETTER",
   1125  ]);
   1126 
   1127  await new Promise((resolve, reject) => {
   1128    source.subscribe({
   1129      next: v => {
   1130        results.push(`Observing ${v}`);
   1131        queueMicrotask(() => results.push(`next() microtask interleaving (v=${v})`));
   1132      },
   1133      error: e => reject(e),
   1134      complete: () => {
   1135        results.push('complete()');
   1136        resolve();
   1137      },
   1138    });
   1139  });
   1140 
   1141  assert_array_equals(results, [
   1142    // Old:
   1143    "[Symbol.asyncIterator] GETTER",
   1144    // New:
   1145    "[Symbol.asyncIterator] GETTER",
   1146    "[Symbol.iterator]() invoked as fallback",
   1147    "Observing 0",
   1148    "next() microtask interleaving (v=0)",
   1149    "Observing 1",
   1150    "next() microtask interleaving (v=1)",
   1151    "Observing 2",
   1152    "next() microtask interleaving (v=2)",
   1153    "Observing 3",
   1154    "next() microtask interleaving (v=3)",
   1155    "complete()",
   1156  ]);
   1157 }, "from(): Asynchronous iterable conversion, with synchronous iterable fallback");
   1158 
   1159 test(() => {
   1160  const results = [];
   1161  let generatorFinalized = false;
   1162 
   1163  const generator = function*() {
   1164    try {
   1165      for (let n = 0; n < 10; n++) {
   1166        yield n;
   1167      }
   1168    } finally {
   1169      generatorFinalized = true;
   1170    }
   1171  };
   1172 
   1173  const observable = Observable.from(generator());
   1174  const abortController = new AbortController();
   1175 
   1176  observable.subscribe(n => {
   1177    results.push(n);
   1178    if (n === 3) {
   1179      abortController.abort();
   1180    }
   1181  }, {signal: abortController.signal});
   1182 
   1183  assert_array_equals(results, [0, 1, 2, 3]);
   1184  assert_true(generatorFinalized);
   1185 }, "from(): Generator finally block runs when subscription is aborted");
   1186 
   1187 test(() => {
   1188  const results = [];
   1189  let generatorFinalized = false;
   1190 
   1191  const generator = function*() {
   1192    try {
   1193      for (let n = 0; n < 10; n++) {
   1194        yield n;
   1195      }
   1196    } catch {
   1197      assert_unreached("generator should not be aborted");
   1198    } finally {
   1199      generatorFinalized = true;
   1200    }
   1201  };
   1202 
   1203  const observable = Observable.from(generator());
   1204 
   1205  observable.subscribe((n) => {
   1206    results.push(n);
   1207  });
   1208 
   1209  assert_array_equals(results, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
   1210  assert_true(generatorFinalized);
   1211 }, "from(): Generator finally block run when Observable completes");
   1212 
   1213 test(() => {
   1214  const results = [];
   1215  let generatorFinalized = false;
   1216 
   1217  const generator = function*() {
   1218    try {
   1219      for (let n = 0; n < 10; n++) {
   1220        yield n;
   1221      }
   1222      throw new Error('from the generator');
   1223    } finally {
   1224      generatorFinalized = true;
   1225    }
   1226  };
   1227 
   1228  const observable = Observable.from(generator());
   1229 
   1230  observable.subscribe({
   1231    next: n => results.push(n),
   1232    error: e => results.push(e.message)
   1233  });
   1234 
   1235  assert_array_equals(results, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, "from the generator"]);
   1236  assert_true(generatorFinalized);
   1237 }, "from(): Generator finally block run when Observable errors");
   1238 
   1239 promise_test(async t => {
   1240  const results = [];
   1241  let generatorFinalized = false;
   1242 
   1243  async function* asyncGenerator() {
   1244    try {
   1245      for (let n = 0; n < 10; n++) {
   1246        yield n;
   1247      }
   1248    } finally {
   1249      generatorFinalized = true;
   1250    }
   1251  }
   1252 
   1253  const observable = Observable.from(asyncGenerator());
   1254  const abortController = new AbortController();
   1255 
   1256  await new Promise((resolve) => {
   1257    observable.subscribe((n) => {
   1258      results.push(n);
   1259      if (n === 3) {
   1260        abortController.abort();
   1261        resolve();
   1262      }
   1263    }, {signal: abortController.signal});
   1264  });
   1265 
   1266  assert_array_equals(results, [0, 1, 2, 3]);
   1267  assert_true(generatorFinalized);
   1268 }, "from(): Async generator finally block run when subscription is aborted");
   1269 
   1270 promise_test(async t => {
   1271  const results = [];
   1272  let generatorFinalized = false;
   1273 
   1274  async function* asyncGenerator() {
   1275    try {
   1276      for (let n = 0; n < 10; n++) {
   1277        yield n;
   1278      }
   1279    } finally {
   1280      generatorFinalized = true;
   1281    }
   1282  }
   1283 
   1284  const observable = Observable.from(asyncGenerator());
   1285 
   1286  await new Promise(resolve => {
   1287    observable.subscribe({
   1288      next: n => results.push(n),
   1289      complete: () => resolve(),
   1290    });
   1291  });
   1292 
   1293  assert_array_equals(results, [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]);
   1294  assert_true(generatorFinalized);
   1295 }, "from(): Async generator finally block runs when Observable completes");
   1296 
   1297 promise_test(async t => {
   1298  const results = [];
   1299  let generatorFinalized = false;
   1300 
   1301  async function* asyncGenerator() {
   1302    try {
   1303      for (let n = 0; n < 10; n++) {
   1304        if (n === 4) {
   1305          throw new Error('from the async generator');
   1306        }
   1307        yield n;
   1308      }
   1309    } finally {
   1310      generatorFinalized = true;
   1311    }
   1312  }
   1313 
   1314  const observable = Observable.from(asyncGenerator());
   1315 
   1316  await new Promise((resolve) => {
   1317    observable.subscribe({
   1318      next: (n) => results.push(n),
   1319      error: (e) => {
   1320        results.push(e.message);
   1321        resolve();
   1322      }
   1323    });
   1324  });
   1325 
   1326  assert_array_equals(results, [0, 1, 2, 3, "from the async generator"]);
   1327  assert_true(generatorFinalized);
   1328 }, "from(): Async generator finally block run when Observable errors");
   1329 
   1330 // Test what happens when `return()` throws an error upon abort.
   1331 test(() => {
   1332  const results = [];
   1333  const iterable = {
   1334    [Symbol.iterator]() {
   1335      return {
   1336        val: 0,
   1337        next() {
   1338          results.push('next() called');
   1339          return {value: this.val, done: this.val++ === 10 ? true : false};
   1340        },
   1341        return() {
   1342          results.push('return() about to throw an error');
   1343          throw new Error('return() error');
   1344        },
   1345      };
   1346    }
   1347  };
   1348 
   1349  const ac = new AbortController();
   1350  const source = Observable.from(iterable);
   1351  source.subscribe(v => {
   1352    if (v === 3) {
   1353      try {
   1354        ac.abort();
   1355      } catch (e) {
   1356        results.push(`AbortController#abort() threw an error: ${e.message}`);
   1357      }
   1358    }
   1359  }, {signal: ac.signal});
   1360 
   1361  assert_array_equals(results, [
   1362    'next() called',
   1363    'next() called',
   1364    'next() called',
   1365    'next() called',
   1366    'return() about to throw an error',
   1367    'AbortController#abort() threw an error: return() error',
   1368  ]);
   1369 }, "from(): Sync iterable: error thrown from IteratorRecord#return() can be " +
   1370   "synchronously caught");
   1371 promise_test(async t => {
   1372  const results = [];
   1373  const iterable = {
   1374    [Symbol.asyncIterator]() {
   1375      return {
   1376        val: 0,
   1377        next() {
   1378          results.push('next() called');
   1379          return {value: this.val, done: this.val++ === 10 ? true : false};
   1380        },
   1381        return() {
   1382          results.push('return() about to throw an error');
   1383          // For async iterables, errors thrown in `return()` end up in a
   1384          // returned rejected Promise, so no error appears on the stack
   1385          // immediately. See [1].
   1386          //
   1387          // [1]: https://whatpr.org/webidl/1397.html#async-iterator-close.
   1388          throw new Error('return() error');
   1389        },
   1390      };
   1391    }
   1392  };
   1393 
   1394  const unhandled_rejection_promise = new Promise((resolve, reject) => {
   1395    const unhandled_rejection_handler = e => resolve(e.reason);
   1396    self.addEventListener("unhandledrejection", unhandled_rejection_handler);
   1397    t.add_cleanup(() =>
   1398        self.removeEventListener("unhandledrejection", unhandled_rejection_handler));
   1399 
   1400    t.step_timeout(() => reject('Timeout'), 1500);
   1401  });
   1402 
   1403  const ac = new AbortController();
   1404  const source = Observable.from(iterable);
   1405  await new Promise((resolve, reject) => {
   1406    source.subscribe(v => {
   1407      if (v === 3) {
   1408        try {
   1409          ac.abort();
   1410          results.push('No error thrown synchronously');
   1411          resolve('No error thrown synchronously');
   1412        } catch (e) {
   1413          results.push(`AbortController#abort() threw an error: ${e.message}`);
   1414          reject(e);
   1415        }
   1416      }
   1417    }, {signal: ac.signal});
   1418  });
   1419 
   1420  assert_array_equals(results, [
   1421    'next() called',
   1422    'next() called',
   1423    'next() called',
   1424    'next() called',
   1425    'return() about to throw an error',
   1426    'No error thrown synchronously',
   1427  ]);
   1428 
   1429  const reason = await unhandled_rejection_promise;
   1430  assert_true(reason instanceof Error);
   1431  assert_equals(reason.message, "return() error",
   1432      "Custom error text passed through rejected Promise");
   1433 }, "from(): Async iterable: error thrown from IteratorRecord#return() is " +
   1434   "wrapped in rejected Promise");
   1435 
   1436 test(() => {
   1437  const results = [];
   1438  const iterable = {
   1439    getter() {
   1440      results.push('GETTER called');
   1441      return () => {
   1442        results.push('Obtaining iterator');
   1443        return {
   1444          next() {
   1445            results.push('next() running');
   1446            return {done: true};
   1447          }
   1448        };
   1449      };
   1450    }
   1451  };
   1452 
   1453  Object.defineProperty(iterable, Symbol.iterator, {
   1454    get: iterable.getter
   1455  });
   1456  {
   1457    const source = Observable.from(iterable);
   1458    assert_array_equals(results, ["GETTER called"]);
   1459    source.subscribe({}, {signal: AbortSignal.abort()});
   1460    assert_array_equals(results, ["GETTER called"]);
   1461  }
   1462  iterable[Symbol.iterator] = undefined;
   1463  Object.defineProperty(iterable, Symbol.asyncIterator, {
   1464    get: iterable.getter
   1465  });
   1466  {
   1467    const source = Observable.from(iterable);
   1468    assert_array_equals(results, ["GETTER called", "GETTER called"]);
   1469    source.subscribe({}, {signal: AbortSignal.abort()});
   1470    assert_array_equals(results, ["GETTER called", "GETTER called"]);
   1471  }
   1472 }, "from(): Subscribing to an iterable Observable with an aborted signal " +
   1473   "does not call next()");
   1474 
   1475 test(() => {
   1476  let results = [];
   1477 
   1478  const iterable = {
   1479    controller: null,
   1480    calledOnce: false,
   1481    getter() {
   1482      results.push('GETTER called');
   1483      if (!this.calledOnce) {
   1484        this.calledOnce = true;
   1485        return () => {
   1486          results.push('NOT CALLED');
   1487          // We don't need to return anything here. The only time this path is
   1488          // hit is during `Observable.from()` which doesn't actually obtain an
   1489          // iterator. It just samples the iterable protocol property to ensure
   1490          // that it's valid.
   1491        };
   1492      }
   1493 
   1494      // This path is only called the second time the iterator protocol getter
   1495      // is run.
   1496      this.controller.abort();
   1497      return () => {
   1498        results.push('iterator obtained');
   1499        return {
   1500          val: 0,
   1501          next() {
   1502            results.push('next() called');
   1503            return {done: true};
   1504          },
   1505          return() {
   1506            results.push('return() called');
   1507          }
   1508        };
   1509      };
   1510    }
   1511  };
   1512 
   1513  // Test for sync iterators.
   1514  {
   1515    const ac = new AbortController();
   1516    iterable.controller = ac;
   1517    Object.defineProperty(iterable, Symbol.iterator, {
   1518      get: iterable.getter,
   1519    });
   1520 
   1521    const source = Observable.from(iterable);
   1522    assert_false(ac.signal.aborted, "[Sync iterator]: signal is not yet aborted after from() conversion");
   1523    assert_array_equals(results, ["GETTER called"]);
   1524 
   1525    source.subscribe({
   1526      next: n => results.push(n),
   1527      complete: () => results.push('complete'),
   1528    }, {signal: ac.signal});
   1529    assert_true(ac.signal.aborted, "[Sync iterator]: signal is aborted during subscription");
   1530    assert_array_equals(results, ["GETTER called", "GETTER called", "iterator obtained"]);
   1531  }
   1532 
   1533  results = [];
   1534 
   1535  // Test for async iterators.
   1536  {
   1537    // Reset `iterable` so it can be reused.
   1538    const ac = new AbortController();
   1539    iterable.controller = ac;
   1540    iterable.calledOnce = false;
   1541    iterable[Symbol.iterator] = undefined;
   1542    Object.defineProperty(iterable, Symbol.asyncIterator, {
   1543      get: iterable.getter
   1544    });
   1545 
   1546    const source = Observable.from(iterable);
   1547    assert_false(ac.signal.aborted, "[Async iterator]: signal is not yet aborted after from() conversion");
   1548    assert_array_equals(results, ["GETTER called"]);
   1549 
   1550    source.subscribe({
   1551      next: n => results.push(n),
   1552      complete: () => results.push('complete'),
   1553    }, {signal: ac.signal});
   1554    assert_true(ac.signal.aborted, "[Async iterator]: signal is aborted during subscription");
   1555    assert_array_equals(results, ["GETTER called", "GETTER called", "iterator obtained"]);
   1556  }
   1557 }, "from(): When iterable conversion aborts the subscription, next() is " +
   1558   "never called");
   1559 
   1560 // This test asserts some very subtle behavior with regard to async iterables
   1561 // and a mid-subscription signal abort. Specifically it detects that a signal
   1562 // abort ensures that the `next()` method is not called again on the iterator
   1563 // again, BUT detects that pending Promise from the *previous* `next()` call
   1564 // still has its IteratorResult object examined. I.e., the implementation
   1565 // inspecting the `done` attribute on the resolved IteratorResult is observable
   1566 // event after abort() takes place.
   1567 promise_test(async () => {
   1568  const results = [];
   1569  let resolveNext = null;
   1570 
   1571  const iterable = {
   1572    [Symbol.asyncIterator]() {
   1573      return {
   1574        next() {
   1575          results.push('next() called');
   1576          return new Promise(resolve => {
   1577            resolveNext = resolve;
   1578          });
   1579        },
   1580        return() {
   1581          results.push('return() called');
   1582        }
   1583      };
   1584    }
   1585  };
   1586 
   1587  const ac = new AbortController();
   1588  const source = Observable.from(iterable);
   1589  source.subscribe({
   1590    next: v => results.push(v),
   1591    complete: () => results.push('complete'),
   1592  }, {signal: ac.signal});
   1593 
   1594  assert_array_equals(results, [
   1595    "next() called",
   1596  ]);
   1597 
   1598  // First abort, ensuring `return()` is called.
   1599  ac.abort();
   1600 
   1601  assert_array_equals(results, [
   1602    "next() called",
   1603    "return() called",
   1604  ]);
   1605 
   1606  // Then resolve the pending `next()` Promise to an object whose `done` getter
   1607  // reports to the test whether it was accessed. We have to wait one microtask
   1608  // for the internal Observable implementation to finish "reacting" to said
   1609  // `next()` promise resolution, for it to grab the `done` attribute.
   1610  await new Promise(resolveOuter => {
   1611    resolveNext({
   1612      get done() {
   1613        results.push('IteratorResult.done GETTER');
   1614        resolveOuter();
   1615        return true;
   1616      }
   1617    });
   1618  });
   1619 
   1620  assert_array_equals(results, [
   1621    "next() called",
   1622    "return() called",
   1623    "IteratorResult.done GETTER",
   1624    // Note that "next() called" does not make another appearance.
   1625  ]);
   1626 }, "from(): Aborting an async iterable subscription stops subsequent next() " +
   1627   "calls, but old next() Promise reactions are web-observable");
   1628 
   1629 test(() => {
   1630  const results = [];
   1631  const iterable = {
   1632    [Symbol.iterator]() {
   1633      return {
   1634        val: 0,
   1635        next() {
   1636          return {value: this.val, done: this.val++ === 4 ? true : false};
   1637        },
   1638        return() {
   1639          results.push('return() called');
   1640        },
   1641      };
   1642    }
   1643  };
   1644 
   1645  const source = Observable.from(iterable);
   1646  const ac = new AbortController();
   1647  source.subscribe({
   1648    next: v => results.push(v),
   1649    complete: () => results.push('complete'),
   1650  }, {signal: ac.signal});
   1651 
   1652  ac.abort(); // Must do nothing!
   1653  assert_array_equals(results, [0, 1, 2, 3, 'complete']);
   1654 }, "from(): Abort after complete does NOT call IteratorRecord#return()");
   1655 
   1656 test(() => {
   1657  const controller = new AbortController();
   1658  // Invalid @@asyncIterator protocol that also aborts the subscription. By the
   1659  // time the invalid-ness of the protocol is detected, the controller has been
   1660  // aborted, meaning that invalid-ness cannot manifest itself in the form of an
   1661  // error that goes to the Observable's subscriber. Instead, it gets reported
   1662  // to the global.
   1663  const asyncIterable = {
   1664    calledOnce: false,
   1665    get[Symbol.asyncIterator]() {
   1666      // This `calledOnce` path is to ensure the Observable first converts
   1667      // correctly via `Observable.from()`, but *later* fails in the path where
   1668      // `@@asyncIterator` is null.
   1669      if (this.calledOnce) {
   1670        controller.abort();
   1671        return null;
   1672      } else {
   1673        this.calledOnce = true;
   1674        return this.validImplementation;
   1675      }
   1676    },
   1677    validImplementation() {
   1678      controller.abort();
   1679      return null;
   1680    }
   1681  };
   1682 
   1683  let reportedError = null;
   1684  self.addEventListener("error", e => reportedError = e.error, {once: true});
   1685 
   1686  let errorThrown = null;
   1687  const observable = Observable.from(asyncIterable);
   1688  observable.subscribe({
   1689    error: e => errorThrown = e,
   1690  }, {signal: controller.signal});
   1691 
   1692  assert_equals(errorThrown, null, "Protocol error is not surfaced to the Subscriber");
   1693 
   1694  assert_not_equals(reportedError, null, "Protocol error is reported to the global");
   1695  assert_true(reportedError instanceof TypeError);
   1696 }, "Invalid async iterator protocol error is surfaced before Subscriber#signal is consulted");
   1697 
   1698 test(() => {
   1699  const controller = new AbortController();
   1700  const iterable = {
   1701    calledOnce: false,
   1702    get[Symbol.iterator]() {
   1703      if (this.calledOnce) {
   1704        controller.abort();
   1705        return null;
   1706      } else {
   1707        this.calledOnce = true;
   1708        return this.validImplementation;
   1709      }
   1710    },
   1711    validImplementation() {
   1712      controller.abort();
   1713      return null;
   1714    }
   1715  };
   1716 
   1717  let reportedError = null;
   1718  self.addEventListener("error", e => reportedError = e.error, {once: true});
   1719 
   1720  let errorThrown = null;
   1721  const observable = Observable.from(iterable);
   1722  observable.subscribe({
   1723    error: e => errorThrown = e,
   1724  }, {signal: controller.signal});
   1725 
   1726  assert_equals(errorThrown, null, "Protocol error is not surfaced to the Subscriber");
   1727 
   1728  assert_not_equals(reportedError, null, "Protocol error is reported to the global");
   1729  assert_true(reportedError instanceof TypeError);
   1730 }, "Invalid iterator protocol error is surfaced before Subscriber#signal is consulted");
   1731 
   1732 // Regression test for https://github.com/WICG/observable/issues/208.
   1733 promise_test(async () => {
   1734  let errorReported = false;
   1735  self.onerror = e => errorReported = true;
   1736 
   1737  // `first()` aborts the subscription after the first item is encountered.
   1738  const value = await Observable.from([1, 2, 3]).first();
   1739  assert_false(errorReported);
   1740 }, "No error is reported when aborting a subscription to a sync iterator " +
   1741   "that has no `return()` implementation");