- From: Adam Rice <notifications@github.com>
- Date: Mon, 25 Jun 2018 01:29:46 -0700
- To: whatwg/streams <streams@noreply.github.com>
- Cc: Subscribed <subscribed@noreply.github.com>
- Message-ID: <whatwg/streams/issues/933@github.com>
The following test fails in Chromium: ```javascript promise_test(async () => { const intercepted = []; const rs = new ReadableStream({ start(controller) { controller.enqueue('a'); controller.close(); } }); const ws = recordingWritableStream(); let callCount = 0; Object.prototype.then = function(resolver) { if (!this.done) { intercepted.push(this.value); } const retval = Object.create(null); retval.done = ++callCount === 3; retval.value = callCount; resolver(retval); }; await rs.pipeTo(ws); delete Object.prototype.then; assert_array_equals(intercepted, [], 'nothing should have been intercepted'); assert_array_equals(ws.events, ['write', 'a', 'close'], 'written chunk should be "a"'); }, 'piping should not be observable'); ``` The actual observed values of `intercepted` and `ws.events` are `["a"]` and `["write", 1, "write", 2, "close"]` respectively. This violates the design principle that the internals of pipeTo() should not be observable. The test passes in the reference implementation, but for the wrong reason: a side-effect of the way the tests are run in Node.js is that the iterator object is created in the wrong realm. I am pretty sure from the definition of [CreateIterResultObject()](https://tc39.github.io/ecma262/#sec-createiterresultobject) that the iterator object is supposed to be created in the realm of the page. The reason the test works/fails is this step in the standard: * [Resolve](https://www.w3.org/2001/tag/doc/promises-guide/#resolve-promise) _readRequest_.[[promise]] with ! CreateIterResultObject(). "Resolve" ends up calling https://tc39.github.io/ecma262/#sec-promise-resolve-functions which looks up `"then"` on the prototype chain, ending up at `Object.prototype.then`. This provides a hook where the page can execute arbitrary Javascript, including reading and writing the iterator object. The hook is normally harmless, except in pipeTo(). I think it would be perfectly valid for an implementation of pipeTo() to pass this test even if CreateIterResultObject() created Objects in the current realm. I don't think pipeTo() is required to call "Resolve" or "CreateIterResultObject" at all. An optimised implementation would probably skip those steps. However, setting `Object.prototype.then` makes the optimisation observable. I think it's important that implementations can optimise pipeTo() without it being observable to the page. I can think of a couple of ways to fix this: 1. Specify pipeTo() as not calling "resolve" or CreateIterResultObject(). 2. Use a special variant of CreateIterResultObject() that sets a `null` prototype on the iterator object. I prefer the second option. I think changing pipeTo() would add a lot of complexity to the standard text, and restrict implementation choice. -- You are receiving this because you are subscribed to this thread. Reply to this email directly or view it on GitHub: https://github.com/whatwg/streams/issues/933
Received on Monday, 25 June 2018 08:30:20 UTC