tor-browser

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

IterableIterator.cpp (13320B)


      1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
      2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
      3 /* This Source Code Form is subject to the terms of the Mozilla Public
      4 * License, v. 2.0. If a copy of the MPL was not distributed with this
      5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
      6 
      7 #include "mozilla/dom/IterableIterator.h"
      8 
      9 #include "mozilla/dom/Promise-inl.h"
     10 
     11 namespace mozilla::dom {
     12 
     13 // Due to IterableIterator being a templated class, we implement the necessary
     14 // CC bits in a superclass that IterableIterator then inherits from. This allows
     15 // us to put the macros outside of the header. The base class has pure virtual
     16 // functions for Traverse/Unlink that the templated subclasses will override.
     17 
     18 NS_IMPL_CYCLE_COLLECTION_CLASS(IterableIteratorBase)
     19 
     20 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_BEGIN(IterableIteratorBase)
     21  tmp->TraverseHelper(cb);
     22 NS_IMPL_CYCLE_COLLECTION_TRAVERSE_END
     23 
     24 NS_IMPL_CYCLE_COLLECTION_UNLINK_BEGIN(IterableIteratorBase)
     25  tmp->UnlinkHelper();
     26 NS_IMPL_CYCLE_COLLECTION_UNLINK_END
     27 
     28 namespace iterator_utils {
     29 
     30 void DictReturn(JSContext* aCx, JS::MutableHandle<JS::Value> aResult,
     31                bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
     32  RootedDictionary<IterableKeyOrValueResult> dict(aCx);
     33  dict.mDone = aDone;
     34  dict.mValue = aValue;
     35  JS::Rooted<JS::Value> dictValue(aCx);
     36  if (!ToJSValue(aCx, dict, &dictValue)) {
     37    aRv.Throw(NS_ERROR_FAILURE);
     38    return;
     39  }
     40  aResult.set(dictValue);
     41 }
     42 
     43 void DictReturn(JSContext* aCx, JS::MutableHandle<JSObject*> aResult,
     44                bool aDone, JS::Handle<JS::Value> aValue, ErrorResult& aRv) {
     45  JS::Rooted<JS::Value> dictValue(aCx);
     46  DictReturn(aCx, &dictValue, aDone, aValue, aRv);
     47  if (aRv.Failed()) {
     48    return;
     49  }
     50  aResult.set(&dictValue.toObject());
     51 }
     52 
     53 void KeyAndValueReturn(JSContext* aCx, JS::Handle<JS::Value> aKey,
     54                       JS::Handle<JS::Value> aValue,
     55                       JS::MutableHandle<JSObject*> aResult, ErrorResult& aRv) {
     56  RootedDictionary<IterableKeyAndValueResult> dict(aCx);
     57  dict.mDone = false;
     58  // Dictionary values are a Sequence, which is a FallibleTArray, so we need
     59  // to check returns when appending.
     60  if (!dict.mValue.AppendElement(aKey, mozilla::fallible)) {
     61    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     62    return;
     63  }
     64  if (!dict.mValue.AppendElement(aValue, mozilla::fallible)) {
     65    aRv.Throw(NS_ERROR_OUT_OF_MEMORY);
     66    return;
     67  }
     68  JS::Rooted<JS::Value> dictValue(aCx);
     69  if (!ToJSValue(aCx, dict, &dictValue)) {
     70    aRv.Throw(NS_ERROR_FAILURE);
     71    return;
     72  }
     73  aResult.set(&dictValue.toObject());
     74 }
     75 
     76 }  // namespace iterator_utils
     77 
     78 namespace binding_detail {
     79 
     80 static already_AddRefed<Promise> PromiseOrErr(
     81    Result<RefPtr<Promise>, nsresult>&& aResult, ErrorResult& aError) {
     82  if (aResult.isErr()) {
     83    aError.Throw(aResult.unwrapErr());
     84    return nullptr;
     85  }
     86 
     87  return aResult.unwrap().forget();
     88 }
     89 
     90 already_AddRefed<Promise> AsyncIterableNextImpl::NextSteps(
     91    JSContext* aCx, AsyncIterableIteratorBase* aObject,
     92    nsIGlobalObject* aGlobalObject, ErrorResult& aRv) {
     93  // 2. If object’s is finished is true, then:
     94  if (aObject->mIsFinished) {
     95    // 1. Let result be CreateIterResultObject(undefined, true).
     96    JS::Rooted<JS::Value> dict(aCx);
     97    iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue, aRv);
     98    if (aRv.Failed()) {
     99      return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
    100    }
    101 
    102    // 2. Perform ! Call(nextPromiseCapability.[[Resolve]], undefined,
    103    //    «result»).
    104    // 3. Return nextPromiseCapability.[[Promise]].
    105    return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
    106  }
    107 
    108  // 4. Let nextPromise be the result of getting the next iteration result with
    109  //    object’s target and object.
    110  RefPtr<Promise> nextPromise;
    111  {
    112    ErrorResult error;
    113    nextPromise = GetNextResult(error);
    114 
    115    error.WouldReportJSException();
    116    if (error.Failed()) {
    117      nextPromise = Promise::Reject(aGlobalObject, std::move(error), aRv);
    118    }
    119  }
    120 
    121  // 5. Let fulfillSteps be the following steps, given next:
    122  auto fulfillSteps = [](JSContext* aCx, JS::Handle<JS::Value> aNext,
    123                         ErrorResult& aRv,
    124                         const RefPtr<AsyncIterableIteratorBase>& aObject,
    125                         const nsCOMPtr<nsIGlobalObject>& aGlobalObject)
    126      -> already_AddRefed<Promise> {
    127    // 1. Set object’s ongoing promise to null.
    128    aObject->mOngoingPromise = nullptr;
    129 
    130    // 2. If next is end of iteration, then:
    131    JS::Rooted<JS::Value> dict(aCx);
    132    if (aNext.isMagic(binding_details::END_OF_ITERATION)) {
    133      // 1. Set object’s is finished to true.
    134      aObject->mIsFinished = true;
    135      // 2. Return CreateIterResultObject(undefined, true).
    136      iterator_utils::DictReturn(aCx, &dict, true, JS::UndefinedHandleValue,
    137                                 aRv);
    138      if (aRv.Failed()) {
    139        return nullptr;
    140      }
    141    } else {
    142      // 3. Otherwise, if interface has a pair asynchronously iterable
    143      // declaration:
    144      //   1. Assert: next is a value pair.
    145      //   2. Return the iterator result for next and kind.
    146      // 4. Otherwise:
    147      //   1. Assert: interface has a value asynchronously iterable declaration.
    148      //   2. Assert: next is a value of the type that appears in the
    149      //   declaration.
    150      //   3. Let value be next, converted to an ECMAScript value.
    151      //   4. Return CreateIterResultObject(value, false).
    152      iterator_utils::DictReturn(aCx, &dict, false, aNext, aRv);
    153      if (aRv.Failed()) {
    154        return nullptr;
    155      }
    156    }
    157    // Note that ThenCatchWithCycleCollectedArgs expects a Promise, so
    158    // we use Promise::Resolve here. The specs do convert this to a
    159    // promise too at another point, but the end result should be the
    160    // same.
    161    return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
    162  };
    163  // 7. Let rejectSteps be the following steps, given reason:
    164  auto rejectSteps = [](JSContext* aCx, JS::Handle<JS::Value> aReason,
    165                        ErrorResult& aRv,
    166                        const RefPtr<AsyncIterableIteratorBase>& aObject,
    167                        const nsCOMPtr<nsIGlobalObject>& aGlobalObject) {
    168    // 1. Set object’s ongoing promise to null.
    169    aObject->mOngoingPromise = nullptr;
    170    // 2. Set object’s is finished to true.
    171    aObject->mIsFinished = true;
    172    // 3. Throw reason.
    173    return Promise::Reject(aGlobalObject, aCx, aReason, aRv);
    174  };
    175  // 9. Perform PerformPromiseThen(nextPromise, onFulfilled, onRejected,
    176  //    nextPromiseCapability).
    177  Result<RefPtr<Promise>, nsresult> result =
    178      nextPromise->ThenCatchWithCycleCollectedArgs(
    179          std::move(fulfillSteps), std::move(rejectSteps), RefPtr{aObject},
    180          nsCOMPtr{aGlobalObject});
    181 
    182  // 10. Return nextPromiseCapability.[[Promise]].
    183  return PromiseOrErr(std::move(result), aRv);
    184 }
    185 
    186 already_AddRefed<Promise> AsyncIterableNextImpl::Next(
    187    JSContext* aCx, AsyncIterableIteratorBase* aObject,
    188    nsISupports* aGlobalObject, ErrorResult& aRv) {
    189  nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject);
    190 
    191  // 3.7.10.2. Asynchronous iterator prototype object
    192  // …
    193  // 10. If ongoingPromise is not null, then:
    194  if (aObject->mOngoingPromise) {
    195    // 1. Let afterOngoingPromiseCapability be
    196    //    ! NewPromiseCapability(%Promise%).
    197    // 2. Let onSettled be CreateBuiltinFunction(nextSteps, « »).
    198 
    199    // aObject is the same object as 'this', so it's fine to capture 'this'
    200    // without taking a strong reference, because we already take a strong
    201    // reference to it through aObject.
    202    auto onSettled = [this](JSContext* aCx, JS::Handle<JS::Value> aValue,
    203                            ErrorResult& aRv,
    204                            const RefPtr<AsyncIterableIteratorBase>& aObject,
    205                            const nsCOMPtr<nsIGlobalObject>& aGlobalObject)
    206                         MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
    207                           return NextSteps(aCx, aObject, aGlobalObject, aRv);
    208                         };
    209 
    210    // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
    211    //    afterOngoingPromiseCapability).
    212    Result<RefPtr<Promise>, nsresult> afterOngoingPromise =
    213        aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgs(
    214            onSettled, onSettled, RefPtr{aObject}, std::move(globalObject));
    215    if (afterOngoingPromise.isErr()) {
    216      aRv.Throw(afterOngoingPromise.unwrapErr());
    217      return nullptr;
    218    }
    219 
    220    // 4. Set object’s ongoing promise to
    221    //    afterOngoingPromiseCapability.[[Promise]].
    222    aObject->mOngoingPromise = afterOngoingPromise.unwrap().forget();
    223  } else {
    224    // 11. Otherwise:
    225    //   1. Set object’s ongoing promise to the result of running nextSteps.
    226    aObject->mOngoingPromise = NextSteps(aCx, aObject, globalObject, aRv);
    227  }
    228 
    229  // 12. Return object’s ongoing promise.
    230  return do_AddRef(aObject->mOngoingPromise);
    231 }
    232 
    233 already_AddRefed<Promise> AsyncIterableReturnImpl::ReturnSteps(
    234    JSContext* aCx, AsyncIterableIteratorBase* aObject,
    235    nsIGlobalObject* aGlobalObject, JS::Handle<JS::Value> aValue,
    236    ErrorResult& aRv) {
    237  // 2. If object’s is finished is true, then:
    238  if (aObject->mIsFinished) {
    239    // 1. Let result be CreateIterResultObject(value, true).
    240    JS::Rooted<JS::Value> dict(aCx);
    241    iterator_utils::DictReturn(aCx, &dict, true, aValue, aRv);
    242    if (aRv.Failed()) {
    243      return Promise::CreateRejectedWithErrorResult(aGlobalObject, aRv);
    244    }
    245 
    246    // 2. Perform ! Call(returnPromiseCapability.[[Resolve]], undefined,
    247    //    «result»).
    248    // 3. Return returnPromiseCapability.[[Promise]].
    249    return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
    250  }
    251 
    252  // 3. Set object’s is finished to true.
    253  aObject->mIsFinished = true;
    254 
    255  // 4. Return the result of running the asynchronous iterator return algorithm
    256  // for interface, given object’s target, object, and value.
    257  ErrorResult error;
    258  RefPtr<Promise> returnPromise = GetReturnPromise(aCx, aValue, error);
    259 
    260  error.WouldReportJSException();
    261  if (error.Failed()) {
    262    return Promise::Reject(aGlobalObject, std::move(error), aRv);
    263  }
    264 
    265  return returnPromise.forget();
    266 }
    267 
    268 already_AddRefed<Promise> AsyncIterableReturnImpl::Return(
    269    JSContext* aCx, AsyncIterableIteratorBase* aObject,
    270    nsISupports* aGlobalObject, JS::Handle<JS::Value> aValue,
    271    ErrorResult& aRv) {
    272  nsCOMPtr<nsIGlobalObject> globalObject = do_QueryInterface(aGlobalObject);
    273 
    274  // 3.7.10.2. Asynchronous iterator prototype object
    275  // …
    276  RefPtr<Promise> returnStepsPromise;
    277  // 11. If ongoingPromise is not null, then:
    278  if (aObject->mOngoingPromise) {
    279    // 1. Let afterOngoingPromiseCapability be
    280    //    ! NewPromiseCapability(%Promise%).
    281    // 2. Let onSettled be CreateBuiltinFunction(returnSteps, « »).
    282 
    283    // aObject is the same object as 'this', so it's fine to capture 'this'
    284    // without taking a strong reference, because we already take a strong
    285    // reference to it through aObject.
    286    auto onSettled =
    287        [this](JSContext* aCx, JS::Handle<JS::Value> aValue, ErrorResult& aRv,
    288               const RefPtr<AsyncIterableIteratorBase>& aObject,
    289               const nsCOMPtr<nsIGlobalObject>& aGlobalObject,
    290               JS::Handle<JS::Value> aVal) MOZ_CAN_RUN_SCRIPT_FOR_DEFINITION {
    291          return ReturnSteps(aCx, aObject, aGlobalObject, aVal, aRv);
    292        };
    293 
    294    // 3. Perform PerformPromiseThen(ongoingPromise, onSettled, onSettled,
    295    //    afterOngoingPromiseCapability).
    296    Result<RefPtr<Promise>, nsresult> afterOngoingPromise =
    297        aObject->mOngoingPromise->ThenCatchWithCycleCollectedArgsJS(
    298            onSettled, onSettled,
    299            std::make_tuple(RefPtr{aObject}, nsCOMPtr{globalObject}),
    300            std::make_tuple(aValue));
    301    if (afterOngoingPromise.isErr()) {
    302      aRv.Throw(afterOngoingPromise.unwrapErr());
    303      return nullptr;
    304    }
    305 
    306    // 4. Set returnStepsPromise to afterOngoingPromiseCapability.[[Promise]].
    307    returnStepsPromise = afterOngoingPromise.unwrap().forget();
    308  } else {
    309    // 12. Otherwise:
    310    //   1. Set returnStepsPromise to the result of running returnSteps.
    311    returnStepsPromise = ReturnSteps(aCx, aObject, globalObject, aValue, aRv);
    312  }
    313 
    314  // 13. Let fulfillSteps be the following steps:
    315  auto onFullFilled = [](JSContext* aCx, JS::Handle<JS::Value>,
    316                         ErrorResult& aRv,
    317                         const nsCOMPtr<nsIGlobalObject>& aGlobalObject,
    318                         JS::Handle<JS::Value> aVal) {
    319    // 1. Return CreateIterResultObject(value, true).
    320    JS::Rooted<JS::Value> dict(aCx);
    321    iterator_utils::DictReturn(aCx, &dict, true, aVal, aRv);
    322    return Promise::Resolve(aGlobalObject, aCx, dict, aRv);
    323  };
    324 
    325  // 14. Let onFulfilled be CreateBuiltinFunction(fulfillSteps, « »).
    326  // 15. Perform PerformPromiseThen(returnStepsPromise, onFulfilled, undefined,
    327  // returnPromiseCapability).
    328  Result<RefPtr<Promise>, nsresult> returnPromise =
    329      returnStepsPromise->ThenWithCycleCollectedArgsJS(
    330          onFullFilled, std::make_tuple(std::move(globalObject)),
    331          std::make_tuple(aValue));
    332 
    333  // 16. Return returnPromiseCapability.[[Promise]].
    334  return PromiseOrErr(std::move(returnPromise), aRv);
    335 }
    336 
    337 }  // namespace binding_detail
    338 
    339 }  // namespace mozilla::dom