diff --git a/components/script/dom/bindings/codegen/parser/WebIDL.py b/components/script/dom/bindings/codegen/parser/WebIDL.py index b2e56c9deaf1..79825c9fa32d 100644 --- a/components/script/dom/bindings/codegen/parser/WebIDL.py +++ b/components/script/dom/bindings/codegen/parser/WebIDL.py @@ -2164,9 +2164,6 @@ def isSequence(self): def isRecord(self): return False - def isReadableStream(self): - return False - def isArrayBuffer(self): return False @@ -2198,8 +2195,7 @@ def isSpiderMonkeyInterface(self): return self.isInterface() and (self.isArrayBuffer() or self.isArrayBufferView() or self.isSharedArrayBuffer() or - self.isTypedArray() or - self.isReadableStream()) + self.isTypedArray()) def isDictionary(self): return False @@ -2401,9 +2397,6 @@ def isSequence(self): def isRecord(self): return self.inner.isRecord() - def isReadableStream(self): - return self.inner.isReadableStream() - def isArrayBuffer(self): return self.inner.isArrayBuffer() @@ -2783,9 +2776,6 @@ def isSequence(self): def isRecord(self): return self.inner.isRecord() - def isReadableStream(self): - return self.inner.isReadableStream() - def isDictionary(self): return self.inner.isDictionary() @@ -3121,7 +3111,6 @@ class IDLBuiltinType(IDLType): 'Uint32Array', 'Float32Array', 'Float64Array', - 'ReadableStream', ) TagLookup = { @@ -3158,7 +3147,6 @@ class IDLBuiltinType(IDLType): Types.Uint32Array: IDLType.Tags.interface, Types.Float32Array: IDLType.Tags.interface, Types.Float64Array: IDLType.Tags.interface, - Types.ReadableStream: IDLType.Tags.interface, } def __init__(self, location, name, type, clamp=False, enforceRange=False, treatNullAsEmpty=False, @@ -3258,9 +3246,6 @@ def isTypedArray(self): return (self._typeTag >= IDLBuiltinType.Types.Int8Array and self._typeTag <= IDLBuiltinType.Types.Float64Array) - def isReadableStream(self): - return self._typeTag == IDLBuiltinType.Types.ReadableStream - def isInterface(self): # TypedArray things are interface types per the TypedArray spec, # but we handle them as builtins because SpiderMonkey implements @@ -3268,8 +3253,7 @@ def isInterface(self): return (self.isArrayBuffer() or self.isArrayBufferView() or self.isSharedArrayBuffer() or - self.isTypedArray() or - self.isReadableStream()) + self.isTypedArray()) def isNonCallbackInterface(self): # All the interfaces we can be are non-callback @@ -3339,7 +3323,6 @@ def isDistinguishableFrom(self, other): # that's not an ArrayBuffer or a callback interface (self.isArrayBuffer() and not other.isArrayBuffer()) or (self.isSharedArrayBuffer() and not other.isSharedArrayBuffer()) or - (self.isReadableStream() and not other.isReadableStream()) or # ArrayBufferView is distinguishable from everything # that's not an ArrayBufferView or typed array. (self.isArrayBufferView() and not other.isArrayBufferView() and @@ -3492,9 +3475,6 @@ def withExtendedAttributes(self, attrs): IDLBuiltinType.Types.Float64Array: IDLBuiltinType(BuiltinLocation(""), "Float64Array", IDLBuiltinType.Types.Float64Array), - IDLBuiltinType.Types.ReadableStream: - IDLBuiltinType(BuiltinLocation(""), "ReadableStream", - IDLBuiltinType.Types.ReadableStream), } @@ -5715,7 +5695,6 @@ def t_OTHER(self, t): "setlike": "SETLIKE", "iterable": "ITERABLE", "namespace": "NAMESPACE", - "ReadableStream": "READABLESTREAM", "constructor": "CONSTRUCTOR", "symbol": "SYMBOL", "async": "ASYNC", @@ -7051,7 +7030,6 @@ def p_DistinguishableType(self, p): DistinguishableType : PrimitiveType Null | ARRAYBUFFER Null | SHAREDARRAYBUFFER Null - | READABLESTREAM Null | OBJECT Null """ if p[1] == "object": @@ -7060,8 +7038,6 @@ def p_DistinguishableType(self, p): type = BuiltinTypes[IDLBuiltinType.Types.ArrayBuffer] elif p[1] == "SharedArrayBuffer": type = BuiltinTypes[IDLBuiltinType.Types.SharedArrayBuffer] - elif p[1] == "ReadableStream": - type = BuiltinTypes[IDLBuiltinType.Types.ReadableStream] else: type = BuiltinTypes[p[1]] diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 2401c5a55cb0..4bf334899996 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -470,6 +470,7 @@ pub mod promiserejectionevent; pub mod radionodelist; pub mod range; pub mod raredata; +pub mod readablestream; pub mod request; pub mod response; pub mod rtcicecandidate; diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs new file mode 100644 index 000000000000..8a06070103fc --- /dev/null +++ b/components/script/dom/readablestream.rs @@ -0,0 +1,240 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +use crate::dom::bindings::codegen::Bindings::ReadableStreamBinding::{ReadableStreamMethods, Wrap}; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector}; +use crate::dom::bindings::root::DomRoot; +use crate::dom::bindings::str::DOMString; +use crate::dom::bindings::utils::to_frozen_array; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::js::conversions::ToJSValConvertible; +use crate::script_runtime::JSContext as SafeJSContext; +use dom_struct::dom_struct; +use js::jsapi::{Heap, JSFunction, JSObject, JS_ValueToFunction}; +use js::jsapi::{ + IsReadableStream, JS_GetProperty, NewReadableDefaultStreamObject, ReadableStreamCancel, + ReadableStreamGetReader, ReadableStreamIsLocked, + ReadableStreamReaderMode as JSReadableStreamReaderMode, ReadableStreamTee, +}; +use js::jsval::{JSVal, UndefinedValue}; +use js::rust::{Handle, IntoHandle, IntoMutableHandle, MutableHandle}; +use std::ffi::CString; +use std::ptr::{self, NonNull}; +use std::rc::Rc; + +#[dom_struct] +pub struct ReadableStream { + reflector_: Reflector, + #[ignore_malloc_size_of = "SM handles JS values"] + stream: Heap<*mut JSObject>, +} + +impl ReadableStream { + /// + #[allow(non_snake_case)] + pub fn Constructor( + cx: SafeJSContext, + global: &GlobalScope, + underlying_source: *mut JSObject, + queuing_strategy: Option<*mut JSObject>, + ) -> DomRoot { + let stream = construct_default_readablestream(cx, underlying_source, queuing_strategy); + + ReadableStream::new(global, stream) + } + + fn new_inherited(stream: Heap<*mut JSObject>) -> ReadableStream { + ReadableStream { + reflector_: Reflector::new(), + stream, + } + } + + fn new(global: &GlobalScope, stream: Heap<*mut JSObject>) -> DomRoot { + reflect_dom_object( + Box::new(ReadableStream::new_inherited(stream)), + global, + Wrap, + ) + } +} + +impl ReadableStreamMethods for ReadableStream { + /// + fn Cancel(&self, reason: DOMString) -> Fallible> { + let cx = self.global().get_cx(); + cancel_readablestream(cx, &self.stream, reason) + } + + /// + fn GetReader(&self, cx: SafeJSContext) -> Fallible> { + get_stream_reader(cx, &self.stream) + } + + /// + fn Locked(&self) -> bool { + stream_is_locked(self.global().get_cx(), &self.stream) + } + + /// + fn Tee(&self, cx: SafeJSContext) -> Fallible { + tee_stream(cx, &self.stream) + } +} + +#[allow(unsafe_code)] +fn tee_stream(cx: SafeJSContext, stream: &Heap<*mut JSObject>) -> Fallible { + unsafe { + rooted!(in(*cx) let mut branch1_stream = UndefinedValue()); + rooted!(in(*cx) let mut branch2_stream = UndefinedValue()); + + if !ReadableStreamTee( + *cx, + stream.handle(), + MutableHandle::new(&mut branch1_stream.to_object()).into_handle_mut(), + MutableHandle::new(&mut branch2_stream.to_object()).into_handle_mut(), + ) { + return Err(Error::Type( + "The stream you are trying to tee is not a ReadableStream, or it is locked." + .to_string(), + )); + } + + Ok(to_frozen_array(&[*branch1_stream, *branch2_stream], cx)) + } +} + +#[allow(unsafe_code)] +fn get_stream_reader( + cx: SafeJSContext, + stream: &Heap<*mut JSObject>, +) -> Fallible> { + unsafe { + let stream = stream.handle(); + if !IsReadableStream(stream.get()) { + return Err(Error::Type( + "The stream you are trying to create a reader for is not a ReadableStream." + .to_string(), + )); + } + + // TODO: support ReadableStreamBYOBReader, + // SM currently seems to only support default mode, + // see https://github.com/servo/mozjs/blob/ + // bc4935b668171863e537d3fb0d911001a6742013/mozjs/js/public/Stream.h#L274 + Ok(NonNull::new_unchecked(ReadableStreamGetReader( + *cx, + stream, + JSReadableStreamReaderMode::Default, + ))) + } +} + +#[allow(unsafe_code)] +fn construct_default_readablestream( + cx: SafeJSContext, + underlying_source: *mut JSObject, + queuing_strategy: Option<*mut JSObject>, +) -> Heap<*mut JSObject> { + let heap = Heap::default(); + let source = Handle::new(&underlying_source); + + unsafe { + if let Some(object) = queuing_strategy.as_ref() { + rooted!(in(*cx) let mut high_watermark = UndefinedValue()); + let name = CString::new("highWaterMark").unwrap(); + JS_GetProperty( + *cx, + Handle::new(object).into_handle(), + name.as_ptr(), + high_watermark.handle_mut().into_handle_mut(), + ); + + rooted!(in(*cx) let mut size = UndefinedValue()); + let name = CString::new("size").unwrap(); + JS_GetProperty( + *cx, + Handle::new(object).into_handle(), + name.as_ptr(), + size.handle_mut().into_handle_mut(), + ); + let func = JS_ValueToFunction(*cx, size.handle().into_handle()); + + rooted!(in(*cx) + let stream = NewReadableDefaultStreamObject( + *cx, + source.into_handle(), + Handle::new(&func).into_handle(), + high_watermark.get().to_double(), + Handle::new(&ptr::null_mut::()).into_handle(), + ) + ); + + assert!(IsReadableStream(stream.get())); + heap.set(stream.get()); + } else { + rooted!(in(*cx) + let stream = NewReadableDefaultStreamObject( + *cx, + source.into_handle(), + Handle::new(&ptr::null_mut::()).into_handle(), + 1.0, + Handle::new(&ptr::null_mut::()).into_handle(), + ) + ); + + assert!(IsReadableStream(stream.get())); + heap.set(stream.get()); + } + } + + heap +} + +#[allow(unsafe_code)] +fn stream_is_locked(cx: SafeJSContext, stream: &Heap<*mut JSObject>) -> bool { + let mut is_locked = false; + unsafe { + let stream = stream.handle(); + let is_locked_ptr = &mut is_locked as *mut _; + ReadableStreamIsLocked(*cx, stream, is_locked_ptr); + } + is_locked +} + +#[allow(unsafe_code)] +fn cancel_readablestream( + cx: SafeJSContext, + stream: &Heap<*mut JSObject>, + reason: DOMString, +) -> Fallible> { + unsafe { + let is_stream = IsReadableStream(stream.get()); + + let is_locked = if is_stream { + stream_is_locked(cx.clone(), stream) + } else { + false + }; + + if !is_stream || is_locked { + return Err(Error::Type( + "The stream you are trying to cancel is not a ReadableStream, or it is locked." + .to_string(), + )); + } + + rooted!(in(*cx) let mut reason_val = UndefinedValue()); + (*reason).to_jsval(*cx, reason_val.handle_mut()); + + rooted!(in(*cx) let raw_cancel_promise = + ReadableStreamCancel(*cx, stream.handle(), reason_val.handle().into_handle())); + + let cancel_promise = Promise::new_with_js_promise(raw_cancel_promise.handle(), cx); + + Ok(cancel_promise) + } +} diff --git a/components/script/dom/webidls/ReadableStream.webidl b/components/script/dom/webidls/ReadableStream.webidl new file mode 100644 index 000000000000..6340aa167301 --- /dev/null +++ b/components/script/dom/webidls/ReadableStream.webidl @@ -0,0 +1,14 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +// https://streams.spec.whatwg.org/#rs-class +[Exposed=(Window,Worker)] +interface ReadableStream { + constructor(object underlyingSource, optional object queuingStrategy); + [Throws] Promise cancel(DOMString reason); + [Throws] object getReader(); + [Throws] /*FrozenArray*/any tee(); + readonly attribute boolean locked; +}; + diff --git a/python/tidy/servo_tidy/tidy.py b/python/tidy/servo_tidy/tidy.py index cbbb1a542d4a..83b3e253d72d 100644 --- a/python/tidy/servo_tidy/tidy.py +++ b/python/tidy/servo_tidy/tidy.py @@ -103,6 +103,7 @@ def wpt_path(*args): b"//immersive-web.github.io/", b"//github.com/immersive-web/webxr-test-api/", b"//gpuweb.github.io", + b"//streams.spec.whatwg.org", # Not a URL b"// This interface is entirely internal to Servo, and should not be" + b" accessible to\n// web pages." diff --git a/tests/wpt/include.ini b/tests/wpt/include.ini index 80103c16bf6f..5bcb674d9d7d 100644 --- a/tests/wpt/include.ini +++ b/tests/wpt/include.ini @@ -157,6 +157,18 @@ skip: true skip: false [selection] skip: false +[streams] + skip: false + [piping] + skip: true + [readable-byte-streams] + skip: true + [readable-streams] + skip: false + [transform-streams] + skip: true + [writable-streams] + skip: true [subresource-integrity] skip: false [touch-events]