[wpt-sync] Sync PR 50723 - DOM: Implement ref-counted Observable producer
Categories
(Core :: DOM: Core & HTML, task, P4)
Tracking
()
Tracking | Status | |
---|---|---|
firefox137 | --- | fixed |
People
(Reporter: wpt-sync, Unassigned)
References
()
Details
(Whiteboard: [wptsync downstream])
Sync web-platform-tests PR 50723 into mozilla-central (this bug is closed when the sync is complete).
PR: https://github.com/web-platform-tests/wpt/pull/50723
Details from upstream follow.
Dominic Farolino <dom@chromium.org> wrote:
DOM: Implement ref-counted Observable producer
This CL implements ref-counted producers, which came out of the W3C
TPAC discussions in 2024. After this CL, multiple
ObservableInternalObserver
objects can be associated/registered with
a single activeSubscriber
. The main meat of this CL involves the
logic managing consumer unsubscription, to ensure that only once all
associated consumers/observers unsubscribe do we actually close down
aSubscriber
.See https://github.com/WICG/observable/pull/197.
R=masonf
Bug: 363015168
Change-Id: I67b63a3f4e38bf5be0236fd1b8f025648a3089bf
Reviewed-on: https://chromium-review.googlesource.com/6221901
WPT-Export-Revision: f70e66f5af9e9056cecfe74b00be782b5ae9dbe3
Assignee | ||
Updated•10 days ago
|
Assignee | ||
Comment 1•10 days ago
|
||
Assignee | ||
Comment 2•10 days ago
|
||
CI Results
Ran 9 Firefox configurations based on mozilla-central, and Firefox, Chrome, and Safari on GitHub CI
Total 4 tests and 47 subtests
Status Summary
Firefox
OK
: 4
FAIL
: 180
Chrome
OK
: 4
PASS
: 166
FAIL
: 14
Safari
OK
: 4
FAIL
: 180
Links
Gecko CI (Treeherder)
GitHub PR Head
GitHub PR Base
Details
New Tests That Don't Pass
- /dom/observable/tentative/observable-constructor.any.html [wpt.fyi]
- Observable constructor:
FAIL
(Chrome:PASS
, Safari:FAIL
) - subscribe() can be called with no arguments:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber interface is not constructible:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscribe with just a function as the next handler:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Observable constructor calls initializer on subscribe:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Observable error path called synchronously:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber must have receiver:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber next & error must recieve argument:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber complete() will set active to false, and abort signal:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber active is readonly:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber signal is readonly:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Observable should error if initializer throws:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription is inactive after complete():
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription is inactive after error():
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription is inactive when aborted signal is passed in:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#signal is not the same AbortSignal as the one passed into
subscribe()
:FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription does not emit values after completion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription does not emit values after error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Completing or nexting a subscriber after an error does nothing:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors pushed to the subscriber that are not handled by the subscription are reported to the global:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors thrown in the initializer that are not handled by the subscription are reported to the global:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription reports errors that are pushed after subscriber is closed by completion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors thrown by initializer function after subscriber is closed by completion are reported:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors thrown by initializer function after subscriber is closed by error are reported:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors pushed by initializer function after subscriber is closed by error are reported:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#complete() cannot re-entrantly invoke itself:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#error() cannot re-entrantly invoke itself:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Unsubscription lifecycle:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns are called in upstream->downstream order on consumer-initiated unsubscription:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#error() value is stored as Subscriber's AbortSignal's reason:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Aborting a subscription should stop emitting values:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Calling subscribe should never throw an error synchronously, initializer throws error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Calling subscribe should never throw an error synchronously, subscriber pushes error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardown should be called when subscription is aborted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called when subscription is closed by completion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called when subscription is closed by subscriber pushing an error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called when subscription is closed by subscriber throwing error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called synchronously during addTeardown() if the subscription is inactive:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Multiple subscriptions share the same producer and teardown runs only after last subscription abort:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - New subscription after complete creates new producer:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - Teardown runs after last unsubscribe regardless of unsubscription order:
FAIL
(Chrome:FAIL
, Safari:FAIL
)
- Observable constructor:
- /dom/observable/tentative/observable-constructor.any.worker.html [wpt.fyi]
- Observable constructor:
FAIL
(Chrome:PASS
, Safari:FAIL
) - subscribe() can be called with no arguments:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber interface is not constructible:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscribe with just a function as the next handler:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Observable constructor calls initializer on subscribe:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Observable error path called synchronously:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber must have receiver:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber next & error must recieve argument:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber complete() will set active to false, and abort signal:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber active is readonly:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber signal is readonly:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Observable should error if initializer throws:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription is inactive after complete():
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription is inactive after error():
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription is inactive when aborted signal is passed in:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#signal is not the same AbortSignal as the one passed into
subscribe()
:FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription does not emit values after completion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription does not emit values after error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Completing or nexting a subscriber after an error does nothing:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors pushed to the subscriber that are not handled by the subscription are reported to the global:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors thrown in the initializer that are not handled by the subscription are reported to the global:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscription reports errors that are pushed after subscriber is closed by completion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors thrown by initializer function after subscriber is closed by completion are reported:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors thrown by initializer function after subscriber is closed by error are reported:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Errors pushed by initializer function after subscriber is closed by error are reported:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#complete() cannot re-entrantly invoke itself:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#error() cannot re-entrantly invoke itself:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Unsubscription lifecycle:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns are called in upstream->downstream order on consumer-initiated unsubscription:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns are called in downstream->upstream order on consumer-initiated unsubscription with pre-aborted Signal:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Producer-initiated unsubscription in a downstream Observable fires abort events before each teardown, in downstream->upstream order:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Subscriber#error() value is stored as Subscriber's AbortSignal's reason:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Aborting a subscription should stop emitting values:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Calling subscribe should never throw an error synchronously, initializer throws error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Calling subscribe should never throw an error synchronously, subscriber pushes error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardown should be called when subscription is aborted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called when subscription is closed by completion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called when subscription is closed by subscriber pushing an error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called when subscription is closed by subscriber throwing error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Teardowns should be called synchronously during addTeardown() if the subscription is inactive:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Multiple subscriptions share the same producer and teardown runs only after last subscription abort:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - New subscription after complete creates new producer:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - Teardown runs after last unsubscribe regardless of unsubscription order:
FAIL
(Chrome:FAIL
, Safari:FAIL
)
- Observable constructor:
- /dom/observable/tentative/observable-from.any.html [wpt.fyi]
- from(): Observable.from() is a function:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Failed conversions:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Given an observable, it returns that exact observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Given an array:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Iterable converts to Observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] side-effects (one observable):
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] not callable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] not callable AFTER SUBSCRIBE throws:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] returns null AFTER SUBSCRIBE throws:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] is not cached:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] side-effects (many observables):
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] next() throws error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Converts Promise to Observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Converts rejected Promise to Observable. No
unhandledrejection
event when error is handled by subscription:FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Promise whose [Symbol.iterator] returns null converts as Promise:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Rethrows the error when Converting an object whose @@iterator method getter throws an error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async iterable protocol null, converts as iterator:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Asynchronous iterable conversion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Asynchronous iterable multiple in-flight subscriptions:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Sync iterable multiple in-flight subscriptions:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Asynchronous generator conversion: can only be used once:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Promise-wrapping semantics of IteratorResult interface:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Errors thrown in Symbol.asyncIterator() are propagated synchronously:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Errors thrown in async iterator's next() GETTER are propagated in a microtask:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Errors thrown in async iterator's next() are propagated in a microtask:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Aborting sync iterable midway through iteration both stops iteration and invokes
IteratorRecord#return():
FAIL(Chrome:
PASS, Safari:
FAIL`) - from(): Aborting async iterable midway through iteration both stops iteration and invokes
IteratorRecord#return():
FAIL(Chrome:
PASS, Safari:
FAIL`) - from(): Sync iterable:
Iterator#return()
must return an Object, or an error is thrown:FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async iterable:
Iterator#return()
must return an Object, or a Promise rejects asynchronously:FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Asynchronous iterable conversion, with synchronous iterable fallback:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Generator finally block runs when subscription is aborted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Generator finally block run when Observable completes:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Generator finally block run when Observable errors:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async generator finally block run when subscription is aborted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async generator finally block runs when Observable completes:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async generator finally block run when Observable errors:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Sync iterable: error thrown from IteratorRecord#return() can be synchronously caught:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async iterable: error thrown from IteratorRecord#return() is wrapped in rejected Promise:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Subscribing to an iterable Observable with an aborted signal does not call next():
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): When iterable conversion aborts the subscription, next() is never called:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Aborting an async iterable subscription stops subsequent next() calls, but old next() Promise reactions are web-observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Abort after complete does NOT call IteratorRecord#return():
FAIL
(Chrome:PASS
, Safari:FAIL
) - Invalid async iterator protocol error is surfaced before Subscriber#signal is consulted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Invalid iterator protocol error is surfaced before Subscriber#signal is consulted:
FAIL
(Chrome:PASS
, Safari:FAIL
)
- from(): Observable.from() is a function:
- /dom/observable/tentative/observable-from.any.worker.html [wpt.fyi]
- from(): Observable.from() is a function:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Failed conversions:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Given an observable, it returns that exact observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Given an array:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Iterable converts to Observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] side-effects (one observable):
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] not callable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] not callable AFTER SUBSCRIBE throws:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] returns null AFTER SUBSCRIBE throws:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] is not cached:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] side-effects (many observables):
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): [Symbol.iterator] next() throws error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Converts Promise to Observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Converts rejected Promise to Observable. No
unhandledrejection
event when error is handled by subscription:FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Rejections not handled by subscription are reported to the global, and still not sent as an unhandledrejection event:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Observable that implements @@iterator protocol gets converted as an Observable, not iterator:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Promise that implements @@iterator protocol gets converted as an iterable, not Promise:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Promise whose [Symbol.iterator] returns null converts as Promise:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Rethrows the error when Converting an object whose @@iterator method getter throws an error:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async iterable protocol null, converts as iterator:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Asynchronous iterable conversion:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Asynchronous iterable multiple in-flight subscriptions:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Sync iterable multiple in-flight subscriptions:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Asynchronous generator conversion: can only be used once:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Promise-wrapping semantics of IteratorResult interface:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Errors thrown in Symbol.asyncIterator() are propagated synchronously:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Errors thrown in async iterator's next() GETTER are propagated in a microtask:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Errors thrown in async iterator's next() are propagated in a microtask:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Aborting sync iterable midway through iteration both stops iteration and invokes
IteratorRecord#return():
FAIL(Chrome:
PASS, Safari:
FAIL`) - from(): Aborting async iterable midway through iteration both stops iteration and invokes
IteratorRecord#return():
FAIL(Chrome:
PASS, Safari:
FAIL`) - from(): Sync iterable:
Iterator#return()
must return an Object, or an error is thrown:FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async iterable:
Iterator#return()
must return an Object, or a Promise rejects asynchronously:FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Asynchronous iterable conversion, with synchronous iterable fallback:
FAIL
(Chrome:FAIL
, Safari:FAIL
) - from(): Generator finally block runs when subscription is aborted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Generator finally block run when Observable completes:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Generator finally block run when Observable errors:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async generator finally block run when subscription is aborted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async generator finally block runs when Observable completes:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async generator finally block run when Observable errors:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Sync iterable: error thrown from IteratorRecord#return() can be synchronously caught:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Async iterable: error thrown from IteratorRecord#return() is wrapped in rejected Promise:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Subscribing to an iterable Observable with an aborted signal does not call next():
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): When iterable conversion aborts the subscription, next() is never called:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Aborting an async iterable subscription stops subsequent next() calls, but old next() Promise reactions are web-observable:
FAIL
(Chrome:PASS
, Safari:FAIL
) - from(): Abort after complete does NOT call IteratorRecord#return():
FAIL
(Chrome:PASS
, Safari:FAIL
) - Invalid async iterator protocol error is surfaced before Subscriber#signal is consulted:
FAIL
(Chrome:PASS
, Safari:FAIL
) - Invalid iterator protocol error is surfaced before Subscriber#signal is consulted:
FAIL
(Chrome:PASS
, Safari:FAIL
)
- from(): Observable.from() is a function:
https://hg.mozilla.org/mozilla-central/rev/4e10402b52c0
https://hg.mozilla.org/mozilla-central/rev/3ddecce89ed2
Description
•