mirror of
https://github.com/servo/servo.git
synced 2025-08-03 04:30:10 +01:00
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:
commit
8536cee72c
77 changed files with 2378 additions and 929 deletions
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
539
components/script/dom/readablestream.rs
Normal file
539
components/script/dom/readablestream.rs
Normal 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),
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
};
|
||||
|
|
|
@ -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();
|
||||
|
|
11
components/script/dom/webidls/ReadableStream.webidl
Normal file
11
components/script/dom/webidls/ReadableStream.webidl
Normal 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 {
|
||||
};
|
|
@ -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)]
|
||||
|
|
|
@ -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 {
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue