Transfer ReadableStream (#36181)

<!-- Please describe your changes on the following line: -->

Add transfer support to ReadableStream. Part of
https://github.com/servo/servo/issues/34676

---
<!-- Thank you for contributing to Servo! Please replace each `[ ]` by
`[X]` when the step is complete, and replace `___` with appropriate
data: -->
- [ ] `./mach build -d` does not report any errors
- [ ] `./mach test-tidy` does not report any errors
- [ ] These changes fix #___ (GitHub issue number if applicable)

<!-- Either: -->
- [ ] There are tests for these changes OR
- [ ] These changes do not require tests because ___

<!-- Also, please make sure that "Allow edits from maintainers" checkbox
is checked, so that we can help you if you get stuck somewhere along the
way.-->

<!-- Pull requests that do not address these steps are welcome, but they
will require additional verification as part of the review process. -->

---------

Signed-off-by: gterzian <2792687+gterzian@users.noreply.github.com>
This commit is contained in:
Gregory Terzian 2025-04-15 15:39:26 +08:00 committed by GitHub
parent c9489ca04f
commit f8b6b9f7b6
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
22 changed files with 983 additions and 75 deletions

View file

@ -2,7 +2,7 @@
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
use std::cell::{Cell, RefCell};
use std::collections::VecDeque;
use std::mem;
use std::ptr::{self};
@ -15,6 +15,7 @@ use js::rust::{
HandleObject as SafeHandleObject, HandleValue as SafeHandleValue,
MutableHandleValue as SafeMutableHandleValue,
};
use script_bindings::codegen::GenericBindings::MessagePortBinding::MessagePortMethods;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy;
@ -22,16 +23,20 @@ use crate::dom::bindings::codegen::Bindings::UnderlyingSinkBinding::UnderlyingSi
use crate::dom::bindings::codegen::Bindings::WritableStreamBinding::WritableStreamMethods;
use crate::dom::bindings::conversions::ConversionResult;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto};
use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom};
use crate::dom::countqueuingstrategy::{extract_high_water_mark, extract_size_algorithm};
use crate::dom::domexception::{DOMErrorName, DOMException};
use crate::dom::globalscope::GlobalScope;
use crate::dom::messageport::MessagePort;
use crate::dom::promise::Promise;
use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler};
use crate::dom::readablestream::get_type_and_value_from_message;
use crate::dom::writablestreamdefaultcontroller::{
UnderlyingSinkType, WritableStreamDefaultController,
};
use crate::dom::writablestreamdefaultwriter::WritableStreamDefaultWriter;
use crate::js::conversions::ToJSValConvertible;
use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::{CanGc, JSContext as SafeJSContext};
@ -175,7 +180,7 @@ impl WritableStream {
}
}
fn new_with_proto(
pub(crate) fn new_with_proto(
global: &GlobalScope,
proto: Option<SafeHandleObject>,
can_gc: CanGc,
@ -834,6 +839,58 @@ impl WritableStream {
// Set stream.[[backpressure]] to backpressure.
self.set_backpressure(backpressure);
}
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
pub(crate) fn setup_cross_realm_transform_writable(
&self,
cx: SafeJSContext,
port: &MessagePort,
can_gc: CanGc,
) {
let port_id = port.message_port_id();
let global = self.global();
// Perform ! InitializeWritableStream(stream).
// Done in `new_inherited`.
// Let sizeAlgorithm be an algorithm that returns 1.
// Re-ordered because of the need to pass it to `new`.
let size_algorithm = extract_size_algorithm(&QueuingStrategy::default(), can_gc);
// Note: other algorithms defined in the controller at call site.
// Let backpressurePromise be a new promise.
let backpressure_promise = Rc::new(RefCell::new(Some(Promise::new(&global, can_gc))));
// Let controller be a new WritableStreamDefaultController.
let controller = WritableStreamDefaultController::new(
&global,
UnderlyingSinkType::Transfer {
backpressure_promise: backpressure_promise.clone(),
port: Dom::from_ref(port),
},
&UnderlyingSink::empty(),
1.0,
size_algorithm,
can_gc,
);
// Add a handler for ports message event with the following steps:
// Add a handler for ports messageerror event with the following steps:
rooted!(in(*cx) let cross_realm_transform_writable = CrossRealmTransformWritable {
controller: Dom::from_ref(&controller),
backpressure_promise: backpressure_promise.clone(),
});
global.note_cross_realm_transform_writable(&cross_realm_transform_writable, port_id);
// Enable ports port message queue.
port.Start();
// Perform ! SetUpWritableStreamDefaultController
controller
.setup(cx, &global, self, &None, can_gc)
.expect("Setup for transfer cannot fail");
}
}
impl WritableStreamMethods<crate::DomTypeHolder> for WritableStream {
@ -967,3 +1024,86 @@ impl WritableStreamMethods<crate::DomTypeHolder> for WritableStream {
self.aquire_default_writer(cx, &global, can_gc)
}
}
impl js::gc::Rootable for CrossRealmTransformWritable {}
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
/// A wrapper to handle `message` and `messageerror` events
/// for the port used by the transfered stream.
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) struct CrossRealmTransformWritable {
/// The controller used in the algorithm.
controller: Dom<WritableStreamDefaultController>,
/// The `backpressurePromise` used in the algorithm.
#[ignore_malloc_size_of = "Rc is hard"]
backpressure_promise: Rc<RefCell<Option<Rc<Promise>>>>,
}
impl CrossRealmTransformWritable {
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
/// Add a handler for ports message event with the following steps:
#[allow(unsafe_code)]
pub(crate) fn handle_message(
&self,
cx: SafeJSContext,
global: &GlobalScope,
message: SafeHandleValue,
_realm: InRealm,
can_gc: CanGc,
) {
rooted!(in(*cx) let mut value = UndefinedValue());
let type_string =
unsafe { get_type_and_value_from_message(cx, message, value.handle_mut(), can_gc) };
// If type is "pull",
// Done below as the steps are the same for both types.
// Otherwise, if type is "error",
if type_string == "error" {
// Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, value).
self.controller
.error_if_needed(cx, value.handle(), global, can_gc);
}
let backpressure_promise = self.backpressure_promise.borrow_mut().take();
// Note: the below steps are for both "pull" and "error" types.
// If backpressurePromise is not undefined,
if let Some(promise) = backpressure_promise {
// Resolve backpressurePromise with undefined.
promise.resolve_native(&(), can_gc);
// Set backpressurePromise to undefined.
// Done above with `take`.
}
}
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
/// Add a handler for ports messageerror event with the following steps:
#[allow(unsafe_code)]
pub(crate) fn handle_error(
&self,
cx: SafeJSContext,
global: &GlobalScope,
port: &MessagePort,
_realm: InRealm,
can_gc: CanGc,
) {
// Let error be a new "DataCloneError" DOMException.
let error = DOMException::new(global, DOMErrorName::DataCloneError, can_gc);
rooted!(in(*cx) let mut rooted_error = UndefinedValue());
unsafe { error.to_jsval(*cx, rooted_error.handle_mut()) };
// Perform ! CrossRealmTransformSendError(port, error).
port.cross_realm_transform_send_error(rooted_error.handle(), can_gc);
// Perform ! WritableStreamDefaultControllerErrorIfNeeded(controller, error).
self.controller
.error_if_needed(cx, rooted_error.handle(), global, can_gc);
// Disentangle port.
global.disentangle_port(port);
}
}