aboutsummaryrefslogtreecommitdiffstats
path: root/components/script/dom
diff options
context:
space:
mode:
Diffstat (limited to 'components/script/dom')
-rw-r--r--components/script/dom/bindings/buffer_source.rs49
-rw-r--r--components/script/dom/bindings/structuredclone.rs7
-rw-r--r--components/script/dom/clipboard.rs146
-rw-r--r--components/script/dom/clipboarditem.rs8
-rw-r--r--components/script/dom/document.rs9
-rw-r--r--components/script/dom/element.rs240
-rw-r--r--components/script/dom/event.rs29
-rw-r--r--components/script/dom/eventsource.rs2
-rw-r--r--components/script/dom/globalscope.rs50
-rw-r--r--components/script/dom/htmlbodyelement.rs19
-rw-r--r--components/script/dom/htmlcanvaselement.rs55
-rw-r--r--components/script/dom/htmlelement.rs3
-rw-r--r--components/script/dom/htmlimageelement.rs2
-rw-r--r--components/script/dom/htmllinkelement.rs2
-rw-r--r--components/script/dom/htmlmediaelement.rs2
-rw-r--r--components/script/dom/htmloptgroupelement.rs4
-rw-r--r--components/script/dom/htmloptionelement.rs24
-rw-r--r--components/script/dom/htmlscriptelement.rs3
-rw-r--r--components/script/dom/htmlselectelement.rs11
-rw-r--r--components/script/dom/htmlvideoelement.rs2
-rw-r--r--components/script/dom/imagedata.rs49
-rw-r--r--components/script/dom/intersectionobserver.rs14
-rw-r--r--components/script/dom/macros.rs35
-rw-r--r--components/script/dom/mod.rs2
-rw-r--r--components/script/dom/node.rs14
-rw-r--r--components/script/dom/notification.rs2
-rw-r--r--components/script/dom/offscreencanvas.rs20
-rw-r--r--components/script/dom/promise.rs17
-rw-r--r--components/script/dom/readablestream.rs7
-rw-r--r--components/script/dom/readablestreamdefaultcontroller.rs2
-rw-r--r--components/script/dom/readablestreamgenericreader.rs1
-rw-r--r--components/script/dom/securitypolicyviolationevent.rs11
-rw-r--r--components/script/dom/servoparser/html.rs41
-rw-r--r--components/script/dom/servoparser/mod.rs3
-rw-r--r--components/script/dom/transformstream.rs1105
-rw-r--r--components/script/dom/transformstreamdefaultcontroller.rs420
-rw-r--r--components/script/dom/trustedhtml.rs18
-rw-r--r--components/script/dom/trustedtypepolicyfactory.rs4
-rw-r--r--components/script/dom/underlyingsourcecontainer.rs18
-rw-r--r--components/script/dom/websocket.rs2
-rw-r--r--components/script/dom/window.rs28
-rw-r--r--components/script/dom/writablestream.rs3
-rw-r--r--components/script/dom/writablestreamdefaultcontroller.rs31
-rw-r--r--components/script/dom/xmlhttprequest.rs2
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);
}
}