Auto merge of #25873 - gterzian:implement_readablestream_support, r=jdm

Implement readablestream support

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

FIX #21482
FIX #24876
FIX #26392

---
<!-- 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. -->
This commit is contained in:
bors-servo 2020-06-04 03:04:00 -04:00 committed by GitHub
commit 8536cee72c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
77 changed files with 2378 additions and 929 deletions

View file

@ -906,6 +906,40 @@ def getJSToNativeConversionInfo(type, descriptorProvider, failureCode=None,
return handleOptional(templateBody, declType, handleDefault("None"))
if type.isReadableStream():
assert not isEnforceRange and not isClamp
if failureCode is None:
unwrapFailureCode = '''throw_type_error(*cx, "This object is not \
an instance of ReadableStream.");\n'''
else:
unwrapFailureCode = failureCode
templateBody = fill(
"""
{
use crate::realms::{AlreadyInRealm, InRealm};
let in_realm_proof = AlreadyInRealm::assert_for_cx(cx);
match ReadableStream::from_js(cx, $${val}.get().to_object(), InRealm::Already(&in_realm_proof)) {
Ok(val) => val,
Err(()) => {
$*{failureCode}
}
}
}
""",
failureCode=unwrapFailureCode + "\n",
)
templateBody = wrapObjectTemplate(templateBody, "None",
isDefinitelyObject, type, failureCode)
declType = CGGeneric("DomRoot<ReadableStream>")
return handleOptional(templateBody, declType,
handleDefault("None"))
elif type.isSpiderMonkeyInterface():
raise TypeError("Can't handle SpiderMonkey interface arguments other than typed arrays yet")
@ -4481,6 +4515,9 @@ def getUnionTypeTemplateVars(type, descriptorProvider):
elif type.isObject():
name = type.name
typeName = "Heap<*mut JSObject>"
elif type.isReadableStream():
name = type.name
typeName = "DomRoot<ReadableStream>"
elif is_typed_array(type):
name = type.name
typeName = "typedarray::Heap" + name

View file

@ -138,6 +138,7 @@ pub unsafe fn create_global_object(
let mut options = RealmOptions::default();
options.creationOptions_.traceGlobal_ = Some(trace);
options.creationOptions_.sharedMemoryAndAtomics_ = true;
options.creationOptions_.streams_ = true;
rval.set(JS_NewGlobalObject(
*cx,

View file

@ -14,14 +14,18 @@ use crate::dom::bindings::str::DOMString;
use crate::dom::bindings::structuredclone::StructuredDataHolder;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::dom::readablestream::ReadableStream;
use crate::realms::{AlreadyInRealm, InRealm};
use crate::script_runtime::JSContext;
use dom_struct::dom_struct;
use encoding_rs::UTF_8;
use js::jsapi::JSObject;
use msg::constellation_msg::{BlobId, BlobIndex, PipelineNamespaceId};
use net_traits::filemanager_thread::RelativePos;
use script_traits::serializable::BlobImpl;
use std::collections::HashMap;
use std::num::NonZeroU32;
use std::ptr::NonNull;
use std::rc::Rc;
use uuid::Uuid;
@ -34,13 +38,7 @@ pub struct Blob {
impl Blob {
pub fn new(global: &GlobalScope, blob_impl: BlobImpl) -> DomRoot<Blob> {
let dom_blob = reflect_dom_object(
Box::new(Blob {
reflector_: Reflector::new(),
blob_id: blob_impl.blob_id(),
}),
global,
);
let dom_blob = reflect_dom_object(Box::new(Blob::new_inherited(&blob_impl)), global);
global.track_blob(&dom_blob, blob_impl);
dom_blob
}
@ -89,6 +87,11 @@ impl Blob {
pub fn get_blob_url_id(&self) -> Uuid {
self.global().get_blob_url_id(&self.blob_id)
}
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
pub fn get_stream(&self) -> DomRoot<ReadableStream> {
self.global().get_blob_stream(&self.blob_id)
}
}
impl Serializable for Blob {
@ -213,6 +216,11 @@ impl BlobMethods for Blob {
DOMString::from(self.type_string())
}
// <https://w3c.github.io/FileAPI/#blob-get-stream>
fn Stream(&self, _cx: JSContext) -> NonNull<JSObject> {
self.get_stream().get_js_stream()
}
// https://w3c.github.io/FileAPI/#slice-method-algo
fn Slice(
&self,

View file

@ -43,6 +43,7 @@ use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope;
use crate::dom::performance::Performance;
use crate::dom::performanceobserver::VALID_ENTRY_TYPES;
use crate::dom::promise::Promise;
use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream};
use crate::dom::serviceworker::ServiceWorker;
use crate::dom::serviceworkerregistration::ServiceWorkerRegistration;
use crate::dom::window::Window;
@ -319,11 +320,19 @@ struct FileListener {
task_canceller: TaskCanceller,
}
struct FileListenerCallback(Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>);
enum FileListenerCallback {
Promise(Box<dyn Fn(Rc<Promise>, Result<Vec<u8>, Error>) + Send>),
Stream,
}
enum FileListenerTarget {
Promise(TrustedPromise),
Stream(Trusted<ReadableStream>),
}
enum FileListenerState {
Empty(FileListenerCallback, TrustedPromise),
Receiving(Vec<u8>, FileListenerCallback, TrustedPromise),
Empty(FileListenerCallback, FileListenerTarget),
Receiving(Vec<u8>, FileListenerCallback, FileListenerTarget),
}
#[derive(JSTraceable, MallocSizeOf)]
@ -356,6 +365,14 @@ pub enum BlobState {
UnManaged,
}
/// The result of looking-up the data for a Blob,
/// containing either the in-memory bytes,
/// or the file-id.
enum BlobResult {
Bytes(Vec<u8>),
File(Uuid, usize),
}
/// Data representing a message-port managed by this global.
#[derive(JSTraceable, MallocSizeOf)]
#[unrooted_must_root_lint::must_root]
@ -532,57 +549,137 @@ impl MessageListener {
}
}
/// Callback used to enqueue file chunks to streams as part of FileListener.
fn stream_handle_incoming(stream: &ReadableStream, bytes: Result<Vec<u8>, Error>) {
match bytes {
Ok(b) => {
stream.enqueue_native(b);
},
Err(e) => {
stream.error_native(e);
},
}
}
/// Callback used to close streams as part of FileListener.
fn stream_handle_eof(stream: &ReadableStream) {
stream.close_native();
}
impl FileListener {
fn handle(&mut self, msg: FileManagerResult<ReadFileProgress>) {
match msg {
Ok(ReadFileProgress::Meta(blob_buf)) => match self.state.take() {
Some(FileListenerState::Empty(callback, promise)) => {
self.state = Some(FileListenerState::Receiving(
blob_buf.bytes,
callback,
promise,
));
Some(FileListenerState::Empty(callback, target)) => {
let bytes = if let FileListenerTarget::Stream(ref trusted_stream) = target {
let trusted = trusted_stream.clone();
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_incoming(&*stream, Ok(blob_buf.bytes));
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
Vec::with_capacity(0)
} else {
blob_buf.bytes
};
self.state = Some(FileListenerState::Receiving(bytes, callback, target));
},
_ => panic!(
"Unexpected FileListenerState when receiving ReadFileProgress::Meta msg."
),
},
Ok(ReadFileProgress::Partial(mut bytes_in)) => match self.state.take() {
Some(FileListenerState::Receiving(mut bytes, callback, promise)) => {
bytes.append(&mut bytes_in);
self.state = Some(FileListenerState::Receiving(bytes, callback, promise));
Some(FileListenerState::Receiving(mut bytes, callback, target)) => {
if let FileListenerTarget::Stream(ref trusted_stream) = target {
let trusted = trusted_stream.clone();
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_incoming(&*stream, Ok(bytes_in));
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
} else {
bytes.append(&mut bytes_in);
};
self.state = Some(FileListenerState::Receiving(bytes, callback, target));
},
_ => panic!(
"Unexpected FileListenerState when receiving ReadFileProgress::Partial msg."
),
},
Ok(ReadFileProgress::EOF) => match self.state.take() {
Some(FileListenerState::Receiving(bytes, callback, trusted_promise)) => {
let _ = self.task_source.queue_with_canceller(
task!(resolve_promise: move || {
Some(FileListenerState::Receiving(bytes, callback, target)) => match target {
FileListenerTarget::Promise(trusted_promise) => {
let callback = match callback {
FileListenerCallback::Promise(callback) => callback,
_ => panic!("Expected promise callback."),
};
let task = task!(resolve_promise: move || {
let promise = trusted_promise.root();
let _ac = enter_realm(&*promise.global());
callback.0(promise, Ok(bytes));
}),
&self.task_canceller,
);
callback(promise, Ok(bytes));
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
},
FileListenerTarget::Stream(trusted_stream) => {
let trusted = trusted_stream.clone();
let task = task!(enqueue_stream_chunk: move || {
let stream = trusted.root();
stream_handle_eof(&*stream);
});
let _ = self
.task_source
.queue_with_canceller(task, &self.task_canceller);
},
},
_ => {
panic!("Unexpected FileListenerState when receiving ReadFileProgress::EOF msg.")
},
},
Err(_) => match self.state.take() {
Some(FileListenerState::Receiving(_, callback, trusted_promise)) |
Some(FileListenerState::Empty(callback, trusted_promise)) => {
let bytes = Err(Error::Network);
let _ = self.task_source.queue_with_canceller(
task!(reject_promise: move || {
let promise = trusted_promise.root();
let _ac = enter_realm(&*promise.global());
callback.0(promise, bytes);
}),
&self.task_canceller,
);
Some(FileListenerState::Receiving(_, callback, target)) |
Some(FileListenerState::Empty(callback, target)) => {
let error = Err(Error::Network);
match target {
FileListenerTarget::Promise(trusted_promise) => {
let callback = match callback {
FileListenerCallback::Promise(callback) => callback,
_ => panic!("Expected promise callback."),
};
let _ = self.task_source.queue_with_canceller(
task!(reject_promise: move || {
let promise = trusted_promise.root();
let _ac = enter_realm(&*promise.global());
callback(promise, error);
}),
&self.task_canceller,
);
},
FileListenerTarget::Stream(trusted_stream) => {
let _ = self.task_source.queue_with_canceller(
task!(error_stream: move || {
let stream = trusted_stream.root();
stream_handle_incoming(&*stream, error);
}),
&self.task_canceller,
);
},
}
},
_ => panic!("Unexpected FileListenerState when receiving Err msg."),
},
@ -1565,6 +1662,70 @@ impl GlobalScope {
}
}
/// Get a slice to the inner data of a Blob,
/// if it's a memory blob, or it's file-id and file-size otherwise.
///
/// Note: this is almost a duplicate of `get_blob_bytes`,
/// tweaked for integration with streams.
/// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
fn get_blob_bytes_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
let parent = {
let blob_state = self.blob_state.borrow();
if let BlobState::Managed(blobs_map) = &*blob_state {
let blob_info = blobs_map
.get(blob_id)
.expect("get_blob_bytes_or_file_id for an unknown blob.");
match blob_info.blob_impl.blob_data() {
BlobData::Sliced(ref parent, ref rel_pos) => {
Some((parent.clone(), rel_pos.clone()))
},
_ => None,
}
} else {
panic!("get_blob_bytes_or_file_id called on a global not managing any blobs.");
}
};
match parent {
Some((parent_id, rel_pos)) => {
match self.get_blob_bytes_non_sliced_or_file_id(&parent_id) {
BlobResult::Bytes(bytes) => {
let range = rel_pos.to_abs_range(bytes.len());
BlobResult::Bytes(bytes.index(range).to_vec())
},
res => res,
}
},
None => self.get_blob_bytes_non_sliced_or_file_id(blob_id),
}
}
/// Get bytes from a non-sliced blob if in memory, or it's file-id and file-size.
///
/// Note: this is almost a duplicate of `get_blob_bytes_non_sliced`,
/// tweaked for integration with streams.
/// TODO: merge with `get_blob_bytes` by way of broader integration with blob streams.
fn get_blob_bytes_non_sliced_or_file_id(&self, blob_id: &BlobId) -> BlobResult {
let blob_state = self.blob_state.borrow();
if let BlobState::Managed(blobs_map) = &*blob_state {
let blob_info = blobs_map
.get(blob_id)
.expect("get_blob_bytes_non_sliced_or_file_id called for a unknown blob.");
match blob_info.blob_impl.blob_data() {
BlobData::File(ref f) => match f.get_cache() {
Some(bytes) => BlobResult::Bytes(bytes.clone()),
None => BlobResult::File(f.get_id(), f.get_size() as usize),
},
BlobData::Memory(ref s) => BlobResult::Bytes(s.clone()),
BlobData::Sliced(_, _) => panic!("This blob doesn't have a parent."),
}
} else {
panic!(
"get_blob_bytes_non_sliced_or_file_id called on a global not managing any blobs."
);
}
}
/// Get a copy of the type_string of a blob.
pub fn get_blob_type_string(&self, blob_id: &BlobId) -> String {
let blob_state = self.blob_state.borrow();
@ -1769,6 +1930,50 @@ impl GlobalScope {
GlobalScope::read_msg(recv)
}
/// <https://w3c.github.io/FileAPI/#blob-get-stream>
pub fn get_blob_stream(&self, blob_id: &BlobId) -> DomRoot<ReadableStream> {
let (file_id, size) = match self.get_blob_bytes_or_file_id(blob_id) {
BlobResult::Bytes(bytes) => {
// If we have all the bytes in memory, queue them and close the stream.
let stream = ReadableStream::new_from_bytes(self, bytes);
return stream;
},
BlobResult::File(id, size) => (id, size),
};
let stream = ReadableStream::new_with_external_underlying_source(
self,
ExternalUnderlyingSource::Blob(size as usize),
);
let recv = self.send_msg(file_id);
let trusted_stream = Trusted::new(&*stream.clone());
let task_canceller = self.task_canceller(TaskSourceName::FileReading);
let task_source = self.file_reading_task_source();
let mut file_listener = FileListener {
state: Some(FileListenerState::Empty(
FileListenerCallback::Stream,
FileListenerTarget::Stream(trusted_stream),
)),
task_source,
task_canceller,
};
ROUTER.add_route(
recv.to_opaque(),
Box::new(move |msg| {
file_listener.handle(
msg.to()
.expect("Deserialization of file listener msg failed."),
);
}),
);
stream
}
pub fn read_file_async(
&self,
id: Uuid,
@ -1783,8 +1988,8 @@ impl GlobalScope {
let mut file_listener = FileListener {
state: Some(FileListenerState::Empty(
FileListenerCallback(callback),
trusted_promise,
FileListenerCallback::Promise(callback),
FileListenerTarget::Promise(trusted_promise),
)),
task_source,
task_canceller,
@ -1894,6 +2099,12 @@ impl GlobalScope {
global_scope_from_global(global, cx)
}
/// Returns the global scope for the given SafeJSContext
#[allow(unsafe_code)]
pub fn from_safe_context(cx: SafeJSContext, realm: InRealm) -> DomRoot<Self> {
unsafe { Self::from_context(*cx, realm) }
}
/// Returns the global object of the realm that the given JS object
/// was created in, after unwrapping any wrappers.
#[allow(unsafe_code)]

View file

@ -2,6 +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 crate::body::Extractable;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrBinding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
@ -799,7 +800,15 @@ impl HTMLFormElement {
},
};
load_data.data = Some(bytes);
let global = self.global();
let request_body = bytes
.extract(&global)
.expect("Couldn't extract body.")
.into_net_request_body()
.0;
load_data.data = Some(request_body);
self.plan_to_navigate(load_data, target);
}

View file

@ -481,6 +481,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;

View file

@ -14,6 +14,7 @@
use crate::dom::bindings::conversions::root_from_object;
use crate::dom::bindings::error::{Error, Fallible};
use crate::dom::bindings::reflector::{DomObject, MutDomObject, Reflector};
use crate::dom::bindings::settings_stack::AutoEntryScript;
use crate::dom::bindings::utils::AsCCharPtrPtr;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promisenativehandler::PromiseNativeHandler;
@ -242,7 +243,8 @@ impl Promise {
}
#[allow(unsafe_code)]
pub fn append_native_handler(&self, handler: &PromiseNativeHandler) {
pub fn append_native_handler(&self, handler: &PromiseNativeHandler, _comp: InRealm) {
let _ais = AutoEntryScript::new(&*handler.global());
let cx = self.global().get_cx();
rooted!(in(*cx) let resolve_func =
create_native_handler_function(*cx,

View file

@ -7,13 +7,14 @@ use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::trace::JSTraceable;
use crate::dom::globalscope::GlobalScope;
use crate::realms::InRealm;
use crate::script_runtime::JSContext as SafeJSContext;
use dom_struct::dom_struct;
use js::jsapi::JSContext;
use js::rust::HandleValue;
use malloc_size_of::MallocSizeOf;
pub trait Callback: JSTraceable + MallocSizeOf {
fn callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm);
fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm);
}
#[dom_struct]
@ -39,12 +40,14 @@ impl PromiseNativeHandler {
)
}
#[allow(unsafe_code)]
fn callback(
callback: &Option<Box<dyn Callback>>,
cx: *mut JSContext,
v: HandleValue,
realm: InRealm,
) {
let cx = unsafe { SafeJSContext::from_ptr(cx) };
if let Some(ref callback) = *callback {
callback.callback(cx, v, realm)
}

View file

@ -0,0 +1,539 @@
/* 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::conversions::{ConversionBehavior, ConversionResult};
use crate::dom::bindings::error::Error;
use crate::dom::bindings::reflector::{reflect_dom_object, DomObject, Reflector};
use crate::dom::bindings::root::DomRoot;
use crate::dom::bindings::settings_stack::AutoEntryScript;
use crate::dom::bindings::utils::get_dictionary_property;
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
use crate::js::conversions::FromJSValConvertible;
use crate::realms::{enter_realm, InRealm};
use crate::script_runtime::JSContext as SafeJSContext;
use dom_struct::dom_struct;
use js::glue::{
CreateReadableStreamUnderlyingSource, DeleteReadableStreamUnderlyingSource,
ReadableStreamUnderlyingSourceTraps,
};
use js::jsapi::{HandleObject, HandleValue, Heap, JSContext, JSObject};
use js::jsapi::{
IsReadableStream, NewReadableExternalSourceStreamObject, ReadableStreamClose,
ReadableStreamDefaultReaderRead, ReadableStreamError, ReadableStreamGetReader,
ReadableStreamIsDisturbed, ReadableStreamIsLocked, ReadableStreamIsReadable,
ReadableStreamReaderMode, ReadableStreamReaderReleaseLock, ReadableStreamUnderlyingSource,
ReadableStreamUpdateDataAvailableFromSource, UnwrapReadableStream,
};
use js::jsval::JSVal;
use js::jsval::UndefinedValue;
use js::rust::HandleValue as SafeHandleValue;
use js::rust::IntoHandle;
use std::cell::{Cell, RefCell};
use std::os::raw::c_void;
use std::ptr::{self, NonNull};
use std::rc::Rc;
use std::slice;
static UNDERLYING_SOURCE_TRAPS: ReadableStreamUnderlyingSourceTraps =
ReadableStreamUnderlyingSourceTraps {
requestData: Some(request_data),
writeIntoReadRequestBuffer: Some(write_into_read_request_buffer),
cancel: Some(cancel),
onClosed: Some(close),
onErrored: Some(error),
finalize: Some(finalize),
};
#[dom_struct]
pub struct ReadableStream {
reflector_: Reflector,
#[ignore_malloc_size_of = "SM handles JS values"]
js_stream: Heap<*mut JSObject>,
#[ignore_malloc_size_of = "SM handles JS values"]
js_reader: Heap<*mut JSObject>,
has_reader: Cell<bool>,
#[ignore_malloc_size_of = "Rc is hard"]
external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>,
}
impl ReadableStream {
fn new_inherited(
external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>,
) -> ReadableStream {
ReadableStream {
reflector_: Reflector::new(),
js_stream: Heap::default(),
js_reader: Heap::default(),
has_reader: Default::default(),
external_underlying_source: external_underlying_source,
}
}
fn new(
global: &GlobalScope,
external_underlying_source: Option<Rc<ExternalUnderlyingSourceController>>,
) -> DomRoot<ReadableStream> {
reflect_dom_object(
Box::new(ReadableStream::new_inherited(external_underlying_source)),
global,
)
}
/// Used from RustCodegen.py
#[allow(unsafe_code)]
pub unsafe fn from_js(
cx: SafeJSContext,
obj: *mut JSObject,
realm: InRealm,
) -> Result<DomRoot<ReadableStream>, ()> {
if !IsReadableStream(obj) {
return Err(());
}
let global = GlobalScope::from_safe_context(cx, realm);
let stream = ReadableStream::new(&global, None);
stream.js_stream.set(UnwrapReadableStream(obj));
Ok(stream)
}
/// Build a stream backed by a Rust source that has already been read into memory.
pub fn new_from_bytes(global: &GlobalScope, bytes: Vec<u8>) -> DomRoot<ReadableStream> {
let stream = ReadableStream::new_with_external_underlying_source(
&global,
ExternalUnderlyingSource::Memory(bytes.len()),
);
stream.enqueue_native(bytes);
stream.close_native();
stream
}
/// Build a stream backed by a Rust underlying source.
#[allow(unsafe_code)]
pub fn new_with_external_underlying_source(
global: &GlobalScope,
source: ExternalUnderlyingSource,
) -> DomRoot<ReadableStream> {
let _ar = enter_realm(global);
let cx = global.get_cx();
let source = Rc::new(ExternalUnderlyingSourceController::new(source));
let stream = ReadableStream::new(&global, Some(source.clone()));
unsafe {
let js_wrapper = CreateReadableStreamUnderlyingSource(
&UNDERLYING_SOURCE_TRAPS,
&*source as *const _ as *const c_void,
);
rooted!(in(*cx)
let js_stream = NewReadableExternalSourceStreamObject(
*cx,
js_wrapper,
ptr::null_mut(),
HandleObject::null(),
)
);
stream.js_stream.set(UnwrapReadableStream(js_stream.get()));
}
stream
}
/// Get a pointer to the underlying JS object.
pub fn get_js_stream(&self) -> NonNull<JSObject> {
NonNull::new(self.js_stream.get())
.expect("Couldn't get a non-null pointer to JS stream object.")
}
#[allow(unsafe_code)]
pub fn enqueue_native(&self, bytes: Vec<u8>) {
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
let handle = unsafe { self.js_stream.handle() };
self.external_underlying_source
.as_ref()
.expect("No external source to enqueue bytes.")
.enqueue_chunk(cx, handle, bytes);
}
#[allow(unsafe_code)]
pub fn error_native(&self, error: Error) {
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
unsafe {
rooted!(in(*cx) let mut js_error = UndefinedValue());
error.to_jsval(*cx, &global, js_error.handle_mut());
ReadableStreamError(
*cx,
self.js_stream.handle(),
js_error.handle().into_handle(),
);
}
}
#[allow(unsafe_code)]
pub fn close_native(&self) {
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
let handle = unsafe { self.js_stream.handle() };
self.external_underlying_source
.as_ref()
.expect("No external source to close.")
.close(cx, handle);
}
/// Does the stream have all data in memory?
pub fn in_memory(&self) -> bool {
self.external_underlying_source
.as_ref()
.map(|source| source.in_memory())
.unwrap_or(false)
}
/// Return bytes for synchronous use, if the stream has all data in memory.
pub fn get_in_memory_bytes(&self) -> Option<Vec<u8>> {
self.external_underlying_source
.as_ref()
.and_then(|source| source.get_in_memory_bytes())
}
/// Acquires a reader and locks the stream,
/// must be done before `read_a_chunk`.
#[allow(unsafe_code)]
pub fn start_reading(&self) -> Result<(), ()> {
if self.is_locked() || self.is_disturbed() {
return Err(());
}
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
unsafe {
rooted!(in(*cx) let reader = ReadableStreamGetReader(
*cx,
self.js_stream.handle(),
ReadableStreamReaderMode::Default,
));
// Note: the stream is locked to the reader.
self.js_reader.set(reader.get());
}
self.has_reader.set(true);
Ok(())
}
/// Read a chunk from the stream,
/// must be called after `start_reading`,
/// and before `stop_reading`.
#[allow(unsafe_code)]
pub fn read_a_chunk(&self) -> Rc<Promise> {
if !self.has_reader.get() {
panic!("Attempt to read stream chunk without having acquired a reader.");
}
let global = self.global();
let _ar = enter_realm(&*global);
let _aes = AutoEntryScript::new(&*global);
let cx = global.get_cx();
unsafe {
rooted!(in(*cx) let promise_obj = ReadableStreamDefaultReaderRead(
*cx,
self.js_reader.handle(),
));
Promise::new_with_js_promise(promise_obj.handle(), cx)
}
}
/// Releases the lock on the reader,
/// must be done after `start_reading`.
#[allow(unsafe_code)]
pub fn stop_reading(&self) {
if !self.has_reader.get() {
panic!("ReadableStream::stop_reading called on a readerless stream.");
}
self.has_reader.set(false);
let global = self.global();
let _ar = enter_realm(&*global);
let cx = global.get_cx();
unsafe {
ReadableStreamReaderReleaseLock(*cx, self.js_reader.handle());
// Note: is this the way to nullify the Heap?
self.js_reader.set(ptr::null_mut());
}
}
#[allow(unsafe_code)]
pub fn is_locked(&self) -> bool {
// If we natively took a reader, we're locked.
if self.has_reader.get() {
return true;
}
// Otherwise, still double-check that script didn't lock the stream.
let cx = self.global().get_cx();
let mut locked_or_disturbed = false;
unsafe {
ReadableStreamIsLocked(*cx, self.js_stream.handle(), &mut locked_or_disturbed);
}
locked_or_disturbed
}
#[allow(unsafe_code)]
pub fn is_disturbed(&self) -> bool {
// Check that script didn't disturb the stream.
let cx = self.global().get_cx();
let mut locked_or_disturbed = false;
unsafe {
ReadableStreamIsDisturbed(*cx, self.js_stream.handle(), &mut locked_or_disturbed);
}
locked_or_disturbed
}
}
#[allow(unsafe_code)]
unsafe extern "C" fn request_data(
source: *const c_void,
cx: *mut JSContext,
stream: HandleObject,
desired_size: usize,
) {
let source = &*(source as *const ExternalUnderlyingSourceController);
source.pull(SafeJSContext::from_ptr(cx), stream, desired_size);
}
#[allow(unsafe_code)]
unsafe extern "C" fn write_into_read_request_buffer(
source: *const c_void,
_cx: *mut JSContext,
_stream: HandleObject,
buffer: *mut c_void,
length: usize,
bytes_written: *mut usize,
) {
let source = &*(source as *const ExternalUnderlyingSourceController);
let slice = slice::from_raw_parts_mut(buffer as *mut u8, length);
source.write_into_buffer(slice);
// Currently we're always able to completely fulfill the write request.
*bytes_written = length;
}
#[allow(unsafe_code)]
unsafe extern "C" fn cancel(
_source: *const c_void,
_cx: *mut JSContext,
_stream: HandleObject,
_reason: HandleValue,
_resolve_to: *mut JSVal,
) {
}
#[allow(unsafe_code)]
unsafe extern "C" fn close(_source: *const c_void, _cx: *mut JSContext, _stream: HandleObject) {}
#[allow(unsafe_code)]
unsafe extern "C" fn error(
_source: *const c_void,
_cx: *mut JSContext,
_stream: HandleObject,
_reason: HandleValue,
) {
}
#[allow(unsafe_code)]
unsafe extern "C" fn finalize(source: *mut ReadableStreamUnderlyingSource) {
DeleteReadableStreamUnderlyingSource(source);
}
pub enum ExternalUnderlyingSource {
/// Facilitate partial integration with sources
/// that are currently read into memory.
Memory(usize),
/// A blob as underlying source, with a known total size.
Blob(usize),
/// A fetch response as underlying source.
FetchResponse,
}
#[derive(JSTraceable, MallocSizeOf)]
struct ExternalUnderlyingSourceController {
/// Loosely matches the underlying queue,
/// <https://streams.spec.whatwg.org/#internal-queues>
buffer: RefCell<Vec<u8>>,
/// Has the stream been closed by native code?
closed: Cell<bool>,
/// Does this stream contains all it's data in memory?
in_memory: Cell<bool>,
}
impl ExternalUnderlyingSourceController {
fn new(source: ExternalUnderlyingSource) -> ExternalUnderlyingSourceController {
let (buffer, in_mem) = match source {
ExternalUnderlyingSource::Blob(size) => (Vec::with_capacity(size), false),
ExternalUnderlyingSource::Memory(size) => (Vec::with_capacity(size), true),
ExternalUnderlyingSource::FetchResponse => (vec![], false),
};
ExternalUnderlyingSourceController {
buffer: RefCell::new(buffer),
closed: Cell::new(false),
in_memory: Cell::new(in_mem),
}
}
/// Does the stream have all data in memory?
pub fn in_memory(&self) -> bool {
self.in_memory.get()
}
/// Return bytes synchronously if the stream has all data in memory.
pub fn get_in_memory_bytes(&self) -> Option<Vec<u8>> {
if self.in_memory.get() {
return Some(self.buffer.borrow().clone());
}
None
}
/// Signal available bytes if the stream is currently readable.
#[allow(unsafe_code)]
fn maybe_signal_available_bytes(
&self,
cx: SafeJSContext,
stream: HandleObject,
available: usize,
) {
if available == 0 {
return;
}
unsafe {
let mut readable = false;
if !ReadableStreamIsReadable(*cx, stream, &mut readable) {
return;
}
if readable {
ReadableStreamUpdateDataAvailableFromSource(*cx, stream, available as u32);
}
}
}
/// Close a currently readable js stream.
#[allow(unsafe_code)]
fn maybe_close_js_stream(&self, cx: SafeJSContext, stream: HandleObject) {
unsafe {
let mut readable = false;
if !ReadableStreamIsReadable(*cx, stream, &mut readable) {
return;
}
if readable {
ReadableStreamClose(*cx, stream);
}
}
}
fn close(&self, cx: SafeJSContext, stream: HandleObject) {
self.closed.set(true);
self.maybe_close_js_stream(cx, stream);
}
fn enqueue_chunk(&self, cx: SafeJSContext, stream: HandleObject, mut chunk: Vec<u8>) {
let available = {
let mut buffer = self.buffer.borrow_mut();
chunk.append(&mut buffer);
*buffer = chunk;
buffer.len()
};
self.maybe_signal_available_bytes(cx, stream, available);
}
#[allow(unsafe_code)]
fn pull(&self, cx: SafeJSContext, stream: HandleObject, _desired_size: usize) {
// Note: for pull sources,
// this would be the time to ask for a chunk.
if self.closed.get() {
return self.maybe_close_js_stream(cx, stream);
}
let available = {
let buffer = self.buffer.borrow();
buffer.len()
};
self.maybe_signal_available_bytes(cx, stream, available);
}
fn get_chunk_with_length(&self, length: usize) -> Vec<u8> {
let mut buffer = self.buffer.borrow_mut();
let buffer_len = buffer.len();
assert!(buffer_len >= length as usize);
buffer.split_off(buffer_len - length)
}
fn write_into_buffer(&self, dest: &mut [u8]) {
let length = dest.len();
let chunk = self.get_chunk_with_length(length);
dest.copy_from_slice(chunk.as_slice());
}
}
#[allow(unsafe_code)]
/// Get the `done` property of an object that a read promise resolved to.
pub fn get_read_promise_done(cx: SafeJSContext, v: &SafeHandleValue) -> Result<bool, Error> {
unsafe {
rooted!(in(*cx) let object = v.to_object());
rooted!(in(*cx) let mut done = UndefinedValue());
match get_dictionary_property(*cx, object.handle(), "done", done.handle_mut()) {
Ok(true) => match bool::from_jsval(*cx, done.handle(), ()) {
Ok(ConversionResult::Success(val)) => Ok(val),
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.to_string())),
_ => Err(Error::Type("Unknown format for done property.".to_string())),
},
Ok(false) => Err(Error::Type("Promise has no done property.".to_string())),
Err(()) => Err(Error::JSFailed),
}
}
}
#[allow(unsafe_code)]
/// Get the `value` property of an object that a read promise resolved to.
pub fn get_read_promise_bytes(cx: SafeJSContext, v: &SafeHandleValue) -> Result<Vec<u8>, Error> {
unsafe {
rooted!(in(*cx) let object = v.to_object());
rooted!(in(*cx) let mut bytes = UndefinedValue());
match get_dictionary_property(*cx, object.handle(), "value", bytes.handle_mut()) {
Ok(true) => {
match Vec::<u8>::from_jsval(*cx, bytes.handle(), ConversionBehavior::EnforceRange) {
Ok(ConversionResult::Success(val)) => Ok(val),
Ok(ConversionResult::Failure(error)) => Err(Error::Type(error.to_string())),
_ => Err(Error::Type("Unknown format for bytes read.".to_string())),
}
},
Ok(false) => Err(Error::Type("Promise has no value property.".to_string())),
Err(()) => Err(Error::JSFailed),
}
}
}

View file

@ -2,8 +2,9 @@
* 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 crate::body::{consume_body, BodyOperations, BodyType};
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::body::Extractable;
use crate::body::{consume_body, BodyMixin, BodyType};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
use crate::dom::bindings::codegen::Bindings::RequestBinding::ReferrerPolicy;
use crate::dom::bindings::codegen::Bindings::RequestBinding::RequestCache;
@ -22,11 +23,13 @@ use crate::dom::bindings::trace::RootedTraceableBox;
use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::{Guard, Headers};
use crate::dom::promise::Promise;
use crate::dom::xmlhttprequest::Extractable;
use crate::dom::readablestream::ReadableStream;
use crate::script_runtime::JSContext as SafeJSContext;
use dom_struct::dom_struct;
use http::header::{HeaderName, HeaderValue};
use http::method::InvalidMethod;
use http::Method as HttpMethod;
use js::jsapi::JSObject;
use net_traits::request::CacheMode as NetTraitsRequestCache;
use net_traits::request::CredentialsMode as NetTraitsRequestCredentials;
use net_traits::request::Destination as NetTraitsRequestDestination;
@ -37,7 +40,7 @@ use net_traits::request::RequestMode as NetTraitsRequestMode;
use net_traits::request::{Origin, Window};
use net_traits::ReferrerPolicy as MsgReferrerPolicy;
use servo_url::ServoUrl;
use std::cell::Cell;
use std::ptr::NonNull;
use std::rc::Rc;
use std::str::FromStr;
@ -45,11 +48,9 @@ use std::str::FromStr;
pub struct Request {
reflector_: Reflector,
request: DomRefCell<NetTraitsRequest>,
body_used: Cell<bool>,
body_stream: MutNullableDom<ReadableStream>,
headers: MutNullableDom<Headers>,
mime_type: DomRefCell<Vec<u8>>,
#[ignore_malloc_size_of = "Rc"]
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
}
impl Request {
@ -57,10 +58,9 @@ impl Request {
Request {
reflector_: Reflector::new(),
request: DomRefCell::new(net_request_from_global(global, url)),
body_used: Cell::new(false),
body_stream: MutNullableDom::new(None),
headers: Default::default(),
mime_type: DomRefCell::new("".to_string().into_bytes()),
body_promise: DomRefCell::new(None),
}
}
@ -72,7 +72,7 @@ impl Request {
#[allow(non_snake_case)]
pub fn Constructor(
global: &GlobalScope,
input: RequestInfo,
mut input: RequestInfo,
init: RootedTraceableBox<RequestInit>,
) -> Fallible<DomRoot<Request>> {
// Step 1
@ -365,9 +365,9 @@ impl Request {
r.request.borrow_mut().headers = r.Headers().get_headers_list();
// Step 33
let mut input_body = if let RequestInfo::Request(ref input_request) = input {
let input_request_request = input_request.request.borrow();
input_request_request.body.clone()
let mut input_body = if let RequestInfo::Request(ref mut input_request) = input {
let mut input_request_request = input_request.request.borrow_mut();
input_request_request.body.take()
} else {
None
};
@ -398,12 +398,10 @@ impl Request {
// Step 36.2 TODO "If init["keepalive"] exists and is true..."
// Step 36.3
let extracted_body_tmp = init_body.extract();
input_body = Some(extracted_body_tmp.0);
let content_type = extracted_body_tmp.1;
let mut extracted_body = init_body.extract(global)?;
// Step 36.4
if let Some(contents) = content_type {
if let Some(contents) = extracted_body.content_type.take() {
let ct_header_name = b"Content-Type";
if !r
.Headers()
@ -427,6 +425,10 @@ impl Request {
}
}
}
let (net_body, stream) = extracted_body.into_net_request_body();
r.body_stream.set(Some(&*stream));
input_body = Some(net_body);
}
// Step 37 "TODO if body is non-null and body's source is null..."
@ -448,13 +450,6 @@ impl Request {
// Step 42
Ok(r)
}
// https://fetch.spec.whatwg.org/#concept-body-locked
fn locked(&self) -> bool {
// TODO: ReadableStream is unimplemented. Just return false
// for now.
false
}
}
impl Request {
@ -467,7 +462,6 @@ impl Request {
fn clone_from(r: &Request) -> Fallible<DomRoot<Request>> {
let req = r.request.borrow();
let url = req.url();
let body_used = r.body_used.get();
let mime_type = r.mime_type.borrow().clone();
let headers_guard = r.Headers().get_guard();
let r_clone = Request::new(&r.global(), url);
@ -477,7 +471,6 @@ impl Request {
borrowed_r_request.origin = req.origin.clone();
}
*r_clone.request.borrow_mut() = req.clone();
r_clone.body_used.set(body_used);
*r_clone.mime_type.borrow_mut() = mime_type;
r_clone.Headers().copy_from_headers(r.Headers())?;
r_clone.Headers().set_guard(headers_guard);
@ -536,16 +529,14 @@ fn includes_credentials(input: &ServoUrl) -> bool {
!input.username().is_empty() || input.password().is_some()
}
// TODO: `Readable Stream` object is not implemented in Servo yet.
// https://fetch.spec.whatwg.org/#concept-body-disturbed
fn request_is_disturbed(_input: &Request) -> bool {
false
fn request_is_disturbed(input: &Request) -> bool {
input.is_disturbed()
}
// TODO: `Readable Stream` object is not implemented in Servo yet.
// https://fetch.spec.whatwg.org/#concept-body-locked
fn request_is_locked(_input: &Request) -> bool {
false
fn request_is_locked(input: &Request) -> bool {
input.is_locked()
}
impl RequestMethods for Request {
@ -622,9 +613,14 @@ impl RequestMethods for Request {
DOMString::from_string(r.integrity_metadata.clone())
}
/// <https://fetch.spec.whatwg.org/#dom-body-body>
fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> {
self.body().map(|stream| stream.get_js_stream())
}
// https://fetch.spec.whatwg.org/#dom-body-bodyused
fn BodyUsed(&self) -> bool {
self.body_used.get()
self.is_disturbed()
}
// https://fetch.spec.whatwg.org/#dom-request-clone
@ -667,29 +663,25 @@ impl RequestMethods for Request {
}
}
impl BodyOperations for Request {
fn get_body_used(&self) -> bool {
self.BodyUsed()
}
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
assert!(self.body_promise.borrow().is_none());
self.body_used.set(true);
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
impl BodyMixin for Request {
fn is_disturbed(&self) -> bool {
let body_stream = self.body_stream.get();
body_stream
.as_ref()
.map_or(false, |stream| stream.is_disturbed())
}
fn is_locked(&self) -> bool {
self.locked()
let body_stream = self.body_stream.get();
body_stream.map_or(false, |stream| stream.is_locked())
}
fn take_body(&self) -> Option<Vec<u8>> {
let mut request = self.request.borrow_mut();
let body = request.body.take();
Some(body.unwrap_or(vec![]))
fn body(&self) -> Option<DomRoot<ReadableStream>> {
self.body_stream.get()
}
fn get_mime_type(&self) -> Ref<Vec<u8>> {
self.mime_type.borrow()
fn get_mime_type(&self) -> Vec<u8> {
self.mime_type.borrow().clone()
}
}

View file

@ -2,8 +2,9 @@
* 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 crate::body::{consume_body, consume_body_with_promise, BodyOperations, BodyType};
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::body::{consume_body, BodyMixin, BodyType};
use crate::body::{Extractable, ExtractedBody};
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HeadersBinding::HeadersMethods;
use crate::dom::bindings::codegen::Bindings::ResponseBinding;
use crate::dom::bindings::codegen::Bindings::ResponseBinding::{
@ -18,16 +19,16 @@ use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::{is_obs_text, is_vchar};
use crate::dom::headers::{Guard, Headers};
use crate::dom::promise::Promise;
use crate::dom::xmlhttprequest::Extractable;
use crate::dom::readablestream::{ExternalUnderlyingSource, ReadableStream};
use crate::script_runtime::JSContext as SafeJSContext;
use crate::script_runtime::StreamConsumer;
use dom_struct::dom_struct;
use http::header::HeaderMap as HyperHeaders;
use hyper::StatusCode;
use hyper_serde::Serde;
use net_traits::response::ResponseBody as NetTraitsResponseBody;
use js::jsapi::JSObject;
use servo_url::ServoUrl;
use std::cell::Cell;
use std::mem;
use std::ptr::NonNull;
use std::rc::Rc;
use std::str::FromStr;
use url::Position;
@ -37,7 +38,6 @@ pub struct Response {
reflector_: Reflector,
headers_reflector: MutNullableDom<Headers>,
mime_type: DomRefCell<Vec<u8>>,
body_used: Cell<bool>,
/// `None` can be considered a StatusCode of `0`.
#[ignore_malloc_size_of = "Defined in hyper"]
status: DomRefCell<Option<StatusCode>>,
@ -45,10 +45,8 @@ pub struct Response {
response_type: DomRefCell<DOMResponseType>,
url: DomRefCell<Option<ServoUrl>>,
url_list: DomRefCell<Vec<ServoUrl>>,
// For now use the existing NetTraitsResponseBody enum
body: DomRefCell<NetTraitsResponseBody>,
#[ignore_malloc_size_of = "Rc"]
body_promise: DomRefCell<Option<(Rc<Promise>, BodyType)>>,
/// The stream of https://fetch.spec.whatwg.org/#body.
body_stream: MutNullableDom<ReadableStream>,
#[ignore_malloc_size_of = "StreamConsumer"]
stream_consumer: DomRefCell<Option<StreamConsumer>>,
redirected: DomRefCell<bool>,
@ -56,19 +54,21 @@ pub struct Response {
#[allow(non_snake_case)]
impl Response {
pub fn new_inherited() -> Response {
pub fn new_inherited(global: &GlobalScope) -> Response {
let stream = ReadableStream::new_with_external_underlying_source(
global,
ExternalUnderlyingSource::FetchResponse,
);
Response {
reflector_: Reflector::new(),
headers_reflector: Default::default(),
mime_type: DomRefCell::new("".to_string().into_bytes()),
body_used: Cell::new(false),
status: DomRefCell::new(Some(StatusCode::OK)),
raw_status: DomRefCell::new(Some((200, b"".to_vec()))),
response_type: DomRefCell::new(DOMResponseType::Default),
url: DomRefCell::new(None),
url_list: DomRefCell::new(vec![]),
body: DomRefCell::new(NetTraitsResponseBody::Empty),
body_promise: DomRefCell::new(None),
body_stream: MutNullableDom::new(Some(&*stream)),
stream_consumer: DomRefCell::new(None),
redirected: DomRefCell::new(false),
}
@ -76,7 +76,7 @@ impl Response {
// https://fetch.spec.whatwg.org/#dom-response
pub fn new(global: &GlobalScope) -> DomRoot<Response> {
reflect_dom_object(Box::new(Response::new_inherited()), global)
reflect_dom_object(Box::new(Response::new_inherited(global)), global)
}
pub fn Constructor(
@ -128,8 +128,14 @@ impl Response {
};
// Step 7.3
let (extracted_body, content_type) = body.extract();
*r.body.borrow_mut() = NetTraitsResponseBody::Done(extracted_body);
let ExtractedBody {
stream,
total_bytes: _,
content_type,
source: _,
} = body.extract(global)?;
r.body_stream.set(Some(&*stream));
// Step 7.4
if let Some(content_type_contents) = content_type {
@ -211,42 +217,32 @@ impl Response {
Ok(r)
}
// https://fetch.spec.whatwg.org/#concept-body-locked
fn locked(&self) -> bool {
// TODO: ReadableStream is unimplemented. Just return false
// for now.
false
pub fn error_stream(&self, error: Error) {
if let Some(body) = self.body_stream.get() {
body.error_native(error);
}
}
}
impl BodyOperations for Response {
fn get_body_used(&self) -> bool {
self.BodyUsed()
}
fn set_body_promise(&self, p: &Rc<Promise>, body_type: BodyType) {
assert!(self.body_promise.borrow().is_none());
self.body_used.set(true);
*self.body_promise.borrow_mut() = Some((p.clone(), body_type));
impl BodyMixin for Response {
fn is_disturbed(&self) -> bool {
self.body_stream
.get()
.map_or(false, |stream| stream.is_disturbed())
}
fn is_locked(&self) -> bool {
self.locked()
self.body_stream
.get()
.map_or(false, |stream| stream.is_locked())
}
fn take_body(&self) -> Option<Vec<u8>> {
let body = mem::replace(&mut *self.body.borrow_mut(), NetTraitsResponseBody::Empty);
match body {
NetTraitsResponseBody::Done(bytes) => Some(bytes),
body => {
let _ = mem::replace(&mut *self.body.borrow_mut(), body);
None
},
}
fn body(&self) -> Option<DomRoot<ReadableStream>> {
self.body_stream.get()
}
fn get_mime_type(&self) -> Ref<Vec<u8>> {
self.mime_type.borrow()
fn get_mime_type(&self) -> Vec<u8> {
self.mime_type.borrow().clone()
}
}
@ -328,7 +324,7 @@ impl ResponseMethods for Response {
// https://fetch.spec.whatwg.org/#dom-response-clone
fn Clone(&self) -> Fallible<DomRoot<Response>> {
// Step 1
if self.is_locked() || self.body_used.get() {
if self.is_locked() || self.is_disturbed() {
return Err(Error::Type("cannot clone a disturbed response".to_string()));
}
@ -346,8 +342,8 @@ impl ResponseMethods for Response {
*new_response.url.borrow_mut() = self.url.borrow().clone();
*new_response.url_list.borrow_mut() = self.url_list.borrow().clone();
if *self.body.borrow() != NetTraitsResponseBody::Empty {
*new_response.body.borrow_mut() = self.body.borrow().clone();
if let Some(stream) = self.body_stream.get().clone() {
new_response.body_stream.set(Some(&*stream));
}
// Step 3
@ -359,7 +355,12 @@ impl ResponseMethods for Response {
// https://fetch.spec.whatwg.org/#dom-body-bodyused
fn BodyUsed(&self) -> bool {
self.body_used.get()
self.is_disturbed()
}
/// <https://fetch.spec.whatwg.org/#dom-body-body>
fn GetBody(&self, _cx: SafeJSContext) -> Option<NonNull<JSObject>> {
self.body().map(|stream| stream.get_js_stream())
}
// https://fetch.spec.whatwg.org/#dom-body-text
@ -424,20 +425,19 @@ impl Response {
*self.status.borrow_mut() = None;
self.set_raw_status(None);
self.set_headers(None);
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
},
DOMResponseType::Opaque => {
*self.url_list.borrow_mut() = vec![];
*self.status.borrow_mut() = None;
self.set_raw_status(None);
self.set_headers(None);
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
self.body_stream.set(None);
},
DOMResponseType::Opaqueredirect => {
*self.status.borrow_mut() = None;
self.set_raw_status(None);
self.set_headers(None);
*self.body.borrow_mut() = NetTraitsResponseBody::Done(vec![]);
self.body_stream.set(None);
},
DOMResponseType::Default => {},
DOMResponseType::Basic => {},
@ -449,17 +449,19 @@ impl Response {
*self.stream_consumer.borrow_mut() = sc;
}
pub fn stream_chunk(&self, stream: &[u8]) {
pub fn stream_chunk(&self, chunk: Vec<u8>) {
// Note, are these two actually mutually exclusive?
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().as_ref() {
stream_consumer.consume_chunk(stream);
stream_consumer.consume_chunk(chunk.as_slice());
} else if let Some(body) = self.body_stream.get() {
body.enqueue_native(chunk);
}
}
#[allow(unrooted_must_root)]
pub fn finish(&self, body: Vec<u8>) {
*self.body.borrow_mut() = NetTraitsResponseBody::Done(body);
if let Some((p, body_type)) = self.body_promise.borrow_mut().take() {
consume_body_with_promise(self, body_type, &p);
pub fn finish(&self) {
if let Some(body) = self.body_stream.get() {
body.close_native();
}
if let Some(stream_consumer) = self.stream_consumer.borrow_mut().take() {
stream_consumer.stream_end();

View file

@ -53,7 +53,7 @@ use crate::realms::InRealm;
use crate::script_runtime::JSContext as SafeJSContext;
use crate::timers::OneshotTimerCallback;
use dom_struct::dom_struct;
use js::jsapi::{Heap, JSContext, JSObject};
use js::jsapi::{Heap, JSObject};
use js::jsapi::{JS_NewPlainObject, JS_NewUint8ClampedArray};
use js::jsval::{JSVal, NullValue};
use js::rust::CustomAutoRooterGuard;
@ -1003,8 +1003,8 @@ impl TestBindingMethods for TestBinding {
resolve.map(SimpleHandler::new),
reject.map(SimpleHandler::new),
);
let p = Promise::new_in_current_realm(&global, comp);
p.append_native_handler(&handler);
let p = Promise::new_in_current_realm(&global, comp.clone());
p.append_native_handler(&handler, comp);
return p;
#[derive(JSTraceable, MallocSizeOf)]
@ -1018,9 +1018,8 @@ impl TestBindingMethods for TestBinding {
}
}
impl Callback for SimpleHandler {
#[allow(unsafe_code)]
fn callback(&self, cx: *mut JSContext, v: HandleValue, realm: InRealm) {
let global = unsafe { GlobalScope::from_context(cx, realm) };
fn callback(&self, cx: SafeJSContext, v: HandleValue, realm: InRealm) {
let global = GlobalScope::from_safe_context(cx, realm);
let _ = self.handler.Call_(&*global, v, ExceptionHandling::Report);
}
}

View file

@ -17,6 +17,7 @@ interface Blob {
optional [Clamp] long long end,
optional DOMString contentType);
[NewObject] object stream();
[NewObject] Promise<DOMString> text();
[NewObject] Promise<ArrayBuffer> arrayBuffer();
};

View file

@ -7,6 +7,7 @@
[Exposed=(Window,Worker)]
interface mixin Body {
readonly attribute boolean bodyUsed;
readonly attribute object? body;
[NewObject] Promise<ArrayBuffer> arrayBuffer();
[NewObject] Promise<Blob> blob();

View file

@ -0,0 +1,11 @@
/* 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 https://mozilla.org/MPL/2.0/. */
// This interface is entirely internal to Servo, and should not be accessible to
// web pages.
[NoInterfaceObject, Exposed=(Window,Worker)]
// Need to escape "ReadableStream" so it's treated as an identifier.
interface _ReadableStream {
};

View file

@ -13,7 +13,7 @@
*/
// https://fetch.spec.whatwg.org/#bodyinit
typedef (Blob or BufferSource or FormData or DOMString or URLSearchParams) BodyInit;
typedef (Blob or BufferSource or FormData or DOMString or URLSearchParams or ReadableStream) BodyInit;
enum XMLHttpRequestResponseType {
"",
@ -21,7 +21,7 @@ enum XMLHttpRequestResponseType {
"blob",
"document",
"json",
"text"
"text",
};
[Exposed=(Window,Worker)]

View file

@ -2,11 +2,10 @@
* 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 crate::body::{BodySource, Extractable, ExtractedBody};
use crate::document_loader::DocumentLoader;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobBinding::BlobMethods;
use crate::dom::bindings::codegen::Bindings::WindowBinding::WindowMethods;
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::BodyInit;
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestMethods;
use crate::dom::bindings::codegen::Bindings::XMLHttpRequestBinding::XMLHttpRequestResponseType;
use crate::dom::bindings::codegen::UnionTypes::DocumentOrBodyInit;
@ -22,15 +21,13 @@ use crate::dom::document::DocumentSource;
use crate::dom::document::{Document, HasBrowsingContext, IsHTMLDocument};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::formdata::FormData;
use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::is_forbidden_header_name;
use crate::dom::htmlformelement::{encode_multipart_form_data, generate_boundary};
use crate::dom::node::Node;
use crate::dom::performanceresourcetiming::InitiatorType;
use crate::dom::progressevent::ProgressEvent;
use crate::dom::readablestream::ReadableStream;
use crate::dom::servoparser::ServoParser;
use crate::dom::urlsearchparams::URLSearchParams;
use crate::dom::window::Window;
use crate::dom::workerglobalscope::WorkerGlobalScope;
use crate::dom::xmlhttprequesteventtarget::XMLHttpRequestEventTarget;
@ -562,42 +559,104 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
_ => data,
};
// Step 4 (first half)
let extracted_or_serialized = match data {
let mut extracted_or_serialized = match data {
Some(DocumentOrBodyInit::Document(ref doc)) => {
let data = Vec::from(serialize_document(&doc)?.as_ref());
let bytes = Vec::from(serialize_document(&doc)?.as_ref());
let content_type = if doc.is_html_document() {
"text/html;charset=UTF-8"
} else {
"application/xml;charset=UTF-8"
};
Some((data, Some(DOMString::from(content_type))))
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: Some(DOMString::from(content_type)),
source: BodySource::Object,
})
},
Some(DocumentOrBodyInit::Blob(ref b)) => Some(b.extract()),
Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(formdata.extract()),
Some(DocumentOrBodyInit::String(ref str)) => Some(str.extract()),
Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(urlsp.extract()),
Some(DocumentOrBodyInit::Blob(ref b)) => {
let extracted_body = b.extract(&self.global()).expect("Couldn't extract body.");
if !extracted_body.in_memory() && self.sync.get() {
warn!("Sync XHR with not in-memory Blob as body not supported");
None
} else {
Some(extracted_body)
}
},
Some(DocumentOrBodyInit::FormData(ref formdata)) => Some(
formdata
.extract(&self.global())
.expect("Couldn't extract body."),
),
Some(DocumentOrBodyInit::String(ref str)) => {
Some(str.extract(&self.global()).expect("Couldn't extract body."))
},
Some(DocumentOrBodyInit::URLSearchParams(ref urlsp)) => Some(
urlsp
.extract(&self.global())
.expect("Couldn't extract body."),
),
Some(DocumentOrBodyInit::ArrayBuffer(ref typedarray)) => {
Some((typedarray.to_vec(), None))
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: None,
source: BodySource::Object,
})
},
Some(DocumentOrBodyInit::ArrayBufferView(ref typedarray)) => {
Some((typedarray.to_vec(), None))
let bytes = typedarray.to_vec();
let total_bytes = bytes.len();
let global = self.global();
let stream = ReadableStream::new_from_bytes(&global, bytes);
Some(ExtractedBody {
stream,
total_bytes: Some(total_bytes),
content_type: None,
source: BodySource::Object,
})
},
Some(DocumentOrBodyInit::ReadableStream(ref stream)) => {
if self.sync.get() {
warn!("Sync XHR with ReadableStream as body not supported");
None
} else {
if stream.is_locked() || stream.is_disturbed() {
return Err(Error::Type(
"The body's stream is disturbed or locked".to_string(),
));
}
Some(ExtractedBody {
stream: stream.clone(),
total_bytes: None,
content_type: None,
source: BodySource::Null,
})
}
},
None => None,
};
self.request_body_len
.set(extracted_or_serialized.as_ref().map_or(0, |e| e.0.len()));
self.request_body_len.set(
extracted_or_serialized
.as_ref()
.map_or(0, |e| e.total_bytes.unwrap_or(0)),
);
// todo preserved headers?
// Step 6
self.upload_complete.set(false);
// Step 7
self.upload_complete.set(match extracted_or_serialized {
None => true,
Some(ref e) if e.0.is_empty() => true,
_ => false,
});
self.upload_complete.set(extracted_or_serialized.is_none());
// Step 8
self.send_flag.set(true);
@ -634,12 +693,17 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
unreachable!()
};
let content_type = match extracted_or_serialized.as_mut() {
Some(body) => body.content_type.take(),
None => None,
};
let mut request = RequestBuilder::new(self.request_url.borrow().clone().unwrap())
.method(self.request_method.borrow().clone())
.headers((*self.request_headers.borrow()).clone())
.unsafe_request(true)
// XXXManishearth figure out how to avoid this clone
.body(extracted_or_serialized.as_ref().map(|e| e.0.clone()))
.body(extracted_or_serialized.map(|e| e.into_net_request_body().0))
// XXXManishearth actually "subresource", but it doesn't exist
// https://github.com/whatwg/xhr/issues/71
.destination(Destination::None)
@ -658,8 +722,8 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
.pipeline_id(Some(self.global().pipeline_id()));
// step 4 (second half)
match extracted_or_serialized {
Some((_, ref content_type)) => {
match content_type {
Some(content_type) => {
let encoding = match data {
Some(DocumentOrBodyInit::String(_)) | Some(DocumentOrBodyInit::Document(_)) =>
// XHR spec differs from http, and says UTF-8 should be in capitals,
@ -672,13 +736,12 @@ impl XMLHttpRequestMethods for XMLHttpRequest {
};
let mut content_type_set = false;
if let Some(ref ct) = *content_type {
if !request.headers.contains_key(header::CONTENT_TYPE) {
request
.headers
.insert(header::CONTENT_TYPE, HeaderValue::from_str(ct).unwrap());
content_type_set = true;
}
if !request.headers.contains_key(header::CONTENT_TYPE) {
request.headers.insert(
header::CONTENT_TYPE,
HeaderValue::from_str(&content_type).unwrap(),
);
content_type_set = true;
}
if !content_type_set {
@ -1555,56 +1618,6 @@ impl XHRTimeoutCallback {
}
}
pub trait Extractable {
fn extract(&self) -> (Vec<u8>, Option<DOMString>);
}
impl Extractable for Blob {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
let content_type = if self.Type().as_ref().is_empty() {
None
} else {
Some(self.Type())
};
let bytes = self.get_bytes().unwrap_or(vec![]);
(bytes, content_type)
}
}
impl Extractable for DOMString {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
(
self.as_bytes().to_owned(),
Some(DOMString::from("text/plain;charset=UTF-8")),
)
}
}
impl Extractable for FormData {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
let boundary = generate_boundary();
let bytes = encode_multipart_form_data(&mut self.datums(), boundary.clone(), UTF_8);
(
bytes,
Some(DOMString::from(format!(
"multipart/form-data;boundary={}",
boundary
))),
)
}
}
impl Extractable for URLSearchParams {
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
(
self.serialize_utf8().into_bytes(),
Some(DOMString::from(
"application/x-www-form-urlencoded;charset=UTF-8",
)),
)
}
}
fn serialize_document(doc: &Document) -> Fallible<DOMString> {
let mut writer = vec![];
match serialize(&mut writer, &doc.upcast::<Node>(), SerializeOpts::default()) {
@ -1613,20 +1626,6 @@ fn serialize_document(doc: &Document) -> Fallible<DOMString> {
}
}
impl Extractable for BodyInit {
// https://fetch.spec.whatwg.org/#concept-bodyinit-extract
fn extract(&self) -> (Vec<u8>, Option<DOMString>) {
match *self {
BodyInit::String(ref s) => s.extract(),
BodyInit::URLSearchParams(ref usp) => usp.extract(),
BodyInit::Blob(ref b) => b.extract(),
BodyInit::FormData(ref formdata) => formdata.extract(),
BodyInit::ArrayBuffer(ref typedarray) => ((typedarray.to_vec(), None)),
BodyInit::ArrayBufferView(ref typedarray) => ((typedarray.to_vec(), None)),
}
}
}
/// Returns whether `bs` is a `field-value`, as defined by
/// [RFC 2616](http://tools.ietf.org/html/rfc2616#page-32).
pub fn is_field_value(slice: &[u8]) -> bool {