Bug 1508346 - Add class ListObject. r=jandem
authorJason Orendorff <jorendorff@mozilla.com>
Fri, 23 Nov 2018 03:00:37 +0000
changeset 447795 ac1c8b26e69085a1ce075939533eb081b9e997ce
parent 447794 e838d2083baf3ce8143cc89046aa9c4cbbf72d42
child 447796 a773a42f4c001614c4ae826bc8ada7de29cea3e6
push id73579
push userjorendorff@mozilla.com
push dateFri, 23 Nov 2018 03:02:06 +0000
treeherderautoland@ac1c8b26e690 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersjandem
bugs1508346
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1508346 - Add class ListObject. r=jandem Differential Revision: https://phabricator.services.mozilla.com/D12326
js/src/builtin/Stream.cpp
js/src/builtin/Stream.h
js/src/moz.build
js/src/vm/AsyncIteration.cpp
js/src/vm/AsyncIteration.h
js/src/vm/List-inl.h
js/src/vm/List.cpp
js/src/vm/List.h
--- a/js/src/builtin/Stream.cpp
+++ b/js/src/builtin/Stream.cpp
@@ -168,17 +168,17 @@ ReturnPromiseRejectedWithPendingError(JS
 /**
  * Creates a NativeObject to be used as a list and stores it on the given
  * container at the given fixed slot offset.
  */
 inline static MOZ_MUST_USE bool
 SetNewList(JSContext* cx, HandleNativeObject unwrappedContainer, uint32_t slot)
 {
     AutoRealm ar(cx, unwrappedContainer);
-    NativeObject* list = NewList(cx);
+    ListObject* list = ListObject::create(cx);
     if (!list) {
         return false;
     }
     unwrappedContainer->setFixedSlot(slot, ObjectValue(*list));
     return true;
 }
 
 class ByteStreamChunk : public NativeObject
@@ -1483,26 +1483,26 @@ ReadableStreamCloseInternal(JSContext* c
     }
 
     // Step 5: If ! IsReadableStreamDefaultReader(reader) is true,
     if (unwrappedReader->is<ReadableStreamDefaultReader>()) {
         ForAuthorCodeBool forAuthorCode = unwrappedReader->forAuthorCode();
 
         // Step a: Repeat for each readRequest that is an element of
         //         reader.[[readRequests]],
-        RootedNativeObject unwrappedReadRequests(cx, unwrappedReader->requests());
-        uint32_t len = unwrappedReadRequests->getDenseInitializedLength();
+        Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
+        uint32_t len = unwrappedReadRequests->length();
         RootedObject readRequest(cx);
         RootedObject resultObj(cx);
         RootedValue resultVal(cx);
         for (uint32_t i = 0; i < len; i++) {
             // Step i: Resolve readRequest.[[promise]] with
             //         ! ReadableStreamCreateReadResult(undefined, true,
             //                                          readRequest.[[forAuthorCode]]).
-            readRequest = &unwrappedReadRequests->getDenseElement(i).toObject();
+            readRequest = &unwrappedReadRequests->getAs<JSObject>(i);
             if (!cx->compartment()->wrap(cx, &readRequest)) {
                 return false;
             }
 
             resultObj = ReadableStreamCreateReadResult(cx, UndefinedHandleValue, true,
                                                        forAuthorCode);
             if (!resultObj) {
                 return false;
@@ -1610,23 +1610,23 @@ ReadableStreamErrorInternal(JSContext* c
     Rooted<ReadableStreamReader*> unwrappedReader(cx, UnwrapReaderFromStream(cx, unwrappedStream));
     if (!unwrappedReader) {
         return false;
     }
 
     // Steps 7,8: (Identical in our implementation.)
     // Step a: Repeat for each readRequest that is an element of
     //         reader.[[readRequests]],
-    RootedNativeObject unwrappedReadRequests(cx, unwrappedReader->requests());
+    Rooted<ListObject*> unwrappedReadRequests(cx, unwrappedReader->requests());
     RootedObject readRequest(cx);
     RootedValue val(cx);
-    uint32_t len = unwrappedReadRequests->getDenseInitializedLength();
+    uint32_t len = unwrappedReadRequests->length();
     for (uint32_t i = 0; i < len; i++) {
         // Step i: Reject readRequest.[[promise]] with e.
-        val = unwrappedReadRequests->getDenseElement(i);
+        val = unwrappedReadRequests->get(i);
         readRequest = &val.toObject();
 
         // Responses have to be created in the compartment from which the
         // error was triggered, which might not be the same as the one the
         // request was created in, so we have to wrap requests here.
         if (!cx->compartment()->wrap(cx, &readRequest)) {
             return false;
         }
@@ -1701,18 +1701,18 @@ ReadableStreamFulfillReadOrReadIntoReque
         return false;
     }
 
     // Step 2: Let readIntoRequest be the first element of
     //         reader.[[readIntoRequests]].
     // Step 3: Remove readIntoRequest from reader.[[readIntoRequests]], shifting
     //         all other elements downward (so that the second becomes the first,
     //         and so on).
-    RootedNativeObject unwrappedReadIntoRequests(cx, unwrappedReader->requests());
-    RootedObject readIntoRequest(cx, ShiftFromList<JSObject>(cx, unwrappedReadIntoRequests));
+    Rooted<ListObject*> unwrappedReadIntoRequests(cx, unwrappedReader->requests());
+    RootedObject readIntoRequest(cx, &unwrappedReadIntoRequests->popFirstAs<JSObject>(cx));
     MOZ_ASSERT(readIntoRequest);
     if (!cx->compartment()->wrap(cx, &readIntoRequest)) {
         return false;
     }
 
     // Step 4: Resolve read{Into}Request.[[promise]] with
     //         ! ReadableStreamCreateReadResult(chunk, done, readIntoRequest.[[forAuthorCode]]).
     RootedObject iterResult(cx,
@@ -1741,17 +1741,17 @@ ReadableStreamGetNumReadRequests(Readabl
     JS::AutoSuppressGCAnalysis nogc;
     ReadableStreamReader* reader = UnwrapReaderFromStreamNoThrow(stream);
 
     // Reader is a dead wrapper, treat it as non-existent.
     if (!reader) {
         return 0;
     }
 
-    return reader->requests()->getDenseInitializedLength();
+    return reader->requests()->length();
 }
 
 /**
  * Streams spec 3.4.12. ReadableStreamHasDefaultReader ( stream )
  */
 static MOZ_MUST_USE bool
 ReadableStreamHasDefaultReader(JSContext* cx,
                                Handle<ReadableStream*> unwrappedStream,
@@ -1978,19 +1978,18 @@ ReadableStreamDefaultReader_releaseLock(
     if (!reader->hasStream()) {
         args.rval().setUndefined();
         return true;
     }
 
     // Step 3: If this.[[readRequests]] is not empty, throw a TypeError exception.
     Value val = reader->getFixedSlot(ReadableStreamReader::Slot_Requests);
     if (!val.isUndefined()) {
-        NativeObject* readRequests = &val.toObject().as<NativeObject>();
-        uint32_t len = readRequests->getDenseInitializedLength();
-        if (len != 0) {
+        ListObject* readRequests = &val.toObject().as<ListObject>();
+        if (readRequests->length() != 0) {
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
                                       JSMSG_READABLESTREAMREADER_NOT_EMPTY,
                                       "releaseLock");
             return false;
         }
     }
 
     // Step 4: Perform ! ReadableStreamReaderGenericRelease(this).
@@ -2534,25 +2533,25 @@ static MOZ_MUST_USE JSObject*
 ReadableStreamControllerCancelSteps(JSContext* cx,
                                     Handle<ReadableStreamController*> unwrappedController,
                                     HandleValue reason)
 {
     AssertSameCompartment(cx, reason);
 
     // Step 1 of 3.10.5.1: If this.[[pendingPullIntos]] is not empty,
     if (!unwrappedController->is<ReadableStreamDefaultController>()) {
-        RootedNativeObject unwrappedPendingPullIntos(cx,
+        Rooted<ListObject*> unwrappedPendingPullIntos(cx,
             unwrappedController->as<ReadableByteStreamController>().pendingPullIntos());
 
-        if (unwrappedPendingPullIntos->getDenseInitializedLength() != 0) {
+        if (unwrappedPendingPullIntos->length() != 0) {
             // Step a: Let firstDescriptor be the first element of
             //         this.[[pendingPullIntos]].
             PullIntoDescriptor* unwrappedDescriptor =
                 UnwrapAndDowncastObject<PullIntoDescriptor>(
-                    cx, PeekList<JSObject>(unwrappedPendingPullIntos));
+                    cx, &unwrappedPendingPullIntos->get(0).toObject());
             if (!unwrappedDescriptor) {
                 return nullptr;
             }
 
             // Step b: Set firstDescriptor.[[bytesFilled]] to 0.
             unwrappedDescriptor->setBytesFilled(0);
         }
     }
@@ -2626,34 +2625,32 @@ DequeueValue(JSContext* cx,
 static JSObject*
 ReadableStreamDefaultControllerPullSteps(JSContext* cx,
                                          Handle<ReadableStreamDefaultController*> unwrappedController)
 {
     // Step 1: Let stream be this.[[controlledReadableStream]].
     Rooted<ReadableStream*> unwrappedStream(cx, unwrappedController->stream());
 
     // Step 2: If this.[[queue]] is not empty,
-    RootedNativeObject unwrappedQueue(cx);
+    Rooted<ListObject*> unwrappedQueue(cx);
     RootedValue val(cx, unwrappedController->getFixedSlot(StreamController::Slot_Queue));
     if (val.isObject()) {
-        unwrappedQueue = &val.toObject().as<NativeObject>();
-    }
-
-    if (unwrappedQueue && unwrappedQueue->getDenseInitializedLength() != 0) {
+        unwrappedQueue = &val.toObject().as<ListObject>();
+    }
+
+    if (unwrappedQueue && unwrappedQueue->length() != 0) {
         // Step a: Let chunk be ! DequeueValue(this.[[queue]]).
         RootedValue chunk(cx);
         if (!DequeueValue(cx, unwrappedController, &chunk)) {
             return nullptr;
         }
 
         // Step b: If this.[[closeRequested]] is true and this.[[queue]] is empty,
         //         perform ! ReadableStreamClose(stream).
-        if (unwrappedController->closeRequested() &&
-            unwrappedQueue->getDenseInitializedLength() == 0)
-        {
+        if (unwrappedController->closeRequested() && unwrappedQueue->length() == 0) {
             if (!ReadableStreamCloseInternal(cx, unwrappedStream)) {
                 return nullptr;
             }
         }
 
         // Step c: Otherwise, perform
         //         ! ReadableStreamDefaultControllerCallPullIfNeeded(this).
         else {
@@ -2921,18 +2918,18 @@ ReadableStreamDefaultControllerClose(JSC
     // Step 3: Assert: stream.[[state]] is "readable".
     MOZ_ASSERT(unwrappedStream->readable());
 
     // Step 4: Set controller.[[closeRequested]] to true.
     unwrappedController->setCloseRequested();
 
     // Step 5: If controller.[[queue]] is empty, perform
     //         ! ReadableStreamClose(stream).
-    RootedNativeObject unwrappedQueue(cx, unwrappedController->queue());
-    if (unwrappedQueue->getDenseInitializedLength() == 0) {
+    Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
+    if (unwrappedQueue->length() == 0) {
         return ReadableStreamCloseInternal(cx, unwrappedStream);
     }
 
     return true;
 }
 
 static MOZ_MUST_USE bool
 EnqueueValueWithSize(JSContext* cx,
@@ -3515,20 +3512,20 @@ ReadableByteStreamControllerPullSteps(JS
             }
 
             queueTotalSize = queueTotalSize - bytesWritten;
         } else {
             // Step 3.b: Let entry be the first element of this.[[queue]].
             // Step 3.c: Remove entry from this.[[queue]], shifting all other
             //           elements downward (so that the second becomes the
             //           first, and so on).
-            RootedNativeObject unwrappedQueue(cx, unwrappedController->queue());
+            Rooted<ListObject*> unwrappedQueue(cx, unwrappedController->queue());
             Rooted<ByteStreamChunk*> unwrappedEntry(cx,
                 UnwrapAndDowncastObject<ByteStreamChunk>(
-                    cx, ShiftFromList<JSObject>(cx, unwrappedQueue)));
+                    cx, &unwrappedQueue->popFirstAs<JSObject>(cx)));
             if (!unwrappedEntry) {
                 return nullptr;
             }
 
             queueTotalSize = queueTotalSize - unwrappedEntry->byteLength();
 
             // Step 3.f: Let view be ! Construct(%Uint8Array%,
             //                                   « entry.[[buffer]],
@@ -3713,23 +3710,23 @@ ReadableByteStreamControllerClose(JSCont
         // Step a: Set controller.[[closeRequested]] to true.
         unwrappedController->setCloseRequested();
 
         // Step b: Return.
         return true;
     }
 
     // Step 5: If controller.[[pendingPullIntos]] is not empty,
-    RootedNativeObject unwrappedPendingPullIntos(cx, unwrappedController->pendingPullIntos());
-    if (unwrappedPendingPullIntos->getDenseInitializedLength() != 0) {
+    Rooted<ListObject*> unwrappedPendingPullIntos(cx, unwrappedController->pendingPullIntos());
+    if (unwrappedPendingPullIntos->length() != 0) {
         // Step a: Let firstPendingPullInto be the first element of
         //         controller.[[pendingPullIntos]].
         Rooted<PullIntoDescriptor*> unwrappedFirstPendingPullInto(cx,
             UnwrapAndDowncastObject<PullIntoDescriptor>(
-                cx, PeekList<JSObject>(unwrappedPendingPullIntos)));
+                cx, &unwrappedPendingPullIntos->get(0).toObject()));
         if (!unwrappedFirstPendingPullInto) {
             return false;
         }
 
         // Step b: If firstPendingPullInto.[[bytesFilled]] > 0,
         if (unwrappedFirstPendingPullInto->bytesFilled() > 0) {
             // Step i: Let e be a new TypeError exception.
             JS_ReportErrorNumberASCII(cx, GetErrorMessage, nullptr,
@@ -3985,23 +3982,23 @@ CLASS_SPEC(CountQueuingStrategy, 1, 0, 0
  * Streams spec, 6.2.1. DequeueValue ( container ) nothrow
  */
 inline static MOZ_MUST_USE bool
 DequeueValue(JSContext* cx, Handle<ReadableStreamController*> unwrappedContainer, MutableHandleValue chunk)
 {
     // Step 1: Assert: container has [[queue]] and [[queueTotalSize]] internal
     //         slots (implicit).
     // Step 2: Assert: queue is not empty.
-    RootedNativeObject unwrappedQueue(cx, unwrappedContainer->queue());
-    MOZ_ASSERT(unwrappedQueue->getDenseInitializedLength() > 0);
+    Rooted<ListObject*> unwrappedQueue(cx, unwrappedContainer->queue());
+    MOZ_ASSERT(unwrappedQueue->length() > 0);
 
     // Step 3. Let pair be the first element of queue.
     // Step 4. Remove pair from queue, shifting all other elements downward
     //         (so that the second becomes the first, and so on).
-    Rooted<QueueEntry*> unwrappedPair(cx, ShiftFromList<QueueEntry>(cx, unwrappedQueue));
+    Rooted<QueueEntry*> unwrappedPair(cx, &unwrappedQueue->popFirstAs<QueueEntry>(cx));
     MOZ_ASSERT(unwrappedPair);
 
     // Step 5: Set container.[[queueTotalSize]] to
     //         container.[[queueTotalSize]] − pair.[[size]].
     // Step 6: If container.[[queueTotalSize]] < 0, set
     //         container.[[queueTotalSize]] to 0.
     //         (This can occur due to rounding errors.)
     double totalSize = unwrappedContainer->queueTotalSize();
@@ -4048,28 +4045,28 @@ EnqueueValueWithSize(JSContext* cx,
                                   JSMSG_NUMBER_MUST_BE_FINITE_NON_NEGATIVE, "size");
         return false;
     }
 
     // Step 4: Append Record {[[value]]: value, [[size]]: size} as the last
     //         element of container.[[queue]].
     {
         AutoRealm ar(cx, unwrappedContainer);
-        RootedNativeObject queue(cx, unwrappedContainer->queue());
+        Rooted<ListObject*> queue(cx, unwrappedContainer->queue());
         RootedValue wrappedVal(cx, value);
         if (!cx->compartment()->wrap(cx, &wrappedVal)) {
             return false;
         }
 
         QueueEntry* entry = QueueEntry::create(cx, wrappedVal, size);
         if (!entry) {
             return false;
         }
         RootedValue val(cx, ObjectValue(*entry));
-        if (!AppendToList(cx, queue, val)) {
+        if (!queue->append(cx, val)) {
             return false;
         }
     }
 
     // Step 5: Set container.[[queueTotalSize]] to
     //         container.[[queueTotalSize]] + size.
     unwrappedContainer->setQueueTotalSize(unwrappedContainer->queueTotalSize() + size);
 
@@ -4105,25 +4102,25 @@ ResetQueue(JSContext* cx, Handle<Readabl
  * Appends the given |obj| to the given list |container|'s list.
  */
 inline static MOZ_MUST_USE bool
 AppendToListAtSlot(JSContext* cx,
                    HandleNativeObject unwrappedContainer,
                    uint32_t slot,
                    HandleObject obj)
 {
-    RootedNativeObject list(cx,
-        &unwrappedContainer->getFixedSlot(slot).toObject().as<NativeObject>());
+    Rooted<ListObject*> list(cx,
+        &unwrappedContainer->getFixedSlot(slot).toObject().as<ListObject>());
 
     AutoRealm ar(cx, list);
     RootedValue val(cx, ObjectValue(*obj));
     if (!cx->compartment()->wrap(cx, &val)) {
         return false;
     }
-    return AppendToList(cx, list, val);
+    return list->append(cx, val);
 }
 
 
 /**
  * Streams spec, 6.3.2. InvokeOrNoop ( O, P, args )
  */
 inline static MOZ_MUST_USE bool
 InvokeOrNoop(JSContext* cx, HandleValue O, HandlePropertyName P, HandleValue arg,
--- a/js/src/builtin/Stream.h
+++ b/js/src/builtin/Stream.h
@@ -4,19 +4,19 @@
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef builtin_Stream_h
 #define builtin_Stream_h
 
 #include "js/Stream.h"
 #include "builtin/Promise.h"
+#include "vm/List.h"
 #include "vm/NativeObject.h"
 
-
 namespace js {
 
 class ReadableStreamReader;
 class ReadableStreamController;
 
 class ReadableStream : public NativeObject
 {
   public:
@@ -170,18 +170,18 @@ class ReadableStreamReader : public Nati
         return getFixedSlot(Slot_ForAuthorCode).toBoolean()
                ? ForAuthorCodeBool::Yes
                : ForAuthorCodeBool::No;
     }
     void setForAuthorCode(ForAuthorCodeBool value) {
         setFixedSlot(Slot_ForAuthorCode, BooleanValue(value == ForAuthorCodeBool::Yes));
     }
 
-    NativeObject* requests() const {
-        return &getFixedSlot(Slot_Requests).toObject().as<NativeObject>();
+    ListObject* requests() const {
+        return &getFixedSlot(Slot_Requests).toObject().as<ListObject>();
     }
     void clearRequests() { setFixedSlot(Slot_Requests, UndefinedValue()); }
 
     JSObject* closedPromise() const { return &getFixedSlot(Slot_ClosedPromise).toObject(); }
     void setClosedPromise(JSObject* wrappedPromise) {
         setFixedSlot(Slot_ClosedPromise, ObjectValue(*wrappedPromise));
     }
 
@@ -214,17 +214,17 @@ class StreamController : public NativeOb
      * but might contain wrappers for objects from other compartments.
      */
     enum Slots {
         Slot_Queue,
         Slot_TotalSize,
         SlotCount
     };
 
-    NativeObject* queue() const { return &getFixedSlot(Slot_Queue).toObject().as<NativeObject>(); }
+    ListObject* queue() const { return &getFixedSlot(Slot_Queue).toObject().as<ListObject>(); }
     double queueTotalSize() const { return getFixedSlot(Slot_TotalSize).toNumber(); }
     void setQueueTotalSize(double size) { setFixedSlot(Slot_TotalSize, NumberValue(size)); }
 };
 
 class ReadableStreamController : public StreamController
 {
   public:
     /**
@@ -344,18 +344,18 @@ class ReadableByteStreamController : pub
         Slot_BYOBRequest = ReadableStreamController::SlotCount,
         Slot_PendingPullIntos,
         Slot_AutoAllocateSize,
         SlotCount
     };
 
     Value byobRequest() const { return getFixedSlot(Slot_BYOBRequest); }
     void clearBYOBRequest() { setFixedSlot(Slot_BYOBRequest, JS::UndefinedValue()); }
-    NativeObject* pendingPullIntos() const {
-        return &getFixedSlot(Slot_PendingPullIntos).toObject().as<NativeObject>();
+    ListObject* pendingPullIntos() const {
+        return &getFixedSlot(Slot_PendingPullIntos).toObject().as<ListObject>();
     }
     Value autoAllocateChunkSize() const { return getFixedSlot(Slot_AutoAllocateSize); }
     void setAutoAllocateChunkSize(const Value & size) {
         setFixedSlot(Slot_AutoAllocateSize, size);
     }
 
     static bool constructor(JSContext* cx, unsigned argc, Value* vp);
     static const ClassSpec classSpec_;
--- a/js/src/moz.build
+++ b/js/src/moz.build
@@ -270,16 +270,17 @@ UNIFIED_SOURCES += [
     'vm/Iteration.cpp',
     'vm/JSAtom.cpp',
     'vm/JSContext.cpp',
     'vm/JSFunction.cpp',
     'vm/JSObject.cpp',
     'vm/JSONParser.cpp',
     'vm/JSONPrinter.cpp',
     'vm/JSScript.cpp',
+    'vm/List.cpp',
     'vm/MemoryMetrics.cpp',
     'vm/NativeObject.cpp',
     'vm/ObjectGroup.cpp',
     'vm/OffThreadScriptCompilation.cpp',
     'vm/PIC.cpp',
     'vm/Printer.cpp',
     'vm/Probes.cpp',
     'vm/ProxyObject.cpp',
--- a/js/src/vm/AsyncIteration.cpp
+++ b/js/src/vm/AsyncIteration.cpp
@@ -13,17 +13,16 @@
 #include "vm/GlobalObject.h"
 #include "vm/Interpreter.h"
 #include "vm/Realm.h"
 #include "vm/SelfHosting.h"
 
 #include "vm/JSContext-inl.h"
 #include "vm/JSObject-inl.h"
 #include "vm/List-inl.h"
-#include "vm/NativeObject-inl.h"
 
 using namespace js;
 
 #define UNWRAPPED_ASYNC_WRAPPED_SLOT 1
 #define WRAPPED_ASYNC_UNWRAPPED_SLOT 0
 
 // Async Iteration proposal 8.3.10 Runtime Semantics: EvaluateBody.
 static bool
@@ -334,60 +333,60 @@ AsyncGeneratorObject::enqueueRequest(JSC
                                      Handle<AsyncGeneratorRequest*> request)
 {
     if (asyncGenObj->isSingleQueue()) {
         if (asyncGenObj->isSingleQueueEmpty()) {
             asyncGenObj->setSingleQueueRequest(request);
             return true;
         }
 
-        RootedNativeObject queue(cx, NewList(cx));
+        Rooted<ListObject*> queue(cx, ListObject::create(cx));
         if (!queue) {
             return false;
         }
 
         RootedValue requestVal(cx, ObjectValue(*asyncGenObj->singleQueueRequest()));
-        if (!AppendToList(cx, queue, requestVal)) {
+        if (!queue->append(cx, requestVal)) {
             return false;
         }
         requestVal = ObjectValue(*request);
-        if (!AppendToList(cx, queue, requestVal)) {
+        if (!queue->append(cx, requestVal)) {
             return false;
         }
 
         asyncGenObj->setQueue(queue);
         return true;
     }
 
-    RootedNativeObject queue(cx, asyncGenObj->queue());
+    Rooted<ListObject*> queue(cx, asyncGenObj->queue());
     RootedValue requestVal(cx, ObjectValue(*request));
-    return AppendToList(cx, queue, requestVal);
+    return queue->append(cx, requestVal);
 }
 
 /* static */ AsyncGeneratorRequest*
 AsyncGeneratorObject::dequeueRequest(JSContext* cx, Handle<AsyncGeneratorObject*> asyncGenObj)
 {
     if (asyncGenObj->isSingleQueue()) {
         AsyncGeneratorRequest* request = asyncGenObj->singleQueueRequest();
         asyncGenObj->clearSingleQueueRequest();
         return request;
     }
 
-    RootedNativeObject queue(cx, asyncGenObj->queue());
-    return ShiftFromList<AsyncGeneratorRequest>(cx, queue);
+    Rooted<ListObject*> queue(cx, asyncGenObj->queue());
+    return &queue->popFirstAs<AsyncGeneratorRequest>(cx);
 }
 
 /* static */ AsyncGeneratorRequest*
 AsyncGeneratorObject::peekRequest(Handle<AsyncGeneratorObject*> asyncGenObj)
 {
     if (asyncGenObj->isSingleQueue()) {
         return asyncGenObj->singleQueueRequest();
     }
 
-    return PeekList<AsyncGeneratorRequest>(asyncGenObj->queue());
+    return &asyncGenObj->queue()->getAs<AsyncGeneratorRequest>(0);
 }
 
 const Class AsyncGeneratorRequest::class_ = {
     "AsyncGeneratorRequest",
     JSCLASS_HAS_RESERVED_SLOTS(AsyncGeneratorRequest::Slots)
 };
 
 // Async Iteration proposal 11.4.3.1.
--- a/js/src/vm/AsyncIteration.h
+++ b/js/src/vm/AsyncIteration.h
@@ -6,16 +6,17 @@
 
 #ifndef vm_AsyncIteration_h
 #define vm_AsyncIteration_h
 
 #include "builtin/Promise.h"
 #include "vm/GeneratorObject.h"
 #include "vm/JSContext.h"
 #include "vm/JSObject.h"
+#include "vm/List.h"
 
 namespace js {
 
 // An async generator is implemented using two function objects, which are
 // referred to as the "unwrapped" and the "wrapped" async generator object.
 // The unwrapped function is a generator function compiled from the async
 // generator's script. |await| expressions within the async generator are
 // compiled like |yield| expression for the generator function with dedicated
@@ -209,20 +210,20 @@ class AsyncGeneratorObject : public Nati
     }
     void clearSingleQueueRequest() {
         setFixedSlot(Slot_QueueOrRequest, NullValue());
     }
     AsyncGeneratorRequest* singleQueueRequest() const {
         return &getFixedSlot(Slot_QueueOrRequest).toObject().as<AsyncGeneratorRequest>();
     }
 
-    NativeObject* queue() const {
-        return &getFixedSlot(Slot_QueueOrRequest).toObject().as<NativeObject>();
+    ListObject* queue() const {
+        return &getFixedSlot(Slot_QueueOrRequest).toObject().as<ListObject>();
     }
-    void setQueue(JSObject* queue_) {
+    void setQueue(ListObject* queue_) {
         setFixedSlot(Slot_QueueOrRequest, ObjectValue(*queue_));
     }
 
   public:
     static const Class class_;
 
     static AsyncGeneratorObject*
     create(JSContext* cx, HandleFunction asyncGen, HandleValue generatorVal);
--- a/js/src/vm/List-inl.h
+++ b/js/src/vm/List-inl.h
@@ -2,67 +2,61 @@
  * vim: set ts=8 sts=4 et sw=4 tw=99:
  * This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this
  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 #ifndef vm_List_inl_h
 #define vm_List_inl_h
 
+#include "vm/List.h"
+
 #include "gc/Rooting.h"
 #include "vm/JSContext.h"
 #include "vm/NativeObject.h"
 
 #include "vm/JSObject-inl.h"
 #include "vm/NativeObject-inl.h"
 
-namespace js {
-
-inline MOZ_MUST_USE NativeObject*
-NewList(JSContext* cx)
+inline /* static */ js::ListObject*
+js::ListObject::create(JSContext* cx)
 {
-    return NewObjectWithNullTaggedProto<PlainObject>(cx);
+    return NewObjectWithNullTaggedProto<ListObject>(cx);
 }
 
-inline MOZ_MUST_USE bool
-AppendToList(JSContext* cx, HandleNativeObject list, HandleValue value)
+inline bool
+js::ListObject::append(JSContext* cx, HandleValue value)
 {
-    uint32_t length = list->getDenseInitializedLength();
+    uint32_t len = length();
 
-    if (!list->ensureElements(cx, length + 1)) {
+    if (!ensureElements(cx, len + 1)) {
         return false;
     }
 
-    list->ensureDenseInitializedLength(cx, length, 1);
-    list->setDenseElementWithType(cx, length, value);
-
+    ensureDenseInitializedLength(cx, len, 1);
+    setDenseElementWithType(cx, len, value);
     return true;
 }
 
-template<class T>
-inline MOZ_MUST_USE T*
-PeekList(NativeObject* list)
+inline JS::Value
+js::ListObject::popFirst(JSContext* cx)
 {
-    MOZ_ASSERT(list->getDenseInitializedLength() > 0);
-    return &list->getDenseElement(0).toObject().as<T>();
-}
+    uint32_t len = length();
+    MOZ_ASSERT(len > 0);
 
-template<class T>
-inline MOZ_MUST_USE T*
-ShiftFromList(JSContext* cx, HandleNativeObject list)
-{
-    uint32_t length = list->getDenseInitializedLength();
-    MOZ_ASSERT(length > 0);
-
-    Rooted<T*> entry(cx, &list->getDenseElement(0).toObject().as<T>());
-    if (!list->tryShiftDenseElements(1)) {
-        list->moveDenseElements(0, 1, length - 1);
-        list->setDenseInitializedLength(length - 1);
-        list->shrinkElements(cx, length - 1);
+    Value entry = get(0);
+    if (!tryShiftDenseElements(1)) {
+        moveDenseElements(0, 1, len - 1);
+        setDenseInitializedLength(len - 1);
+        shrinkElements(cx, len - 1);
     }
 
-    MOZ_ASSERT(list->getDenseInitializedLength() == length - 1);
+    MOZ_ASSERT(length() == len - 1);
     return entry;
 }
 
-} /* namespace js */
+template <class T>
+inline T&
+js::ListObject::popFirstAs(JSContext* cx) {
+    return popFirst(cx).toObject().as<T>();
+}
 
-#endif /* vm_List_inl_h */
+#endif // vm_List_inl_h
new file mode 100644
--- /dev/null
+++ b/js/src/vm/List.cpp
@@ -0,0 +1,7 @@
+#include "vm/List-inl.h"
+
+using namespace js;
+
+const Class ListObject::class_ = {
+    "List"
+};
new file mode 100644
--- /dev/null
+++ b/js/src/vm/List.h
@@ -0,0 +1,69 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*-
+ * vim: set ts=8 sts=4 et sw=4 tw=99:
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#ifndef vm_List_h
+#define vm_List_h
+
+#include "NamespaceImports.h"
+#include "js/Value.h"
+#include "vm/NativeObject.h"
+
+namespace js {
+
+/**
+ * The List specification type, ECMA-262 6.2.1.
+ * <https://tc39.github.io/ecma262/#sec-list-and-record-specification-type>
+ *
+ * Lists are simple mutable sequences of values. Many standards use them.
+ * Abstractly, they're not objects; they don't have properties or prototypes;
+ * they're for internal specification use only. ListObject is our most direct
+ * implementation of a List: store the values in the slots of a JSObject.
+ *
+ * We often implement Lists in other ways. For example, builtin/Utilities.js
+ * contains a completely unrelated List constructor that's used in self-hosted
+ * code. And AsyncGeneratorObject optimizes away the ListObject in the common
+ * case where its internal queue never holds more than one element.
+ *
+ * ListObjects must not be exposed to content scripts.
+ */
+class ListObject : public NativeObject {
+  public:
+    static const Class class_;
+
+    inline static MOZ_MUST_USE ListObject* create(JSContext* cx);
+
+    uint32_t length() const { return getDenseInitializedLength(); }
+
+    const Value& get(uint32_t index) const { return getDenseElement(index); }
+
+    template <class T>
+    T& getAs(uint32_t index) const { return get(index).toObject().as<T>(); }
+
+    /**
+     * Add an element to the end of the list. Returns false on OOM.
+     */
+    inline MOZ_MUST_USE bool append(JSContext* cx, HandleValue value);
+
+    /**
+     * Remove and return the first element of the list.
+     *
+     * Precondition: This list is not empty.
+     */
+    inline JS::Value popFirst(JSContext* cx);
+
+    /**
+     * Remove and return the first element of the list.
+     *
+     * Precondition: This list is not empty, and the first element
+     * is an object of class T.
+     */
+    template <class T>
+    inline T& popFirstAs(JSContext* cx);
+};
+
+} // namespace js
+
+#endif // vm_List_h