New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Implement streams by adopting the reference implementation #3200
base: main
Are you sure you want to change the base?
Conversation
git-subtree-dir: lib/jsdom/living/streams git-subtree-split: 033c6d900c438371083785828c659e3150cf4847
Several types must retain globalObject references, for the purpose of creating other impls, but may be constructed without running their constructors.
These tests crash the harness in confusing ways, and in any case can't pass until postMessage() is more completely implemented.
This reverts commit 393edff. Import rewrites are no longer needed now that files have been moved to the appropriate directory.
8a7d92b5 Resolve BYOB reads immediately on cancel git-subtree-dir: lib/jsdom/living/streams git-subtree-split: 8a7d92b559c2b3f5028d9f48b49de90044be35c1
…reference-subtree-move
Should help clarify when a value is a promise _capability_ versus just a promise.
Better reflects its current purpose.
So, looking into the Object.getPrototypeOf(
Object.getPrototypeOf(
(async function* () {}).prototype // an anonymous prototype
) // the AsyncGenerator prototype
) // the AsyncIterator prototype which presumably has to be executed as a script in the vm in order to get the right realm. This could maybe be achieved by adding code to webidl2js that checks on whether the globalObject is a vm context and if true runs the above script in it, but that would
My current best idea for how to get the function setInCtorRegistry(globalObject, key, value) {
const ctorRegistry = utils.initCtorRegistry(globalObject);
ctorRegistry[key] = value;
} or less generally function registerAsyncIteratorPrototype(globalObject, value) {
setInCtorRegistry(globalObject, "%AsyncIteratorPrototype%", value)
} which could be called by the user before @domenic what do you think? |
I like the |
Cool! One issue that raises is that all the names in the constructor registry then become (at least technically) part of that public API. At one time that was just the names of webidl types; after the sync iterator prototype fixes it now includes some stringly-typed intrinsics ( (I can imagine it eventually expanding to include all intrinsics, allowing us to replace code like Design questions: Should the stringly-typed things maybe be replaced with symbols? How much of the constructor registry should be documented, and should any be explicitly reserved for internal use? I'll try get a draft of this up and running and see what other issues come up. |
Note that per the currently-stalled jsdom/webidl2js#242 the plan is to use the normal names as much as possible (e.g. I think the I don't think symbols have any advantages here over strings; that seems like an unnecessary level of indirection. Documenting the new public API ( |
Introduced in beaac8f ("Remove the promise sidetable")
I found a possible workaround for nodejs/node#38918: if we run scripts in the vm that define empty properties for all of the globals we later install, subsequent attempts to set them appear to behave correctly. This allows running the |
// We need to set the 'name' property: | ||
// eslint-disable-next-line prefer-arrow-callback |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
FWIW you can do
const size = chunk => chunk.byteLength;
sizeFunctionWeakMap.set(globalObject, size);
The size
function's name property will be automatically set.
Also, do we need to create the function in globalObject
, to ensure the prototype is correct?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is straight from the reference impl. Personally I have a mild preference for function size() {}
over const size =
, I think it makes what's going on a little more clear.
I think you're right about the prototype, but that's a jsdom-wide problem, isn't it? E.g. Object.getPrototypeOf(window.Headers) !== window.Function.prototype
. Not sure it's worth hacking around that issue here specifically, unless there's a really simple fix.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think using function size() { }
is important because per spec the result has a .prototype
property, whereas arrow functions do not. (I wonder if that's tested.)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you're right about the prototype, but that's a jsdom-wide problem, isn't it?
Ah yes, indeed.
@domenic Printing
new ByteLengthQueuingStrategy({ highWaterMark: 100 }).size.prototype
gives undefined in Chrome and Firefox. The Streams spec calls CreateBuiltinFunction without calling MakeConstructor, which is the abstract op that adds the prototype
property. So it would appear that we should in fact use arrow functions after all
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've identified two places here where webidl2js extensions might be useful: Expose realm intrinsics to implsIn order to fix the
Alternatives include: an (Byte streams need the (In theory init(impl, globalObject)Many of the stream type impls need to save a reference to their relevant realms. The usual idiom is to do this in the impl constructor; but the impl constructor is not guaranteed to be called, and in fact the streams reference implementation often does not call it (I think in order to stay closer to the spec). Currently I'm setting If |
Can you explain how these are different? The spec wants you to use current realm, not relevant realm. But getting the current realm is hard with current architecture, as discussed in #2727. However the relevant global object should be the same as I can understand how there's a problem with webidl2js-generated
This idea makes a lot of sense to me. |
The issue is that user code can mess with There's some The current implementation passes these tests because it uses wrong-realm |
Ah, I see, that makes perfect sense. In general jsdom is not very robust against people messing with the intrinsics. We get some protection for free from the impl/wrapper separation, e.g. we can guarantee that So my inclination would be to just fail these tests for now. Maybe in the future we could revisit the problem generally, but I'd like to do so in an automated fashion, e.g. using some derivative of https://github.com/jackbsteinberg/get-originals-rewriter |
Okay, I'll continue with using @TimothyGu pointed out in jsdom/webidl2js#247 (comment) that we actually often need to capture properties of intrinsics, like |
Thrilled to see this PR, just want to heap some encouragement/appreciation :) we'd find this very useful for testing some modules for Scala.js libs such as fs2. |
This PR implements the whatwg streams standard by adding the reference implementation as a git subtree and adapting it to run in jsdom. Most but not all streams features work or can be made to work. Merging future upstream changes to the reference implementation is reasonably practical.
What do people think? Is adopting the reference impl worth pursuing further? Any ideas on how to fix the prototype/realms issues?
Missing streams features
postMessage()
implementation doesn't seem to support transferlists at all, so this feature's somewhat meaningless. The tests fail; I haven't investigated why in particular.ArrayBuffer
ownership by readable byte streams: the spec requires readable byte streams to do a lot of transferingArrayBuffer
s and checking if they're detached. I don't know of any way to do this, neither in pure ecmascript nor in node. The reference implementation faked the impossible operations with a bunch of property-mangling and copying; this PR makes them no-ops. This causes the relevant tests to fail.Remaining Bugs
AsyncIterator
prototype is taken from the Node realm, not the jsdom realm.Promise
prototype is taken from the Node realm, not the jsdom realm.patched-global
tests fail due to vm: in strict mode, assigning functions to certain globals throws, but succeeds nodejs/node#38918. One possible solution might be to install interfaces on thewindow
prior to callingvm.createContext()
.web-platform-tests
submodule to get the latest streams tests. Many other tests were incidentally affected (renamed files, new tests, etc.). I've tracked down some of the changes, but a bunch of failures remain & need to be validated (or the update rolled back).Merging upstream changes
git subtree pull --squash -P lib/jsdom/living/streams https://github.com/whatwg/streams.git main
(approximately)Alternatives
ArrayBuffer
handling for byte streams. Might never happen, though there's been a bit of interest in the Node repos recently.FileAPI
,Blob
,fetch
.TODO
AsyncIterator
prototype issue - blocked on Create async iterators and iterator result objects in the correct realm webidl2js#247Promise
prototype issuerethrowAssertionErrorRejection()
)