diff options
Diffstat (limited to 'components/script/dom')
44 files changed, 2233 insertions, 283 deletions
diff --git a/components/script/dom/bindings/buffer_source.rs b/components/script/dom/bindings/buffer_source.rs index dd6984e1eab..14a71532e9d 100644 --- a/components/script/dom/bindings/buffer_source.rs +++ b/components/script/dom/bindings/buffer_source.rs @@ -37,7 +37,7 @@ use js::rust::{ #[cfg(feature = "webgpu")] use js::typedarray::{ArrayBuffer, HeapArrayBuffer}; use js::typedarray::{ - ArrayBufferU8, ArrayBufferView, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, + ArrayBufferU8, ArrayBufferViewU8, CreateWith, TypedArray, TypedArrayElement, TypedArrayElementCreator, }; @@ -63,36 +63,25 @@ pub(crate) enum BufferSource { ArrayBuffer(Box<Heap<*mut JSObject>>), } -pub(crate) fn new_initialized_heap_buffer_source<T>( - init: HeapTypedArrayInit, +pub(crate) fn create_heap_buffer_source_with_length<T>( + cx: JSContext, + len: u32, can_gc: CanGc, -) -> Result<HeapBufferSource<T>, ()> +) -> Fallible<HeapBufferSource<T>> where T: TypedArrayElement + TypedArrayElementCreator, T::Element: Clone + Copy, { - let heap_buffer_source = match init { - HeapTypedArrayInit::Buffer(buffer_source) => HeapBufferSource { - buffer_source, - phantom: PhantomData, - }, - HeapTypedArrayInit::Info { len, cx } => { - rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); - let typed_array_result = - create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut(), can_gc); - if typed_array_result.is_err() { - return Err(()); - } - - HeapBufferSource::<T>::new(BufferSource::ArrayBufferView(Heap::boxed(*array.handle()))) - }, - }; - Ok(heap_buffer_source) -} + rooted!(in (*cx) let mut array = ptr::null_mut::<JSObject>()); + let typed_array_result = + create_buffer_source_with_length::<T>(cx, len as usize, array.handle_mut(), can_gc); + if typed_array_result.is_err() { + return Err(Error::JSFailed); + } -pub(crate) enum HeapTypedArrayInit { - Buffer(BufferSource), - Info { len: u32, cx: JSContext }, + Ok(HeapBufferSource::<T>::new(BufferSource::ArrayBufferView( + Heap::boxed(*array.handle()), + ))) } pub(crate) struct HeapBufferSource<T> { @@ -131,11 +120,11 @@ where } pub(crate) fn from_view( - chunk: CustomAutoRooterGuard<ArrayBufferView>, - ) -> HeapBufferSource<ArrayBufferViewU8> { - HeapBufferSource::<ArrayBufferViewU8>::new(BufferSource::ArrayBufferView(Heap::boxed( - unsafe { *chunk.underlying_object() }, - ))) + chunk: CustomAutoRooterGuard<TypedArray<T, *mut JSObject>>, + ) -> HeapBufferSource<T> { + HeapBufferSource::<T>::new(BufferSource::ArrayBufferView(Heap::boxed(unsafe { + *chunk.underlying_object() + }))) } pub(crate) fn default() -> Self { diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs index c9a49ba00c9..70638238123 100644 --- a/components/script/dom/bindings/structuredclone.rs +++ b/components/script/dom/bindings/structuredclone.rs @@ -44,7 +44,7 @@ use crate::dom::dompointreadonly::DOMPointReadOnly; use crate::dom::globalscope::GlobalScope; use crate::dom::messageport::MessagePort; use crate::dom::readablestream::ReadableStream; -use crate::dom::types::DOMException; +use crate::dom::types::{DOMException, TransformStream}; use crate::dom::writablestream::WritableStream; use crate::realms::{AlreadyInRealm, InRealm, enter_realm}; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; @@ -65,6 +65,7 @@ pub(super) enum StructuredCloneTags { ReadableStream = 0xFFFF8006, DomException = 0xFFFF8007, WritableStream = 0xFFFF8008, + TransformStream = 0xFFFF8009, Max = 0xFFFFFFFF, } @@ -85,6 +86,7 @@ impl From<TransferrableInterface> for StructuredCloneTags { TransferrableInterface::MessagePort => StructuredCloneTags::MessagePort, TransferrableInterface::ReadableStream => StructuredCloneTags::ReadableStream, TransferrableInterface::WritableStream => StructuredCloneTags::WritableStream, + TransferrableInterface::TransformStream => StructuredCloneTags::TransformStream, } } } @@ -265,6 +267,7 @@ fn receiver_for_type( TransferrableInterface::MessagePort => receive_object::<MessagePort>, TransferrableInterface::ReadableStream => receive_object::<ReadableStream>, TransferrableInterface::WritableStream => receive_object::<WritableStream>, + TransferrableInterface::TransformStream => receive_object::<TransformStream>, } } @@ -390,6 +393,7 @@ fn transfer_for_type(val: TransferrableInterface) -> TransferOperation { TransferrableInterface::MessagePort => try_transfer::<MessagePort>, TransferrableInterface::ReadableStream => try_transfer::<ReadableStream>, TransferrableInterface::WritableStream => try_transfer::<WritableStream>, + TransferrableInterface::TransformStream => try_transfer::<TransformStream>, } } @@ -438,6 +442,7 @@ unsafe fn can_transfer_for_type( TransferrableInterface::MessagePort => can_transfer::<MessagePort>(obj, cx), TransferrableInterface::ReadableStream => can_transfer::<ReadableStream>(obj, cx), TransferrableInterface::WritableStream => can_transfer::<WritableStream>(obj, cx), + TransferrableInterface::TransformStream => can_transfer::<TransformStream>(obj, cx), } } diff --git a/components/script/dom/clipboard.rs b/components/script/dom/clipboard.rs index 94c8b3c0f19..25878a1b29b 100644 --- a/components/script/dom/clipboard.rs +++ b/components/script/dom/clipboard.rs @@ -3,24 +3,75 @@ * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::rc::Rc; +use std::str::FromStr; use constellation_traits::BlobImpl; +use data_url::mime::Mime; use dom_struct::dom_struct; use embedder_traits::EmbedderMsg; +use js::rust::HandleValue as SafeHandleValue; use crate::dom::bindings::codegen::Bindings::ClipboardBinding::{ ClipboardMethods, PresentationStyle, }; +use crate::dom::bindings::error::Error; use crate::dom::bindings::refcounted::TrustedPromise; use crate::dom::bindings::reflector::{DomGlobal, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::DOMString; use crate::dom::blob::Blob; +use crate::dom::clipboarditem::Representation; use crate::dom::eventtarget::EventTarget; use crate::dom::globalscope::GlobalScope; use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; use crate::dom::window::Window; -use crate::script_runtime::CanGc; +use crate::realms::{InRealm, enter_realm}; +use crate::routed_promise::{RoutedPromiseListener, route_promise}; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +/// The fulfillment handler for the reacting to representationDataPromise part of +/// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseFulfillmentHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc<Promise>, +} + +impl Callback for RepresentationDataPromiseFulfillmentHandler { + /// The fulfillment case of Step 3.4.1.1.4.3 of + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If v is a DOMString, then follow the below steps: + // Resolve p with v. + // Return p. + self.promise.resolve(cx, v, can_gc); + + // NOTE: Since we ask text from arboard, v can't be a Blob + // If v is a Blob, then follow the below steps: + // Let string be the result of UTF-8 decoding v’s underlying byte sequence. + // Resolve p with string. + // Return p. + } +} + +/// The rejection handler for the reacting to representationDataPromise part of +/// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. +#[derive(Clone, JSTraceable, MallocSizeOf)] +struct RepresentationDataPromiseRejectionHandler { + #[ignore_malloc_size_of = "Rc are hard"] + promise: Rc<Promise>, +} + +impl Callback for RepresentationDataPromiseRejectionHandler { + /// The rejection case of Step 3.4.1.1.4.3 of + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext>. + fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Reject p with "NotFoundError" DOMException in realm. + // Return p. + self.promise.reject_error(Error::NotFound, can_gc); + } +} #[dom_struct] pub(crate) struct Clipboard { @@ -40,6 +91,34 @@ impl Clipboard { } impl ClipboardMethods<crate::DomTypeHolder> for Clipboard { + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-readtext> + fn ReadText(&self, can_gc: CanGc) -> Rc<Promise> { + // Step 1 Let realm be this's relevant realm. + let global = self.global(); + + // Step 2 Let p be a new promise in realm. + let p = Promise::new(&global, can_gc); + + // Step 3 Run the following steps in parallel: + + // TODO Step 3.1 Let r be the result of running check clipboard read permission. + // Step 3.2 If r is false, then: + // Step 3.2.1 Queue a global task on the permission task source, given realm’s global object, + // to reject p with "NotAllowedError" DOMException in realm. + // Step 3.2.2 Abort these steps. + + // Step 3.3 Let data be a copy of the system clipboard data. + let window = global.as_window(); + let sender = route_promise(&p, self, global.task_manager().clipboard_task_source()); + window.send_to_embedder(EmbedderMsg::GetClipboardText(window.webview_id(), sender)); + + // Step 3.4 Queue a global task on the clipboard task source, + // given realm’s global object, to perform the below steps: + // NOTE: We queue the task inside route_promise and perform the steps inside handle_response + + p + } + /// <https://w3c.github.io/clipboard-apis/#dom-clipboard-writetext> fn WriteText(&self, data: DOMString, can_gc: CanGc) -> Rc<Promise> { // Step 1 Let realm be this's relevant realm. @@ -95,6 +174,71 @@ impl ClipboardMethods<crate::DomTypeHolder> for Clipboard { } } +impl RoutedPromiseListener<Result<String, String>> for Clipboard { + fn handle_response( + &self, + response: Result<String, String>, + promise: &Rc<Promise>, + can_gc: CanGc, + ) { + let global = self.global(); + let text = response.unwrap_or_default(); + + // Step 3.4.1 For each systemClipboardItem in data: + // Step 3.4.1.1 For each systemClipboardRepresentation in systemClipboardItem: + // TODO: Arboard provide the first item that has a String representation + + // Step 3.4.1.1.1 Let mimeType be the result of running the + // well-known mime type from os specific format algorithm given systemClipboardRepresentation’s name. + // Note: This is done by arboard, so we just convert the format to a MIME + let mime_type = Mime::from_str("text/plain").unwrap(); + + // Step 3.4.1.1.2 If mimeType is null, continue this loop. + // Note: Since the previous step is infallible, we don't need to handle this case + + // Step 3.4.1.1.3 Let representation be a new representation. + let representation = Representation { + mime_type, + is_custom: false, + data: Promise::new_resolved( + &global, + GlobalScope::get_cx(), + DOMString::from(text), + can_gc, + ), + }; + + // Step 3.4.1.1.4 If representation’s MIME type essence is "text/plain", then: + + // Step 3.4.1.1.4.1 Set representation’s MIME type to mimeType. + // Note: Done when creating a new representation + + // Step 3.4.1.1.4.2 Let representationDataPromise be the representation’s data. + // Step 3.4.1.1.4.3 React to representationDataPromise: + let fulfillment_handler = Box::new(RepresentationDataPromiseFulfillmentHandler { + promise: promise.clone(), + }); + let rejection_handler = Box::new(RepresentationDataPromiseRejectionHandler { + promise: promise.clone(), + }); + let handler = PromiseNativeHandler::new( + &global, + Some(fulfillment_handler), + Some(rejection_handler), + can_gc, + ); + let realm = enter_realm(&*global); + let comp = InRealm::Entered(&realm); + representation + .data + .append_native_handler(&handler, comp, can_gc); + + // Step 3.4.2 Reject p with "NotFoundError" DOMException in realm. + // Step 3.4.3 Return p. + // NOTE: We follow the same behaviour of Gecko by doing nothing if no text is available instead of rejecting p + } +} + /// <https://w3c.github.io/clipboard-apis/#write-blobs-and-option-to-the-clipboard> fn write_blobs_and_option_to_the_clipboard( window: &Window, diff --git a/components/script/dom/clipboarditem.rs b/components/script/dom/clipboarditem.rs index c1c66a403b3..129beb686c1 100644 --- a/components/script/dom/clipboarditem.rs +++ b/components/script/dom/clipboarditem.rs @@ -29,13 +29,13 @@ const CUSTOM_FORMAT_PREFIX: &str = "web "; /// <https://w3c.github.io/clipboard-apis/#representation> #[derive(JSTraceable, MallocSizeOf)] -struct Representation { +pub(super) struct Representation { #[no_trace] #[ignore_malloc_size_of = "Extern type"] - mime_type: Mime, - is_custom: bool, + pub mime_type: Mime, + pub is_custom: bool, #[ignore_malloc_size_of = "Rc is hard"] - data: Rc<Promise>, + pub data: Rc<Promise>, } #[dom_struct] diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs index e3590461604..ad95b9b9a94 100644 --- a/components/script/dom/document.rs +++ b/components/script/dom/document.rs @@ -4313,15 +4313,13 @@ impl Document { }, Some(csp_list) => { let element = csp::Element { - nonce: el - .get_attribute(&ns!(), &local_name!("nonce")) - .map(|attr| Cow::Owned(attr.value().to_string())), + nonce: el.nonce_attribute_if_nonceable().map(Cow::Owned), }; csp_list.should_elements_inline_type_behavior_be_blocked(&element, type_, source) }, }; - self.global().report_csp_violations(violations); + self.global().report_csp_violations(violations, Some(el)); result } @@ -6569,9 +6567,6 @@ impl DocumentMethods<crate::DomTypeHolder> for Document { Ok(()) } - // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers - document_and_element_event_handlers!(); - // https://fullscreen.spec.whatwg.org/#handler-document-onfullscreenerror event_handler!(fullscreenerror, GetOnfullscreenerror, SetOnfullscreenerror); diff --git a/components/script/dom/element.rs b/components/script/dom/element.rs index 7f38e55fb14..4f1b957c12c 100644 --- a/components/script/dom/element.rs +++ b/components/script/dom/element.rs @@ -12,6 +12,7 @@ use std::rc::Rc; use std::str::FromStr; use std::{fmt, mem}; +use content_security_policy as csp; use cssparser::match_ignore_ascii_case; use devtools_traits::AttrInfo; use dom_struct::dom_struct; @@ -62,6 +63,7 @@ use xml5ever::serialize::TraversalScope::{ ChildrenOnly as XmlChildrenOnly, IncludeNode as XmlIncludeNode, }; +use crate::conversions::Convert; use crate::dom::activation::Activatable; use crate::dom::attr::{Attr, AttrHelpersForLayout}; use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut, ref_filter_map}; @@ -79,7 +81,9 @@ use crate::dom::bindings::codegen::Bindings::ShadowRootBinding::{ use crate::dom::bindings::codegen::Bindings::WindowBinding::{ ScrollBehavior, ScrollToOptions, WindowMethods, }; -use crate::dom::bindings::codegen::UnionTypes::{NodeOrString, TrustedScriptURLOrUSVString}; +use crate::dom::bindings::codegen::UnionTypes::{ + NodeOrString, TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString, TrustedScriptURLOrUSVString, +}; use crate::dom::bindings::conversions::DerivedFrom; use crate::dom::bindings::error::{Error, ErrorResult, Fallible}; use crate::dom::bindings::inheritance::{Castable, ElementTypeId, HTMLElementTypeId, NodeTypeId}; @@ -124,6 +128,7 @@ use crate::dom::htmllinkelement::HTMLLinkElement; use crate::dom::htmlobjectelement::HTMLObjectElement; use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmloutputelement::HTMLOutputElement; +use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::htmlselectelement::HTMLSelectElement; use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable}; use crate::dom::htmlstyleelement::HTMLStyleElement; @@ -150,6 +155,7 @@ use crate::dom::raredata::ElementRareData; use crate::dom::servoparser::ServoParser; use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot}; use crate::dom::text::Text; +use crate::dom::trustedhtml::TrustedHTML; use crate::dom::validation::Validatable; use crate::dom::validitystate::ValidationFlags; use crate::dom::virtualmethods::{VirtualMethods, vtable_for}; @@ -353,7 +359,7 @@ impl Element { if damage == NodeDamage::OtherNodeDamage { doc.note_node_with_dirty_descendants(self.upcast()); - restyle.damage = RestyleDamage::rebuild_and_reflow(); + restyle.damage = RestyleDamage::reconstruct(); } } @@ -2120,6 +2126,87 @@ impl Element { node.owner_doc().element_attr_will_change(self, attr); } + /// <https://html.spec.whatwg.org/multipage/#the-style-attribute> + fn update_style_attribute(&self, attr: &Attr, mutation: AttributeMutation) { + let doc = self.upcast::<Node>().owner_doc(); + // Modifying the `style` attribute might change style. + *self.style_attribute.borrow_mut() = match mutation { + AttributeMutation::Set(..) => { + // This is the fast path we use from + // CSSStyleDeclaration. + // + // Juggle a bit to keep the borrow checker happy + // while avoiding the extra clone. + let is_declaration = matches!(*attr.value(), AttrValue::Declaration(..)); + + let block = if is_declaration { + let mut value = AttrValue::String(String::new()); + attr.swap_value(&mut value); + let (serialization, block) = match value { + AttrValue::Declaration(s, b) => (s, b), + _ => unreachable!(), + }; + let mut value = AttrValue::String(serialization); + attr.swap_value(&mut value); + block + } else { + let win = self.owner_window(); + let source = &**attr.value(); + // However, if the Should element's inline behavior be blocked by + // Content Security Policy? algorithm returns "Blocked" when executed + // upon the attribute's element, "style attribute", and the attribute's value, + // then the style rules defined in the attribute's value must not be applied to the element. [CSP] + if doc.should_elements_inline_type_behavior_be_blocked( + self, + csp::InlineCheckType::StyleAttribute, + source, + ) == csp::CheckResult::Blocked + { + return; + } + Arc::new(doc.style_shared_lock().wrap(parse_style_attribute( + source, + &UrlExtraData(doc.base_url().get_arc()), + win.css_error_reporter(), + doc.quirks_mode(), + CssRuleType::Style, + ))) + }; + + Some(block) + }, + AttributeMutation::Removed => None, + }; + } + + /// <https://www.w3.org/TR/CSP/#is-element-nonceable> + pub(crate) fn nonce_attribute_if_nonceable(&self) -> Option<String> { + // Step 1: If element does not have an attribute named "nonce", return "Not Nonceable". + let nonce_attribute = self.get_attribute(&ns!(), &local_name!("nonce"))?; + // Step 2: If element is a script element, then for each attribute of element’s attribute list: + if self.downcast::<HTMLScriptElement>().is_some() { + for attr in self.attrs().iter() { + // Step 2.1: If attribute’s name contains an ASCII case-insensitive match + // for "<script" or "<style", return "Not Nonceable". + let attr_name = attr.name().to_ascii_lowercase(); + if attr_name.contains("<script") || attr_name.contains("<style") { + return None; + } + // Step 2.2: If attribute’s value contains an ASCII case-insensitive match + // for "<script" or "<style", return "Not Nonceable". + let attr_value = attr.value().to_ascii_lowercase(); + if attr_value.contains("<script") || attr_value.contains("<style") { + return None; + } + } + } + // Step 3: If element had a duplicate-attribute parse error during tokenization, return "Not Nonceable". + // TODO(https://github.com/servo/servo/issues/4577 and https://github.com/whatwg/html/issues/3257): + // Figure out how to retrieve this information from the parser + // Step 4: Return "Nonceable". + Some(nonce_attribute.value().to_string().trim().to_owned()) + } + // https://dom.spec.whatwg.org/#insert-adjacent pub(crate) fn insert_adjacent( &self, @@ -2239,18 +2326,25 @@ impl Element { Ok(fragment) } + /// Step 4 of <https://html.spec.whatwg.org/multipage/#dom-element-insertadjacenthtml> pub(crate) fn fragment_parsing_context( owner_doc: &Document, element: Option<&Self>, can_gc: CanGc, ) -> DomRoot<Self> { + // If context is not an Element or all of the following are true: match element { Some(elem) + // context's node document is an HTML document; + // context's local name is "html"; and + // context's namespace is the HTML namespace, if elem.local_name() != &local_name!("html") || !elem.html_element_in_html_document() => { DomRoot::from_ref(elem) }, + // set context to the result of creating an element + // given this's node document, "body", and the HTML namespace. _ => DomRoot::upcast(HTMLBodyElement::new( local_name!("body"), None, @@ -2363,6 +2457,13 @@ impl Element { Dom::from_ref(&*ElementInternals::new(elem, can_gc)) })) } + + pub(crate) fn outer_html(&self, can_gc: CanGc) -> Fallible<DOMString> { + match self.GetOuterHTML(can_gc)? { + TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(str) => Ok(str), + TrustedHTMLOrNullIsEmptyString::TrustedHTML(_) => unreachable!(), + } + } } impl ElementMethods<crate::DomTypeHolder> for Element { @@ -3017,7 +3118,17 @@ impl ElementMethods<crate::DomTypeHolder> for Element { } /// <https://html.spec.whatwg.org/multipage/#dom-element-sethtmlunsafe> - fn SetHTMLUnsafe(&self, html: DOMString, can_gc: CanGc) { + fn SetHTMLUnsafe(&self, html: TrustedHTMLOrString, can_gc: CanGc) -> ErrorResult { + // Step 1. Let compliantHTML be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, html, "Element setHTMLUnsafe", and "script". + let html = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + html, + "Element", + "setHTMLUnsafe", + can_gc, + )?); // Step 2. Let target be this's template contents if this is a template element; otherwise this. let target = if let Some(template) = self.downcast::<HTMLTemplateElement>() { DomRoot::upcast(template.Content(can_gc)) @@ -3027,6 +3138,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element { // Step 3. Unsafely set HTML given target, this, and compliantHTML Node::unsafely_set_html(&target, self, html, can_gc); + Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-element-gethtml> @@ -3042,7 +3154,7 @@ impl ElementMethods<crate::DomTypeHolder> for Element { } /// <https://html.spec.whatwg.org/multipage/#dom-element-innerhtml> - fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible<DOMString> { + fn GetInnerHTML(&self, can_gc: CanGc) -> Fallible<TrustedHTMLOrNullIsEmptyString> { let qname = QualName::new( self.prefix().clone(), self.namespace().clone(), @@ -3059,16 +3171,28 @@ impl ElementMethods<crate::DomTypeHolder> for Element { .xml_serialize(XmlChildrenOnly(Some(qname))) }; - Ok(result) + Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result)) } /// <https://html.spec.whatwg.org/multipage/#dom-element-innerhtml> - fn SetInnerHTML(&self, value: DOMString, can_gc: CanGc) -> ErrorResult { - // Step 2. + fn SetInnerHTML(&self, value: TrustedHTMLOrNullIsEmptyString, can_gc: CanGc) -> ErrorResult { + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, the given value, "Element innerHTML", and "script". + let value = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + value.convert(), + "Element", + "innerHTML", + can_gc, + )?); // https://github.com/w3c/DOM-Parsing/issues/1 let target = if let Some(template) = self.downcast::<HTMLTemplateElement>() { + // Step 4: If context is a template element, then set context to + // the template element's template contents (a DocumentFragment). DomRoot::upcast(template.Content(can_gc)) } else { + // Step 2: Let context be this. DomRoot::from_ref(self.upcast()) }; @@ -3085,15 +3209,17 @@ impl ElementMethods<crate::DomTypeHolder> for Element { return Ok(()); } - // Step 1. + // Step 3: Let fragment be the result of invoking the fragment parsing algorithm steps + // with context and compliantString. let frag = self.parse_fragment(value, can_gc)?; + // Step 5: Replace all with fragment within context. Node::replace_all(Some(frag.upcast()), &target, can_gc); Ok(()) } /// <https://html.spec.whatwg.org/multipage/#dom-element-outerhtml> - fn GetOuterHTML(&self, can_gc: CanGc) -> Fallible<DOMString> { + fn GetOuterHTML(&self, can_gc: CanGc) -> Fallible<TrustedHTMLOrNullIsEmptyString> { // FIXME: This should use the fragment serialization algorithm, which takes // care of distinguishing between html/xml documents let result = if self.owner_document().is_html_document() { @@ -3103,27 +3229,39 @@ impl ElementMethods<crate::DomTypeHolder> for Element { self.upcast::<Node>().xml_serialize(XmlIncludeNode) }; - Ok(result) + Ok(TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(result)) } /// <https://html.spec.whatwg.org/multipage/#dom-element-outerhtml> - fn SetOuterHTML(&self, value: DOMString, can_gc: CanGc) -> ErrorResult { + fn SetOuterHTML(&self, value: TrustedHTMLOrNullIsEmptyString, can_gc: CanGc) -> ErrorResult { + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, the given value, "Element outerHTML", and "script". + let value = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + value.convert(), + "Element", + "outerHTML", + can_gc, + )?); let context_document = self.owner_document(); let context_node = self.upcast::<Node>(); - // Step 1. + // Step 2: Let parent be this's parent. let context_parent = match context_node.GetParentNode() { None => { - // Step 2. + // Step 3: If parent is null, return. There would be no way to + // obtain a reference to the nodes created even if the remaining steps were run. return Ok(()); }, Some(parent) => parent, }; let parent = match context_parent.type_id() { - // Step 3. + // Step 4: If parent is a Document, throw a "NoModificationAllowedError" DOMException. NodeTypeId::Document(_) => return Err(Error::NoModificationAllowed), - // Step 4. + // Step 5: If parent is a DocumentFragment, set parent to the result of + // creating an element given this's node document, "body", and the HTML namespace. NodeTypeId::DocumentFragment(_) => { let body_elem = Element::create( QualName::new(None, ns!(html), local_name!("body")), @@ -3139,9 +3277,10 @@ impl ElementMethods<crate::DomTypeHolder> for Element { _ => context_node.GetParentElement().unwrap(), }; - // Step 5. + // Step 6: Let fragment be the result of invoking the + // fragment parsing algorithm steps given parent and compliantString. let frag = parent.parse_fragment(value, can_gc)?; - // Step 6. + // Step 7: Replace this with fragment within this's parent. context_parent.ReplaceChild(frag.upcast(), context_node, can_gc)?; Ok(()) } @@ -3308,38 +3447,57 @@ impl ElementMethods<crate::DomTypeHolder> for Element { fn InsertAdjacentHTML( &self, position: DOMString, - text: DOMString, + text: TrustedHTMLOrString, can_gc: CanGc, ) -> ErrorResult { - // Step 1. + // Step 1: Let compliantString be the result of invoking the + // Get Trusted Type compliant string algorithm with TrustedHTML, + // this's relevant global object, string, "Element insertAdjacentHTML", and "script". + let text = DOMString::from(TrustedHTML::get_trusted_script_compliant_string( + &self.owner_global(), + text, + "Element", + "insertAdjacentHTML", + can_gc, + )?); let position = position.parse::<AdjacentPosition>()?; + // Step 2: Let context be null. + // Step 3: Use the first matching item from this list: let context = match position { + // If position is an ASCII case-insensitive match for the string "beforebegin" + // If position is an ASCII case-insensitive match for the string "afterend" AdjacentPosition::BeforeBegin | AdjacentPosition::AfterEnd => { match self.upcast::<Node>().GetParentNode() { + // Step 3.2: If context is null or a Document, throw a "NoModificationAllowedError" DOMException. Some(ref node) if node.is::<Document>() => { return Err(Error::NoModificationAllowed); }, None => return Err(Error::NoModificationAllowed), + // Step 3.1: Set context to this's parent. Some(node) => node, } }, + // If position is an ASCII case-insensitive match for the string "afterbegin" + // If position is an ASCII case-insensitive match for the string "beforeend" AdjacentPosition::AfterBegin | AdjacentPosition::BeforeEnd => { + // Set context to this. DomRoot::from_ref(self.upcast::<Node>()) }, }; - // Step 2. + // Step 4. let context = Element::fragment_parsing_context( &context.owner_doc(), context.downcast::<Element>(), can_gc, ); - // Step 3. + // Step 5: Let fragment be the result of invoking the + // fragment parsing algorithm steps with context and compliantString. let fragment = context.parse_fragment(text, can_gc)?; - // Step 4. + // Step 6. self.insert_adjacent(position, fragment.upcast(), can_gc) .map(|_| ()) } @@ -3800,43 +3958,7 @@ impl VirtualMethods for Element { &local_name!("tabindex") | &local_name!("draggable") | &local_name!("hidden") => { self.update_sequentially_focusable_status(can_gc) }, - &local_name!("style") => { - // Modifying the `style` attribute might change style. - *self.style_attribute.borrow_mut() = match mutation { - AttributeMutation::Set(..) => { - // This is the fast path we use from - // CSSStyleDeclaration. - // - // Juggle a bit to keep the borrow checker happy - // while avoiding the extra clone. - let is_declaration = matches!(*attr.value(), AttrValue::Declaration(..)); - - let block = if is_declaration { - let mut value = AttrValue::String(String::new()); - attr.swap_value(&mut value); - let (serialization, block) = match value { - AttrValue::Declaration(s, b) => (s, b), - _ => unreachable!(), - }; - let mut value = AttrValue::String(serialization); - attr.swap_value(&mut value); - block - } else { - let win = self.owner_window(); - Arc::new(doc.style_shared_lock().wrap(parse_style_attribute( - &attr.value(), - &UrlExtraData(doc.base_url().get_arc()), - win.css_error_reporter(), - doc.quirks_mode(), - CssRuleType::Style, - ))) - }; - - Some(block) - }, - AttributeMutation::Removed => None, - }; - }, + &local_name!("style") => self.update_style_attribute(attr, mutation), &local_name!("id") => { *self.id_attribute.borrow_mut() = mutation.new_value(attr).and_then(|value| { let value = value.as_atom(); diff --git a/components/script/dom/event.rs b/components/script/dom/event.rs index e199e12f655..743ced42a4b 100644 --- a/components/script/dom/event.rs +++ b/components/script/dom/event.rs @@ -1040,14 +1040,39 @@ impl From<bool> for EventCancelable { } impl From<EventCancelable> for bool { - fn from(bubbles: EventCancelable) -> Self { - match bubbles { + fn from(cancelable: EventCancelable) -> Self { + match cancelable { EventCancelable::Cancelable => true, EventCancelable::NotCancelable => false, } } } +#[derive(Clone, Copy, MallocSizeOf, PartialEq)] +pub(crate) enum EventComposed { + Composed, + NotComposed, +} + +impl From<bool> for EventComposed { + fn from(boolean: bool) -> Self { + if boolean { + EventComposed::Composed + } else { + EventComposed::NotComposed + } + } +} + +impl From<EventComposed> for bool { + fn from(composed: EventComposed) -> Self { + match composed { + EventComposed::Composed => true, + EventComposed::NotComposed => false, + } + } +} + #[derive(Clone, Copy, Debug, Eq, JSTraceable, PartialEq)] #[repr(u16)] #[derive(MallocSizeOf)] diff --git a/components/script/dom/eventsource.rs b/components/script/dom/eventsource.rs index 273abda0a02..7cf7bd6106f 100644 --- a/components/script/dom/eventsource.rs +++ b/components/script/dom/eventsource.rs @@ -448,7 +448,7 @@ impl FetchResponseListener for EventSourceContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs index 98c4c3ed53d..55db2e4d248 100644 --- a/components/script/dom/globalscope.rs +++ b/components/script/dom/globalscope.rs @@ -106,6 +106,7 @@ use crate::dom::crypto::Crypto; use crate::dom::dedicatedworkerglobalscope::{ DedicatedWorkerControlMsg, DedicatedWorkerGlobalScope, }; +use crate::dom::element::Element; use crate::dom::errorevent::ErrorEvent; use crate::dom::event::{Event, EventBubbles, EventCancelable, EventStatus}; use crate::dom::eventsource::EventSource; @@ -115,6 +116,7 @@ use crate::dom::htmlscriptelement::{ScriptId, SourceCode}; use crate::dom::imagebitmap::ImageBitmap; use crate::dom::messageevent::MessageEvent; use crate::dom::messageport::MessagePort; +use crate::dom::node::Node; use crate::dom::paintworkletglobalscope::PaintWorkletGlobalScope; use crate::dom::performance::Performance; use crate::dom::performanceobserver::VALID_ENTRY_TYPES; @@ -2930,7 +2932,7 @@ impl GlobalScope { let (is_js_evaluation_allowed, violations) = csp_list.is_js_evaluation_allowed(source); - self.report_csp_violations(violations); + self.report_csp_violations(violations, None); is_js_evaluation_allowed == CheckResult::Allowed } @@ -2957,7 +2959,7 @@ impl GlobalScope { let (result, violations) = csp_list.should_navigation_request_be_blocked(&request, NavigationCheckType::Other); - self.report_csp_violations(violations); + self.report_csp_violations(violations, None); result == CheckResult::Blocked } @@ -3444,8 +3446,13 @@ impl GlobalScope { unreachable!(); } + /// <https://www.w3.org/TR/CSP/#report-violation> #[allow(unsafe_code)] - pub(crate) fn report_csp_violations(&self, violations: Vec<Violation>) { + pub(crate) fn report_csp_violations( + &self, + violations: Vec<Violation>, + element: Option<&Element>, + ) { let scripted_caller = unsafe { describe_scripted_caller(*GlobalScope::get_cx()) }.unwrap_or_default(); for violation in violations { @@ -3471,7 +3478,38 @@ impl GlobalScope { .line_number(scripted_caller.line) .column_number(scripted_caller.col + 1) .build(self); - let task = CSPViolationReportTask::new(self, report); + // Step 1: Let global be violation’s global object. + // We use `self` as `global`; + // Step 2: Let target be violation’s element. + let target = element.and_then(|event_target| { + // Step 3.1: If target is not null, and global is a Window, + // and target’s shadow-including root is not global’s associated Document, set target to null. + if let Some(window) = self.downcast::<Window>() { + if !window + .Document() + .upcast::<Node>() + .is_shadow_including_inclusive_ancestor_of(event_target.upcast()) + { + return None; + } + } + Some(event_target) + }); + let target = match target { + // Step 3.2: If target is null: + None => { + // Step 3.2.2: If target is a Window, set target to target’s associated Document. + if let Some(window) = self.downcast::<Window>() { + Trusted::new(window.Document().upcast()) + } else { + // Step 3.2.1: Set target to violation’s global object. + Trusted::new(self.upcast()) + } + }, + Some(event_target) => Trusted::new(event_target.upcast()), + }; + // Step 3: Queue a task to run the following steps: + let task = CSPViolationReportTask::new(Trusted::new(self), target, report); self.task_manager() .dom_manipulation_task_source() .queue(task); @@ -3524,10 +3562,6 @@ impl GlobalScopeHelpers<crate::DomTypeHolder> for GlobalScope { GlobalScope::from_reflector(reflector, realm) } - unsafe fn from_object_maybe_wrapped(obj: *mut JSObject, cx: *mut JSContext) -> DomRoot<Self> { - GlobalScope::from_object_maybe_wrapped(obj, cx) - } - fn origin(&self) -> &MutableOrigin { GlobalScope::origin(self) } diff --git a/components/script/dom/htmlbodyelement.rs b/components/script/dom/htmlbodyelement.rs index b4efba9bed9..19b0ab4efce 100644 --- a/components/script/dom/htmlbodyelement.rs +++ b/components/script/dom/htmlbodyelement.rs @@ -181,26 +181,31 @@ impl VirtualMethods for HTMLBodyElement { (name, AttributeMutation::Set(_)) if name.starts_with("on") => { let window = self.owner_window(); // https://html.spec.whatwg.org/multipage/ - // #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-3 + // #event-handlers-on-elements,-document-objects,-and-window-objects:event-handlers-6 match name { - &local_name!("onfocus") | - &local_name!("onload") | - &local_name!("onscroll") | &local_name!("onafterprint") | &local_name!("onbeforeprint") | &local_name!("onbeforeunload") | + &local_name!("onerror") | + &local_name!("onfocus") | &local_name!("onhashchange") | + &local_name!("onload") | &local_name!("onlanguagechange") | &local_name!("onmessage") | + &local_name!("onmessageerror") | &local_name!("onoffline") | &local_name!("ononline") | &local_name!("onpagehide") | + &local_name!("onpagereveal") | &local_name!("onpageshow") | + &local_name!("onpageswap") | &local_name!("onpopstate") | - &local_name!("onstorage") | + &local_name!("onrejectionhandled") | &local_name!("onresize") | - &local_name!("onunload") | - &local_name!("onerror") => { + &local_name!("onscroll") | + &local_name!("onstorage") | + &local_name!("onunhandledrejection") | + &local_name!("onunload") => { let source = &**attr.value(); let evtarget = window.upcast::<EventTarget>(); // forwarded event let source_line = 1; //TODO(#9604) obtain current JS execution line diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs index 56e008839ba..c2bfc9c2d7f 100644 --- a/components/script/dom/htmlcanvaselement.rs +++ b/components/script/dom/htmlcanvaselement.rs @@ -103,10 +103,14 @@ impl EncodedImageType { } } +/// <https://html.spec.whatwg.org/multipage/#htmlcanvaselement> #[dom_struct] pub(crate) struct HTMLCanvasElement { htmlelement: HTMLElement, - context: DomRefCell<Option<RenderingContext>>, + + /// <https://html.spec.whatwg.org/multipage/#concept-canvas-context-mode> + context_mode: DomRefCell<Option<RenderingContext>>, + // This id and hashmap are used to keep track of ongoing toBlob() calls. callback_id: Cell<u32>, #[ignore_malloc_size_of = "not implemented for webidl callbacks"] @@ -121,7 +125,7 @@ impl HTMLCanvasElement { ) -> HTMLCanvasElement { HTMLCanvasElement { htmlelement: HTMLElement::new_inherited(local_name, prefix, document), - context: DomRefCell::new(None), + context_mode: DomRefCell::new(None), callback_id: Cell::new(0), blob_callbacks: RefCell::new(HashMap::new()), } @@ -146,7 +150,7 @@ impl HTMLCanvasElement { } fn recreate_contexts_after_resize(&self) { - if let Some(ref context) = *self.context.borrow() { + if let Some(ref context) = *self.context_mode.borrow() { context.resize() } } @@ -156,14 +160,14 @@ impl HTMLCanvasElement { } pub(crate) fn origin_is_clean(&self) -> bool { - match *self.context.borrow() { + match *self.context_mode.borrow() { Some(ref context) => context.origin_is_clean(), _ => true, } } pub(crate) fn mark_as_dirty(&self) { - if let Some(ref context) = *self.context.borrow() { + if let Some(ref context) = *self.context_mode.borrow() { context.mark_as_dirty() } } @@ -193,7 +197,7 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { #[allow(unsafe_code)] fn data(self) -> HTMLCanvasData { let source = unsafe { - match self.unsafe_get().context.borrow_for_layout().as_ref() { + match self.unsafe_get().context_mode.borrow_for_layout().as_ref() { Some(RenderingContext::Context2d(context)) => { context.to_layout().canvas_data_source() }, @@ -221,7 +225,7 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> { impl HTMLCanvasElement { pub(crate) fn context(&self) -> Option<Ref<RenderingContext>> { - ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref()) + ref_filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref()) } fn get_or_init_2d_context(&self, can_gc: CanGc) -> Option<DomRoot<CanvasRenderingContext2D>> { @@ -235,7 +239,8 @@ impl HTMLCanvasElement { let window = self.owner_window(); let size = self.get_size(); let context = CanvasRenderingContext2D::new(window.as_global_scope(), self, size, can_gc); - *self.context.borrow_mut() = Some(RenderingContext::Context2d(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = + Some(RenderingContext::Context2d(Dom::from_ref(&*context))); Some(context) } @@ -263,7 +268,7 @@ impl HTMLCanvasElement { attrs, can_gc, )?; - *self.context.borrow_mut() = Some(RenderingContext::WebGL(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGL(Dom::from_ref(&*context))); Some(context) } @@ -288,7 +293,7 @@ impl HTMLCanvasElement { let attrs = Self::get_gl_attributes(cx, options)?; let canvas = HTMLCanvasElementOrOffscreenCanvas::HTMLCanvasElement(DomRoot::from_ref(self)); let context = WebGL2RenderingContext::new(&window, &canvas, size, attrs, can_gc)?; - *self.context.borrow_mut() = Some(RenderingContext::WebGL2(Dom::from_ref(&*context))); + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGL2(Dom::from_ref(&*context))); Some(context) } @@ -315,7 +320,7 @@ impl HTMLCanvasElement { .expect("Failed to get WebGPU channel") .map(|channel| { let context = GPUCanvasContext::new(&global_scope, self, channel, can_gc); - *self.context.borrow_mut() = + *self.context_mode.borrow_mut() = Some(RenderingContext::WebGPU(Dom::from_ref(&*context))); context }) @@ -323,7 +328,7 @@ impl HTMLCanvasElement { /// Gets the base WebGLRenderingContext for WebGL or WebGL 2, if exists. pub(crate) fn get_base_webgl_context(&self) -> Option<DomRoot<WebGLRenderingContext>> { - match *self.context.borrow() { + match *self.context_mode.borrow() { Some(RenderingContext::WebGL(ref context)) => Some(DomRoot::from_ref(context)), Some(RenderingContext::WebGL2(ref context)) => Some(context.base_context()), _ => None, @@ -352,7 +357,7 @@ impl HTMLCanvasElement { } pub(crate) fn get_image_data(&self) -> Option<Snapshot> { - match self.context.borrow().as_ref() { + match self.context_mode.borrow().as_ref() { Some(context) => context.get_image_data(), None => { let size = self.get_size(); @@ -431,12 +436,12 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { // https://html.spec.whatwg.org/multipage/#dom-canvas-width make_uint_getter!(Width, "width", DEFAULT_WIDTH); - // https://html.spec.whatwg.org/multipage/#dom-canvas-width - // When setting the value of the width or height attribute, if the context mode of the canvas element - // is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the - // attribute's value unchanged. + /// <https://html.spec.whatwg.org/multipage/#dom-canvas-width> fn SetWidth(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(RenderingContext::Placeholder(_)) = *self.context.borrow() { + // > When setting the value of the width or height attribute, if the context mode of the canvas element + // > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the + // > attribute's value unchanged. + if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() { return Err(Error::InvalidState); } @@ -453,9 +458,12 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { // https://html.spec.whatwg.org/multipage/#dom-canvas-height make_uint_getter!(Height, "height", DEFAULT_HEIGHT); - // https://html.spec.whatwg.org/multipage/#dom-canvas-height + /// <https://html.spec.whatwg.org/multipage/#dom-canvas-height> fn SetHeight(&self, value: u32, can_gc: CanGc) -> Fallible<()> { - if let Some(RenderingContext::Placeholder(_)) = *self.context.borrow() { + // > When setting the value of the width or height attribute, if the context mode of the canvas element + // > is set to placeholder, the user agent must throw an "InvalidStateError" DOMException and leave the + // > attribute's value unchanged. + if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() { return Err(Error::InvalidState); } @@ -478,7 +486,7 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { can_gc: CanGc, ) -> Fallible<Option<RootedRenderingContext>> { // Always throw an InvalidState exception when the canvas is in Placeholder mode (See table in the spec). - if let Some(RenderingContext::Placeholder(_)) = *self.context.borrow() { + if let Some(RenderingContext::Placeholder(_)) = *self.context_mode.borrow() { return Err(Error::InvalidState); } @@ -622,7 +630,7 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { /// <https://html.spec.whatwg.org/multipage/#dom-canvas-transfercontroltooffscreen> fn TransferControlToOffscreen(&self, can_gc: CanGc) -> Fallible<DomRoot<OffscreenCanvas>> { - if self.context.borrow().is_some() { + if self.context_mode.borrow().is_some() { // Step 1. // If this canvas element's context mode is not set to none, throw an "InvalidStateError" DOMException. return Err(Error::InvalidState); @@ -641,8 +649,9 @@ impl HTMLCanvasElementMethods<crate::DomTypeHolder> for HTMLCanvasElement { Some(&Dom::from_ref(self)), can_gc, ); + // Step 4. Set this canvas element's context mode to placeholder. - *self.context.borrow_mut() = + *self.context_mode.borrow_mut() = Some(RenderingContext::Placeholder(offscreen_canvas.as_traced())); // Step 5. Return offscreenCanvas. diff --git a/components/script/dom/htmlelement.rs b/components/script/dom/htmlelement.rs index 59b71543d6d..32a979ad138 100644 --- a/components/script/dom/htmlelement.rs +++ b/components/script/dom/htmlelement.rs @@ -191,9 +191,6 @@ impl HTMLElementMethods<crate::DomTypeHolder> for HTMLElement { // https://html.spec.whatwg.org/multipage/#globaleventhandlers global_event_handlers!(NoOnload); - // https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers - document_and_element_event_handlers!(); - // https://html.spec.whatwg.org/multipage/#dom-dataset fn Dataset(&self, can_gc: CanGc) -> DomRoot<DOMStringMap> { self.dataset.or_init(|| DOMStringMap::new(self, can_gc)) diff --git a/components/script/dom/htmlimageelement.rs b/components/script/dom/htmlimageelement.rs index e0c8e9ef726..a79c7f6e463 100644 --- a/components/script/dom/htmlimageelement.rs +++ b/components/script/dom/htmlimageelement.rs @@ -298,7 +298,7 @@ impl FetchResponseListener for ImageContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmllinkelement.rs b/components/script/dom/htmllinkelement.rs index 51aa6bee286..18bd426acdb 100644 --- a/components/script/dom/htmllinkelement.rs +++ b/components/script/dom/htmllinkelement.rs @@ -773,7 +773,7 @@ impl FetchResponseListener for PrefetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs index 361c22c1250..391da272ef3 100644 --- a/components/script/dom/htmlmediaelement.rs +++ b/components/script/dom/htmlmediaelement.rs @@ -2951,7 +2951,7 @@ impl FetchResponseListener for HTMLMediaElementFetchListener { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/htmloptgroupelement.rs b/components/script/dom/htmloptgroupelement.rs index 55ffa92257b..f5256e71b70 100644 --- a/components/script/dom/htmloptgroupelement.rs +++ b/components/script/dom/htmloptgroupelement.rs @@ -135,8 +135,8 @@ impl VirtualMethods for HTMLOptGroupElement { } fn bind_to_tree(&self, context: &BindContext, can_gc: CanGc) { - if let Some(s) = self.super_type() { - s.bind_to_tree(context, can_gc); + if let Some(super_type) = self.super_type() { + super_type.bind_to_tree(context, can_gc); } self.update_select_validity(can_gc); diff --git a/components/script/dom/htmloptionelement.rs b/components/script/dom/htmloptionelement.rs index b573388c73a..800e88f0758 100644 --- a/components/script/dom/htmloptionelement.rs +++ b/components/script/dom/htmloptionelement.rs @@ -29,7 +29,7 @@ use crate::dom::htmlformelement::HTMLFormElement; use crate::dom::htmloptgroupelement::HTMLOptGroupElement; use crate::dom::htmlscriptelement::HTMLScriptElement; use crate::dom::htmlselectelement::HTMLSelectElement; -use crate::dom::node::{BindContext, Node, ShadowIncluding, UnbindContext}; +use crate::dom::node::{BindContext, ChildrenMutation, Node, ShadowIncluding, UnbindContext}; use crate::dom::text::Text; use crate::dom::validation::Validatable; use crate::dom::validitystate::ValidationFlags; @@ -380,4 +380,26 @@ impl VirtualMethods for HTMLOptionElement { el.check_disabled_attribute(); } } + + fn children_changed(&self, mutation: &ChildrenMutation) { + if let Some(super_type) = self.super_type() { + super_type.children_changed(mutation); + } + + // Changing the descendants of a selected option can change it's displayed label + // if it does not have a label attribute + if !self + .upcast::<Element>() + .has_attribute(&local_name!("label")) + { + if let Some(owner_select) = self.owner_select_element() { + if owner_select + .selected_option() + .is_some_and(|selected_option| self == &*selected_option) + { + owner_select.update_shadow_tree(CanGc::note()); + } + } + } + } } diff --git a/components/script/dom/htmlscriptelement.rs b/components/script/dom/htmlscriptelement.rs index 777b30d1e63..4ee1397b4ed 100644 --- a/components/script/dom/htmlscriptelement.rs +++ b/components/script/dom/htmlscriptelement.rs @@ -546,7 +546,8 @@ impl FetchResponseListener for ClassicContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + let elem = self.elem.root(); + global.report_csp_violations(violations, Some(elem.upcast::<Element>())); } } diff --git a/components/script/dom/htmlselectelement.rs b/components/script/dom/htmlselectelement.rs index f4a62abe8b4..56fac20e841 100644 --- a/components/script/dom/htmlselectelement.rs +++ b/components/script/dom/htmlselectelement.rs @@ -153,7 +153,7 @@ impl HTMLSelectElement { n } - // https://html.spec.whatwg.org/multipage/#concept-select-option-list + /// <https://html.spec.whatwg.org/multipage/#concept-select-option-list> pub(crate) fn list_of_options( &self, ) -> impl Iterator<Item = DomRoot<HTMLOptionElement>> + use<'_> { @@ -353,8 +353,10 @@ impl HTMLSelectElement { .fire_bubbling_event(atom!("change"), can_gc); } - fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> { - self.list_of_options().find(|opt_elem| opt_elem.Selected()) + pub(crate) fn selected_option(&self) -> Option<DomRoot<HTMLOptionElement>> { + self.list_of_options() + .find(|opt_elem| opt_elem.Selected()) + .or_else(|| self.list_of_options().next()) } pub(crate) fn show_menu(&self, can_gc: CanGc) -> Option<usize> { @@ -539,7 +541,8 @@ impl HTMLSelectElementMethods<crate::DomTypeHolder> for HTMLSelectElement { /// <https://html.spec.whatwg.org/multipage/#dom-select-value> fn Value(&self) -> DOMString { - self.selected_option() + self.list_of_options() + .find(|opt_elem| opt_elem.Selected()) .map(|opt_elem| opt_elem.Value()) .unwrap_or_default() } diff --git a/components/script/dom/htmlvideoelement.rs b/components/script/dom/htmlvideoelement.rs index 6f27c164d02..c5d21c19d9b 100644 --- a/components/script/dom/htmlvideoelement.rs +++ b/components/script/dom/htmlvideoelement.rs @@ -422,7 +422,7 @@ impl FetchResponseListener for PosterFrameFetchContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/imagedata.rs b/components/script/dom/imagedata.rs index bd45a80fce2..a891064952a 100644 --- a/components/script/dom/imagedata.rs +++ b/components/script/dom/imagedata.rs @@ -9,13 +9,13 @@ use std::vec::Vec; use dom_struct::dom_struct; use euclid::default::{Rect, Size2D}; use ipc_channel::ipc::IpcSharedMemory; -use js::jsapi::{Heap, JSObject}; +use js::gc::CustomAutoRooterGuard; +use js::jsapi::JSObject; use js::rust::HandleObject; use js::typedarray::{ClampedU8, CreateWith, Uint8ClampedArray}; -use super::bindings::buffer_source::{ - BufferSource, HeapBufferSource, HeapTypedArrayInit, new_initialized_heap_buffer_source, -}; +use super::bindings::buffer_source::{HeapBufferSource, create_heap_buffer_source_with_length}; +use crate::dom::bindings::buffer_source::create_buffer_source; use crate::dom::bindings::codegen::Bindings::CanvasRenderingContext2DBinding::ImageDataMethods; use crate::dom::bindings::error::{Error, Fallible}; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object_with_proto}; @@ -55,31 +55,31 @@ impl ImageData { rooted!(in (*cx) let mut js_object = ptr::null_mut::<JSObject>()); if let Some(ref mut d) = data { d.resize(len as usize, 0); + + let typed_array = + create_buffer_source::<ClampedU8>(cx, &d[..], js_object.handle_mut(), can_gc) + .map_err(|_| Error::JSFailed)?; + let data = CreateWith::Slice(&d[..]); Uint8ClampedArray::create(*cx, data, js_object.handle_mut()).unwrap(); - Self::new_with_jsobject(global, None, width, Some(height), js_object.get(), can_gc) + auto_root!(in(*cx) let data = typed_array); + Self::new_with_data(global, None, width, Some(height), data, can_gc) } else { - Self::new_without_jsobject(global, None, width, height, can_gc) + Self::new_without_data(global, None, width, height, can_gc) } } } #[allow(unsafe_code)] - fn new_with_jsobject( + fn new_with_data( global: &GlobalScope, proto: Option<HandleObject>, width: u32, opt_height: Option<u32>, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard<Uint8ClampedArray>, can_gc: CanGc, ) -> Fallible<DomRoot<ImageData>> { - let heap_typed_array = match new_initialized_heap_buffer_source::<ClampedU8>( - HeapTypedArrayInit::Buffer(BufferSource::ArrayBufferView(Heap::boxed(jsobject))), - can_gc, - ) { - Ok(heap_typed_array) => heap_typed_array, - Err(_) => return Err(Error::JSFailed), - }; + let heap_typed_array = HeapBufferSource::<ClampedU8>::from_view(data); let typed_array = match heap_typed_array.get_typed_array() { Ok(array) => array, @@ -117,13 +117,14 @@ impl ImageData { )) } - fn new_without_jsobject( + fn new_without_data( global: &GlobalScope, proto: Option<HandleObject>, width: u32, height: u32, can_gc: CanGc, ) -> Fallible<DomRoot<ImageData>> { + // If one or both of sw and sh are zero, then throw an "IndexSizeError" DOMException. if width == 0 || height == 0 { return Err(Error::IndexSize); } @@ -139,13 +140,8 @@ impl ImageData { let cx = GlobalScope::get_cx(); - let heap_typed_array = match new_initialized_heap_buffer_source::<ClampedU8>( - HeapTypedArrayInit::Info { len, cx }, - can_gc, - ) { - Ok(heap_typed_array) => heap_typed_array, - Err(_) => return Err(Error::JSFailed), - }; + let heap_typed_array = create_heap_buffer_source_with_length::<ClampedU8>(cx, len, can_gc)?; + let imagedata = Box::new(ImageData { reflector_: Reflector::new(), width, @@ -198,20 +194,19 @@ impl ImageDataMethods<crate::DomTypeHolder> for ImageData { width: u32, height: u32, ) -> Fallible<DomRoot<Self>> { - Self::new_without_jsobject(global, proto, width, height, can_gc) + Self::new_without_data(global, proto, width, height, can_gc) } /// <https://html.spec.whatwg.org/multipage/#dom-imagedata-with-data> fn Constructor_( - _cx: JSContext, global: &GlobalScope, proto: Option<HandleObject>, can_gc: CanGc, - jsobject: *mut JSObject, + data: CustomAutoRooterGuard<Uint8ClampedArray>, width: u32, opt_height: Option<u32>, ) -> Fallible<DomRoot<Self>> { - Self::new_with_jsobject(global, proto, width, opt_height, jsobject, can_gc) + Self::new_with_data(global, proto, width, opt_height, data, can_gc) } /// <https://html.spec.whatwg.org/multipage/#dom-imagedata-width> diff --git a/components/script/dom/intersectionobserver.rs b/components/script/dom/intersectionobserver.rs index ec98116d3a4..6a6f9ce45eb 100644 --- a/components/script/dom/intersectionobserver.rs +++ b/components/script/dom/intersectionobserver.rs @@ -524,11 +524,10 @@ impl IntersectionObserver { // Step 9 // > Let targetArea be targetRect’s area. - let target_area = target_rect.size.width.0 * target_rect.size.height.0; - // Step 10 // > Let intersectionArea be intersectionRect’s area. - let intersection_area = intersection_rect.size.width.0 * intersection_rect.size.height.0; + // These steps are folded in Step 12, rewriting (w1 * h1) / (w2 * h2) as (w1 / w2) * (h1 / h2) + // to avoid multiplication overflows. // Step 11 // > Let isIntersecting be true if targetRect and rootBounds intersect or are edge-adjacent, @@ -545,9 +544,12 @@ impl IntersectionObserver { // Step 12 // > If targetArea is non-zero, let intersectionRatio be intersectionArea divided by targetArea. // > Otherwise, let intersectionRatio be 1 if isIntersecting is true, or 0 if isIntersecting is false. - let intersection_ratio = match target_area { - 0 => is_intersecting.into(), - _ => (intersection_area as f64) / (target_area as f64), + let intersection_ratio = if target_rect.size.width.0 == 0 || target_rect.size.height.0 == 0 + { + is_intersecting.into() + } else { + (intersection_rect.size.width.0 as f64 / target_rect.size.width.0 as f64) * + (intersection_rect.size.height.0 as f64 / target_rect.size.height.0 as f64) }; // Step 13 diff --git a/components/script/dom/macros.rs b/components/script/dom/macros.rs index cc44497d0b9..564fe810db0 100644 --- a/components/script/dom/macros.rs +++ b/components/script/dom/macros.rs @@ -523,21 +523,29 @@ macro_rules! global_event_handlers( ); (NoOnload) => ( event_handler!(abort, GetOnabort, SetOnabort); + event_handler!(auxclick, GetOnauxclick, SetOnauxclick); event_handler!(animationend, GetOnanimationend, SetOnanimationend); event_handler!(animationiteration, GetOnanimationiteration, SetOnanimationiteration); + event_handler!(beforeinput, GetOnbeforeinput, SetOnbeforeinput); + event_handler!(beforematch, GetOnbeforematch, SetOnbeforematch); + event_handler!(beforetoggle, GetOnbeforetoggle, SetOnbeforetoggle); event_handler!(cancel, GetOncancel, SetOncancel); event_handler!(canplay, GetOncanplay, SetOncanplay); event_handler!(canplaythrough, GetOncanplaythrough, SetOncanplaythrough); event_handler!(change, GetOnchange, SetOnchange); event_handler!(click, GetOnclick, SetOnclick); event_handler!(close, GetOnclose, SetOnclose); + event_handler!(command, GetOncommand, SetOncommand); + event_handler!(contextlost, GetOncontextlost, SetOncontextlost); event_handler!(contextmenu, GetOncontextmenu, SetOncontextmenu); + event_handler!(contextrestored, GetOncontextrestored, SetOncontextrestored); + event_handler!(copy, GetOncopy, SetOncopy); event_handler!(cuechange, GetOncuechange, SetOncuechange); + event_handler!(cut, GetOncut, SetOncut); event_handler!(dblclick, GetOndblclick, SetOndblclick); event_handler!(drag, GetOndrag, SetOndrag); event_handler!(dragend, GetOndragend, SetOndragend); event_handler!(dragenter, GetOndragenter, SetOndragenter); - event_handler!(dragexit, GetOndragexit, SetOndragexit); event_handler!(dragleave, GetOndragleave, SetOndragleave); event_handler!(dragover, GetOndragover, SetOndragover); event_handler!(dragstart, GetOndragstart, SetOndragstart); @@ -561,20 +569,21 @@ macro_rules! global_event_handlers( event_handler!(mouseout, GetOnmouseout, SetOnmouseout); event_handler!(mouseover, GetOnmouseover, SetOnmouseover); event_handler!(mouseup, GetOnmouseup, SetOnmouseup); - event_handler!(wheel, GetOnwheel, SetOnwheel); + event_handler!(paste, GetOnpaste, SetOnpaste); event_handler!(pause, GetOnpause, SetOnpause); event_handler!(play, GetOnplay, SetOnplay); event_handler!(playing, GetOnplaying, SetOnplaying); event_handler!(progress, GetOnprogress, SetOnprogress); event_handler!(ratechange, GetOnratechange, SetOnratechange); event_handler!(reset, GetOnreset, SetOnreset); + event_handler!(scrollend, GetOnscrollend, SetOnscrollend); event_handler!(securitypolicyviolation, GetOnsecuritypolicyviolation, SetOnsecuritypolicyviolation); event_handler!(seeked, GetOnseeked, SetOnseeked); event_handler!(seeking, GetOnseeking, SetOnseeking); event_handler!(select, GetOnselect, SetOnselect); event_handler!(selectionchange, GetOnselectionchange, SetOnselectionchange); event_handler!(selectstart, GetOnselectstart, SetOnselectstart); - event_handler!(show, GetOnshow, SetOnshow); + event_handler!(slotchange, GetOnslotchange, SetOnslotchange); event_handler!(stalled, GetOnstalled, SetOnstalled); event_handler!(submit, GetOnsubmit, SetOnsubmit); event_handler!(suspend, GetOnsuspend, SetOnsuspend); @@ -585,6 +594,11 @@ macro_rules! global_event_handlers( event_handler!(transitionrun, GetOntransitionrun, SetOntransitionrun); event_handler!(volumechange, GetOnvolumechange, SetOnvolumechange); event_handler!(waiting, GetOnwaiting, SetOnwaiting); + event_handler!(webkitanimationend, GetOnwebkitanimationend, SetOnwebkitanimationend); + event_handler!(webkitanimationiteration, GetOnwebkitanimationiteration, SetOnwebkitanimationiteration); + event_handler!(webkitanimationstart, GetOnwebkitanimationstart, SetOnwebkitanimationstart); + event_handler!(webkittransitionend, GetOnwebkittransitionend, SetOnwebkittransitionend); + event_handler!(wheel, GetOnwheel, SetOnwheel); ) ); @@ -605,7 +619,9 @@ macro_rules! window_event_handlers( event_handler!(offline, GetOnoffline, SetOnoffline); event_handler!(online, GetOnonline, SetOnonline); event_handler!(pagehide, GetOnpagehide, SetOnpagehide); + event_handler!(pagereveal, GetOnpagereveal, SetOnpagereveal); event_handler!(pageshow, GetOnpageshow, SetOnpageshow); + event_handler!(pageswap, GetOnpageswap, SetOnpageswap); event_handler!(popstate, GetOnpopstate, SetOnpopstate); event_handler!(rejectionhandled, GetOnrejectionhandled, SetOnrejectionhandled); @@ -633,7 +649,9 @@ macro_rules! window_event_handlers( window_owned_event_handler!(offline, GetOnoffline, SetOnoffline); window_owned_event_handler!(online, GetOnonline, SetOnonline); window_owned_event_handler!(pagehide, GetOnpagehide, SetOnpagehide); + window_owned_event_handler!(pagereveal, GetOnpagereveal, SetOnpagereveal); window_owned_event_handler!(pageshow, GetOnpageshow, SetOnpageshow); + window_owned_event_handler!(pageswap, GetOnpageswap, SetOnpageswap); window_owned_event_handler!(popstate, GetOnpopstate, SetOnpopstate); window_owned_event_handler!(rejectionhandled, GetOnrejectionhandled, SetOnrejectionhandled); @@ -646,17 +664,6 @@ macro_rules! window_event_handlers( ); ); -// https://html.spec.whatwg.org/multipage/#documentandelementeventhandlers -// see webidls/EventHandler.webidl -// As more methods get added, just update them here. -macro_rules! document_and_element_event_handlers( - () => ( - event_handler!(cut, GetOncut, SetOncut); - event_handler!(copy, GetOncopy, SetOncopy); - event_handler!(paste, GetOnpaste, SetOnpaste); - ) -); - /// DOM struct implementation for simple interfaces inheriting from PerformanceEntry. macro_rules! impl_performance_entry_struct( ($binding:ident, $struct:ident, $type:expr) => ( diff --git a/components/script/dom/mod.rs b/components/script/dom/mod.rs index 4bc272db8dd..1622cf57b79 100644 --- a/components/script/dom/mod.rs +++ b/components/script/dom/mod.rs @@ -631,6 +631,8 @@ pub(crate) mod webgpu; pub(crate) use self::webgpu::*; #[cfg(not(feature = "webgpu"))] pub(crate) mod gpucanvascontext; +pub(crate) mod transformstream; +pub(crate) mod transformstreamdefaultcontroller; pub(crate) mod wheelevent; #[allow(dead_code)] pub(crate) mod window; diff --git a/components/script/dom/node.rs b/components/script/dom/node.rs index e9d36a01426..ca785773b48 100644 --- a/components/script/dom/node.rs +++ b/components/script/dom/node.rs @@ -3110,11 +3110,15 @@ impl NodeMethods<crate::DomTypeHolder> for Node { /// <https://dom.spec.whatwg.org/#dom-node-childnodes> fn ChildNodes(&self, can_gc: CanGc) -> DomRoot<NodeList> { - self.ensure_rare_data().child_list.or_init(|| { - let doc = self.owner_doc(); - let window = doc.window(); - NodeList::new_child_list(window, self, can_gc) - }) + if let Some(list) = self.ensure_rare_data().child_list.get() { + return list; + } + + let doc = self.owner_doc(); + let window = doc.window(); + let list = NodeList::new_child_list(window, self, can_gc); + self.ensure_rare_data().child_list.set(Some(&list)); + list } /// <https://dom.spec.whatwg.org/#dom-node-firstchild> diff --git a/components/script/dom/notification.rs b/components/script/dom/notification.rs index 4dbb97430e5..1aecb785475 100644 --- a/components/script/dom/notification.rs +++ b/components/script/dom/notification.rs @@ -795,7 +795,7 @@ impl FetchResponseListener for ResourceFetchListener { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/offscreencanvas.rs b/components/script/dom/offscreencanvas.rs index 9947d35f4e0..bceed49ac7d 100644 --- a/components/script/dom/offscreencanvas.rs +++ b/components/script/dom/offscreencanvas.rs @@ -24,12 +24,20 @@ use crate::dom::htmlcanvaselement::HTMLCanvasElement; use crate::dom::offscreencanvasrenderingcontext2d::OffscreenCanvasRenderingContext2D; use crate::script_runtime::{CanGc, JSContext}; +/// <https://html.spec.whatwg.org/multipage/#offscreencanvas> #[dom_struct] pub(crate) struct OffscreenCanvas { eventtarget: EventTarget, width: Cell<u64>, height: Cell<u64>, + + /// Represents both the [bitmap] and the [context mode] of the canvas. + /// + /// [bitmap]: https://html.spec.whatwg.org/multipage/#offscreencanvas-bitmap + /// [context mode]: https://html.spec.whatwg.org/multipage/#offscreencanvas-context-mode context: DomRefCell<Option<OffscreenRenderingContext>>, + + /// <https://html.spec.whatwg.org/multipage/#offscreencanvas-placeholder> placeholder: Option<Dom<HTMLCanvasElement>>, } @@ -119,7 +127,7 @@ impl OffscreenCanvas { } impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas> fn Constructor( global: &GlobalScope, proto: Option<HandleObject>, @@ -131,7 +139,7 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { Ok(offscreencanvas) } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-getcontext + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-getcontext> fn GetContext( &self, _cx: JSContext, @@ -155,12 +163,12 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { } } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width> fn Width(&self) -> u64 { self.width.get() } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-width> fn SetWidth(&self, value: u64, can_gc: CanGc) { self.width.set(value); @@ -173,12 +181,12 @@ impl OffscreenCanvasMethods<crate::DomTypeHolder> for OffscreenCanvas { } } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height> fn Height(&self) -> u64 { self.height.get() } - // https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height + /// <https://html.spec.whatwg.org/multipage/#dom-offscreencanvas-height> fn SetHeight(&self, value: u64, can_gc: CanGc) { self.height.set(value); diff --git a/components/script/dom/promise.rs b/components/script/dom/promise.rs index 80b62f161bc..0efffbe6fe2 100644 --- a/components/script/dom/promise.rs +++ b/components/script/dom/promise.rs @@ -29,7 +29,6 @@ use js::rust::wrappers::{ ResolvePromise, SetAnyPromiseIsHandled, SetPromiseUserInputEventHandlingState, }; use js::rust::{HandleObject, HandleValue, MutableHandleObject, Runtime}; -use script_bindings::interfaces::PromiseHelpers; use crate::dom::bindings::conversions::root_from_object; use crate::dom::bindings::error::{Error, ErrorToJsval}; @@ -388,16 +387,6 @@ fn create_native_handler_function( } } -impl PromiseHelpers<crate::DomTypeHolder> for Promise { - fn new_resolved( - global: &GlobalScope, - cx: SafeJSContext, - value: impl ToJSValConvertible, - ) -> Rc<Promise> { - Promise::new_resolved(global, cx, value, CanGc::note()) - } -} - impl FromJSValConvertibleRc for Promise { #[allow(unsafe_code)] unsafe fn from_jsval( @@ -407,16 +396,12 @@ impl FromJSValConvertibleRc for Promise { if value.get().is_null() { return Ok(ConversionResult::Failure("null not allowed".into())); } - if !value.get().is_object() { - return Ok(ConversionResult::Failure("not an object".into())); - } - rooted!(in(cx) let obj = value.get().to_object()); let cx = SafeJSContext::from_ptr(cx); let in_realm_proof = AlreadyInRealm::assert_for_cx(cx); let global_scope = GlobalScope::from_context(*cx, InRealm::Already(&in_realm_proof)); - let promise = Promise::new_resolved(&global_scope, cx, *obj, CanGc::note()); + let promise = Promise::new_resolved(&global_scope, cx, value, CanGc::note()); Ok(ConversionResult::Success(promise)) } } diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs index 4982bfa32e3..d631a01e1e7 100644 --- a/components/script/dom/readablestream.rs +++ b/components/script/dom/readablestream.rs @@ -11,6 +11,7 @@ use std::rc::Rc; use base::id::{MessagePortId, MessagePortIndex}; use constellation_traits::MessagePortImpl; use dom_struct::dom_struct; +use ipc_channel::ipc::IpcSharedMemory; use js::conversions::ToJSValConvertible; use js::jsapi::{Heap, JSObject}; use js::jsval::{JSVal, NullValue, ObjectValue, UndefinedValue}; @@ -1131,12 +1132,14 @@ impl ReadableStream { /// Return bytes for synchronous use, if the stream has all data in memory. /// Useful for native source integration only. - pub(crate) fn get_in_memory_bytes(&self) -> Option<Vec<u8>> { + pub(crate) fn get_in_memory_bytes(&self) -> Option<IpcSharedMemory> { match self.controller.borrow().as_ref() { Some(ControllerType::Default(controller)) => controller .get() .expect("Stream should have controller.") - .get_in_memory_bytes(), + .get_in_memory_bytes() + .as_deref() + .map(IpcSharedMemory::from_bytes), _ => { unreachable!("Getting in-memory bytes for a stream with a non-default controller") }, diff --git a/components/script/dom/readablestreamdefaultcontroller.rs b/components/script/dom/readablestreamdefaultcontroller.rs index c52fb712a03..80c800d1bbe 100644 --- a/components/script/dom/readablestreamdefaultcontroller.rs +++ b/components/script/dom/readablestreamdefaultcontroller.rs @@ -383,7 +383,6 @@ impl ReadableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#set-up-readable-stream-default-controller> - #[allow(unsafe_code)] pub(crate) fn setup( &self, stream: DomRoot<ReadableStream>, @@ -866,7 +865,6 @@ impl ReadableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#rs-default-controller-has-backpressure> - #[allow(unused)] pub(crate) fn has_backpressure(&self) -> bool { // If ! ReadableStreamDefaultControllerShouldCallPull(controller) is true, return false. // Otherwise, return true. diff --git a/components/script/dom/readablestreamgenericreader.rs b/components/script/dom/readablestreamgenericreader.rs index 8ba1149bcb5..d2a109ee692 100644 --- a/components/script/dom/readablestreamgenericreader.rs +++ b/components/script/dom/readablestreamgenericreader.rs @@ -80,7 +80,6 @@ pub(crate) trait ReadableStreamGenericReader { } /// <https://streams.spec.whatwg.org/#readable-stream-reader-generic-release> - #[allow(unsafe_code)] fn generic_release(&self, can_gc: CanGc) -> Fallible<()> { // Let stream be reader.[[stream]]. diff --git a/components/script/dom/securitypolicyviolationevent.rs b/components/script/dom/securitypolicyviolationevent.rs index 3580e525e55..3c528cc5814 100644 --- a/components/script/dom/securitypolicyviolationevent.rs +++ b/components/script/dom/securitypolicyviolationevent.rs @@ -15,7 +15,7 @@ use crate::dom::bindings::codegen::Bindings::SecurityPolicyViolationEventBinding use crate::dom::bindings::inheritance::Castable; use crate::dom::bindings::root::DomRoot; use crate::dom::bindings::str::{DOMString, USVString}; -use crate::dom::event::{Event, EventBubbles, EventCancelable}; +use crate::dom::event::{Event, EventBubbles, EventCancelable, EventComposed}; use crate::dom::globalscope::GlobalScope; use crate::script_runtime::CanGc; @@ -70,12 +70,14 @@ impl SecurityPolicyViolationEvent { ) } + #[allow(clippy::too_many_arguments)] fn new_with_proto( global: &GlobalScope, proto: Option<HandleObject>, type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, + composed: EventComposed, init: &SecurityPolicyViolationEventInit, can_gc: CanGc, ) -> DomRoot<Self> { @@ -83,6 +85,7 @@ impl SecurityPolicyViolationEvent { { let event = ev.upcast::<Event>(); event.init_event(type_, bool::from(bubbles), bool::from(cancelable)); + event.set_composed(bool::from(composed)); } ev } @@ -92,10 +95,13 @@ impl SecurityPolicyViolationEvent { type_: Atom, bubbles: EventBubbles, cancelable: EventCancelable, + composed: EventComposed, init: &SecurityPolicyViolationEventInit, can_gc: CanGc, ) -> DomRoot<Self> { - Self::new_with_proto(global, None, type_, bubbles, cancelable, init, can_gc) + Self::new_with_proto( + global, None, type_, bubbles, cancelable, composed, init, can_gc, + ) } } @@ -115,6 +121,7 @@ impl SecurityPolicyViolationEventMethods<crate::DomTypeHolder> for SecurityPolic Atom::from(type_), EventBubbles::from(init.parent.bubbles), EventCancelable::from(init.parent.cancelable), + EventComposed::from(init.parent.composed), init, can_gc, ) diff --git a/components/script/dom/servoparser/html.rs b/components/script/dom/servoparser/html.rs index 7fd0429612a..9dfbeda4030 100644 --- a/components/script/dom/servoparser/html.rs +++ b/components/script/dom/servoparser/html.rs @@ -16,6 +16,7 @@ use html5ever::{QualName, local_name, ns}; use markup5ever::TokenizerResult; use script_bindings::trace::CustomTraceable; use servo_url::ServoUrl; +use style::attr::AttrValue; use style::context::QuirksMode as StyleContextQuirksMode; use xml5ever::LocalName; @@ -116,18 +117,34 @@ impl Tokenizer { } } -fn start_element<S: Serializer>(node: &Element, serializer: &mut S) -> io::Result<()> { - let name = QualName::new(None, node.namespace().clone(), node.local_name().clone()); - let attrs = node - .attrs() - .iter() - .map(|attr| { - let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone()); - let value = attr.value().clone(); - (qname, value) - }) - .collect::<Vec<_>>(); - let attr_refs = attrs.iter().map(|(qname, value)| { +/// <https://html.spec.whatwg.org/multipage/#html-fragment-serialisation-algorithm> +fn start_element<S: Serializer>(element: &Element, serializer: &mut S) -> io::Result<()> { + let name = QualName::new( + None, + element.namespace().clone(), + element.local_name().clone(), + ); + + let mut attributes = vec![]; + + // The "is" value of an element is treated as if it was an attribute and it is serialized before all + // other attributes. If the element already has an "is" attribute then the "is" value is ignored. + if !element.has_attribute(&LocalName::from("is")) { + if let Some(is_value) = element.get_is() { + let qualified_name = QualName::new(None, ns!(), LocalName::from("is")); + + attributes.push((qualified_name, AttrValue::String(is_value.to_string()))); + } + } + + // Collect all the "normal" attributes + attributes.extend(element.attrs().iter().map(|attr| { + let qname = QualName::new(None, attr.namespace().clone(), attr.local_name().clone()); + let value = attr.value().clone(); + (qname, value) + })); + + let attr_refs = attributes.iter().map(|(qname, value)| { let ar: AttrRef = (qname, &**value); ar }); diff --git a/components/script/dom/servoparser/mod.rs b/components/script/dom/servoparser/mod.rs index 3a1efdfb291..9e45124522a 100644 --- a/components/script/dom/servoparser/mod.rs +++ b/components/script/dom/servoparser/mod.rs @@ -1075,7 +1075,8 @@ impl FetchResponseListener for ParserContext { }; let document = &parser.document; let global = &document.global(); - global.report_csp_violations(violations); + // TODO(https://github.com/w3c/webappsec-csp/issues/687): Update after spec is resolved + global.report_csp_violations(violations, None); } } diff --git a/components/script/dom/transformstream.rs b/components/script/dom/transformstream.rs new file mode 100644 index 00000000000..0251498980d --- /dev/null +++ b/components/script/dom/transformstream.rs @@ -0,0 +1,1105 @@ +/* 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/. */ + +use std::cell::Cell; +use std::collections::HashMap; +use std::ptr::{self}; +use std::rc::Rc; + +use base::id::{MessagePortId, MessagePortIndex}; +use constellation_traits::MessagePortImpl; +use dom_struct::dom_struct; +use js::jsapi::{Heap, IsPromiseObject, JSObject}; +use js::jsval::{JSVal, ObjectValue, UndefinedValue}; +use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, IntoHandle}; +use script_bindings::callback::ExceptionHandling; +use script_bindings::realms::InRealm; + +use super::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize; +use super::bindings::structuredclone::StructuredData; +use super::bindings::transferable::Transferable; +use super::messageport::MessagePort; +use super::promisenativehandler::Callback; +use super::types::{TransformStreamDefaultController, WritableStream}; +use crate::dom::bindings::cell::DomRefCell; +use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy; +use crate::dom::bindings::codegen::Bindings::TransformStreamBinding::TransformStreamMethods; +use crate::dom::bindings::codegen::Bindings::TransformerBinding::Transformer; +use crate::dom::bindings::conversions::ConversionResult; +use crate::dom::bindings::error::{Error, Fallible}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object_with_proto}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::countqueuingstrategy::{extract_high_water_mark, extract_size_algorithm}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::readablestream::{ReadableStream, create_readable_stream}; +use crate::dom::types::PromiseNativeHandler; +use crate::dom::underlyingsourcecontainer::UnderlyingSourceType; +use crate::dom::writablestream::create_writable_stream; +use crate::dom::writablestreamdefaultcontroller::UnderlyingSinkType; +use crate::realms::enter_realm; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +impl js::gc::Rootable for TransformBackPressureChangePromiseFulfillment {} + +/// Reacting to backpressureChangePromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct TransformBackPressureChangePromiseFulfillment { + /// The result of reacting to backpressureChangePromise. + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, + + #[ignore_malloc_size_of = "mozjs"] + chunk: Box<Heap<JSVal>>, + + /// The writable used in the fulfillment steps + writable: Dom<WritableStream>, + + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for TransformBackPressureChangePromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Let writable be stream.[[writable]]. + // Let state be writable.[[state]]. + // If state is "erroring", throw writable.[[storedError]]. + if self.writable.is_erroring() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.writable.get_stored_error(error.handle_mut()); + self.result_promise.reject(cx, error.handle(), can_gc); + return; + } + + // Assert: state is "writable". + assert!(self.writable.is_writable()); + + // Return ! TransformStreamDefaultControllerPerformTransform(controller, chunk). + rooted!(in(*cx) let mut chunk = UndefinedValue()); + chunk.set(self.chunk.get()); + let transform_result = self + .controller + .transform_stream_default_controller_perform_transform( + cx, + &self.writable.global(), + chunk.handle(), + can_gc, + ) + .expect("perform transform failed"); + + // PerformTransformFulfillment and PerformTransformRejection do not need + // to be rooted because they only contain an Rc. + let handler = PromiseNativeHandler::new( + &self.writable.global(), + Some(Box::new(PerformTransformFulfillment { + result_promise: self.result_promise.clone(), + })), + Some(Box::new(PerformTransformRejection { + result_promise: self.result_promise.clone(), + })), + can_gc, + ); + + let realm = enter_realm(&*self.writable.global()); + let comp = InRealm::Entered(&realm); + transform_result.append_native_handler(&handler, comp, can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to fulfillment of performTransform as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct PerformTransformFulfillment { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for PerformTransformFulfillment { + fn callback(&self, _cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Fulfilled: resolve the outer promise + self.result_promise.resolve_native(&(), can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to rejection of performTransform as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct PerformTransformRejection { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for PerformTransformRejection { + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Stream already errored in perform_transform, just reject result_promise + self.result_promise.reject(cx, v, can_gc); + } +} + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +/// Reacting to rejection of backpressureChangePromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> +struct BackpressureChangeRejection { + #[ignore_malloc_size_of = "Rc is hard"] + result_promise: Rc<Promise>, +} + +impl Callback for BackpressureChangeRejection { + fn callback(&self, cx: SafeJSContext, reason: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + self.result_promise.reject(cx, reason, can_gc); + } +} + +impl js::gc::Rootable for CancelPromiseFulfillment {} + +/// Reacting to fulfillment of the cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct CancelPromiseFulfillment { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, + #[ignore_malloc_size_of = "mozjs"] + reason: Box<Heap<JSVal>>, +} + +impl Callback for CancelPromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If readable.[[state]] is "errored", reject controller.[[finishPromise]] with readable.[[storedError]]. + if self.readable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.readable.get_stored_error(error.handle_mut()); + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject_native(&error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], reason). + rooted!(in(*cx) let mut reason = UndefinedValue()); + reason.set(self.reason.get()); + self.readable + .get_default_controller() + .error(reason.handle(), can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for CancelPromiseRejection {} + +/// Reacting to rejection of cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct CancelPromiseRejection { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for CancelPromiseRejection { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], r). + self.readable.get_default_controller().error(v, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +impl js::gc::Rootable for SourceCancelPromiseFulfillment {} + +/// Reacting to fulfillment of the cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct SourceCancelPromiseFulfillment { + writeable: Dom<WritableStream>, + controller: Dom<TransformStreamDefaultController>, + stream: Dom<TransformStream>, + #[ignore_malloc_size_of = "mozjs"] + reason: Box<Heap<JSVal>>, +} + +impl Callback for SourceCancelPromiseFulfillment { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If cancelPromise was fulfilled, then: + let finish_promise = self + .controller + .get_finish_promise() + .expect("finish promise is not set"); + + let global = &self.writeable.global(); + // If writable.[[state]] is "errored", reject controller.[[finishPromise]] with writable.[[storedError]]. + if self.writeable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.writeable.get_stored_error(error.handle_mut()); + finish_promise.reject(cx, error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(writable.[[controller]], reason). + rooted!(in(*cx) let mut reason = UndefinedValue()); + reason.set(self.reason.get()); + self.writeable.get_default_controller().error_if_needed( + cx, + reason.handle(), + global, + can_gc, + ); + + // Perform ! TransformStreamUnblockWrite(stream). + self.stream.unblock_write(global, can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + finish_promise.resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for SourceCancelPromiseRejection {} + +/// Reacting to rejection of cancelpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct SourceCancelPromiseRejection { + writeable: Dom<WritableStream>, + controller: Dom<TransformStreamDefaultController>, + stream: Dom<TransformStream>, +} + +impl Callback for SourceCancelPromiseRejection { + /// Reacting to backpressureChangePromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(writable.[[controller]], r). + let global = &self.writeable.global(); + + self.writeable + .get_default_controller() + .error_if_needed(cx, v, global, can_gc); + + // Perform ! TransformStreamUnblockWrite(stream). + self.stream.unblock_write(global, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +impl js::gc::Rootable for FlushPromiseFulfillment {} + +/// Reacting to fulfillment of the flushpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct FlushPromiseFulfillment { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for FlushPromiseFulfillment { + /// Reacting to flushpromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, _v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If flushPromise was fulfilled, then: + let finish_promise = self + .controller + .get_finish_promise() + .expect("finish promise is not set"); + + // If readable.[[state]] is "errored", reject controller.[[finishPromise]] with readable.[[storedError]]. + if self.readable.is_errored() { + rooted!(in(*cx) let mut error = UndefinedValue()); + self.readable.get_stored_error(error.handle_mut()); + finish_promise.reject(cx, error.handle(), can_gc); + } else { + // Otherwise: + // Perform ! ReadableStreamDefaultControllerClose(readable.[[controller]]). + self.readable.get_default_controller().close(can_gc); + + // Resolve controller.[[finishPromise]] with undefined. + finish_promise.resolve_native(&(), can_gc); + } + } +} + +impl js::gc::Rootable for FlushPromiseRejection {} +/// Reacting to rejection of flushpromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> + +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct FlushPromiseRejection { + readable: Dom<ReadableStream>, + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for FlushPromiseRejection { + /// Reacting to flushpromise with the following fulfillment steps: + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // If flushPromise was rejected with reason r, then: + // Perform ! ReadableStreamDefaultControllerError(readable.[[controller]], r). + self.readable.get_default_controller().error(v, can_gc); + + // Reject controller.[[finishPromise]] with r. + self.controller + .get_finish_promise() + .expect("finish promise is not set") + .reject(cx, v, can_gc); + } +} + +/// <https://streams.spec.whatwg.org/#ts-class> +#[dom_struct] +pub struct TransformStream { + reflector_: Reflector, + + /// <https://streams.spec.whatwg.org/#transformstream-backpressure> + backpressure: Cell<bool>, + + /// <https://streams.spec.whatwg.org/#transformstream-backpressurechangepromise> + #[ignore_malloc_size_of = "Rc is hard"] + backpressure_change_promise: DomRefCell<Option<Rc<Promise>>>, + + /// <https://streams.spec.whatwg.org/#transformstream-controller> + controller: MutNullableDom<TransformStreamDefaultController>, + + /// <https://streams.spec.whatwg.org/#transformstream-detached> + detached: Cell<bool>, + + /// <https://streams.spec.whatwg.org/#transformstream-readable> + readable: MutNullableDom<ReadableStream>, + + /// <https://streams.spec.whatwg.org/#transformstream-writable> + writable: MutNullableDom<WritableStream>, +} + +impl TransformStream { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + /// <https://streams.spec.whatwg.org/#initialize-transform-stream> + fn new_inherited() -> TransformStream { + TransformStream { + reflector_: Reflector::new(), + backpressure: Default::default(), + backpressure_change_promise: DomRefCell::new(None), + controller: MutNullableDom::new(None), + detached: Cell::new(false), + readable: MutNullableDom::new(None), + writable: MutNullableDom::new(None), + } + } + + pub(crate) fn new_with_proto( + global: &GlobalScope, + proto: Option<SafeHandleObject>, + can_gc: CanGc, + ) -> DomRoot<TransformStream> { + reflect_dom_object_with_proto( + Box::new(TransformStream::new_inherited()), + global, + proto, + can_gc, + ) + } + + pub(crate) fn get_controller(&self) -> DomRoot<TransformStreamDefaultController> { + self.controller.get().expect("controller is not set") + } + + pub(crate) fn get_writable(&self) -> DomRoot<WritableStream> { + self.writable.get().expect("writable stream is not set") + } + + pub(crate) fn get_readable(&self) -> DomRoot<ReadableStream> { + self.readable.get().expect("readable stream is not set") + } + + pub(crate) fn get_backpressure(&self) -> bool { + self.backpressure.get() + } + + /// <https://streams.spec.whatwg.org/#initialize-transform-stream> + #[allow(clippy::too_many_arguments)] + fn initialize( + &self, + cx: SafeJSContext, + global: &GlobalScope, + start_promise: Rc<Promise>, + writable_high_water_mark: f64, + writable_size_algorithm: Rc<QueuingStrategySize>, + readable_high_water_mark: f64, + readable_size_algorithm: Rc<QueuingStrategySize>, + can_gc: CanGc, + ) -> Fallible<()> { + // Let startAlgorithm be an algorithm that returns startPromise. + // Let writeAlgorithm be the following steps, taking a chunk argument: + // Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk). + // Let abortAlgorithm be the following steps, taking a reason argument: + // Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason). + // Let closeAlgorithm be the following steps: + // Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). + // Set stream.[[writable]] to ! CreateWritableStream(startAlgorithm, writeAlgorithm, + // closeAlgorithm, abortAlgorithm, writableHighWaterMark, writableSizeAlgorithm). + // Note: Those steps are implemented using UnderlyingSinkType::Transform. + + let writable = create_writable_stream( + cx, + global, + writable_high_water_mark, + writable_size_algorithm, + UnderlyingSinkType::Transform(Dom::from_ref(self), start_promise.clone()), + can_gc, + )?; + self.writable.set(Some(&writable)); + + // Let pullAlgorithm be the following steps: + + // Return ! TransformStreamDefaultSourcePullAlgorithm(stream). + + // Let cancelAlgorithm be the following steps, taking a reason argument: + + // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason). + + // Set stream.[[readable]] to ! CreateReadableStream(startAlgorithm, pullAlgorithm, + // cancelAlgorithm, readableHighWaterMark, readableSizeAlgorithm). + + let readable = create_readable_stream( + global, + UnderlyingSourceType::Transform(Dom::from_ref(self), start_promise.clone()), + Some(readable_size_algorithm), + Some(readable_high_water_mark), + can_gc, + ); + self.readable.set(Some(&readable)); + + // Set stream.[[backpressure]] and stream.[[backpressureChangePromise]] to undefined. + // Note: This is done in the constructor. + + // Perform ! TransformStreamSetBackpressure(stream, true). + self.set_backpressure(global, true, can_gc); + + // Set stream.[[controller]] to undefined. + self.controller.set(None); + + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-set-backpressure> + pub(crate) fn set_backpressure(&self, global: &GlobalScope, backpressure: bool, can_gc: CanGc) { + // Assert: stream.[[backpressure]] is not backpressure. + assert!(self.backpressure.get() != backpressure); + + // If stream.[[backpressureChangePromise]] is not undefined, resolve + // stream.[[backpressureChangePromise]] with undefined. + if let Some(promise) = self.backpressure_change_promise.borrow_mut().take() { + promise.resolve_native(&(), can_gc); + } + + // Set stream.[[backpressureChangePromise]] to a new promise.; + *self.backpressure_change_promise.borrow_mut() = Some(Promise::new(global, can_gc)); + + // Set stream.[[backpressure]] to backpressure. + self.backpressure.set(backpressure); + } + + /// <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller> + fn set_up_transform_stream_default_controller( + &self, + controller: &TransformStreamDefaultController, + ) { + // Assert: stream implements TransformStream. + // Note: this is checked with type. + + // Assert: stream.[[controller]] is undefined. + assert!(self.controller.get().is_none()); + + // Set controller.[[stream]] to stream. + controller.set_stream(self); + + // Set stream.[[controller]] to controller. + self.controller.set(Some(controller)); + + // Set controller.[[transformAlgorithm]] to transformAlgorithm. + // Set controller.[[flushAlgorithm]] to flushAlgorithm. + // Set controller.[[cancelAlgorithm]] to cancelAlgorithm. + // Note: These are set in the constructor. + } + + /// <https://streams.spec.whatwg.org/#set-up-transform-stream-default-controller-from-transformer> + fn set_up_transform_stream_default_controller_from_transformer( + &self, + global: &GlobalScope, + transformer_obj: SafeHandleObject, + transformer: &Transformer, + can_gc: CanGc, + ) { + // Let controller be a new TransformStreamDefaultController. + let controller = TransformStreamDefaultController::new(global, transformer, can_gc); + + // Let transformAlgorithm be the following steps, taking a chunk argument: + // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). + // If result is an abrupt completion, return a promise rejected with result.[[Value]]. + // Otherwise, return a promise resolved with undefined. + + // Let flushAlgorithm be an algorithm which returns a promise resolved with undefined. + // Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. + + // If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which + // takes an argument + // chunk and returns the result of invoking transformerDict["transform"] with argument + // list « chunk, controller » + // and callback this value transformer. + + // If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns + // the result + // of invoking transformerDict["flush"] with argument list « controller » and callback + // this value transformer. + + // If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument + // reason and returns the result of invoking transformerDict["cancel"] with argument list « reason » + // and callback this value transformer. + controller.set_transform_obj(transformer_obj); + + // Perform ! SetUpTransformStreamDefaultController(stream, controller, + // transformAlgorithm, flushAlgorithm, cancelAlgorithm). + self.set_up_transform_stream_default_controller(&controller); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-write-algorithm> + pub(crate) fn transform_stream_default_sink_write_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Assert: stream.[[writable]].[[state]] is "writable". + assert!(self.writable.get().is_some()); + + // Let controller be stream.[[controller]]. + let controller = self.controller.get().expect("controller is not set"); + + // If stream.[[backpressure]] is true, + if self.backpressure.get() { + // Let backpressureChangePromise be stream.[[backpressureChangePromise]]. + let backpressure_change_promise = self.backpressure_change_promise.borrow(); + + // Assert: backpressureChangePromise is not undefined. + assert!(backpressure_change_promise.is_some()); + + // Return the result of reacting to backpressureChangePromise with the following fulfillment steps: + let result_promise = Promise::new(global, can_gc); + rooted!(in(*cx) let mut fulfillment_handler = Some(TransformBackPressureChangePromiseFulfillment { + controller: Dom::from_ref(&controller), + writable: Dom::from_ref(&self.writable.get().expect("writable stream")), + chunk: Heap::boxed(chunk.get()), + result_promise: result_promise.clone(), + })); + + let handler = PromiseNativeHandler::new( + global, + fulfillment_handler.take().map(|h| Box::new(h) as Box<_>), + Some(Box::new(BackpressureChangeRejection { + result_promise: result_promise.clone(), + })), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + backpressure_change_promise + .as_ref() + .expect("Promise must be some by now.") + .append_native_handler(&handler, comp, can_gc); + + return Ok(result_promise); + } + + // Return ! TransformStreamDefaultControllerPerformTransform(controller, chunk). + controller.transform_stream_default_controller_perform_transform(cx, global, chunk, can_gc) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-abort-algorithm> + pub(crate) fn transform_stream_default_sink_abort_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self.controller.get().expect("controller is not set"); + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let readable be stream.[[readable]]. + let readable = self.readable.get().expect("readable stream is not set"); + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let cancelPromise be the result of performing controller.[[cancelAlgorithm]], passing reason. + let cancel_promise = controller.perform_cancel(cx, global, reason, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to cancelPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(CancelPromiseFulfillment { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + reason: Heap::boxed(reason.get()), + })), + Some(Box::new(CancelPromiseRejection { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + cancel_promise.append_native_handler(&handler, comp, can_gc); + + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-sink-close-algorithm> + pub(crate) fn transform_stream_default_sink_close_algorithm( + &self, + cx: SafeJSContext, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self + .controller + .get() + .ok_or(Error::Type("controller is not set".to_string()))?; + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let readable be stream.[[readable]]. + let readable = self + .readable + .get() + .ok_or(Error::Type("readable stream is not set".to_string()))?; + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let flushPromise be the result of performing controller.[[flushAlgorithm]]. + let flush_promise = controller.perform_flush(cx, global, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to flushPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(FlushPromiseFulfillment { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + Some(Box::new(FlushPromiseRejection { + readable: Dom::from_ref(&readable), + controller: Dom::from_ref(&controller), + })), + can_gc, + ); + + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + flush_promise.append_native_handler(&handler, comp, can_gc); + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-source-cancel> + pub(crate) fn transform_stream_default_source_cancel( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let controller be stream.[[controller]]. + let controller = self + .controller + .get() + .ok_or(Error::Type("controller is not set".to_string()))?; + + // If controller.[[finishPromise]] is not undefined, return controller.[[finishPromise]]. + if let Some(finish_promise) = controller.get_finish_promise() { + return Ok(finish_promise); + } + + // Let writable be stream.[[writable]]. + let writable = self + .writable + .get() + .ok_or(Error::Type("writable stream is not set".to_string()))?; + + // Let controller.[[finishPromise]] be a new promise. + controller.set_finish_promise(Promise::new(global, can_gc)); + + // Let cancelPromise be the result of performing controller.[[cancelAlgorithm]], passing reason. + let cancel_promise = controller.perform_cancel(cx, global, reason, can_gc)?; + + // Perform ! TransformStreamDefaultControllerClearAlgorithms(controller). + controller.clear_algorithms(); + + // React to cancelPromise: + let handler = PromiseNativeHandler::new( + global, + Some(Box::new(SourceCancelPromiseFulfillment { + writeable: Dom::from_ref(&writable), + controller: Dom::from_ref(&controller), + stream: Dom::from_ref(self), + reason: Heap::boxed(reason.get()), + })), + Some(Box::new(SourceCancelPromiseRejection { + writeable: Dom::from_ref(&writable), + controller: Dom::from_ref(&controller), + stream: Dom::from_ref(self), + })), + can_gc, + ); + + // Return controller.[[finishPromise]]. + let finish_promise = controller + .get_finish_promise() + .expect("finish promise is not set"); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + cancel_promise.append_native_handler(&handler, comp, can_gc); + Ok(finish_promise) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-source-pull> + pub(crate) fn transform_stream_default_source_pull( + &self, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Assert: stream.[[backpressure]] is true. + assert!(self.backpressure.get()); + + // Assert: stream.[[backpressureChangePromise]] is not undefined. + assert!(self.backpressure_change_promise.borrow().is_some()); + + // Perform ! TransformStreamSetBackpressure(stream, false). + self.set_backpressure(global, false, can_gc); + + // Return stream.[[backpressureChangePromise]]. + Ok(self + .backpressure_change_promise + .borrow() + .clone() + .expect("Promise must be some by now.")) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-error-writable-and-unblock-write> + pub(crate) fn error_writable_and_unblock_write( + &self, + cx: SafeJSContext, + global: &GlobalScope, + error: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! TransformStreamDefaultControllerClearAlgorithms(stream.[[controller]]). + self.get_controller().clear_algorithms(); + + // Perform ! WritableStreamDefaultControllerErrorIfNeeded(stream.[[writable]].[[controller]], e). + self.get_writable() + .get_default_controller() + .error_if_needed(cx, error, global, can_gc); + + // Perform ! TransformStreamUnblockWrite(stream). + self.unblock_write(global, can_gc) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-unblock-write> + pub(crate) fn unblock_write(&self, global: &GlobalScope, can_gc: CanGc) { + // If stream.[[backpressure]] is true, perform ! TransformStreamSetBackpressure(stream, false). + if self.backpressure.get() { + self.set_backpressure(global, false, can_gc); + } + } + + /// <https://streams.spec.whatwg.org/#transform-stream-error> + pub(crate) fn error( + &self, + cx: SafeJSContext, + global: &GlobalScope, + error: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! ReadableStreamDefaultControllerError(stream.[[readable]].[[controller]], e). + self.get_readable() + .get_default_controller() + .error(error, can_gc); + + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, e). + self.error_writable_and_unblock_write(cx, global, error, can_gc); + } +} + +impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream { + /// <https://streams.spec.whatwg.org/#ts-constructor> + #[allow(unsafe_code)] + fn Constructor( + cx: SafeJSContext, + global: &GlobalScope, + proto: Option<SafeHandleObject>, + can_gc: CanGc, + transformer: Option<*mut JSObject>, + writable_strategy: &QueuingStrategy, + readable_strategy: &QueuingStrategy, + ) -> Fallible<DomRoot<TransformStream>> { + // If transformer is missing, set it to null. + rooted!(in(*cx) let transformer_obj = transformer.unwrap_or(ptr::null_mut())); + + // Let underlyingSinkDict be underlyingSink, + // converted to an IDL value of type UnderlyingSink. + let transformer_dict = if !transformer_obj.is_null() { + rooted!(in(*cx) let obj_val = ObjectValue(transformer_obj.get())); + match Transformer::new(cx, obj_val.handle()) { + Ok(ConversionResult::Success(val)) => val, + Ok(ConversionResult::Failure(error)) => return Err(Error::Type(error.to_string())), + _ => { + return Err(Error::JSFailed); + }, + } + } else { + Transformer::empty() + }; + + // If transformerDict["readableType"] exists, throw a RangeError exception. + if !transformer_dict.readableType.handle().is_undefined() { + return Err(Error::Range("readableType is set".to_string())); + } + + // If transformerDict["writableType"] exists, throw a RangeError exception. + if !transformer_dict.writableType.handle().is_undefined() { + return Err(Error::Range("writableType is set".to_string())); + } + + // Let readableHighWaterMark be ? ExtractHighWaterMark(readableStrategy, 0). + let readable_high_water_mark = extract_high_water_mark(readable_strategy, 0.0)?; + + // Let readableSizeAlgorithm be ! ExtractSizeAlgorithm(readableStrategy). + let readable_size_algorithm = extract_size_algorithm(readable_strategy, can_gc); + + // Let writableHighWaterMark be ? ExtractHighWaterMark(writableStrategy, 1). + let writable_high_water_mark = extract_high_water_mark(writable_strategy, 1.0)?; + + // Let writableSizeAlgorithm be ! ExtractSizeAlgorithm(writableStrategy). + let writable_size_algorithm = extract_size_algorithm(writable_strategy, can_gc); + + // Let startPromise be a new promise. + let start_promise = Promise::new(global, can_gc); + + // Perform ! InitializeTransformStream(this, startPromise, writableHighWaterMark, + // writableSizeAlgorithm, readableHighWaterMark, readableSizeAlgorithm). + let stream = TransformStream::new_with_proto(global, proto, can_gc); + stream.initialize( + cx, + global, + start_promise.clone(), + writable_high_water_mark, + writable_size_algorithm, + readable_high_water_mark, + readable_size_algorithm, + can_gc, + )?; + + // Perform ? SetUpTransformStreamDefaultControllerFromTransformer(this, transformer, transformerDict). + stream.set_up_transform_stream_default_controller_from_transformer( + global, + transformer_obj.handle(), + &transformer_dict, + can_gc, + ); + + // If transformerDict["start"] exists, then resolve startPromise with the + // result of invoking transformerDict["start"] + // with argument list « this.[[controller]] » and callback this value transformer. + if let Some(start) = &transformer_dict.start { + rooted!(in(*cx) let mut result_object = ptr::null_mut::<JSObject>()); + rooted!(in(*cx) let mut result: JSVal); + rooted!(in(*cx) let this_object = transformer_obj.get()); + start.Call_( + &this_object.handle(), + &stream.get_controller(), + result.handle_mut(), + ExceptionHandling::Rethrow, + can_gc, + )?; + let is_promise = unsafe { + if result.is_object() { + result_object.set(result.to_object()); + IsPromiseObject(result_object.handle().into_handle()) + } else { + false + } + }; + let promise = if is_promise { + let promise = Promise::new_with_js_promise(result_object.handle(), cx); + promise + } else { + Promise::new_resolved(global, cx, result.get(), can_gc) + }; + start_promise.resolve_native(&promise, can_gc); + } else { + // Otherwise, resolve startPromise with undefined. + start_promise.resolve_native(&(), can_gc); + }; + + Ok(stream) + } + + /// <https://streams.spec.whatwg.org/#ts-readable> + fn Readable(&self) -> DomRoot<ReadableStream> { + // Return this.[[readable]]. + self.readable.get().expect("readable stream is not set") + } + + /// <https://streams.spec.whatwg.org/#ts-writable> + fn Writable(&self) -> DomRoot<WritableStream> { + // Return this.[[writable]]. + self.writable.get().expect("writable stream is not set") + } +} + +/// <https://streams.spec.whatwg.org/#ts-transfer> +impl Transferable for TransformStream { + type Index = MessagePortIndex; + type Data = MessagePortImpl; + + fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> { + let global = self.global(); + let realm = enter_realm(&*global); + let comp = InRealm::Entered(&realm); + let cx = GlobalScope::get_cx(); + let can_gc = CanGc::note(); + + // Let readable be value.[[readable]]. + let readable = self.get_readable(); + + // Let writable be value.[[writable]]. + 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() { + 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()); + + // 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); + + // 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(); + + // Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »). + // Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »). + port_2.transfer() + } + + fn transfer_receive( + owner: &GlobalScope, + id: MessagePortId, + port_impl: MessagePortImpl, + ) -> Result<DomRoot<Self>, ()> { + let can_gc = CanGc::note(); + + // 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)?; + + // 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)); + + Ok(stream) + } + + fn serialized_storage<'a>( + 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, + } + } +} diff --git a/components/script/dom/transformstreamdefaultcontroller.rs b/components/script/dom/transformstreamdefaultcontroller.rs new file mode 100644 index 00000000000..41813ef6f96 --- /dev/null +++ b/components/script/dom/transformstreamdefaultcontroller.rs @@ -0,0 +1,420 @@ +/* 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/. */ + +use std::cell::RefCell; +use std::rc::Rc; + +use dom_struct::dom_struct; +use js::jsapi::{ + ExceptionStackBehavior, Heap, JS_IsExceptionPending, JS_SetPendingException, JSObject, +}; +use js::jsval::UndefinedValue; +use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue}; + +use super::bindings::cell::DomRefCell; +use super::bindings::codegen::Bindings::TransformerBinding::{ + Transformer, TransformerCancelCallback, TransformerFlushCallback, TransformerTransformCallback, +}; +use super::types::TransformStream; +use crate::dom::bindings::callback::ExceptionHandling; +use crate::dom::bindings::codegen::Bindings::TransformStreamDefaultControllerBinding::TransformStreamDefaultControllerMethods; +use crate::dom::bindings::error::{Error, ErrorToJsval, Fallible}; +use crate::dom::bindings::reflector::{DomGlobal, Reflector, reflect_dom_object}; +use crate::dom::bindings::root::{Dom, DomRoot, MutNullableDom}; +use crate::dom::globalscope::GlobalScope; +use crate::dom::promise::Promise; +use crate::dom::promisenativehandler::{Callback, PromiseNativeHandler}; +use crate::realms::{InRealm, enter_realm}; +use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; + +impl js::gc::Rootable for TransformTransformPromiseRejection {} + +/// Reacting to transformPromise as part of +/// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform> +#[derive(JSTraceable, MallocSizeOf)] +#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)] +struct TransformTransformPromiseRejection { + controller: Dom<TransformStreamDefaultController>, +} + +impl Callback for TransformTransformPromiseRejection { + /// Reacting to transformPromise with the following fulfillment steps: + #[allow(unsafe_code)] + fn callback(&self, cx: SafeJSContext, v: SafeHandleValue, _realm: InRealm, can_gc: CanGc) { + // Perform ! TransformStreamError(controller.[[stream]], r). + self.controller + .error(cx, &self.controller.global(), v, can_gc); + + // Throw r. + // Note: this is done part of perform_transform(). + } +} + +/// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller> +#[dom_struct] +pub struct TransformStreamDefaultController { + reflector_: Reflector, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-cancelalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + cancel: RefCell<Option<Rc<TransformerCancelCallback>>>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-flushalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + flush: RefCell<Option<Rc<TransformerFlushCallback>>>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-transformalgorithm> + #[ignore_malloc_size_of = "Rc is hard"] + transform: RefCell<Option<Rc<TransformerTransformCallback>>>, + + /// The JS object used as `this` when invoking sink algorithms. + #[ignore_malloc_size_of = "mozjs"] + transform_obj: Heap<*mut JSObject>, + + /// <https://streams.spec.whatwg.org/#TransformStreamDefaultController-stream> + stream: MutNullableDom<TransformStream>, + + /// <https://streams.spec.whatwg.org/#transformstreamdefaultcontroller-finishpromise> + #[ignore_malloc_size_of = "Rc is hard"] + finish_promise: DomRefCell<Option<Rc<Promise>>>, +} + +impl TransformStreamDefaultController { + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + fn new_inherited(transformer: &Transformer) -> TransformStreamDefaultController { + TransformStreamDefaultController { + reflector_: Reflector::new(), + cancel: RefCell::new(transformer.cancel.clone()), + flush: RefCell::new(transformer.flush.clone()), + transform: RefCell::new(transformer.transform.clone()), + finish_promise: DomRefCell::new(None), + stream: MutNullableDom::new(None), + transform_obj: Default::default(), + } + } + + #[cfg_attr(crown, allow(crown::unrooted_must_root))] + pub(crate) fn new( + global: &GlobalScope, + transformer: &Transformer, + can_gc: CanGc, + ) -> DomRoot<TransformStreamDefaultController> { + reflect_dom_object( + Box::new(TransformStreamDefaultController::new_inherited(transformer)), + global, + can_gc, + ) + } + + /// Setting the JS object after the heap has settled down. + pub(crate) fn set_transform_obj(&self, this_object: SafeHandleObject) { + self.transform_obj.set(*this_object); + } + + pub(crate) fn set_stream(&self, stream: &TransformStream) { + self.stream.set(Some(stream)); + } + + pub(crate) fn get_finish_promise(&self) -> Option<Rc<Promise>> { + self.finish_promise.borrow().clone() + } + + pub(crate) fn set_finish_promise(&self, promise: Rc<Promise>) { + *self.finish_promise.borrow_mut() = Some(promise); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-perform-transform> + pub(crate) fn transform_stream_default_controller_perform_transform( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // Let transformPromise be the result of performing controller.[[transformAlgorithm]], passing chunk. + let transform_promise = self.perform_transform(cx, global, chunk, can_gc)?; + + // Return the result of reacting to transformPromise with the following rejection steps given the argument r: + rooted!(in(*cx) let mut reject_handler = Some(TransformTransformPromiseRejection { + controller: Dom::from_ref(self), + })); + + let handler = PromiseNativeHandler::new( + global, + None, + reject_handler.take().map(|h| Box::new(h) as Box<_>), + can_gc, + ); + let realm = enter_realm(global); + let comp = InRealm::Entered(&realm); + transform_promise.append_native_handler(&handler, comp, can_gc); + + Ok(transform_promise) + } + + pub(crate) fn perform_transform( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["transform"] exists, set transformAlgorithm to an algorithm which + // takes an argument chunk and returns the result of invoking transformerDict["transform"] with argument list + // « chunk, controller » and callback this value transformer. + let algo = self.transform.borrow().clone(); + let result = if let Some(transform) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = transform.Call_( + &this_object.handle(), + chunk, + self, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let transformAlgorithm be the following steps, taking a chunk argument: + // Let result be TransformStreamDefaultControllerEnqueue(controller, chunk). + // If result is an abrupt completion, return a promise rejected with result.[[Value]]. + let promise = if let Err(error) = self.enqueue(cx, global, chunk, can_gc) { + rooted!(in(*cx) let mut error_val = UndefinedValue()); + error.to_jsval(cx, global, error_val.handle_mut(), can_gc); + Promise::new_rejected(global, cx, error_val.handle(), can_gc) + } else { + // Otherwise, return a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + promise + }; + + Ok(result) + } + + pub(crate) fn perform_cancel( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["cancel"] exists, set cancelAlgorithm to an algorithm which takes an argument + // reason and returns the result of invoking transformerDict["cancel"] with argument list « reason » + // and callback this value transformer. + let algo = self.cancel.borrow().clone(); + let result = if let Some(cancel) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = cancel.Call_( + &this_object.handle(), + chunk, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let cancelAlgorithm be an algorithm which returns a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + Ok(result) + } + + pub(crate) fn perform_flush( + &self, + cx: SafeJSContext, + global: &GlobalScope, + can_gc: CanGc, + ) -> Fallible<Rc<Promise>> { + // If transformerDict["flush"] exists, set flushAlgorithm to an algorithm which returns the result of + // invoking transformerDict["flush"] with argument list « controller » and callback this value transformer. + let algo = self.flush.borrow().clone(); + let result = if let Some(flush) = algo { + rooted!(in(*cx) let this_object = self.transform_obj.get()); + let call_result = flush.Call_( + &this_object.handle(), + self, + ExceptionHandling::Rethrow, + can_gc, + ); + match call_result { + Ok(p) => p, + Err(e) => { + let p = Promise::new(global, can_gc); + p.reject_error(e, can_gc); + p + }, + } + } else { + // Let flushAlgorithm be an algorithm which returns a promise resolved with undefined. + Promise::new_resolved(global, cx, (), can_gc) + }; + + Ok(result) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-enqueue> + #[allow(unsafe_code)] + pub(crate) fn enqueue( + &self, + cx: SafeJSContext, + global: &GlobalScope, + chunk: SafeHandleValue, + can_gc: CanGc, + ) -> Fallible<()> { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().expect("stream is null"); + + // Let readableController be stream.[[readable]].[[controller]]. + let readable = stream.get_readable(); + let readable_controller = readable.get_default_controller(); + + // If ! ReadableStreamDefaultControllerCanCloseOrEnqueue(readableController) + // is false, throw a TypeError exception. + if !readable_controller.can_close_or_enqueue() { + return Err(Error::Type( + "ReadableStreamDefaultControllerCanCloseOrEnqueue is false".to_owned(), + )); + } + + // Let enqueueResult be ReadableStreamDefaultControllerEnqueue(readableController, chunk). + // If enqueueResult is an abrupt completion, + if let Err(error) = readable_controller.enqueue(cx, chunk, can_gc) { + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, enqueueResult.[[Value]]). + rooted!(in(*cx) let mut rooted_error = UndefinedValue()); + error + .clone() + .to_jsval(cx, global, rooted_error.handle_mut(), can_gc); + stream.error_writable_and_unblock_write(cx, global, rooted_error.handle(), can_gc); + + // Throw stream.[[readable]].[[storedError]]. + unsafe { + if !JS_IsExceptionPending(*cx) { + rooted!(in(*cx) let mut stored_error = UndefinedValue()); + readable.get_stored_error(stored_error.handle_mut()); + + JS_SetPendingException( + *cx, + stored_error.handle().into(), + ExceptionStackBehavior::Capture, + ); + } + } + return Err(error); + } + + // Let backpressure be ! ReadableStreamDefaultControllerHasBackpressure(readableController). + let backpressure = readable_controller.has_backpressure(); + + // If backpressure is not stream.[[backpressure]], + if backpressure != stream.get_backpressure() { + // Assert: backpressure is true. + assert!(backpressure); + + // Perform ! TransformStreamSetBackpressure(stream, true). + stream.set_backpressure(global, true, can_gc); + } + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-error> + pub(crate) fn error( + &self, + cx: SafeJSContext, + global: &GlobalScope, + reason: SafeHandleValue, + can_gc: CanGc, + ) { + // Perform ! TransformStreamError(controller.[[stream]], e). + self.stream + .get() + .expect("stream is undefined") + .error(cx, global, reason, can_gc); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-clear-algorithms> + pub(crate) fn clear_algorithms(&self) { + // Set controller.[[transformAlgorithm]] to undefined. + self.transform.replace(None); + + // Set controller.[[flushAlgorithm]] to undefined. + self.flush.replace(None); + + // Set controller.[[cancelAlgorithm]] to undefined. + self.cancel.replace(None); + } + + /// <https://streams.spec.whatwg.org/#transform-stream-default-controller-terminate> + pub(crate) fn terminate(&self, cx: SafeJSContext, global: &GlobalScope, can_gc: CanGc) { + // Let stream be controller.[[stream]]. + let stream = self.stream.get().expect("stream is null"); + + // Let readableController be stream.[[readable]].[[controller]]. + let readable = stream.get_readable(); + let readable_controller = readable.get_default_controller(); + + // Perform ! ReadableStreamDefaultControllerClose(readableController). + readable_controller.close(can_gc); + + // Let error be a TypeError exception indicating that the stream has been terminated. + let error = Error::Type("stream has been terminated".to_owned()); + + // Perform ! TransformStreamErrorWritableAndUnblockWrite(stream, error). + rooted!(in(*cx) let mut rooted_error = UndefinedValue()); + error.to_jsval(cx, global, rooted_error.handle_mut(), can_gc); + stream.error_writable_and_unblock_write(cx, global, rooted_error.handle(), can_gc); + } +} + +impl TransformStreamDefaultControllerMethods<crate::DomTypeHolder> + for TransformStreamDefaultController +{ + /// <https://streams.spec.whatwg.org/#ts-default-controller-desired-size> + fn GetDesiredSize(&self) -> Option<f64> { + // Let readableController be this.[[stream]].[[readable]].[[controller]]. + let readable_controller = self + .stream + .get() + .expect("stream is null") + .get_readable() + .get_default_controller(); + + // Return ! ReadableStreamDefaultControllerGetDesiredSize(readableController). + readable_controller.get_desired_size() + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-enqueue> + fn Enqueue(&self, cx: SafeJSContext, chunk: SafeHandleValue, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerEnqueue(this, chunk). + self.enqueue(cx, &self.global(), chunk, can_gc) + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-error> + fn Error(&self, cx: SafeJSContext, reason: SafeHandleValue, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerError(this, e). + self.error(cx, &self.global(), reason, can_gc); + Ok(()) + } + + /// <https://streams.spec.whatwg.org/#ts-default-controller-terminate> + fn Terminate(&self, can_gc: CanGc) -> Fallible<()> { + // Perform ? TransformStreamDefaultControllerTerminate(this). + self.terminate(GlobalScope::get_cx(), &self.global(), can_gc); + Ok(()) + } +} diff --git a/components/script/dom/trustedhtml.rs b/components/script/dom/trustedhtml.rs index 8508f28c150..d1ca3cd5e71 100644 --- a/components/script/dom/trustedhtml.rs +++ b/components/script/dom/trustedhtml.rs @@ -6,8 +6,11 @@ use std::fmt; use dom_struct::dom_struct; +use crate::conversions::Convert; use crate::dom::bindings::codegen::Bindings::TrustedHTMLBinding::TrustedHTMLMethods; -use crate::dom::bindings::codegen::UnionTypes::TrustedHTMLOrString; +use crate::dom::bindings::codegen::UnionTypes::{ + TrustedHTMLOrNullIsEmptyString, TrustedHTMLOrString, +}; use crate::dom::bindings::error::Fallible; use crate::dom::bindings::reflector::{Reflector, reflect_dom_object}; use crate::dom::bindings::root::DomRoot; @@ -80,3 +83,16 @@ impl TrustedHTMLMethods<crate::DomTypeHolder> for TrustedHTML { DOMString::from(&*self.data) } } + +impl Convert<TrustedHTMLOrString> for TrustedHTMLOrNullIsEmptyString { + fn convert(self) -> TrustedHTMLOrString { + match self { + TrustedHTMLOrNullIsEmptyString::TrustedHTML(trusted_html) => { + TrustedHTMLOrString::TrustedHTML(trusted_html) + }, + TrustedHTMLOrNullIsEmptyString::NullIsEmptyString(str) => { + TrustedHTMLOrString::String(str) + }, + } + } +} diff --git a/components/script/dom/trustedtypepolicyfactory.rs b/components/script/dom/trustedtypepolicyfactory.rs index 0927446b904..284fa7045eb 100644 --- a/components/script/dom/trustedtypepolicyfactory.rs +++ b/components/script/dom/trustedtypepolicyfactory.rs @@ -66,7 +66,7 @@ impl TrustedTypePolicyFactory { (CheckResult::Allowed, Vec::new()) }; - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); // Step 2: If allowedByCSP is "Blocked", throw a TypeError and abort further steps. if allowed_by_csp == CheckResult::Blocked { @@ -230,7 +230,7 @@ impl TrustedTypePolicyFactory { .should_sink_type_mismatch_violation_be_blocked_by_csp( sink, sink_group, &input, ); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); // Step 6.2: If disposition is “Allowed”, return stringified input and abort further steps. if disposition == CheckResult::Allowed { Ok(input) diff --git a/components/script/dom/underlyingsourcecontainer.rs b/components/script/dom/underlyingsourcecontainer.rs index 4acb58bafef..dd4b867df45 100644 --- a/components/script/dom/underlyingsourcecontainer.rs +++ b/components/script/dom/underlyingsourcecontainer.rs @@ -20,6 +20,7 @@ use crate::dom::defaultteeunderlyingsource::DefaultTeeUnderlyingSource; use crate::dom::globalscope::GlobalScope; use crate::dom::messageport::MessagePort; use crate::dom::promise::Promise; +use crate::dom::transformstream::TransformStream; use crate::script_runtime::{CanGc, JSContext as SafeJSContext}; /// <https://streams.spec.whatwg.org/#underlying-source-api> @@ -46,8 +47,7 @@ pub(crate) enum UnderlyingSourceType { /// A struct representing a JS object as underlying source, /// and the actual JS object for use as `thisArg` in callbacks. /// This is used for the `TransformStream` API. - #[allow(unused)] - Transform(/* Dom<TransformStream>, Rc<Promise>*/), + Transform(Dom<TransformStream>, Rc<Promise>), } impl UnderlyingSourceType { @@ -139,9 +139,9 @@ impl UnderlyingSourceContainer { // Call the cancel algorithm for the appropriate branch. tee_underlying_source.cancel_algorithm(cx, global, reason, can_gc) }, - UnderlyingSourceType::Transform() => { + UnderlyingSourceType::Transform(stream, _) => { // Return ! TransformStreamDefaultSourceCancelAlgorithm(stream, reason). - todo!(); + Some(stream.transform_stream_default_source_cancel(cx, global, reason, can_gc)) }, UnderlyingSourceType::Transfer(port) => { // Let cancelAlgorithm be the following steps, taking a reason argument: @@ -213,9 +213,9 @@ impl UnderlyingSourceContainer { Some(Ok(promise)) }, // Note: other source type have no pull steps for now. - UnderlyingSourceType::Transform() => { + UnderlyingSourceType::Transform(stream, _) => { // Return ! TransformStreamDefaultSourcePullAlgorithm(stream). - todo!(); + Some(stream.transform_stream_default_source_pull(&self.global(), can_gc)) }, _ => None, } @@ -280,9 +280,9 @@ impl UnderlyingSourceContainer { // from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable None }, - UnderlyingSourceType::Transform() => { - // Some(transform_underlying_source.start_algorithm()) - todo!(); + UnderlyingSourceType::Transform(_, start_promise) => { + // Let startAlgorithm be an algorithm that returns startPromise. + Some(Ok(start_promise.clone())) }, _ => None, } diff --git a/components/script/dom/websocket.rs b/components/script/dom/websocket.rs index b9b1b50c901..bbb637dfe28 100644 --- a/components/script/dom/websocket.rs +++ b/components/script/dom/websocket.rs @@ -471,7 +471,7 @@ struct ReportCSPViolationTask { impl TaskOnce for ReportCSPViolationTask { fn run_once(self) { let global = self.websocket.root().global(); - global.report_csp_violations(self.violations); + global.report_csp_violations(self.violations, None); } } diff --git a/components/script/dom/window.rs b/components/script/dom/window.rs index d888cc8d917..24e694b4f06 100644 --- a/components/script/dom/window.rs +++ b/components/script/dom/window.rs @@ -65,6 +65,7 @@ use profile_traits::time::ProfilerChan as TimeProfilerChan; use script_bindings::codegen::GenericBindings::NavigatorBinding::NavigatorMethods; use script_bindings::codegen::GenericBindings::PerformanceBinding::PerformanceMethods; use script_bindings::interfaces::WindowHelpers; +use script_bindings::root::Root; use script_layout_interface::{ FragmentType, Layout, PendingImageState, QueryMsg, Reflow, ReflowGoal, ReflowRequest, TrustedNodeAddress, combine_id_with_fragment_type, @@ -146,6 +147,7 @@ use crate::dom::performance::Performance; use crate::dom::promise::Promise; use crate::dom::screen::Screen; use crate::dom::selection::Selection; +use crate::dom::shadowroot::ShadowRoot; use crate::dom::storage::Storage; #[cfg(feature = "bluetooth")] use crate::dom::testrunner::TestRunner; @@ -165,7 +167,7 @@ use crate::script_runtime::{CanGc, JSContext, Runtime}; use crate::script_thread::ScriptThread; use crate::timers::{IsInterval, TimerCallback}; use crate::unminify::unminified_path; -use crate::webdriver_handlers::jsval_to_webdriver; +use crate::webdriver_handlers::{find_node_by_unique_id_in_document, jsval_to_webdriver}; use crate::{fetch, window_named_properties}; /// A callback to call when a response comes back from the `ImageCache`. @@ -1394,6 +1396,30 @@ impl WindowMethods<crate::DomTypeHolder> for Window { } } + fn WebdriverElement(&self, id: DOMString) -> Option<DomRoot<Element>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast) + } + + fn WebdriverFrame(&self, id: DOMString) -> Option<DomRoot<Element>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast::<HTMLIFrameElement>) + .map(Root::upcast::<Element>) + } + + fn WebdriverWindow(&self, _id: DOMString) -> Option<DomRoot<Window>> { + warn!("Window references are not supported in webdriver yet"); + None + } + + fn WebdriverShadowRoot(&self, id: DOMString) -> Option<DomRoot<ShadowRoot>> { + find_node_by_unique_id_in_document(&self.Document(), id.into()) + .ok() + .and_then(Root::downcast) + } + // https://drafts.csswg.org/cssom/#dom-window-getcomputedstyle fn GetComputedStyle( &self, diff --git a/components/script/dom/writablestream.rs b/components/script/dom/writablestream.rs index 1b029f592de..f528f4fbde2 100644 --- a/components/script/dom/writablestream.rs +++ b/components/script/dom/writablestream.rs @@ -962,14 +962,13 @@ impl WritableStream { /// <https://streams.spec.whatwg.org/#create-writable-stream> #[cfg_attr(crown, allow(crown::unrooted_must_root))] -#[allow(unused)] pub(crate) fn create_writable_stream( cx: SafeJSContext, global: &GlobalScope, - can_gc: CanGc, writable_high_water_mark: f64, writable_size_algorithm: Rc<QueuingStrategySize>, underlying_sink_type: UnderlyingSinkType, + can_gc: CanGc, ) -> Fallible<DomRoot<WritableStream>> { // Assert: ! IsNonNegativeNumber(highWaterMark) is true. assert!(writable_high_water_mark >= 0.0); diff --git a/components/script/dom/writablestreamdefaultcontroller.rs b/components/script/dom/writablestreamdefaultcontroller.rs index 084165a6892..135ee6faa59 100644 --- a/components/script/dom/writablestreamdefaultcontroller.rs +++ b/components/script/dom/writablestreamdefaultcontroller.rs @@ -12,6 +12,7 @@ use js::jsval::{JSVal, UndefinedValue}; use js::rust::{HandleObject as SafeHandleObject, HandleValue as SafeHandleValue, IntoHandle}; use super::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategySize; +use super::types::TransformStream; use crate::dom::bindings::callback::ExceptionHandling; use crate::dom::bindings::codegen::Bindings::UnderlyingSinkBinding::{ UnderlyingSinkAbortCallback, UnderlyingSinkCloseCallback, UnderlyingSinkStartCallback, @@ -290,8 +291,7 @@ pub enum UnderlyingSinkType { port: Dom<MessagePort>, }, /// Algorithms supporting transform streams are implemented in Rust. - #[allow(unused)] - Transform(/*Dom<TransformStream>, Rc<Promise>*/), + Transform(Dom<TransformStream>, Rc<Promise>), } impl UnderlyingSinkType { @@ -413,7 +413,7 @@ impl WritableStreamDefaultController { } => { backpressure_promise.borrow_mut().take(); }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(_, _) => { return; }, } @@ -423,7 +423,6 @@ impl WritableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#set-up-writable-stream-default-controller> - #[allow(unsafe_code)] pub(crate) fn setup( &self, cx: SafeJSContext, @@ -560,9 +559,9 @@ impl WritableStreamDefaultController { // Let startAlgorithm be an algorithm that returns undefined. Ok(Promise::new_resolved(global, cx, (), can_gc)) }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(_, start_promise) => { // Let startAlgorithm be an algorithm that returns startPromise. - todo!() + Ok(start_promise.clone()) }, } } @@ -622,9 +621,11 @@ impl WritableStreamDefaultController { } promise }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkAbortAlgorithm(stream, reason). - todo!() + stream + .transform_stream_default_sink_abort_algorithm(cx, global, reason, can_gc) + .expect("Transform stream default sink abort algorithm should not fail.") }, }; @@ -707,9 +708,11 @@ impl WritableStreamDefaultController { .append_native_handler(&handler, comp, can_gc); result_promise }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkWriteAlgorithm(stream, chunk). - todo!() + stream + .transform_stream_default_sink_write_algorithm(cx, global, chunk, can_gc) + .expect("Transform stream default sink write algorithm should not fail.") }, } } @@ -757,9 +760,11 @@ impl WritableStreamDefaultController { // Return a promise resolved with undefined. Promise::new_resolved(global, cx, (), can_gc) }, - UnderlyingSinkType::Transform() => { + UnderlyingSinkType::Transform(stream, _) => { // Return ! TransformStreamDefaultSinkCloseAlgorithm(stream). - todo!() + stream + .transform_stream_default_sink_close_algorithm(cx, global, can_gc) + .expect("Transform stream default sink close algorithm should not fail.") }, } } @@ -1038,7 +1043,7 @@ impl WritableStreamDefaultController { } /// <https://streams.spec.whatwg.org/#writable-stream-default-controller-error> - fn error( + pub(crate) fn error( &self, stream: &WritableStream, cx: SafeJSContext, diff --git a/components/script/dom/xmlhttprequest.rs b/components/script/dom/xmlhttprequest.rs index 9cef58acc9a..ca5bb72a1dc 100644 --- a/components/script/dom/xmlhttprequest.rs +++ b/components/script/dom/xmlhttprequest.rs @@ -148,7 +148,7 @@ impl FetchResponseListener for XHRContext { fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<csp::Violation>) { let global = &self.resource_timing_global(); - global.report_csp_violations(violations); + global.report_csp_violations(violations, None); } } |