Repository navigation
implement text-{en,de}coder-streams - #18
karthik2804 wants to merge 3 commits into
Conversation
a082d75 to
f08c7d0
Compare
50829c2 to
207fe7f
Compare
207fe7f to
0c7a667
Compare
0c7a667 to
b60c024
Compare
This commit moves the existing TextDecoder and TextEncoder implementations into a new `encoding` crate, and adds the corresponding stream classes. Signed-off-by: Karthik Ganeshram <kganeshr@akamai.com>
b60c024 to
8cc93c3
Compare
tschneidereit
left a comment
There was a problem hiding this comment.
Thank you for this! I left a whole bunch of comments, a few of which definitely need addressing. Please push changes addressing them as new commits instead of force-pushing changes to the existing commit, so I can see what changed.
(Note: I'll probably make sense to start reading the comments in text_encoder_stream.rs first, because I started commenting there first, and some comments in text_decoder_stream.rs might make less sense otherwise.)
| fatal: bool, | ||
|
|
||
| /// <https://encoding.spec.whatwg.org/#textdecoder-ignore-bom> | ||
| #[no_trace(name = "ignoreBOM")] |
There was a problem hiding this comment.
no_trace doesn't have a name parameter. This doesn't cause a compile error only because the parameter is ignored.
| #[no_trace(name = "ignoreBOM")] | |
| #[no_trace] |
| // (Omitted — encoding_rs's Decoder buffers partial sequences internally; | ||
| // see the comment on the struct definition.) | ||
|
|
||
| let self_payload = scope.root_value(self.as_value()); |
There was a problem hiding this comment.
Function::new_callback takes impl ToJSVal, so no need to convert to a value here. You can just pass self to new_callback
| let self_payload = scope.root_value(self.as_value()); |
| c"textDecoderTransform", | ||
| 1, | ||
| decoder_transform_cb, | ||
| self_payload, |
There was a problem hiding this comment.
| self_payload, | |
| self, |
| c"textDecoderFlush", | ||
| 0, | ||
| decoder_flush_cb, | ||
| self_payload, |
There was a problem hiding this comment.
| self_payload, | |
| self, |
| let transformer = Object::new_plain(scope)?; | ||
| transformer.set_property(scope, c"transform", transform_cb)?; | ||
| transformer.set_property(scope, c"flush", flush_cb)?; |
There was a problem hiding this comment.
This is unfortunately unsafe: it allows code to intercept the set_property calls by installing setters on Object.prototype, and thus modify internal behavior of the builtin.
Additionally, if Object.prototype has start or cancel functions or a readableType member, those would all be used in setting up the TransformStream, and expose / enable modifying internal behavior.
This can be worked around by creating an object without a prototype. However, in this case we should do something entirely different to begin with: use the native transform and flush algorithms directly.
The way that could work is by mirroring ReadableStream::new_native, with a new function with this signature:
impl TransformStream<'_> {
/// <https://streams.spec.whatwg.org/#transformstream-set-up>
pub fn new_native<'r>(
scope: &'r Scope<'_>,
transform: AlgorithmArg<'r>,
flush: AlgorithmArg<'r>,
cancel: AlgorithmArg<'r>,
) -> Result<TransformStream<'r>, ExnThrown>;
}The body would roughly be:
js::class::create_instance_with::<TransformStreamImpl>, ascreate_readable_streamdoes.initialize_transform_streamwith a start promise resolved withundefined, writable high-water mark1, readable high-water mark0, and nosizealgorithms.TransformStreamDefaultController::new.set_up_transform_stream_default_controller, passingAlgorithmArg::Nativefor the algorithms.
You could then avoid creating JSFunctions for transform and flush entirely.
| chunk: Uint8Array<'_>, | ||
| ) -> Result<(), ExnThrown> { | ||
| let chunk_val = scope.root_value(chunk.as_value()); | ||
| stream.data().transform.get(scope).enqueue(scope, chunk_val) |
There was a problem hiding this comment.
This is unsafe, because it retains the data() borrow across the calls into JS that enqueue can trigger. Those could then call back into the EncoderStream and take a data_mut, causing a mutable borrow to coexist with an immutable one.
| // Step 6: Set this’s [[TransformStream]] to transformStream. | ||
| self.data_mut().transform.set(transform_stream); | ||
|
|
||
| // Step 7: Set this’s [[pending high surrogate]] to null. |
There was a problem hiding this comment.
This step doesn't exist in the spec at all. Make sure to double-check that the step comments are fully accurate throughout the implementation, please. (The actual operation also happens to be redundant, since leading_surrogate is initialized to its default None value anyway.)
|
|
||
| /// <https://encoding.spec.whatwg.org/#dom-textencoder-encoding> | ||
| #[getter] | ||
| fn encoding(&self) -> String { |
There was a problem hiding this comment.
I think you should be able to return an &'static str here, but that might be wrong. Would be good to try though, to avoid the allocation of a string we'll throw away immediately.
| args: CallbackArgs<'_>, | ||
| payload: HandleValue<'_>, | ||
| ) -> Result<Value, ExnThrown> { | ||
| let stream: TextDecoderStream<'_> = Object::from_value(scope, *payload) |
There was a problem hiding this comment.
This can also use cast_payload
| } | ||
|
|
||
| /// The <https://encoding.spec.whatwg.org/#decode-and-enqueue-a-chunk> algorithm. | ||
| fn decoder_transform_cb( |
There was a problem hiding this comment.
I think this can reuse most of TextDecoder::decode, if the core of that is split out into a pub(crate) helper function, with the decoder passed in as an argument.
That'd give us the fast paths provided by that implementation, and avoid a few perf hazards in here. In particular, the input is copied into a Vec instead of directly using the input buffer's bytes as a slice, and the decoded result is converted from a String to a JSString, running decoding again. Both are avoided by reusing a shared implementation.
Signed-off-by: Karthik Ganeshram <kganeshr@akamai.com>
Signed-off-by: Karthik Ganeshram <kganeshr@akamai.com>
This commit moves the existing TextDecoder and TextEncoder implementations into a new
encodingcrate, and adds the corresponding stream classes.