diff options
Diffstat (limited to 'components/script')
-rw-r--r-- | components/script/canvas_state.rs | 4 | ||||
-rw-r--r-- | components/script/dom/bindings/structuredclone.rs | 8 | ||||
-rw-r--r-- | components/script/dom/blob.rs | 71 | ||||
-rw-r--r-- | components/script/dom/headers.rs | 331 | ||||
-rw-r--r-- | components/script/dom/htmlcanvaselement.rs | 8 | ||||
-rw-r--r-- | components/script/dom/htmlinputelement.rs | 4 | ||||
-rw-r--r-- | components/script/dom/htmlmediaelement.rs | 4 | ||||
-rw-r--r-- | components/script/dom/htmltablecellelement.rs | 16 | ||||
-rw-r--r-- | components/script/dom/htmltablecolelement.rs | 14 | ||||
-rw-r--r-- | components/script/dom/readablestream.rs | 46 | ||||
-rw-r--r-- | components/script/dom/transformstream.rs | 104 | ||||
-rw-r--r-- | components/script/dom/webglrenderingcontext.rs | 3 | ||||
-rw-r--r-- | components/script/dom/webgpu/gpu.rs | 8 | ||||
-rw-r--r-- | components/script/dom/xmlhttprequest.rs | 155 | ||||
-rw-r--r-- | components/script/script_thread.rs | 38 | ||||
-rw-r--r-- | components/script/stylesheet_loader.rs | 14 | ||||
-rw-r--r-- | components/script/test.rs | 1 |
17 files changed, 453 insertions, 376 deletions
diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs index dabe6a5728b..d4840a5b470 100644 --- a/components/script/canvas_state.rs +++ b/components/script/canvas_state.rs @@ -17,7 +17,7 @@ use cssparser::color::clamp_unit_f32; use cssparser::{Parser, ParserInput}; use euclid::default::{Point2D, Rect, Size2D, Transform2D}; use euclid::vec2; -use ipc_channel::ipc::{self, IpcSender}; +use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory}; use net_traits::image_cache::{ImageCache, ImageResponse}; use net_traits::request::CorsSettings; use pixels::PixelFormat; @@ -350,7 +350,7 @@ impl CanvasState { size.cast(), format, alpha_mode, - img.bytes(), + IpcSharedMemory::from_bytes(img.first_frame().bytes), )) } diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index 70638238123..c23156817cb 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -14,7 +14,7 @@ use base::id::{ }; use constellation_traits::{ BlobImpl, DomException, DomPoint, MessagePortImpl, Serializable as SerializableInterface, - StructuredSerializedData, Transferrable as TransferrableInterface, + StructuredSerializedData, Transferrable as TransferrableInterface, TransformStreamData, }; use js::gc::RootedVec; use js::glue::{ @@ -517,6 +517,8 @@ pub(crate) struct StructuredDataReader<'a> { /// used as part of the "transfer-receiving" steps of ports, /// to produce the DOM ports stored in `message_ports` above. pub(crate) port_impls: Option<HashMap<MessagePortId, MessagePortImpl>>, + /// A map of transform stream implementations, + pub(crate) transform_streams_port_impls: Option<HashMap<MessagePortId, TransformStreamData>>, /// A map of blob implementations, /// used as part of the "deserialize" steps of blobs, /// to produce the DOM blobs stored in `blobs` above. @@ -535,6 +537,8 @@ pub(crate) struct StructuredDataWriter { pub(crate) errors: DOMErrorRecord, /// Transferred ports. pub(crate) ports: Option<HashMap<MessagePortId, MessagePortImpl>>, + /// Transferred transform streams. + pub(crate) transform_streams_port: Option<HashMap<MessagePortId, TransformStreamData>>, /// Serialized points. pub(crate) points: Option<HashMap<DomPointId, DomPoint>>, /// Serialized exceptions. @@ -591,6 +595,7 @@ pub(crate) fn write( let data = StructuredSerializedData { serialized: data, ports: sc_writer.ports.take(), + transform_streams: sc_writer.transform_streams_port.take(), points: sc_writer.points.take(), exceptions: sc_writer.exceptions.take(), blobs: sc_writer.blobs.take(), @@ -613,6 +618,7 @@ pub(crate) fn read( let mut sc_reader = StructuredDataReader { roots, port_impls: data.ports.take(), + transform_streams_port_impls: data.transform_streams.take(), blob_impls: data.blobs.take(), points: data.points.take(), exceptions: data.exceptions.take(), diff --git a/components/script/dom/blob.rs b/components/script/dom/blob.rs index c5c5c480707..18e968aaa70 100644 --- a/components/script/dom/blob.rs +++ b/components/script/dom/blob.rs @@ -12,11 +12,10 @@ use dom_struct::dom_struct; use encoding_rs::UTF_8; use js::jsapi::JSObject; use js::rust::HandleObject; -use js::typedarray::Uint8; +use js::typedarray::{ArrayBufferU8, Uint8}; use net_traits::filemanager_thread::RelativePos; use uuid::Uuid; -use crate::body::{FetchedData, run_array_buffer_data_algorithm}; use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::codegen::Bindings::BlobBinding; use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods; @@ -226,7 +225,7 @@ impl BlobMethods<crate::DomTypeHolder> for Blob { Blob::new(&global, blob_impl, can_gc) } - // https://w3c.github.io/FileAPI/#text-method-algo + /// <https://w3c.github.io/FileAPI/#text-method-algo> fn Text(&self, can_gc: CanGc) -> Rc<Promise> { let global = self.global(); let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>(); @@ -250,35 +249,51 @@ impl BlobMethods<crate::DomTypeHolder> for Blob { } // https://w3c.github.io/FileAPI/#arraybuffer-method-algo - fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> { - let global = self.global(); - let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>(); - let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc); + fn ArrayBuffer(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> { + let cx = GlobalScope::get_cx(); + let global = GlobalScope::from_safe_context(cx, in_realm); + let promise = Promise::new_in_current_realm(in_realm, can_gc); - let id = self.get_blob_url_id(); + // 1. Let stream be the result of calling get stream on this. + let stream = self.get_stream(can_gc); - global.read_file_async( - id, - p.clone(), - Box::new(|promise, bytes| { - match bytes { - Ok(b) => { - let cx = GlobalScope::get_cx(); - let result = run_array_buffer_data_algorithm(cx, b, CanGc::note()); - - match result { - Ok(FetchedData::ArrayBuffer(a)) => { - promise.resolve_native(&a, CanGc::note()) - }, - Err(e) => promise.reject_error(e, CanGc::note()), - _ => panic!("Unexpected result from run_array_buffer_data_algorithm"), - } - }, - Err(e) => promise.reject_error(e, CanGc::note()), - }; + // 2. Let reader be the result of getting a reader from stream. + // If that threw an exception, return a new promise rejected with that exception. + let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) { + Ok(reader) => reader, + Err(error) => { + promise.reject_error(error, can_gc); + return promise; + }, + }; + + // 3. Let promise be the result of reading all bytes from stream with reader. + let success_promise = promise.clone(); + let failure_promise = promise.clone(); + reader.read_all_bytes( + cx, + &global, + Rc::new(move |bytes| { + rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>()); + // 4. Return the result of transforming promise by a fulfillment handler that returns a new + // [ArrayBuffer] + let array_buffer = create_buffer_source::<ArrayBufferU8>( + cx, + bytes, + js_object.handle_mut(), + can_gc, + ) + .expect("Converting input to ArrayBufferU8 should never fail"); + success_promise.resolve_native(&array_buffer, can_gc); }), + Rc::new(move |cx, value| { + failure_promise.reject(cx, value, can_gc); + }), + in_realm, + can_gc, ); - p + + promise } /// <https://w3c.github.io/FileAPI/#dom-blob-bytes> diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs index 10a8be731bf..f195992faa5 100644 --- a/components/script/dom/headers.rs +++ b/components/script/dom/headers.rs @@ -5,14 +5,15 @@ use std::cell::Cell; use std::str::{self, FromStr}; -use data_url::mime::Mime as DataUrlMime; use dom_struct::dom_struct; use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue}; use js::rust::HandleObject; use net_traits::fetch::headers::{ - get_decode_and_split_header_value, get_value_from_header_list, is_forbidden_method, + extract_mime_type, get_decode_and_split_header_value, get_value_from_header_list, + is_forbidden_method, }; use net_traits::request::is_cors_safelisted_request_header; +use net_traits::trim_http_whitespace; use crate::dom::bindings::cell::DomRefCell; use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods}; @@ -33,7 +34,7 @@ pub(crate) struct Headers { header_list: DomRefCell<HyperHeaders>, } -// https://fetch.spec.whatwg.org/#concept-headers-guard +/// <https://fetch.spec.whatwg.org/#concept-headers-guard> #[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)] pub(crate) enum Guard { Immutable, @@ -66,7 +67,7 @@ impl Headers { } impl HeadersMethods<crate::DomTypeHolder> for Headers { - // https://fetch.spec.whatwg.org/#dom-headers + /// <https://fetch.spec.whatwg.org/#dom-headers> fn Constructor( global: &GlobalScope, proto: Option<HandleObject>, @@ -78,47 +79,41 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { Ok(dom_headers_new) } - // https://fetch.spec.whatwg.org/#concept-headers-append + /// <https://fetch.spec.whatwg.org/#concept-headers-append> fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult { - // Step 1 - let value = normalize_value(value); + // 1. Normalize value. + let value = trim_http_whitespace(&value); - // Step 2 - // https://fetch.spec.whatwg.org/#headers-validate - let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; + // 2. If validating (name, value) for headers returns false, then return. + let Some((mut valid_name, valid_value)) = + self.validate_name_and_value(name, ByteString::new(value.into()))? + else { + return Ok(()); + }; valid_name = valid_name.to_lowercase(); - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { - return Ok(()); - } - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); - } - - // Step 3 + // 3. If headers’s guard is "request-no-cors": if self.guard.get() == Guard::RequestNoCors { + // 3.1. Let temporaryValue be the result of getting name from headers’s header list. let tmp_value = if let Some(mut value) = get_value_from_header_list(&valid_name, &self.header_list.borrow()) { + // 3.3. Otherwise, set temporaryValue to temporaryValue, followed by 0x2C 0x20, followed by value. value.extend(b", "); - value.extend(valid_value.clone()); + value.extend(valid_value.to_vec()); value } else { - valid_value.clone() + // 3.2. If temporaryValue is null, then set temporaryValue to value. + valid_value.to_vec() }; - + // 3.4. If (name, temporaryValue) is not a no-CORS-safelisted request-header, then return. if !is_cors_safelisted_request_header(&valid_name, &tmp_value) { return Ok(()); } } - // Step 4 + // 4. Append (name, value) to headers’s header list. match HeaderValue::from_bytes(&valid_value) { Ok(value) => { self.header_list @@ -134,7 +129,7 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { }, }; - // Step 5 + // 5. If headers’s guard is "request-no-cors", then remove privileged no-CORS request-headers from headers. if self.guard.get() == Guard::RequestNoCors { self.remove_privileged_no_cors_request_headers(); } @@ -142,50 +137,53 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { Ok(()) } - // https://fetch.spec.whatwg.org/#dom-headers-delete + /// <https://fetch.spec.whatwg.org/#dom-headers-delete> fn Delete(&self, name: ByteString) -> ErrorResult { - // Step 1 - let (mut valid_name, valid_value) = validate_name_and_value(name, ByteString::new(vec![]))?; + // Step 1 If validating (name, ``) for this returns false, then return. + let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?; + let Some((mut valid_name, _valid_value)) = name_and_value else { + return Ok(()); + }; valid_name = valid_name.to_lowercase(); - // Step 2 - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - // Step 3 - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { - return Ok(()); - } - // Step 4 + // Step 2 If this’s guard is "request-no-cors", name is not a no-CORS-safelisted request-header name, + // and name is not a privileged no-CORS request-header name, then return. if self.guard.get() == Guard::RequestNoCors && !is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec()) { return Ok(()); } - // Step 5 - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); + + // 3. If this’s header list does not contain name, then return. + // 4. Delete name from this’s header list. + self.header_list.borrow_mut().remove(valid_name); + + // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this. + if self.guard.get() == Guard::RequestNoCors { + self.remove_privileged_no_cors_request_headers(); } - // Step 6 - self.header_list.borrow_mut().remove(&valid_name); + Ok(()) } - // https://fetch.spec.whatwg.org/#dom-headers-get + /// <https://fetch.spec.whatwg.org/#dom-headers-get> fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> { - // Step 1 + // 1. If name is not a header name, then throw a TypeError. let valid_name = validate_name(name)?; + + // 2. Return the result of getting name from this’s header list. Ok( get_value_from_header_list(&valid_name, &self.header_list.borrow()) .map(ByteString::new), ) } - // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie + /// <https://fetch.spec.whatwg.org/#dom-headers-getsetcookie> fn GetSetCookie(&self) -> Vec<ByteString> { + // 1. If this’s header list does not contain `Set-Cookie`, then return « ». + // 2. Return the values of all headers in this’s header list whose name is a + // byte-case-insensitive match for `Set-Cookie`, in order. self.header_list .borrow() .get_all("set-cookie") @@ -194,42 +192,36 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { .collect() } - // https://fetch.spec.whatwg.org/#dom-headers-has + /// <https://fetch.spec.whatwg.org/#dom-headers-has> fn Has(&self, name: ByteString) -> Fallible<bool> { - // Step 1 + // 1. If name is not a header name, then throw a TypeError. let valid_name = validate_name(name)?; - // Step 2 + // 2. Return true if this’s header list contains name; otherwise false. Ok(self.header_list.borrow_mut().get(&valid_name).is_some()) } - // https://fetch.spec.whatwg.org/#dom-headers-set + /// <https://fetch.spec.whatwg.org/#dom-headers-set> fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> { - // Step 1 - let value = normalize_value(value); - // Step 2 - let (mut valid_name, valid_value) = validate_name_and_value(name, value)?; - valid_name = valid_name.to_lowercase(); - // Step 3 - if self.guard.get() == Guard::Immutable { - return Err(Error::Type("Guard is immutable".to_string())); - } - // Step 4 - if self.guard.get() == Guard::Request && - is_forbidden_request_header(&valid_name, &valid_value) - { + // 1. Normalize value + let value = trim_http_whitespace(&value); + + // 2. If validating (name, value) for this returns false, then return. + let Some((mut valid_name, valid_value)) = + self.validate_name_and_value(name, ByteString::new(value.into()))? + else { return Ok(()); - } - // Step 5 + }; + valid_name = valid_name.to_lowercase(); + + // 3. If this’s guard is "request-no-cors" and (name, value) is not a + // no-CORS-safelisted request-header, then return. if self.guard.get() == Guard::RequestNoCors && - !is_cors_safelisted_request_header(&valid_name, &valid_value) + !is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec()) { return Ok(()); } - // Step 6 - if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { - return Ok(()); - } - // Step 7 + + // 4. Set (name, value) in this’s header list. // https://fetch.spec.whatwg.org/#concept-header-list-set match HeaderValue::from_bytes(&valid_value) { Ok(value) => { @@ -245,6 +237,12 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers { ); }, }; + + // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this. + if self.guard.get() == Guard::RequestNoCors { + self.remove_privileged_no_cors_request_headers(); + } + Ok(()) } } @@ -260,7 +258,7 @@ impl Headers { Ok(()) } - // https://fetch.spec.whatwg.org/#concept-headers-fill + /// <https://fetch.spec.whatwg.org/#concept-headers-fill> pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult { match filler { Some(HeadersInit::ByteStringSequenceSequence(v)) => { @@ -316,12 +314,12 @@ impl Headers { self.header_list.borrow_mut().clone() } - // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type + /// <https://fetch.spec.whatwg.org/#concept-header-extract-mime-type> pub(crate) fn extract_mime_type(&self) -> Vec<u8> { extract_mime_type(&self.header_list.borrow()).unwrap_or_default() } - // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine + /// <https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine> pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> { let borrowed_header_list = self.header_list.borrow(); let mut header_vec = vec![]; @@ -341,11 +339,38 @@ impl Headers { header_vec } - // https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name + /// <https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name> pub(crate) fn remove_privileged_no_cors_request_headers(&self) { - // https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name + // <https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name> self.header_list.borrow_mut().remove("range"); } + + /// <https://fetch.spec.whatwg.org/#headers-validate> + pub(crate) fn validate_name_and_value( + &self, + name: ByteString, + value: ByteString, + ) -> Fallible<Option<(String, ByteString)>> { + // 1. If name is not a header name or value is not a header value, then throw a TypeError. + let valid_name = validate_name(name)?; + if !is_legal_header_value(&value) { + return Err(Error::Type("Header value is not valid".to_string())); + } + // 2. If headers’s guard is "immutable", then throw a TypeError. + if self.guard.get() == Guard::Immutable { + return Err(Error::Type("Guard is immutable".to_string())); + } + // 3. If headers’s guard is "request" and (name, value) is a forbidden request-header, then return false. + if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) { + return Ok(None); + } + // 4. If headers’s guard is "response" and name is a forbidden response-header name, then return false. + if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) { + return Ok(None); + } + + Ok(Some((valid_name, value))) + } } impl Iterable for Headers { @@ -391,6 +416,7 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool { "keep-alive", "origin", "referer", + "set-cookie", "te", "trailer", "transfer-encoding", @@ -448,26 +474,11 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool { false } -// https://fetch.spec.whatwg.org/#forbidden-response-header-name +/// <https://fetch.spec.whatwg.org/#forbidden-response-header-name> fn is_forbidden_response_header(name: &str) -> bool { - matches!(name, "set-cookie" | "set-cookie2") -} - -// There is some unresolved confusion over the definition of a name and a value. -// -// As of December 2019, WHATWG has no formal grammar production for value; -// https://fetch.spec.whatg.org/#concept-header-value just says not to have -// newlines, nulls, or leading/trailing whitespace. It even allows -// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this. -// The HeaderValue class does not fully reflect this, so headers -// containing bytes with values 1..31 or 127 can't be created, failing -// WPT tests but probably not affecting anything important on the real Internet. -fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec<u8>)> { - let valid_name = validate_name(name)?; - if !is_legal_header_value(&value) { - return Err(Error::Type("Header value is not valid".to_string())); - } - Ok((valid_name, value.into())) + // A forbidden response-header name is a header name that is a byte-case-insensitive match for one of + let name = name.to_ascii_lowercase(); + matches!(name.as_str(), "set-cookie" | "set-cookie2") } fn validate_name(name: ByteString) -> Fallible<String> { @@ -480,47 +491,20 @@ fn validate_name(name: ByteString) -> Fallible<String> { } } -// Removes trailing and leading HTTP whitespace bytes. -// https://fetch.spec.whatwg.org/#concept-header-value-normalize -pub fn normalize_value(value: ByteString) -> ByteString { - match ( - index_of_first_non_whitespace(&value), - index_of_last_non_whitespace(&value), - ) { - (Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()), - _ => ByteString::new(vec![]), - } -} - -fn is_http_whitespace(byte: u8) -> bool { - byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' ' -} - -fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> { - for (index, &byte) in value.iter().enumerate() { - if !is_http_whitespace(byte) { - return Some(index); - } - } - None -} - -fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> { - for (index, &byte) in value.iter().enumerate().rev() { - if !is_http_whitespace(byte) { - return Some(index); - } - } - None -} - -// http://tools.ietf.org/html/rfc7230#section-3.2 +/// <http://tools.ietf.org/html/rfc7230#section-3.2> fn is_field_name(name: &ByteString) -> bool { is_token(name) } -// https://fetch.spec.whatg.org/#concept-header-value -fn is_legal_header_value(value: &ByteString) -> bool { +// As of December 2019, WHATWG has no formal grammar production for value; +// https://fetch.spec.whatg.org/#concept-header-value just says not to have +// newlines, nulls, or leading/trailing whitespace. It even allows +// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this. +// The HeaderValue class does not fully reflect this, so headers +// containing bytes with values 1..31 or 127 can't be created, failing +// WPT tests but probably not affecting anything important on the real Internet. +/// <https://fetch.spec.whatg.org/#concept-header-value> +fn is_legal_header_value(value: &[u8]) -> bool { let value_len = value.len(); if value_len == 0 { return true; @@ -533,7 +517,7 @@ fn is_legal_header_value(value: &ByteString) -> bool { b' ' | b'\t' => return false, _ => {}, }; - for &ch in &value[..] { + for &ch in value { match ch { b'\0' | b'\n' | b'\r' => return false, _ => {}, @@ -555,81 +539,12 @@ fn is_legal_header_value(value: &ByteString) -> bool { // } } -// https://tools.ietf.org/html/rfc5234#appendix-B.1 +/// <https://tools.ietf.org/html/rfc5234#appendix-B.1> pub(crate) fn is_vchar(x: u8) -> bool { matches!(x, 0x21..=0x7E) } -// http://tools.ietf.org/html/rfc7230#section-3.2.6 +/// <http://tools.ietf.org/html/rfc7230#section-3.2.6> pub(crate) fn is_obs_text(x: u8) -> bool { matches!(x, 0x80..=0xFF) } - -// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type -// This function uses data_url::Mime to parse the MIME Type because -// mime::Mime does not provide a parser following the Fetch spec -// see https://github.com/hyperium/mime/issues/106 -pub(crate) fn extract_mime_type(headers: &HyperHeaders) -> Option<Vec<u8>> { - let mut charset: Option<String> = None; - let mut essence: String = "".to_string(); - let mut mime_type: Option<DataUrlMime> = None; - - // Step 4 - let headers_values = headers.get_all(http::header::CONTENT_TYPE).iter(); - - // Step 5 - if headers_values.size_hint() == (0, Some(0)) { - return None; - } - - // Step 6 - for header_value in headers_values { - // Step 6.1 - match DataUrlMime::from_str(header_value.to_str().unwrap_or("")) { - // Step 6.2 - Err(_) => continue, - Ok(temp_mime) => { - let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype); - - // Step 6.2 - if temp_essence == "*/*" { - continue; - } - - let temp_charset = &temp_mime.get_parameter("charset"); - - // Step 6.3 - mime_type = Some(DataUrlMime { - type_: temp_mime.type_.to_string(), - subtype: temp_mime.subtype.to_string(), - parameters: temp_mime.parameters.clone(), - }); - - // Step 6.4 - if temp_essence != essence { - charset = temp_charset.map(|c| c.to_string()); - temp_essence.clone_into(&mut essence); - } else { - // Step 6.5 - if temp_charset.is_none() && charset.is_some() { - let DataUrlMime { - type_: t, - subtype: st, - parameters: p, - } = mime_type.unwrap(); - let mut params = p; - params.push(("charset".to_string(), charset.clone().unwrap())); - mime_type = Some(DataUrlMime { - type_: t.to_string(), - subtype: st.to_string(), - parameters: params, - }) - } - } - }, - } - } - - // Step 7, 8 - mime_type.map(|m| format!("{}", m).into_bytes()) -} diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 27da9f2b537..499e91c127b 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -27,6 +27,7 @@ use servo_media::streams::registry::MediaStreamId; use snapshot::Snapshot; use style::attr::AttrValue; +use super::node::NodeDamage; pub(crate) use crate::canvas_context::*; use crate::conversions::Convert; use crate::dom::attr::Attr; @@ -687,8 +688,11 @@ impl VirtualMethods for HTMLCanvasElement { .unwrap() .attribute_mutated(attr, mutation, can_gc); match attr.local_name() { - &local_name!("width") | &local_name!("height") => self.recreate_contexts_after_resize(), - _ => (), + &local_name!("width") | &local_name!("height") => { + self.recreate_contexts_after_resize(); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + }, + _ => {}, }; } diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs index 3c78ba67772..c6a3e2227f5 100644 --- a/components/script/dom/htmlinputelement.rs +++ b/components/script/dom/htmlinputelement.rs @@ -520,6 +520,7 @@ impl HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } fn does_minmaxlength_apply(&self) -> bool { @@ -2668,6 +2669,7 @@ impl VirtualMethods for HTMLInputElement { let mut value = textinput.single_line_content().clone(); self.sanitize_value(&mut value); textinput.set_content(value); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); // Steps 7-9 if !previously_selectable && self.selection_api_applies() { @@ -2695,6 +2697,8 @@ impl VirtualMethods for HTMLInputElement { self.sanitize_value(&mut value); self.textinput.borrow_mut().set_content(value); self.update_placeholder_shown_state(); + + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); }, local_name!("name") if self.input_type() == InputType::Radio => { self.radio_group_updated( diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 391da272ef3..d1791620592 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -1369,7 +1369,6 @@ impl HTMLMediaElement { .lock() .unwrap() .render_poster_frame(image); - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); if pref!(media_testing_enabled) { self.owner_global() @@ -1618,7 +1617,6 @@ impl HTMLMediaElement { // TODO: 6. Abort the overall resource selection algorithm. }, PlayerEvent::VideoFrameUpdated => { - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); // Check if the frame was resized if let Some(frame) = self.video_renderer.lock().unwrap().current_frame { self.handle_resize(Some(frame.width as u32), Some(frame.height as u32)); @@ -2017,12 +2015,12 @@ impl HTMLMediaElement { pub(crate) fn clear_current_frame_data(&self) { self.handle_resize(None, None); self.video_renderer.lock().unwrap().current_frame = None; - self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } fn handle_resize(&self, width: Option<u32>, height: Option<u32>) { if let Some(video_elem) = self.downcast::<HTMLVideoElement>() { video_elem.resize(width, height); + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); } } diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs index 8b553923230..78f00132580 100644 --- a/components/script/dom/htmltablecellelement.rs +++ b/components/script/dom/htmltablecellelement.rs @@ -9,6 +9,9 @@ use style::attr::{AttrValue, LengthOrPercentageOrAuto}; use style::color::AbsoluteColor; use style::context::QuirksMode; +use super::attr::Attr; +use super::element::AttributeMutation; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods; use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods; use crate::dom::bindings::inheritance::Castable; @@ -174,6 +177,19 @@ impl VirtualMethods for HTMLTableCellElement { Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("colspan")) { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + if matches!(*attr.local_name(), local_name!("rowspan")) { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("colspan") => { diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs index 9e8eecf1147..70355f274fc 100644 --- a/components/script/dom/htmltablecolelement.rs +++ b/components/script/dom/htmltablecolelement.rs @@ -7,8 +7,10 @@ use html5ever::{LocalName, Prefix, local_name, ns}; use js::rust::HandleObject; use style::attr::{AttrValue, LengthOrPercentageOrAuto}; +use super::attr::Attr; use super::bindings::root::LayoutDom; -use super::element::Element; +use super::element::{AttributeMutation, Element}; +use super::node::NodeDamage; use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods; use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; @@ -93,6 +95,16 @@ impl VirtualMethods for HTMLTableColElement { Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods) } + fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) { + if let Some(super_type) = self.super_type() { + super_type.attribute_mutated(attr, mutation, can_gc); + } + + if matches!(*attr.local_name(), local_name!("span")) { + self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage); + } + } + fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue { match *local_name { local_name!("span") => { diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index d631a01e1e7..d2c1d853f86 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -24,7 +24,7 @@ use js::typedarray::ArrayBufferViewU8; use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy; use crate::dom::bindings::codegen::Bindings::ReadableStreamBinding::{ ReadableStreamGetReaderOptions, ReadableStreamMethods, ReadableStreamReaderMode, - StreamPipeOptions, + ReadableWritablePair, StreamPipeOptions, }; use script_bindings::str::DOMString; @@ -2006,6 +2006,50 @@ impl ReadableStreamMethods<crate::DomTypeHolder> for ReadableStream { can_gc, ) } + + /// <https://streams.spec.whatwg.org/#rs-pipe-through> + fn PipeThrough( + &self, + transform: &ReadableWritablePair, + options: &StreamPipeOptions, + realm: InRealm, + can_gc: CanGc, + ) -> Fallible<DomRoot<ReadableStream>> { + let global = self.global(); + let cx = GlobalScope::get_cx(); + + // If ! IsReadableStreamLocked(this) is true, throw a TypeError exception. + if self.is_locked() { + return Err(Error::Type("Source stream is locked".to_owned())); + } + + // If ! IsWritableStreamLocked(transform["writable"]) is true, throw a TypeError exception. + if transform.writable.is_locked() { + return Err(Error::Type("Destination stream is locked".to_owned())); + } + + // Let signal be options["signal"] if it exists, or undefined otherwise. + // TODO: implement AbortSignal. + + // Let promise be ! ReadableStreamPipeTo(this, transform["writable"], + // options["preventClose"], options["preventAbort"], options["preventCancel"], signal). + let promise = self.pipe_to( + cx, + &global, + &transform.writable, + options.preventAbort, + options.preventCancel, + options.preventClose, + realm, + can_gc, + ); + + // Set promise.[[PromiseIsHandled]] to true. + promise.set_promise_is_handled(); + + // Return transform["readable"]. + Ok(transform.readable.clone()) + } } #[allow(unsafe_code)] diff --git a/components/script/dom/transformstream.rs b/components/script/dom/transformstream.rs index 0251498980d..446bf71f172 100644 --- a/components/script/dom/transformstream.rs +++ b/components/script/dom/transformstream.rs @@ -8,7 +8,7 @@ use std::ptr::{self}; use std::rc::Rc; use base::id::{MessagePortId, MessagePortIndex}; -use constellation_traits::MessagePortImpl; +use constellation_traits::TransformStreamData; use dom_struct::dom_struct; use js::jsapi::{Heap, IsPromiseObject, JSObject}; use js::jsval::{JSVal, ObjectValue, UndefinedValue}; @@ -1007,9 +1007,9 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream { /// <https://streams.spec.whatwg.org/#ts-transfer> impl Transferable for TransformStream { type Index = MessagePortIndex; - type Data = MessagePortImpl; + type Data = TransformStreamData; - fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { + fn transfer(&self) -> Result<(MessagePortId, TransformStreamData), ()> { let global = self.global(); let realm = enter_realm(&*global); let comp = InRealm::Entered(&realm); @@ -1023,73 +1023,85 @@ impl Transferable for TransformStream { let writable = self.get_writable(); // If ! IsReadableStreamLocked(readable) is true, throw a "DataCloneError" DOMException. - if readable.is_locked() { - return Err(()); - } - // If ! IsWritableStreamLocked(writable) is true, throw a "DataCloneError" DOMException. - if writable.is_locked() { + if readable.is_locked() || writable.is_locked() { return Err(()); } - // Create the shared port pair - let port_1 = MessagePort::new(&global, can_gc); - global.track_message_port(&port_1, None); - let port_2 = MessagePort::new(&global, can_gc); - global.track_message_port(&port_2, None); - global.entangle_ports(*port_1.message_port_id(), *port_2.message_port_id()); + // First port pair (readable → proxy writable) + let port1 = MessagePort::new(&global, can_gc); + global.track_message_port(&port1, None); + let port1_peer = MessagePort::new(&global, can_gc); + global.track_message_port(&port1_peer, None); + global.entangle_ports(*port1.message_port_id(), *port1_peer.message_port_id()); + + let proxy_readable = ReadableStream::new_with_proto(&global, None, can_gc); + proxy_readable.setup_cross_realm_transform_readable(cx, &port1, can_gc); + proxy_readable + .pipe_to(cx, &global, &writable, false, false, false, comp, can_gc) + .set_promise_is_handled(); + + // Second port pair (proxy readable → writable) + let port2 = MessagePort::new(&global, can_gc); + global.track_message_port(&port2, None); + let port2_peer = MessagePort::new(&global, can_gc); + global.track_message_port(&port2_peer, None); + global.entangle_ports(*port2.message_port_id(), *port2_peer.message_port_id()); - // Create a proxy WritableStream wired to port_1 let proxy_writable = WritableStream::new_with_proto(&global, None, can_gc); - proxy_writable.setup_cross_realm_transform_writable(cx, &port_1, can_gc); + proxy_writable.setup_cross_realm_transform_writable(cx, &port2, can_gc); // Pipe readable into the proxy writable (→ port_1) - let pipe1 = readable.pipe_to( - cx, - &global, - &proxy_writable, - false, - false, - false, - comp, - can_gc, - ); - pipe1.set_promise_is_handled(); - - // Create a proxy ReadableStream wired to port_1 - let proxy_readable = ReadableStream::new_with_proto(&global, None, can_gc); - proxy_readable.setup_cross_realm_transform_readable(cx, &port_1, can_gc); - - // Pipe proxy readable (← port_1) into writable - let pipe2 = - proxy_readable.pipe_to(cx, &global, &writable, false, false, false, comp, can_gc); - pipe2.set_promise_is_handled(); + readable + .pipe_to( + cx, + &global, + &proxy_writable, + false, + false, + false, + comp, + can_gc, + ) + .set_promise_is_handled(); // Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »). // Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »). - port_2.transfer() + Ok(( + *port1_peer.message_port_id(), + TransformStreamData { + readable: port1_peer.transfer()?, + writable: port2_peer.transfer()?, + }, + )) } fn transfer_receive( owner: &GlobalScope, - id: MessagePortId, - port_impl: MessagePortImpl, + _id: MessagePortId, + data: TransformStreamData, ) -> Result<DomRoot<Self>, ()> { let can_gc = CanGc::note(); + let cx = GlobalScope::get_cx(); + + let port1 = MessagePort::transfer_receive(owner, data.readable.0, data.readable.1)?; + let port2 = MessagePort::transfer_receive(owner, data.writable.0, data.writable.1)?; // Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm). // Set value.[[readable]] to readableRecord.[[Deserialized]]. - let readable = ReadableStream::transfer_receive(owner, id, port_impl.clone())?; - // Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm). - let writable = WritableStream::transfer_receive(owner, id, port_impl)?; + let proxy_readable = ReadableStream::new_with_proto(owner, None, can_gc); + proxy_readable.setup_cross_realm_transform_readable(cx, &port2, can_gc); + + let proxy_writable = WritableStream::new_with_proto(owner, None, can_gc); + proxy_writable.setup_cross_realm_transform_writable(cx, &port1, can_gc); // Set value.[[readable]] to readableRecord.[[Deserialized]]. // Set value.[[writable]] to writableRecord.[[Deserialized]]. // Set value.[[backpressure]], value.[[backpressureChangePromise]], and value.[[controller]] to undefined. let stream = TransformStream::new_with_proto(owner, None, can_gc); - stream.readable.set(Some(&readable)); - stream.writable.set(Some(&writable)); + stream.readable.set(Some(&proxy_readable)); + stream.writable.set(Some(&proxy_writable)); Ok(stream) } @@ -1098,8 +1110,8 @@ impl Transferable for TransformStream { data: StructuredData<'a, '_>, ) -> &'a mut Option<HashMap<MessagePortId, Self::Data>> { match data { - StructuredData::Reader(r) => &mut r.port_impls, - StructuredData::Writer(w) => &mut w.ports, + StructuredData::Reader(r) => &mut r.transform_streams_port_impls, + StructuredData::Writer(w) => &mut w.transform_streams_port, } } } diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs index 98170f9655b..b82051b3b12 100644 --- a/components/script/dom/webglrenderingcontext.rs +++ b/components/script/dom/webglrenderingcontext.rs @@ -619,7 +619,8 @@ impl WebGLRenderingContext { let size = Size2D::new(img.width, img.height); - TexPixels::new(img.bytes(), size, img.format, false) + let data = IpcSharedMemory::from_bytes(img.first_frame().bytes); + TexPixels::new(data, size, img.format, false) }, // TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D, // but we need to refactor it moving it to `HTMLCanvasElement` and support diff --git a/components/script/dom/webgpu/gpu.rs b/components/script/dom/webgpu/gpu.rs index 20380e07bfb..b2534cda9a8 100644 --- a/components/script/dom/webgpu/gpu.rs +++ b/components/script/dom/webgpu/gpu.rs @@ -86,8 +86,12 @@ impl GPUMethods<crate::DomTypeHolder> for GPU { /// <https://gpuweb.github.io/gpuweb/#dom-gpu-getpreferredcanvasformat> fn GetPreferredCanvasFormat(&self) -> GPUTextureFormat { - // TODO: real implementation - GPUTextureFormat::Rgba8unorm + // From https://github.com/mozilla-firefox/firefox/blob/24d49101ce17b78c3ba1217d00297fe2891be6b3/dom/webgpu/Instance.h#L68 + if cfg!(target_os = "android") { + GPUTextureFormat::Rgba8unorm + } else { + GPUTextureFormat::Bgra8unorm + } } /// <https://www.w3.org/TR/webgpu/#dom-gpu-wgsllanguagefeatures> diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index ca5bb72a1dc..4e7c136f42b 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -26,6 +26,7 @@ use js::jsval::{JSVal, NullValue}; use js::rust::wrappers::JS_ParseJSON; use js::rust::{HandleObject, MutableHandleValue}; use js::typedarray::{ArrayBuffer, ArrayBufferU8}; +use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime; use net_traits::http_status::HttpStatus; use net_traits::request::{CredentialsMode, Referrer, RequestBuilder, RequestId, RequestMode}; use net_traits::{ @@ -59,7 +60,7 @@ use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLD use crate::dom::event::{Event, EventBubbles, EventCancelable}; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; -use crate::dom::headers::{extract_mime_type, is_forbidden_request_header}; +use crate::dom::headers::is_forbidden_request_header; use crate::dom::node::Node; use crate::dom::performanceresourcetiming::InitiatorType; use crate::dom::progressevent::ProgressEvent; @@ -1324,11 +1325,7 @@ impl XMLHttpRequest { return response; } // Step 2 - let mime = self - .final_mime_type() - .as_ref() - .map(|m| normalize_type_string(&m.to_string())) - .unwrap_or("".to_owned()); + let mime = normalize_type_string(&self.final_mime_type().to_string()); // Step 3, 4 let bytes = self.response.borrow().to_vec(); @@ -1366,64 +1363,77 @@ impl XMLHttpRequest { return response; } - // Step 1 + // Step 1: If xhr’s response’s body is null, then return. if self.response_status.get().is_err() { return None; } - // Step 2 - let mime_type = self.final_mime_type(); - // Step 5.3, 7 - let charset = self.final_charset().unwrap_or(UTF_8); - let temp_doc: DomRoot<Document>; - match mime_type { - Some(ref mime) if mime.matches(TEXT, HTML) => { - // Step 4 - if self.response_type.get() == XMLHttpRequestResponseType::_empty { - return None; - } else { - // TODO Step 5.2 "If charset is null, prescan the first 1024 bytes of xhr’s received bytes" - // Step 5 - temp_doc = self.document_text_html(can_gc); - } - }, - // Step 7 - None => { - temp_doc = self.handle_xml(can_gc); - // Not sure it the parser should throw an error for this case - // The specification does not indicates this test, - // but for now we check the document has no child nodes - let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); - if has_no_child_nodes { - return None; - } - }, - Some(ref mime) - if mime.matches(TEXT, XML) || - mime.matches(APPLICATION, XML) || - mime.has_suffix(XML) => - { - temp_doc = self.handle_xml(can_gc); - // Not sure it the parser should throw an error for this case - // The specification does not indicates this test, - // but for now we check the document has no child nodes - let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); - if has_no_child_nodes { - return None; - } - }, - // Step 3 - _ => { + // Step 2: Let finalMIME be the result of get a final MIME type for xhr. + let final_mime = self.final_mime_type(); + + // Step 3: If finalMIME is not an HTML MIME type or an XML MIME type, then return. + let is_xml_mime_type = final_mime.matches(TEXT, XML) || + final_mime.matches(APPLICATION, XML) || + final_mime.has_suffix(XML); + if !final_mime.matches(TEXT, HTML) && !is_xml_mime_type { + return None; + } + + // Step 4: If xhr’s response type is the empty string and finalMIME is an HTML MIME + // type, then return. + let charset; + let temp_doc; + if final_mime.matches(TEXT, HTML) { + if self.response_type.get() == XMLHttpRequestResponseType::_empty { return None; - }, + } + + // Step 5: If finalMIME is an HTML MIME type, then: + // Step 5.1: Let charset be the result of get a final encoding for xhr. + // Step 5.2: If charset is null, prescan the first 1024 bytes of xhr’s received bytes + // and if that does not terminate unsuccessfully then let charset be the return value. + // TODO: This isn't happening right now. + // Step 5.3. If charset is null, then set charset to UTF-8. + charset = Some(self.final_charset().unwrap_or(UTF_8)); + + // Step 5.4: Let document be a document that represents the result parsing xhr’s + // received bytes following the rules set forth in the HTML Standard for an HTML parser + // with scripting disabled and a known definite encoding charset. [HTML] + temp_doc = self.document_text_html(can_gc); + } else { + assert!(is_xml_mime_type); + + // Step 6: Otherwise, let document be a document that represents the result of running + // the XML parser with XML scripting support disabled on xhr’s received bytes. If that + // fails (unsupported character encoding, namespace well-formedness error, etc.), then + // return null. [HTML] + // + // TODO: The spec seems to suggest the charset should come from the XML parser here. + temp_doc = self.handle_xml(can_gc); + charset = self.final_charset(); + + // Not sure it the parser should throw an error for this case + // The specification does not indicates this test, + // but for now we check the document has no child nodes + let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none(); + if has_no_child_nodes { + return None; + } } - // Step 8 + + // Step 7: If charset is null, then set charset to UTF-8. + let charset = charset.unwrap_or(UTF_8); + + // Step 8: Set document’s encoding to charset. temp_doc.set_encoding(charset); - // Step 9 to 11 - // Done by handle_text_html and handle_xml + // Step 9: Set document’s content type to finalMIME. + // Step 10: Set document’s URL to xhr’s response’s URL. + // Step 11: Set document’s origin to xhr’s relevant settings object’s origin. + // + // Done by `handle_text_html()` and `handle_xml()`. - // Step 12 + // Step 12: Set xhr’s response object to document. self.response_xml.set(Some(&temp_doc)); self.response_xml.get() } @@ -1507,7 +1517,7 @@ impl XMLHttpRequest { Ok(parsed) => Some(parsed), Err(_) => None, // Step 7 }; - let content_type = self.final_mime_type(); + let content_type = Some(self.final_mime_type()); Document::new( win, HasBrowsingContext::No, @@ -1598,14 +1608,16 @@ impl XMLHttpRequest { // 3. If responseMIME’s parameters["charset"] exists, then set label to it. let response_charset = self .response_mime_type() - .and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string())); + .get_parameter(CHARSET) + .map(ToString::to_string); // 4. If xhr’s override MIME type’s parameters["charset"] exists, then set label to it. let override_charset = self .override_mime_type .borrow() .as_ref() - .and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string())); + .and_then(|mime| mime.get_parameter(CHARSET)) + .map(ToString::to_string); // 5. If label is null, then return null. // 6. Let encoding be the result of getting an encoding from label. @@ -1617,23 +1629,22 @@ impl XMLHttpRequest { } /// <https://xhr.spec.whatwg.org/#response-mime-type> - fn response_mime_type(&self) -> Option<Mime> { - return extract_mime_type(&self.response_headers.borrow()) - .and_then(|mime_as_bytes| { - String::from_utf8(mime_as_bytes) - .unwrap_or_default() - .parse() - .ok() - }) - .or(Some(Mime::new(TEXT, XML))); + fn response_mime_type(&self) -> Mime { + // 1. Let mimeType be the result of extracting a MIME type from xhr’s response’s + // header list. + // 2. If mimeType is failure, then set mimeType to text/xml. + // 3. Return mimeType. + extract_mime_type_as_dataurl_mime(&self.response_headers.borrow()) + .unwrap_or_else(|| Mime::new(TEXT, XML)) } /// <https://xhr.spec.whatwg.org/#final-mime-type> - fn final_mime_type(&self) -> Option<Mime> { - match *self.override_mime_type.borrow() { - Some(ref override_mime) => Some(override_mime.clone()), - None => self.response_mime_type(), - } + fn final_mime_type(&self) -> Mime { + self.override_mime_type + .borrow() + .as_ref() + .map(MimeExt::clone) + .unwrap_or_else(|| self.response_mime_type()) } } diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs index 4815e6feae1..3ee5bfbd662 100644 --- a/components/script/script_thread.rs +++ b/components/script/script_thread.rs @@ -1081,6 +1081,10 @@ impl ScriptThread { for event in document.take_pending_input_events().into_iter() { document.update_active_keyboard_modifiers(event.active_keyboard_modifiers); + // We do this now, because the event is consumed below, but the order doesn't really + // matter as the event will be handled before any new ScriptThread messages are processed. + self.notify_webdriver_input_event_completed(pipeline_id, &event.event); + match event.event { InputEvent::MouseButton(mouse_button_event) => { document.handle_mouse_button_event( @@ -1144,6 +1148,19 @@ impl ScriptThread { ScriptThread::set_user_interacting(false); } + fn notify_webdriver_input_event_completed(&self, pipeline_id: PipelineId, event: &InputEvent) { + let Some(id) = event.webdriver_message_id() else { + return; + }; + + if let Err(error) = self.senders.pipeline_to_constellation_sender.send(( + pipeline_id, + ScriptToConstellationMessage::WebDriverInputComplete(id), + )) { + warn!("ScriptThread failed to send WebDriverInputComplete {id:?}: {error:?}",); + } + } + /// <https://html.spec.whatwg.org/multipage/#update-the-rendering> /// /// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually @@ -3420,7 +3437,7 @@ impl ScriptThread { // the pointer, when the user presses down and releases the primary pointer button" // Servo-specific: Trigger if within 10px of the down point - if let InputEvent::MouseButton(mouse_button_event) = event.event { + if let InputEvent::MouseButton(mouse_button_event) = &event.event { if let MouseButton::Left = mouse_button_event.button { match mouse_button_event.action { MouseButtonAction::Up => { @@ -3429,16 +3446,23 @@ impl ScriptThread { let pixel_dist = (pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y).sqrt(); if pixel_dist < 10.0 * document.window().device_pixel_ratio().get() { - document.note_pending_input_event(event.clone()); + // Pass webdriver_id to the newly generated click event + document.note_pending_input_event(ConstellationInputEvent { + hit_test_result: event.hit_test_result.clone(), + pressed_mouse_buttons: event.pressed_mouse_buttons, + active_keyboard_modifiers: event.active_keyboard_modifiers, + event: event.event.clone().with_webdriver_message_id(None), + }); document.note_pending_input_event(ConstellationInputEvent { hit_test_result: event.hit_test_result, pressed_mouse_buttons: event.pressed_mouse_buttons, active_keyboard_modifiers: event.active_keyboard_modifiers, - event: InputEvent::MouseButton(MouseButtonEvent { - action: MouseButtonAction::Click, - button: mouse_button_event.button, - point: mouse_button_event.point, - }), + event: InputEvent::MouseButton(MouseButtonEvent::new( + MouseButtonAction::Click, + mouse_button_event.button, + mouse_button_event.point, + )) + .with_webdriver_message_id(event.event.webdriver_message_id()), }); return; } diff --git a/components/script/stylesheet_loader.rs b/components/script/stylesheet_loader.rs index a18d63e323b..5efaf78e542 100644 --- a/components/script/stylesheet_loader.rs +++ b/components/script/stylesheet_loader.rs @@ -163,7 +163,8 @@ impl FetchResponseListener for StylesheetContext { Some(meta) => meta, None => return, }; - let is_css = metadata.content_type.is_some_and(|ct| { + + let mut is_css = metadata.content_type.is_some_and(|ct| { let mime: Mime = ct.into_inner().into(); mime.type_() == mime::TEXT && mime.subtype() == mime::CSS }) || ( @@ -177,6 +178,17 @@ impl FetchResponseListener for StylesheetContext { document.origin().immutable().clone() == metadata.final_url.origin() ); + // From <https://html.spec.whatwg.org/multipage/#link-type-stylesheet>: + // > Quirk: If the document has been set to quirks mode, has the same origin as + // > the URL of the external resource, and the Content-Type metadata of the + // > external resource is not a supported style sheet type, the user agent must + // > instead assume it to be text/css. + if document.quirks_mode() == QuirksMode::Quirks && + document.url().origin() == self.url.origin() + { + is_css = true; + } + let data = if is_css { let data = std::mem::take(&mut self.data); self.unminify_css(data, metadata.final_url.clone()) diff --git a/components/script/test.rs b/components/script/test.rs index c5933696efc..35e05621125 100644 --- a/components/script/test.rs +++ b/components/script/test.rs @@ -7,7 +7,6 @@ pub use crate::dom::bindings::refcounted::TrustedPromise; //pub use crate::dom::bindings::root::Dom; pub use crate::dom::bindings::str::{ByteString, DOMString}; -pub use crate::dom::headers::normalize_value; //pub use crate::dom::node::Node; pub mod area { |