aboutsummaryrefslogtreecommitdiffstats
path: root/components/script
diff options
context:
space:
mode:
Diffstat (limited to 'components/script')
-rw-r--r--components/script/Cargo.toml1
-rw-r--r--components/script/animations.rs4
-rw-r--r--components/script/canvas_state.rs4
-rw-r--r--components/script/dom/bindings/structuredclone.rs33
-rw-r--r--components/script/dom/document.rs44
-rw-r--r--components/script/dom/globalscope.rs6
-rw-r--r--components/script/dom/headers.rs258
-rw-r--r--components/script/dom/htmlareaelement.rs26
-rw-r--r--components/script/dom/htmlcanvaselement.rs8
-rw-r--r--components/script/dom/htmlinputelement.rs4
-rw-r--r--components/script/dom/htmlmediaelement.rs4
-rw-r--r--components/script/dom/htmltablecellelement.rs16
-rw-r--r--components/script/dom/htmltablecolelement.rs14
-rw-r--r--components/script/dom/imagebitmap.rs133
-rw-r--r--components/script/dom/readablestream.rs46
-rw-r--r--components/script/dom/transformstream.rs104
-rw-r--r--components/script/dom/urlpattern.rs182
-rw-r--r--components/script/dom/webglrenderingcontext.rs3
-rw-r--r--components/script/dom/webgpu/gpu.rs8
-rw-r--r--components/script/image_animation.rs95
-rw-r--r--components/script/script_thread.rs42
-rw-r--r--components/script/test.rs1
-rw-r--r--components/script/timers.rs6
23 files changed, 783 insertions, 259 deletions
diff --git a/components/script/Cargo.toml b/components/script/Cargo.toml
index 6d588a3984e..09dab3471df 100644
--- a/components/script/Cargo.toml
+++ b/components/script/Cargo.toml
@@ -94,7 +94,6 @@ net_traits = { workspace = true }
nom = "7.1.3"
num-traits = { workspace = true }
num_cpus = { workspace = true }
-parking_lot = { workspace = true }
percent-encoding = { workspace = true }
phf = "0.11"
pixels = { path = "../pixels" }
diff --git a/components/script/animations.rs b/components/script/animations.rs
index 693c3ae978f..6119de7c1dd 100644
--- a/components/script/animations.rs
+++ b/components/script/animations.rs
@@ -76,6 +76,10 @@ impl Animations {
self.pending_events.borrow_mut().clear();
}
+ pub(crate) fn animations_present(&self) -> bool {
+ self.has_running_animations.get() || !self.pending_events.borrow().is_empty()
+ }
+
// Mark all animations dirty, if they haven't been marked dirty since the
// specified `current_timeline_value`. Returns true if animations were marked
// dirty or false otherwise.
diff --git a/components/script/canvas_state.rs b/components/script/canvas_state.rs
index dabe6a5728b..d4840a5b470 100644
--- a/components/script/canvas_state.rs
+++ b/components/script/canvas_state.rs
@@ -17,7 +17,7 @@ use cssparser::color::clamp_unit_f32;
use cssparser::{Parser, ParserInput};
use euclid::default::{Point2D, Rect, Size2D, Transform2D};
use euclid::vec2;
-use ipc_channel::ipc::{self, IpcSender};
+use ipc_channel::ipc::{self, IpcSender, IpcSharedMemory};
use net_traits::image_cache::{ImageCache, ImageResponse};
use net_traits::request::CorsSettings;
use pixels::PixelFormat;
@@ -350,7 +350,7 @@ impl CanvasState {
size.cast(),
format,
alpha_mode,
- img.bytes(),
+ IpcSharedMemory::from_bytes(img.first_frame().bytes),
))
}
diff --git a/components/script/dom/bindings/structuredclone.rs b/components/script/dom/bindings/structuredclone.rs
index 70638238123..73974abe714 100644
--- a/components/script/dom/bindings/structuredclone.rs
+++ b/components/script/dom/bindings/structuredclone.rs
@@ -10,11 +10,13 @@ use std::os::raw;
use std::ptr;
use base::id::{
- BlobId, DomExceptionId, DomPointId, Index, MessagePortId, NamespaceIndex, PipelineNamespaceId,
+ BlobId, DomExceptionId, DomPointId, ImageBitmapId, Index, MessagePortId, NamespaceIndex,
+ PipelineNamespaceId,
};
use constellation_traits::{
BlobImpl, DomException, DomPoint, MessagePortImpl, Serializable as SerializableInterface,
- StructuredSerializedData, Transferrable as TransferrableInterface,
+ SerializableImageBitmap, StructuredSerializedData, Transferrable as TransferrableInterface,
+ TransformStreamData,
};
use js::gc::RootedVec;
use js::glue::{
@@ -42,6 +44,7 @@ use crate::dom::blob::Blob;
use crate::dom::dompoint::DOMPoint;
use crate::dom::dompointreadonly::DOMPointReadOnly;
use crate::dom::globalscope::GlobalScope;
+use crate::dom::imagebitmap::ImageBitmap;
use crate::dom::messageport::MessagePort;
use crate::dom::readablestream::ReadableStream;
use crate::dom::types::{DOMException, TransformStream};
@@ -66,6 +69,7 @@ pub(super) enum StructuredCloneTags {
DomException = 0xFFFF8007,
WritableStream = 0xFFFF8008,
TransformStream = 0xFFFF8009,
+ ImageBitmap = 0xFFFF800A,
Max = 0xFFFFFFFF,
}
@@ -76,6 +80,7 @@ impl From<SerializableInterface> for StructuredCloneTags {
SerializableInterface::DomPointReadOnly => StructuredCloneTags::DomPointReadOnly,
SerializableInterface::DomPoint => StructuredCloneTags::DomPoint,
SerializableInterface::DomException => StructuredCloneTags::DomException,
+ SerializableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap,
}
}
}
@@ -83,6 +88,7 @@ impl From<SerializableInterface> for StructuredCloneTags {
impl From<TransferrableInterface> for StructuredCloneTags {
fn from(v: TransferrableInterface) -> Self {
match v {
+ TransferrableInterface::ImageBitmap => StructuredCloneTags::ImageBitmap,
TransferrableInterface::MessagePort => StructuredCloneTags::MessagePort,
TransferrableInterface::ReadableStream => StructuredCloneTags::ReadableStream,
TransferrableInterface::WritableStream => StructuredCloneTags::WritableStream,
@@ -104,6 +110,7 @@ fn reader_for_type(
SerializableInterface::DomPointReadOnly => read_object::<DOMPointReadOnly>,
SerializableInterface::DomPoint => read_object::<DOMPoint>,
SerializableInterface::DomException => read_object::<DOMException>,
+ SerializableInterface::ImageBitmap => read_object::<ImageBitmap>,
}
}
@@ -237,6 +244,7 @@ fn serialize_for_type(val: SerializableInterface) -> SerializeOperation {
SerializableInterface::DomPointReadOnly => try_serialize::<DOMPointReadOnly>,
SerializableInterface::DomPoint => try_serialize::<DOMPoint>,
SerializableInterface::DomException => try_serialize::<DOMException>,
+ SerializableInterface::ImageBitmap => try_serialize::<ImageBitmap>,
}
}
@@ -264,6 +272,7 @@ fn receiver_for_type(
) -> fn(&GlobalScope, &mut StructuredDataReader<'_>, u64, RawMutableHandleObject) -> Result<(), ()>
{
match val {
+ TransferrableInterface::ImageBitmap => receive_object::<ImageBitmap>,
TransferrableInterface::MessagePort => receive_object::<MessagePort>,
TransferrableInterface::ReadableStream => receive_object::<ReadableStream>,
TransferrableInterface::WritableStream => receive_object::<WritableStream>,
@@ -390,6 +399,7 @@ type TransferOperation = unsafe fn(
fn transfer_for_type(val: TransferrableInterface) -> TransferOperation {
match val {
+ TransferrableInterface::ImageBitmap => try_transfer::<ImageBitmap>,
TransferrableInterface::MessagePort => try_transfer::<MessagePort>,
TransferrableInterface::ReadableStream => try_transfer::<ReadableStream>,
TransferrableInterface::WritableStream => try_transfer::<WritableStream>,
@@ -439,6 +449,7 @@ unsafe fn can_transfer_for_type(
root_from_object::<T>(*obj, cx).map(|o| Transferable::can_transfer(&*o))
}
match transferable {
+ TransferrableInterface::ImageBitmap => can_transfer::<ImageBitmap>(obj, cx),
TransferrableInterface::MessagePort => can_transfer::<MessagePort>(obj, cx),
TransferrableInterface::ReadableStream => can_transfer::<ReadableStream>(obj, cx),
TransferrableInterface::WritableStream => can_transfer::<WritableStream>(obj, cx),
@@ -517,6 +528,8 @@ pub(crate) struct StructuredDataReader<'a> {
/// used as part of the "transfer-receiving" steps of ports,
/// to produce the DOM ports stored in `message_ports` above.
pub(crate) port_impls: Option<HashMap<MessagePortId, MessagePortImpl>>,
+ /// A map of transform stream implementations,
+ pub(crate) transform_streams_port_impls: Option<HashMap<MessagePortId, TransformStreamData>>,
/// A map of blob implementations,
/// used as part of the "deserialize" steps of blobs,
/// to produce the DOM blobs stored in `blobs` above.
@@ -525,6 +538,10 @@ pub(crate) struct StructuredDataReader<'a> {
pub(crate) points: Option<HashMap<DomPointId, DomPoint>>,
/// A map of serialized exceptions.
pub(crate) exceptions: Option<HashMap<DomExceptionId, DomException>>,
+ // A map of serialized image bitmaps.
+ pub(crate) image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
+ /// A map of transferred image bitmaps.
+ pub(crate) transferred_image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
}
/// A data holder for transferred and serialized objects.
@@ -535,12 +552,18 @@ pub(crate) struct StructuredDataWriter {
pub(crate) errors: DOMErrorRecord,
/// Transferred ports.
pub(crate) ports: Option<HashMap<MessagePortId, MessagePortImpl>>,
+ /// Transferred transform streams.
+ pub(crate) transform_streams_port: Option<HashMap<MessagePortId, TransformStreamData>>,
/// Serialized points.
pub(crate) points: Option<HashMap<DomPointId, DomPoint>>,
/// Serialized exceptions.
pub(crate) exceptions: Option<HashMap<DomExceptionId, DomException>>,
/// Serialized blobs.
pub(crate) blobs: Option<HashMap<BlobId, BlobImpl>>,
+ /// Serialized image bitmaps.
+ pub(crate) image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
+ /// Transferred image bitmaps.
+ pub(crate) transferred_image_bitmaps: Option<HashMap<ImageBitmapId, SerializableImageBitmap>>,
}
/// Writes a structured clone. Returns a `DataClone` error if that fails.
@@ -591,9 +614,12 @@ pub(crate) fn write(
let data = StructuredSerializedData {
serialized: data,
ports: sc_writer.ports.take(),
+ transform_streams: sc_writer.transform_streams_port.take(),
points: sc_writer.points.take(),
exceptions: sc_writer.exceptions.take(),
blobs: sc_writer.blobs.take(),
+ image_bitmaps: sc_writer.image_bitmaps.take(),
+ transferred_image_bitmaps: sc_writer.transferred_image_bitmaps.take(),
};
Ok(data)
@@ -613,10 +639,13 @@ pub(crate) fn read(
let mut sc_reader = StructuredDataReader {
roots,
port_impls: data.ports.take(),
+ transform_streams_port_impls: data.transform_streams.take(),
blob_impls: data.blobs.take(),
points: data.points.take(),
exceptions: data.exceptions.take(),
errors: DOMErrorRecord { message: None },
+ image_bitmaps: data.image_bitmaps.take(),
+ transferred_image_bitmaps: data.transferred_image_bitmaps.take(),
};
let sc_reader_ptr = &mut sc_reader as *mut _;
unsafe {
diff --git a/components/script/dom/document.rs b/components/script/dom/document.rs
index 78cb2c33075..6056f1f1e5a 100644
--- a/components/script/dom/document.rs
+++ b/components/script/dom/document.rs
@@ -5051,6 +5051,35 @@ impl Document {
self.image_animation_manager.borrow_mut()
}
+ pub(crate) fn update_animating_images(&self) {
+ let mut image_animation_manager = self.image_animation_manager.borrow_mut();
+ if !image_animation_manager.image_animations_present() {
+ return;
+ }
+ image_animation_manager
+ .update_active_frames(&self.window, self.current_animation_timeline_value());
+
+ if !self.animations().animations_present() {
+ let next_scheduled_time =
+ image_animation_manager.next_schedule_time(self.current_animation_timeline_value());
+ // TODO: Once we have refresh signal from the compositor,
+ // we should get rid of timer for animated image update.
+ if let Some(next_scheduled_time) = next_scheduled_time {
+ self.schedule_image_animation_update(next_scheduled_time);
+ }
+ }
+ }
+
+ fn schedule_image_animation_update(&self, next_scheduled_time: f64) {
+ let callback = ImageAnimationUpdateCallback {
+ document: Trusted::new(self),
+ };
+ self.global().schedule_callback(
+ OneshotTimerCallback::ImageAnimationUpdate(callback),
+ Duration::from_secs_f64(next_scheduled_time),
+ );
+ }
+
/// <https://html.spec.whatwg.org/multipage/#shared-declarative-refresh-steps>
pub(crate) fn shared_declarative_refresh_steps(&self, content: &[u8]) {
// 1. If document's will declaratively refresh is true, then return.
@@ -6747,6 +6776,21 @@ impl FakeRequestAnimationFrameCallback {
}
}
+/// This is a temporary workaround to update animated images,
+/// we should get rid of this after we have refresh driver #3406
+#[derive(JSTraceable, MallocSizeOf)]
+pub(crate) struct ImageAnimationUpdateCallback {
+ /// The document.
+ #[ignore_malloc_size_of = "non-owning"]
+ document: Trusted<Document>,
+}
+
+impl ImageAnimationUpdateCallback {
+ pub(crate) fn invoke(self, can_gc: CanGc) {
+ with_script_thread(|script_thread| script_thread.update_the_rendering(true, can_gc))
+ }
+}
+
#[derive(JSTraceable, MallocSizeOf)]
pub(crate) enum AnimationFrameCallback {
DevtoolsFramerateTick {
diff --git a/components/script/dom/globalscope.rs b/components/script/dom/globalscope.rs
index 55db2e4d248..ec2ed70dd15 100644
--- a/components/script/dom/globalscope.rs
+++ b/components/script/dom/globalscope.rs
@@ -2992,8 +2992,7 @@ impl GlobalScope {
if let Some(snapshot) = canvas.get_image_data() {
let size = snapshot.size().cast();
- let image_bitmap =
- ImageBitmap::new(self, size.width, size.height, can_gc).unwrap();
+ let image_bitmap = ImageBitmap::new(self, size.width, size.height, can_gc);
image_bitmap.set_bitmap_data(snapshot.to_vec());
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
@@ -3009,8 +3008,7 @@ impl GlobalScope {
if let Some(snapshot) = canvas.get_image_data() {
let size = snapshot.size().cast();
- let image_bitmap =
- ImageBitmap::new(self, size.width, size.height, can_gc).unwrap();
+ let image_bitmap = ImageBitmap::new(self, size.width, size.height, can_gc);
image_bitmap.set_bitmap_data(snapshot.to_vec());
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
diff --git a/components/script/dom/headers.rs b/components/script/dom/headers.rs
index 0e8dcf92ccd..f195992faa5 100644
--- a/components/script/dom/headers.rs
+++ b/components/script/dom/headers.rs
@@ -13,6 +13,7 @@ use net_traits::fetch::headers::{
is_forbidden_method,
};
use net_traits::request::is_cors_safelisted_request_header;
+use net_traits::trim_http_whitespace;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
@@ -33,7 +34,7 @@ pub(crate) struct Headers {
header_list: DomRefCell<HyperHeaders>,
}
-// https://fetch.spec.whatwg.org/#concept-headers-guard
+/// <https://fetch.spec.whatwg.org/#concept-headers-guard>
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
pub(crate) enum Guard {
Immutable,
@@ -66,7 +67,7 @@ impl Headers {
}
impl HeadersMethods<crate::DomTypeHolder> for Headers {
- // https://fetch.spec.whatwg.org/#dom-headers
+ /// <https://fetch.spec.whatwg.org/#dom-headers>
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
@@ -78,47 +79,41 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
Ok(dom_headers_new)
}
- // https://fetch.spec.whatwg.org/#concept-headers-append
+ /// <https://fetch.spec.whatwg.org/#concept-headers-append>
fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult {
- // Step 1
- let value = normalize_value(value);
+ // 1. Normalize value.
+ let value = trim_http_whitespace(&value);
- // Step 2
- // https://fetch.spec.whatwg.org/#headers-validate
- let (mut valid_name, valid_value) = validate_name_and_value(name, value)?;
+ // 2. If validating (name, value) for headers returns false, then return.
+ let Some((mut valid_name, valid_value)) =
+ self.validate_name_and_value(name, ByteString::new(value.into()))?
+ else {
+ return Ok(());
+ };
valid_name = valid_name.to_lowercase();
- if self.guard.get() == Guard::Immutable {
- return Err(Error::Type("Guard is immutable".to_string()));
- }
- if self.guard.get() == Guard::Request &&
- is_forbidden_request_header(&valid_name, &valid_value)
- {
- return Ok(());
- }
- if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
- return Ok(());
- }
-
- // Step 3
+ // 3. If headers’s guard is "request-no-cors":
if self.guard.get() == Guard::RequestNoCors {
+ // 3.1. Let temporaryValue be the result of getting name from headers’s header list.
let tmp_value = if let Some(mut value) =
get_value_from_header_list(&valid_name, &self.header_list.borrow())
{
+ // 3.3. Otherwise, set temporaryValue to temporaryValue, followed by 0x2C 0x20, followed by value.
value.extend(b", ");
- value.extend(valid_value.clone());
+ value.extend(valid_value.to_vec());
value
} else {
- valid_value.clone()
+ // 3.2. If temporaryValue is null, then set temporaryValue to value.
+ valid_value.to_vec()
};
-
+ // 3.4. If (name, temporaryValue) is not a no-CORS-safelisted request-header, then return.
if !is_cors_safelisted_request_header(&valid_name, &tmp_value) {
return Ok(());
}
}
- // Step 4
+ // 4. Append (name, value) to headers’s header list.
match HeaderValue::from_bytes(&valid_value) {
Ok(value) => {
self.header_list
@@ -134,7 +129,7 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
},
};
- // Step 5
+ // 5. If headers’s guard is "request-no-cors", then remove privileged no-CORS request-headers from headers.
if self.guard.get() == Guard::RequestNoCors {
self.remove_privileged_no_cors_request_headers();
}
@@ -142,50 +137,53 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
Ok(())
}
- // https://fetch.spec.whatwg.org/#dom-headers-delete
+ /// <https://fetch.spec.whatwg.org/#dom-headers-delete>
fn Delete(&self, name: ByteString) -> ErrorResult {
- // Step 1
- let (mut valid_name, valid_value) = validate_name_and_value(name, ByteString::new(vec![]))?;
+ // Step 1 If validating (name, ``) for this returns false, then return.
+ let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?;
+ let Some((mut valid_name, _valid_value)) = name_and_value else {
+ return Ok(());
+ };
valid_name = valid_name.to_lowercase();
- // Step 2
- if self.guard.get() == Guard::Immutable {
- return Err(Error::Type("Guard is immutable".to_string()));
- }
- // Step 3
- if self.guard.get() == Guard::Request &&
- is_forbidden_request_header(&valid_name, &valid_value)
- {
- return Ok(());
- }
- // Step 4
+ // Step 2 If this’s guard is "request-no-cors", name is not a no-CORS-safelisted request-header name,
+ // and name is not a privileged no-CORS request-header name, then return.
if self.guard.get() == Guard::RequestNoCors &&
!is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec())
{
return Ok(());
}
- // Step 5
- if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
- return Ok(());
+
+ // 3. If this’s header list does not contain name, then return.
+ // 4. Delete name from this’s header list.
+ self.header_list.borrow_mut().remove(valid_name);
+
+ // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this.
+ if self.guard.get() == Guard::RequestNoCors {
+ self.remove_privileged_no_cors_request_headers();
}
- // Step 6
- self.header_list.borrow_mut().remove(&valid_name);
+
Ok(())
}
- // https://fetch.spec.whatwg.org/#dom-headers-get
+ /// <https://fetch.spec.whatwg.org/#dom-headers-get>
fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> {
- // Step 1
+ // 1. If name is not a header name, then throw a TypeError.
let valid_name = validate_name(name)?;
+
+ // 2. Return the result of getting name from this’s header list.
Ok(
get_value_from_header_list(&valid_name, &self.header_list.borrow())
.map(ByteString::new),
)
}
- // https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
+ /// <https://fetch.spec.whatwg.org/#dom-headers-getsetcookie>
fn GetSetCookie(&self) -> Vec<ByteString> {
+ // 1. If this’s header list does not contain `Set-Cookie`, then return « ».
+ // 2. Return the values of all headers in this’s header list whose name is a
+ // byte-case-insensitive match for `Set-Cookie`, in order.
self.header_list
.borrow()
.get_all("set-cookie")
@@ -194,42 +192,36 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
.collect()
}
- // https://fetch.spec.whatwg.org/#dom-headers-has
+ /// <https://fetch.spec.whatwg.org/#dom-headers-has>
fn Has(&self, name: ByteString) -> Fallible<bool> {
- // Step 1
+ // 1. If name is not a header name, then throw a TypeError.
let valid_name = validate_name(name)?;
- // Step 2
+ // 2. Return true if this’s header list contains name; otherwise false.
Ok(self.header_list.borrow_mut().get(&valid_name).is_some())
}
- // https://fetch.spec.whatwg.org/#dom-headers-set
+ /// <https://fetch.spec.whatwg.org/#dom-headers-set>
fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> {
- // Step 1
- let value = normalize_value(value);
- // Step 2
- let (mut valid_name, valid_value) = validate_name_and_value(name, value)?;
- valid_name = valid_name.to_lowercase();
- // Step 3
- if self.guard.get() == Guard::Immutable {
- return Err(Error::Type("Guard is immutable".to_string()));
- }
- // Step 4
- if self.guard.get() == Guard::Request &&
- is_forbidden_request_header(&valid_name, &valid_value)
- {
+ // 1. Normalize value
+ let value = trim_http_whitespace(&value);
+
+ // 2. If validating (name, value) for this returns false, then return.
+ let Some((mut valid_name, valid_value)) =
+ self.validate_name_and_value(name, ByteString::new(value.into()))?
+ else {
return Ok(());
- }
- // Step 5
+ };
+ valid_name = valid_name.to_lowercase();
+
+ // 3. If this’s guard is "request-no-cors" and (name, value) is not a
+ // no-CORS-safelisted request-header, then return.
if self.guard.get() == Guard::RequestNoCors &&
- !is_cors_safelisted_request_header(&valid_name, &valid_value)
+ !is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec())
{
return Ok(());
}
- // Step 6
- if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
- return Ok(());
- }
- // Step 7
+
+ // 4. Set (name, value) in this’s header list.
// https://fetch.spec.whatwg.org/#concept-header-list-set
match HeaderValue::from_bytes(&valid_value) {
Ok(value) => {
@@ -245,6 +237,12 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
);
},
};
+
+ // 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this.
+ if self.guard.get() == Guard::RequestNoCors {
+ self.remove_privileged_no_cors_request_headers();
+ }
+
Ok(())
}
}
@@ -260,7 +258,7 @@ impl Headers {
Ok(())
}
- // https://fetch.spec.whatwg.org/#concept-headers-fill
+ /// <https://fetch.spec.whatwg.org/#concept-headers-fill>
pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult {
match filler {
Some(HeadersInit::ByteStringSequenceSequence(v)) => {
@@ -316,12 +314,12 @@ impl Headers {
self.header_list.borrow_mut().clone()
}
- // https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
+ /// <https://fetch.spec.whatwg.org/#concept-header-extract-mime-type>
pub(crate) fn extract_mime_type(&self) -> Vec<u8> {
extract_mime_type(&self.header_list.borrow()).unwrap_or_default()
}
- // https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
+ /// <https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine>
pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> {
let borrowed_header_list = self.header_list.borrow();
let mut header_vec = vec![];
@@ -341,11 +339,38 @@ impl Headers {
header_vec
}
- // https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name
+ /// <https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name>
pub(crate) fn remove_privileged_no_cors_request_headers(&self) {
- // https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name
+ // <https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name>
self.header_list.borrow_mut().remove("range");
}
+
+ /// <https://fetch.spec.whatwg.org/#headers-validate>
+ pub(crate) fn validate_name_and_value(
+ &self,
+ name: ByteString,
+ value: ByteString,
+ ) -> Fallible<Option<(String, ByteString)>> {
+ // 1. If name is not a header name or value is not a header value, then throw a TypeError.
+ let valid_name = validate_name(name)?;
+ if !is_legal_header_value(&value) {
+ return Err(Error::Type("Header value is not valid".to_string()));
+ }
+ // 2. If headers’s guard is "immutable", then throw a TypeError.
+ if self.guard.get() == Guard::Immutable {
+ return Err(Error::Type("Guard is immutable".to_string()));
+ }
+ // 3. If headers’s guard is "request" and (name, value) is a forbidden request-header, then return false.
+ if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) {
+ return Ok(None);
+ }
+ // 4. If headers’s guard is "response" and name is a forbidden response-header name, then return false.
+ if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
+ return Ok(None);
+ }
+
+ Ok(Some((valid_name, value)))
+ }
}
impl Iterable for Headers {
@@ -391,6 +416,7 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
"keep-alive",
"origin",
"referer",
+ "set-cookie",
"te",
"trailer",
"transfer-encoding",
@@ -448,26 +474,11 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
false
}
-// https://fetch.spec.whatwg.org/#forbidden-response-header-name
+/// <https://fetch.spec.whatwg.org/#forbidden-response-header-name>
fn is_forbidden_response_header(name: &str) -> bool {
- matches!(name, "set-cookie" | "set-cookie2")
-}
-
-// There is some unresolved confusion over the definition of a name and a value.
-//
-// As of December 2019, WHATWG has no formal grammar production for value;
-// https://fetch.spec.whatg.org/#concept-header-value just says not to have
-// newlines, nulls, or leading/trailing whitespace. It even allows
-// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this.
-// The HeaderValue class does not fully reflect this, so headers
-// containing bytes with values 1..31 or 127 can't be created, failing
-// WPT tests but probably not affecting anything important on the real Internet.
-fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec<u8>)> {
- let valid_name = validate_name(name)?;
- if !is_legal_header_value(&value) {
- return Err(Error::Type("Header value is not valid".to_string()));
- }
- Ok((valid_name, value.into()))
+ // A forbidden response-header name is a header name that is a byte-case-insensitive match for one of
+ let name = name.to_ascii_lowercase();
+ matches!(name.as_str(), "set-cookie" | "set-cookie2")
}
fn validate_name(name: ByteString) -> Fallible<String> {
@@ -480,47 +491,20 @@ fn validate_name(name: ByteString) -> Fallible<String> {
}
}
-// Removes trailing and leading HTTP whitespace bytes.
-// https://fetch.spec.whatwg.org/#concept-header-value-normalize
-pub fn normalize_value(value: ByteString) -> ByteString {
- match (
- index_of_first_non_whitespace(&value),
- index_of_last_non_whitespace(&value),
- ) {
- (Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()),
- _ => ByteString::new(vec![]),
- }
-}
-
-fn is_http_whitespace(byte: u8) -> bool {
- byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' '
-}
-
-fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> {
- for (index, &byte) in value.iter().enumerate() {
- if !is_http_whitespace(byte) {
- return Some(index);
- }
- }
- None
-}
-
-fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> {
- for (index, &byte) in value.iter().enumerate().rev() {
- if !is_http_whitespace(byte) {
- return Some(index);
- }
- }
- None
-}
-
-// http://tools.ietf.org/html/rfc7230#section-3.2
+/// <http://tools.ietf.org/html/rfc7230#section-3.2>
fn is_field_name(name: &ByteString) -> bool {
is_token(name)
}
-// https://fetch.spec.whatg.org/#concept-header-value
-fn is_legal_header_value(value: &ByteString) -> bool {
+// As of December 2019, WHATWG has no formal grammar production for value;
+// https://fetch.spec.whatg.org/#concept-header-value just says not to have
+// newlines, nulls, or leading/trailing whitespace. It even allows
+// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this.
+// The HeaderValue class does not fully reflect this, so headers
+// containing bytes with values 1..31 or 127 can't be created, failing
+// WPT tests but probably not affecting anything important on the real Internet.
+/// <https://fetch.spec.whatg.org/#concept-header-value>
+fn is_legal_header_value(value: &[u8]) -> bool {
let value_len = value.len();
if value_len == 0 {
return true;
@@ -533,7 +517,7 @@ fn is_legal_header_value(value: &ByteString) -> bool {
b' ' | b'\t' => return false,
_ => {},
};
- for &ch in &value[..] {
+ for &ch in value {
match ch {
b'\0' | b'\n' | b'\r' => return false,
_ => {},
@@ -555,12 +539,12 @@ fn is_legal_header_value(value: &ByteString) -> bool {
// }
}
-// https://tools.ietf.org/html/rfc5234#appendix-B.1
+/// <https://tools.ietf.org/html/rfc5234#appendix-B.1>
pub(crate) fn is_vchar(x: u8) -> bool {
matches!(x, 0x21..=0x7E)
}
-// http://tools.ietf.org/html/rfc7230#section-3.2.6
+/// <http://tools.ietf.org/html/rfc7230#section-3.2.6>
pub(crate) fn is_obs_text(x: u8) -> bool {
matches!(x, 0x80..=0xFF)
}
diff --git a/components/script/dom/htmlareaelement.rs b/components/script/dom/htmlareaelement.rs
index a9d94cbd7b2..535d296a29f 100644
--- a/components/script/dom/htmlareaelement.rs
+++ b/components/script/dom/htmlareaelement.rs
@@ -46,6 +46,8 @@ pub enum Area {
bottom_right: (f32, f32),
},
Polygon {
+ /// Stored as a flat array of coordinates
+ /// e.g. [x1, y1, x2, y2, x3, y3] for a triangle
points: Vec<f32>,
},
}
@@ -203,8 +205,28 @@ impl Area {
p.y >= top_left.1
},
- //TODO polygon hit_test
- _ => false,
+ Area::Polygon { ref points } => {
+ // Ray-casting algorithm to determine if point is inside polygon
+ // https://en.wikipedia.org/wiki/Point_in_polygon#Ray_casting_algorithm
+ let mut inside = false;
+
+ debug_assert!(points.len() % 2 == 0);
+ let vertices = points.len() / 2;
+
+ for i in 0..vertices {
+ let next_i = if i + 1 == vertices { 0 } else { i + 1 };
+
+ let xi = points[2 * i];
+ let yi = points[2 * i + 1];
+ let xj = points[2 * next_i];
+ let yj = points[2 * next_i + 1];
+
+ if (yi > p.y) != (yj > p.y) && p.x < (xj - xi) * (p.y - yi) / (yj - yi) + xi {
+ inside = !inside;
+ }
+ }
+ inside
+ },
}
}
diff --git a/components/script/dom/htmlcanvaselement.rs b/components/script/dom/htmlcanvaselement.rs
index 27da9f2b537..499e91c127b 100644
--- a/components/script/dom/htmlcanvaselement.rs
+++ b/components/script/dom/htmlcanvaselement.rs
@@ -27,6 +27,7 @@ use servo_media::streams::registry::MediaStreamId;
use snapshot::Snapshot;
use style::attr::AttrValue;
+use super::node::NodeDamage;
pub(crate) use crate::canvas_context::*;
use crate::conversions::Convert;
use crate::dom::attr::Attr;
@@ -687,8 +688,11 @@ impl VirtualMethods for HTMLCanvasElement {
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match attr.local_name() {
- &local_name!("width") | &local_name!("height") => self.recreate_contexts_after_resize(),
- _ => (),
+ &local_name!("width") | &local_name!("height") => {
+ self.recreate_contexts_after_resize();
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+ },
+ _ => {},
};
}
diff --git a/components/script/dom/htmlinputelement.rs b/components/script/dom/htmlinputelement.rs
index 3c78ba67772..c6a3e2227f5 100644
--- a/components/script/dom/htmlinputelement.rs
+++ b/components/script/dom/htmlinputelement.rs
@@ -520,6 +520,7 @@ impl HTMLInputElement {
let mut value = textinput.single_line_content().clone();
self.sanitize_value(&mut value);
textinput.set_content(value);
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
fn does_minmaxlength_apply(&self) -> bool {
@@ -2668,6 +2669,7 @@ impl VirtualMethods for HTMLInputElement {
let mut value = textinput.single_line_content().clone();
self.sanitize_value(&mut value);
textinput.set_content(value);
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
// Steps 7-9
if !previously_selectable && self.selection_api_applies() {
@@ -2695,6 +2697,8 @@ impl VirtualMethods for HTMLInputElement {
self.sanitize_value(&mut value);
self.textinput.borrow_mut().set_content(value);
self.update_placeholder_shown_state();
+
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
},
local_name!("name") if self.input_type() == InputType::Radio => {
self.radio_group_updated(
diff --git a/components/script/dom/htmlmediaelement.rs b/components/script/dom/htmlmediaelement.rs
index 391da272ef3..d1791620592 100644
--- a/components/script/dom/htmlmediaelement.rs
+++ b/components/script/dom/htmlmediaelement.rs
@@ -1369,7 +1369,6 @@ impl HTMLMediaElement {
.lock()
.unwrap()
.render_poster_frame(image);
- self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
if pref!(media_testing_enabled) {
self.owner_global()
@@ -1618,7 +1617,6 @@ impl HTMLMediaElement {
// TODO: 6. Abort the overall resource selection algorithm.
},
PlayerEvent::VideoFrameUpdated => {
- self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
// Check if the frame was resized
if let Some(frame) = self.video_renderer.lock().unwrap().current_frame {
self.handle_resize(Some(frame.width as u32), Some(frame.height as u32));
@@ -2017,12 +2015,12 @@ impl HTMLMediaElement {
pub(crate) fn clear_current_frame_data(&self) {
self.handle_resize(None, None);
self.video_renderer.lock().unwrap().current_frame = None;
- self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
fn handle_resize(&self, width: Option<u32>, height: Option<u32>) {
if let Some(video_elem) = self.downcast::<HTMLVideoElement>() {
video_elem.resize(width, height);
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
}
}
diff --git a/components/script/dom/htmltablecellelement.rs b/components/script/dom/htmltablecellelement.rs
index 8b553923230..78f00132580 100644
--- a/components/script/dom/htmltablecellelement.rs
+++ b/components/script/dom/htmltablecellelement.rs
@@ -9,6 +9,9 @@ use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use style::color::AbsoluteColor;
use style::context::QuirksMode;
+use super::attr::Attr;
+use super::element::AttributeMutation;
+use super::node::NodeDamage;
use crate::dom::bindings::codegen::Bindings::HTMLTableCellElementBinding::HTMLTableCellElementMethods;
use crate::dom::bindings::codegen::Bindings::NodeBinding::NodeMethods;
use crate::dom::bindings::inheritance::Castable;
@@ -174,6 +177,19 @@ impl VirtualMethods for HTMLTableCellElement {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
+ fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
+ if let Some(super_type) = self.super_type() {
+ super_type.attribute_mutated(attr, mutation, can_gc);
+ }
+
+ if matches!(*attr.local_name(), local_name!("colspan")) {
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+ }
+ if matches!(*attr.local_name(), local_name!("rowspan")) {
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+ }
+ }
+
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("colspan") => {
diff --git a/components/script/dom/htmltablecolelement.rs b/components/script/dom/htmltablecolelement.rs
index 9e8eecf1147..70355f274fc 100644
--- a/components/script/dom/htmltablecolelement.rs
+++ b/components/script/dom/htmltablecolelement.rs
@@ -7,8 +7,10 @@ use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
+use super::attr::Attr;
use super::bindings::root::LayoutDom;
-use super::element::Element;
+use super::element::{AttributeMutation, Element};
+use super::node::NodeDamage;
use crate::dom::bindings::codegen::Bindings::HTMLTableColElementBinding::HTMLTableColElementMethods;
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::root::DomRoot;
@@ -93,6 +95,16 @@ impl VirtualMethods for HTMLTableColElement {
Some(self.upcast::<HTMLElement>() as &dyn VirtualMethods)
}
+ fn attribute_mutated(&self, attr: &Attr, mutation: AttributeMutation, can_gc: CanGc) {
+ if let Some(super_type) = self.super_type() {
+ super_type.attribute_mutated(attr, mutation, can_gc);
+ }
+
+ if matches!(*attr.local_name(), local_name!("span")) {
+ self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
+ }
+ }
+
fn parse_plain_attribute(&self, local_name: &LocalName, value: DOMString) -> AttrValue {
match *local_name {
local_name!("span") => {
diff --git a/components/script/dom/imagebitmap.rs b/components/script/dom/imagebitmap.rs
index ef6538e7451..97a1a54bba7 100644
--- a/components/script/dom/imagebitmap.rs
+++ b/components/script/dom/imagebitmap.rs
@@ -3,15 +3,20 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */
use std::cell::Cell;
+use std::collections::HashMap;
use std::vec::Vec;
+use base::id::{ImageBitmapId, ImageBitmapIndex};
+use constellation_traits::SerializableImageBitmap;
use dom_struct::dom_struct;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::ImageBitmapBinding::ImageBitmapMethods;
-use crate::dom::bindings::error::Fallible;
use crate::dom::bindings::reflector::{Reflector, reflect_dom_object};
use crate::dom::bindings::root::DomRoot;
+use crate::dom::bindings::serializable::Serializable;
+use crate::dom::bindings::structuredclone::StructuredData;
+use crate::dom::bindings::transferable::Transferable;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::CanGc;
@@ -29,33 +34,36 @@ pub(crate) struct ImageBitmap {
}
impl ImageBitmap {
- fn new_inherited(width_arg: u32, height_arg: u32) -> ImageBitmap {
+ fn new_inherited(width: u32, height: u32) -> ImageBitmap {
ImageBitmap {
reflector_: Reflector::new(),
- width: width_arg,
- height: height_arg,
+ width,
+ height,
bitmap_data: DomRefCell::new(Some(vec![])),
origin_clean: Cell::new(true),
}
}
- #[allow(dead_code)]
pub(crate) fn new(
global: &GlobalScope,
width: u32,
height: u32,
can_gc: CanGc,
- ) -> Fallible<DomRoot<ImageBitmap>> {
+ ) -> DomRoot<ImageBitmap> {
//assigning to a variable the return object of new_inherited
let imagebitmap = Box::new(ImageBitmap::new_inherited(width, height));
- Ok(reflect_dom_object(imagebitmap, global, can_gc))
+ reflect_dom_object(imagebitmap, global, can_gc)
}
pub(crate) fn set_bitmap_data(&self, data: Vec<u8>) {
*self.bitmap_data.borrow_mut() = Some(data);
}
+ pub(crate) fn origin_is_clean(&self) -> bool {
+ self.origin_clean.get()
+ }
+
pub(crate) fn set_origin_clean(&self, origin_is_clean: bool) {
self.origin_clean.set(origin_is_clean);
}
@@ -67,6 +75,117 @@ impl ImageBitmap {
}
}
+impl Serializable for ImageBitmap {
+ type Index = ImageBitmapIndex;
+ type Data = SerializableImageBitmap;
+
+ /// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:serialization-steps>
+ fn serialize(&self) -> Result<(ImageBitmapId, Self::Data), ()> {
+ // Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
+ if !self.origin_is_clean() {
+ return Err(());
+ }
+
+ // If value has a [[Detached]] internal slot whose value is true,
+ // then throw a "DataCloneError" DOMException.
+ if self.is_detached() {
+ return Err(());
+ }
+
+ // Step 2. Set serialized.[[BitmapData]] to a copy of value's bitmap data.
+ let serialized = SerializableImageBitmap {
+ width: self.width,
+ height: self.height,
+ bitmap_data: self.bitmap_data.borrow().clone().unwrap(),
+ };
+
+ Ok((ImageBitmapId::new(), serialized))
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:deserialization-steps>
+ fn deserialize(
+ owner: &GlobalScope,
+ serialized: Self::Data,
+ can_gc: CanGc,
+ ) -> Result<DomRoot<Self>, ()> {
+ let image_bitmap = ImageBitmap::new(owner, serialized.width, serialized.height, can_gc);
+
+ // Step 1. Set value's bitmap data to serialized.[[BitmapData]].
+ image_bitmap.set_bitmap_data(serialized.bitmap_data);
+
+ Ok(image_bitmap)
+ }
+
+ fn serialized_storage<'a>(
+ reader: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<ImageBitmapId, Self::Data>> {
+ match reader {
+ StructuredData::Reader(r) => &mut r.image_bitmaps,
+ StructuredData::Writer(w) => &mut w.image_bitmaps,
+ }
+ }
+}
+
+impl Transferable for ImageBitmap {
+ type Index = ImageBitmapIndex;
+ type Data = SerializableImageBitmap;
+
+ fn can_transfer(&self) -> bool {
+ if !self.origin_is_clean() || self.is_detached() {
+ return false;
+ }
+ true
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-steps>
+ fn transfer(&self) -> Result<(ImageBitmapId, SerializableImageBitmap), ()> {
+ // Step 1. If value's origin-clean flag is not set, then throw a "DataCloneError" DOMException.
+ if !self.origin_is_clean() {
+ return Err(());
+ }
+
+ // If value has a [[Detached]] internal slot whose value is true,
+ // then throw a "DataCloneError" DOMException.
+ if self.is_detached() {
+ return Err(());
+ }
+
+ // Step 2. Set dataHolder.[[BitmapData]] to value's bitmap data.
+ // Step 3. Unset value's bitmap data.
+ let serialized = SerializableImageBitmap {
+ width: self.width,
+ height: self.height,
+ bitmap_data: self.bitmap_data.borrow_mut().take().unwrap(),
+ };
+
+ Ok((ImageBitmapId::new(), serialized))
+ }
+
+ /// <https://html.spec.whatwg.org/multipage/#the-imagebitmap-interface:transfer-receiving-steps>
+ fn transfer_receive(
+ owner: &GlobalScope,
+ _: ImageBitmapId,
+ serialized: SerializableImageBitmap,
+ ) -> Result<DomRoot<Self>, ()> {
+ let image_bitmap =
+ ImageBitmap::new(owner, serialized.width, serialized.height, CanGc::note());
+
+ // Step 1. Set value's bitmap data to serialized.[[BitmapData]].
+ image_bitmap.set_bitmap_data(serialized.bitmap_data);
+
+ Ok(image_bitmap)
+ }
+
+ fn serialized_storage<'a>(
+ data: StructuredData<'a, '_>,
+ ) -> &'a mut Option<HashMap<ImageBitmapId, Self::Data>> {
+ match data {
+ StructuredData::Reader(r) => &mut r.transferred_image_bitmaps,
+ StructuredData::Writer(w) => &mut w.transferred_image_bitmaps,
+ }
+ }
+}
+
impl ImageBitmapMethods<crate::DomTypeHolder> for ImageBitmap {
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-height>
fn Height(&self) -> u32 {
diff --git a/components/script/dom/readablestream.rs b/components/script/dom/readablestream.rs
index d631a01e1e7..d2c1d853f86 100644
--- a/components/script/dom/readablestream.rs
+++ b/components/script/dom/readablestream.rs
@@ -24,7 +24,7 @@ use js::typedarray::ArrayBufferViewU8;
use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy;
use crate::dom::bindings::codegen::Bindings::ReadableStreamBinding::{
ReadableStreamGetReaderOptions, ReadableStreamMethods, ReadableStreamReaderMode,
- StreamPipeOptions,
+ ReadableWritablePair, StreamPipeOptions,
};
use script_bindings::str::DOMString;
@@ -2006,6 +2006,50 @@ impl ReadableStreamMethods<crate::DomTypeHolder> for ReadableStream {
can_gc,
)
}
+
+ /// <https://streams.spec.whatwg.org/#rs-pipe-through>
+ fn PipeThrough(
+ &self,
+ transform: &ReadableWritablePair,
+ options: &StreamPipeOptions,
+ realm: InRealm,
+ can_gc: CanGc,
+ ) -> Fallible<DomRoot<ReadableStream>> {
+ let global = self.global();
+ let cx = GlobalScope::get_cx();
+
+ // If ! IsReadableStreamLocked(this) is true, throw a TypeError exception.
+ if self.is_locked() {
+ return Err(Error::Type("Source stream is locked".to_owned()));
+ }
+
+ // If ! IsWritableStreamLocked(transform["writable"]) is true, throw a TypeError exception.
+ if transform.writable.is_locked() {
+ return Err(Error::Type("Destination stream is locked".to_owned()));
+ }
+
+ // Let signal be options["signal"] if it exists, or undefined otherwise.
+ // TODO: implement AbortSignal.
+
+ // Let promise be ! ReadableStreamPipeTo(this, transform["writable"],
+ // options["preventClose"], options["preventAbort"], options["preventCancel"], signal).
+ let promise = self.pipe_to(
+ cx,
+ &global,
+ &transform.writable,
+ options.preventAbort,
+ options.preventCancel,
+ options.preventClose,
+ realm,
+ can_gc,
+ );
+
+ // Set promise.[[PromiseIsHandled]] to true.
+ promise.set_promise_is_handled();
+
+ // Return transform["readable"].
+ Ok(transform.readable.clone())
+ }
}
#[allow(unsafe_code)]
diff --git a/components/script/dom/transformstream.rs b/components/script/dom/transformstream.rs
index 0251498980d..446bf71f172 100644
--- a/components/script/dom/transformstream.rs
+++ b/components/script/dom/transformstream.rs
@@ -8,7 +8,7 @@ use std::ptr::{self};
use std::rc::Rc;
use base::id::{MessagePortId, MessagePortIndex};
-use constellation_traits::MessagePortImpl;
+use constellation_traits::TransformStreamData;
use dom_struct::dom_struct;
use js::jsapi::{Heap, IsPromiseObject, JSObject};
use js::jsval::{JSVal, ObjectValue, UndefinedValue};
@@ -1007,9 +1007,9 @@ impl TransformStreamMethods<crate::DomTypeHolder> for TransformStream {
/// <https://streams.spec.whatwg.org/#ts-transfer>
impl Transferable for TransformStream {
type Index = MessagePortIndex;
- type Data = MessagePortImpl;
+ type Data = TransformStreamData;
- fn transfer(&self) -> Result<(MessagePortId, MessagePortImpl), ()> {
+ fn transfer(&self) -> Result<(MessagePortId, TransformStreamData), ()> {
let global = self.global();
let realm = enter_realm(&*global);
let comp = InRealm::Entered(&realm);
@@ -1023,73 +1023,85 @@ impl Transferable for TransformStream {
let writable = self.get_writable();
// If ! IsReadableStreamLocked(readable) is true, throw a "DataCloneError" DOMException.
- if readable.is_locked() {
- return Err(());
- }
-
// If ! IsWritableStreamLocked(writable) is true, throw a "DataCloneError" DOMException.
- if writable.is_locked() {
+ if readable.is_locked() || writable.is_locked() {
return Err(());
}
- // Create the shared port pair
- let port_1 = MessagePort::new(&global, can_gc);
- global.track_message_port(&port_1, None);
- let port_2 = MessagePort::new(&global, can_gc);
- global.track_message_port(&port_2, None);
- global.entangle_ports(*port_1.message_port_id(), *port_2.message_port_id());
+ // First port pair (readable → proxy writable)
+ let port1 = MessagePort::new(&global, can_gc);
+ global.track_message_port(&port1, None);
+ let port1_peer = MessagePort::new(&global, can_gc);
+ global.track_message_port(&port1_peer, None);
+ global.entangle_ports(*port1.message_port_id(), *port1_peer.message_port_id());
+
+ let proxy_readable = ReadableStream::new_with_proto(&global, None, can_gc);
+ proxy_readable.setup_cross_realm_transform_readable(cx, &port1, can_gc);
+ proxy_readable
+ .pipe_to(cx, &global, &writable, false, false, false, comp, can_gc)
+ .set_promise_is_handled();
+
+ // Second port pair (proxy readable → writable)
+ let port2 = MessagePort::new(&global, can_gc);
+ global.track_message_port(&port2, None);
+ let port2_peer = MessagePort::new(&global, can_gc);
+ global.track_message_port(&port2_peer, None);
+ global.entangle_ports(*port2.message_port_id(), *port2_peer.message_port_id());
- // Create a proxy WritableStream wired to port_1
let proxy_writable = WritableStream::new_with_proto(&global, None, can_gc);
- proxy_writable.setup_cross_realm_transform_writable(cx, &port_1, can_gc);
+ proxy_writable.setup_cross_realm_transform_writable(cx, &port2, can_gc);
// Pipe readable into the proxy writable (→ port_1)
- let pipe1 = readable.pipe_to(
- cx,
- &global,
- &proxy_writable,
- false,
- false,
- false,
- comp,
- can_gc,
- );
- pipe1.set_promise_is_handled();
-
- // Create a proxy ReadableStream wired to port_1
- let proxy_readable = ReadableStream::new_with_proto(&global, None, can_gc);
- proxy_readable.setup_cross_realm_transform_readable(cx, &port_1, can_gc);
-
- // Pipe proxy readable (← port_1) into writable
- let pipe2 =
- proxy_readable.pipe_to(cx, &global, &writable, false, false, false, comp, can_gc);
- pipe2.set_promise_is_handled();
+ readable
+ .pipe_to(
+ cx,
+ &global,
+ &proxy_writable,
+ false,
+ false,
+ false,
+ comp,
+ can_gc,
+ )
+ .set_promise_is_handled();
// Set dataHolder.[[readable]] to ! StructuredSerializeWithTransfer(readable, « readable »).
// Set dataHolder.[[writable]] to ! StructuredSerializeWithTransfer(writable, « writable »).
- port_2.transfer()
+ Ok((
+ *port1_peer.message_port_id(),
+ TransformStreamData {
+ readable: port1_peer.transfer()?,
+ writable: port2_peer.transfer()?,
+ },
+ ))
}
fn transfer_receive(
owner: &GlobalScope,
- id: MessagePortId,
- port_impl: MessagePortImpl,
+ _id: MessagePortId,
+ data: TransformStreamData,
) -> Result<DomRoot<Self>, ()> {
let can_gc = CanGc::note();
+ let cx = GlobalScope::get_cx();
+
+ let port1 = MessagePort::transfer_receive(owner, data.readable.0, data.readable.1)?;
+ let port2 = MessagePort::transfer_receive(owner, data.writable.0, data.writable.1)?;
// Let readableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[readable]], the current Realm).
// Set value.[[readable]] to readableRecord.[[Deserialized]].
- let readable = ReadableStream::transfer_receive(owner, id, port_impl.clone())?;
-
// Let writableRecord be ! StructuredDeserializeWithTransfer(dataHolder.[[writable]], the current Realm).
- let writable = WritableStream::transfer_receive(owner, id, port_impl)?;
+ let proxy_readable = ReadableStream::new_with_proto(owner, None, can_gc);
+ proxy_readable.setup_cross_realm_transform_readable(cx, &port2, can_gc);
+
+ let proxy_writable = WritableStream::new_with_proto(owner, None, can_gc);
+ proxy_writable.setup_cross_realm_transform_writable(cx, &port1, can_gc);
// Set value.[[readable]] to readableRecord.[[Deserialized]].
// Set value.[[writable]] to writableRecord.[[Deserialized]].
// Set value.[[backpressure]], value.[[backpressureChangePromise]], and value.[[controller]] to undefined.
let stream = TransformStream::new_with_proto(owner, None, can_gc);
- stream.readable.set(Some(&readable));
- stream.writable.set(Some(&writable));
+ stream.readable.set(Some(&proxy_readable));
+ stream.writable.set(Some(&proxy_writable));
Ok(stream)
}
@@ -1098,8 +1110,8 @@ impl Transferable for TransformStream {
data: StructuredData<'a, '_>,
) -> &'a mut Option<HashMap<MessagePortId, Self::Data>> {
match data {
- StructuredData::Reader(r) => &mut r.port_impls,
- StructuredData::Writer(w) => &mut w.ports,
+ StructuredData::Reader(r) => &mut r.transform_streams_port_impls,
+ StructuredData::Writer(w) => &mut w.transform_streams_port,
}
}
}
diff --git a/components/script/dom/urlpattern.rs b/components/script/dom/urlpattern.rs
index c811d3a9a70..63665f6df0b 100644
--- a/components/script/dom/urlpattern.rs
+++ b/components/script/dom/urlpattern.rs
@@ -4,6 +4,7 @@
use dom_struct::dom_struct;
use js::rust::HandleObject;
+use script_bindings::codegen::GenericBindings::URLPatternBinding::URLPatternResult;
use script_bindings::codegen::GenericUnionTypes::USVStringOrURLPatternInit;
use script_bindings::error::{Error, Fallible};
use script_bindings::reflector::Reflector;
@@ -46,7 +47,7 @@ impl URLPattern {
) -> Fallible<DomRoot<URLPattern>> {
// The section below converts from servos types to the types used in the urlpattern crate
let base_url = base_url.map(|usv_string| usv_string.0);
- let input = bindings_to_third_party::map_urlpattern_input(input, base_url.clone());
+ let input = bindings_to_third_party::map_urlpattern_input(input);
let options = urlpattern::UrlPatternOptions {
ignore_case: options.ignoreCase,
};
@@ -94,6 +95,50 @@ impl URLPatternMethods<crate::DomTypeHolder> for URLPattern {
URLPattern::initialize(global, proto, input, None, options, can_gc)
}
+ /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-test>
+ fn Test(
+ &self,
+ input: USVStringOrURLPatternInit,
+ base_url: Option<USVString>,
+ ) -> Fallible<bool> {
+ let input = bindings_to_third_party::map_urlpattern_input(input);
+ let inputs = urlpattern::quirks::process_match_input(input, base_url.as_deref())
+ .map_err(|error| Error::Type(format!("{error}")))?;
+ let Some((match_input, _)) = inputs else {
+ return Ok(false);
+ };
+
+ self.associated_url_pattern
+ .test(match_input)
+ .map_err(|error| Error::Type(format!("{error}")))
+ }
+
+ /// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec>
+ fn Exec(
+ &self,
+ input: USVStringOrURLPatternInit,
+ base_url: Option<USVString>,
+ ) -> Fallible<Option<URLPatternResult>> {
+ let input = bindings_to_third_party::map_urlpattern_input(input);
+ let inputs = urlpattern::quirks::process_match_input(input, base_url.as_deref())
+ .map_err(|error| Error::Type(format!("{error}")))?;
+ let Some((match_input, inputs)) = inputs else {
+ return Ok(None);
+ };
+
+ let result = self
+ .associated_url_pattern
+ .exec(match_input)
+ .map_err(|error| Error::Type(format!("{error}")))?;
+ let Some(result) = result else {
+ return Ok(None);
+ };
+
+ Ok(Some(third_party_to_bindings::map_urlpattern_result(
+ result, inputs,
+ )))
+ }
+
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol>
fn Protocol(&self) -> USVString {
// Step 1. Return this’s associated URL pattern’s protocol component’s pattern string.
@@ -151,54 +196,115 @@ impl URLPatternMethods<crate::DomTypeHolder> for URLPattern {
}
mod bindings_to_third_party {
+ use script_bindings::codegen::GenericBindings::URLPatternBinding::URLPatternInit;
+
use crate::dom::urlpattern::USVStringOrURLPatternInit;
+ fn map_urlpatterninit(pattern_init: URLPatternInit) -> urlpattern::quirks::UrlPatternInit {
+ urlpattern::quirks::UrlPatternInit {
+ protocol: pattern_init.protocol.map(|protocol| protocol.0),
+ username: pattern_init.username.map(|username| username.0),
+ password: pattern_init.password.map(|password| password.0),
+ hostname: pattern_init.hostname.map(|hostname| hostname.0),
+ port: pattern_init.port.map(|hash| hash.0),
+ pathname: pattern_init
+ .pathname
+ .as_ref()
+ .map(|usv_string| usv_string.to_string()),
+ search: pattern_init.search.map(|search| search.0),
+ hash: pattern_init.hash.map(|hash| hash.0),
+ base_url: pattern_init.baseURL.map(|base_url| base_url.0),
+ }
+ }
+
pub(super) fn map_urlpattern_input(
input: USVStringOrURLPatternInit,
- base_url: Option<String>,
) -> urlpattern::quirks::StringOrInit {
match input {
USVStringOrURLPatternInit::USVString(usv_string) => {
urlpattern::quirks::StringOrInit::String(usv_string.0)
},
USVStringOrURLPatternInit::URLPatternInit(pattern_init) => {
- let pattern_init = urlpattern::quirks::UrlPatternInit {
- protocol: pattern_init
- .protocol
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- username: pattern_init
- .username
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- password: pattern_init
- .password
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- hostname: pattern_init
- .hostname
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- port: pattern_init
- .port
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- pathname: pattern_init
- .pathname
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- search: pattern_init
- .search
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- hash: pattern_init
- .hash
- .as_ref()
- .map(|usv_string| usv_string.to_string()),
- base_url,
- };
- urlpattern::quirks::StringOrInit::Init(pattern_init)
+ urlpattern::quirks::StringOrInit::Init(map_urlpatterninit(pattern_init))
+ },
+ }
+ }
+}
+
+mod third_party_to_bindings {
+ use script_bindings::codegen::GenericBindings::URLPatternBinding::{
+ URLPatternComponentResult, URLPatternInit, URLPatternResult,
+ };
+ use script_bindings::codegen::GenericUnionTypes::USVStringOrUndefined;
+ use script_bindings::record::Record;
+ use script_bindings::str::USVString;
+
+ use crate::dom::bindings::codegen::UnionTypes::USVStringOrURLPatternInit;
+
+ // FIXME: For some reason codegen puts a lot of options into these types that don't make sense
+
+ fn map_component_result(
+ component_result: urlpattern::UrlPatternComponentResult,
+ ) -> URLPatternComponentResult {
+ let mut groups = Record::new();
+ for (key, value) in component_result.groups.iter() {
+ let value = match value {
+ Some(value) => USVStringOrUndefined::USVString(USVString(value.to_owned())),
+ None => USVStringOrUndefined::Undefined(()),
+ };
+
+ groups.insert(USVString(key.to_owned()), value);
+ }
+
+ URLPatternComponentResult {
+ input: Some(component_result.input.into()),
+ groups: Some(groups),
+ }
+ }
+
+ fn map_urlpatterninit(pattern_init: urlpattern::quirks::UrlPatternInit) -> URLPatternInit {
+ URLPatternInit {
+ baseURL: pattern_init.base_url.map(USVString),
+ protocol: pattern_init.protocol.map(USVString),
+ username: pattern_init.username.map(USVString),
+ password: pattern_init.password.map(USVString),
+ hostname: pattern_init.hostname.map(USVString),
+ port: pattern_init.port.map(USVString),
+ pathname: pattern_init.pathname.map(USVString),
+ search: pattern_init.search.map(USVString),
+ hash: pattern_init.hash.map(USVString),
+ }
+ }
+
+ pub(super) fn map_urlpattern_result(
+ result: urlpattern::UrlPatternResult,
+ (string_or_init, base_url): urlpattern::quirks::Inputs,
+ ) -> URLPatternResult {
+ let string_or_init = match string_or_init {
+ urlpattern::quirks::StringOrInit::String(string) => {
+ USVStringOrURLPatternInit::USVString(USVString(string))
+ },
+ urlpattern::quirks::StringOrInit::Init(pattern_init) => {
+ USVStringOrURLPatternInit::URLPatternInit(map_urlpatterninit(pattern_init))
},
+ };
+
+ let mut inputs = vec![string_or_init];
+
+ if let Some(base_url) = base_url {
+ inputs.push(USVStringOrURLPatternInit::USVString(USVString(base_url)));
+ }
+
+ URLPatternResult {
+ inputs: Some(inputs),
+ protocol: Some(map_component_result(result.protocol)),
+ username: Some(map_component_result(result.username)),
+ password: Some(map_component_result(result.password)),
+ hostname: Some(map_component_result(result.hostname)),
+ port: Some(map_component_result(result.port)),
+ pathname: Some(map_component_result(result.pathname)),
+ search: Some(map_component_result(result.search)),
+ hash: Some(map_component_result(result.hash)),
}
}
}
diff --git a/components/script/dom/webglrenderingcontext.rs b/components/script/dom/webglrenderingcontext.rs
index 98170f9655b..b82051b3b12 100644
--- a/components/script/dom/webglrenderingcontext.rs
+++ b/components/script/dom/webglrenderingcontext.rs
@@ -619,7 +619,8 @@ impl WebGLRenderingContext {
let size = Size2D::new(img.width, img.height);
- TexPixels::new(img.bytes(), size, img.format, false)
+ let data = IpcSharedMemory::from_bytes(img.first_frame().bytes);
+ TexPixels::new(data, size, img.format, false)
},
// TODO(emilio): Getting canvas data is implemented in CanvasRenderingContext2D,
// but we need to refactor it moving it to `HTMLCanvasElement` and support
diff --git a/components/script/dom/webgpu/gpu.rs b/components/script/dom/webgpu/gpu.rs
index 20380e07bfb..b2534cda9a8 100644
--- a/components/script/dom/webgpu/gpu.rs
+++ b/components/script/dom/webgpu/gpu.rs
@@ -86,8 +86,12 @@ impl GPUMethods<crate::DomTypeHolder> for GPU {
/// <https://gpuweb.github.io/gpuweb/#dom-gpu-getpreferredcanvasformat>
fn GetPreferredCanvasFormat(&self) -> GPUTextureFormat {
- // TODO: real implementation
- GPUTextureFormat::Rgba8unorm
+ // From https://github.com/mozilla-firefox/firefox/blob/24d49101ce17b78c3ba1217d00297fe2891be6b3/dom/webgpu/Instance.h#L68
+ if cfg!(target_os = "android") {
+ GPUTextureFormat::Rgba8unorm
+ } else {
+ GPUTextureFormat::Bgra8unorm
+ }
}
/// <https://www.w3.org/TR/webgpu/#dom-gpu-wgsllanguagefeatures>
diff --git a/components/script/image_animation.rs b/components/script/image_animation.rs
index 89e4c93b828..9592f86fedb 100644
--- a/components/script/image_animation.rs
+++ b/components/script/image_animation.rs
@@ -2,20 +2,37 @@
* 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 fxhash::FxHashMap;
+use compositing_traits::{ImageUpdate, SerializableImageData};
+use embedder_traits::UntrustedNodeAddress;
+use fxhash::{FxHashMap, FxHashSet};
+use ipc_channel::ipc::IpcSharedMemory;
+use libc::c_void;
+use script_bindings::root::Dom;
use script_layout_interface::ImageAnimationState;
use style::dom::OpaqueNode;
+use webrender_api::units::DeviceIntSize;
+use webrender_api::{ImageDescriptor, ImageDescriptorFlags, ImageFormat};
+
+use crate::dom::bindings::cell::DomRefCell;
+use crate::dom::bindings::trace::NoTrace;
+use crate::dom::node::{Node, from_untrusted_node_address};
+use crate::dom::window::Window;
#[derive(Clone, Debug, Default, JSTraceable, MallocSizeOf)]
+#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub struct ImageAnimationManager {
#[no_trace]
pub node_to_image_map: FxHashMap<OpaqueNode, ImageAnimationState>,
+
+ /// A list of nodes with in-progress image animations.
+ rooted_nodes: DomRefCell<FxHashMap<NoTrace<OpaqueNode>, Dom<Node>>>,
}
impl ImageAnimationManager {
pub fn new() -> Self {
ImageAnimationManager {
node_to_image_map: Default::default(),
+ rooted_nodes: DomRefCell::new(FxHashMap::default()),
}
}
@@ -25,5 +42,81 @@ impl ImageAnimationManager {
pub fn restore_image_animate_set(&mut self, map: FxHashMap<OpaqueNode, ImageAnimationState>) {
let _ = std::mem::replace(&mut self.node_to_image_map, map);
+ self.root_newly_animating_dom_nodes();
+ self.unroot_unused_nodes();
+ }
+
+ pub fn next_schedule_time(&self, now: f64) -> Option<f64> {
+ self.node_to_image_map
+ .values()
+ .map(|state| state.time_to_next_frame(now))
+ .min_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal))
+ }
+
+ pub fn image_animations_present(&self) -> bool {
+ !self.node_to_image_map.is_empty()
+ }
+
+ pub fn update_active_frames(&mut self, window: &Window, now: f64) {
+ let rooted_nodes = self.rooted_nodes.borrow();
+ let updates: Vec<ImageUpdate> = self
+ .node_to_image_map
+ .iter_mut()
+ .filter_map(|(node, state)| {
+ if state.update_frame_for_animation_timeline_value(now) {
+ let image = &state.image;
+ let frame = image
+ .frames()
+ .nth(state.active_frame)
+ .expect("active_frame should within range of frames");
+
+ if let Some(node) = rooted_nodes.get(&NoTrace(*node)) {
+ node.dirty(crate::dom::node::NodeDamage::OtherNodeDamage);
+ }
+ Some(ImageUpdate::UpdateImage(
+ image.id.unwrap(),
+ ImageDescriptor {
+ format: ImageFormat::BGRA8,
+ size: DeviceIntSize::new(image.width as i32, image.height as i32),
+ stride: None,
+ offset: 0,
+ flags: ImageDescriptorFlags::ALLOW_MIPMAPS,
+ },
+ SerializableImageData::Raw(IpcSharedMemory::from_bytes(frame.bytes)),
+ ))
+ } else {
+ None
+ }
+ })
+ .collect();
+ window.compositor_api().update_images(updates);
+ }
+
+ // Unroot any nodes that we have rooted but no longer have animating images.
+ fn unroot_unused_nodes(&self) {
+ let nodes: FxHashSet<&OpaqueNode> = self.node_to_image_map.keys().collect();
+ self.rooted_nodes
+ .borrow_mut()
+ .retain(|node, _| nodes.contains(&node.0));
+ }
+
+ /// Ensure that all nodes with Image animations are rooted. This should be called
+ /// immediately after a restyle, to ensure that these addresses are still valid.
+ #[allow(unsafe_code)]
+ fn root_newly_animating_dom_nodes(&self) {
+ let mut rooted_nodes = self.rooted_nodes.borrow_mut();
+ for opaque_node in self.node_to_image_map.keys() {
+ let opaque_node = *opaque_node;
+ if rooted_nodes.contains_key(&NoTrace(opaque_node)) {
+ continue;
+ }
+ let address = UntrustedNodeAddress(opaque_node.0 as *const c_void);
+ unsafe {
+ rooted_nodes.insert(
+ NoTrace(opaque_node),
+ Dom::from_ref(&*from_untrusted_node_address(address)),
+ )
+ };
+ }
}
}
diff --git a/components/script/script_thread.rs b/components/script/script_thread.rs
index 4815e6feae1..e0309298f3d 100644
--- a/components/script/script_thread.rs
+++ b/components/script/script_thread.rs
@@ -1081,6 +1081,10 @@ impl ScriptThread {
for event in document.take_pending_input_events().into_iter() {
document.update_active_keyboard_modifiers(event.active_keyboard_modifiers);
+ // We do this now, because the event is consumed below, but the order doesn't really
+ // matter as the event will be handled before any new ScriptThread messages are processed.
+ self.notify_webdriver_input_event_completed(pipeline_id, &event.event);
+
match event.event {
InputEvent::MouseButton(mouse_button_event) => {
document.handle_mouse_button_event(
@@ -1144,6 +1148,19 @@ impl ScriptThread {
ScriptThread::set_user_interacting(false);
}
+ fn notify_webdriver_input_event_completed(&self, pipeline_id: PipelineId, event: &InputEvent) {
+ let Some(id) = event.webdriver_message_id() else {
+ return;
+ };
+
+ if let Err(error) = self.senders.pipeline_to_constellation_sender.send((
+ pipeline_id,
+ ScriptToConstellationMessage::WebDriverInputComplete(id),
+ )) {
+ warn!("ScriptThread failed to send WebDriverInputComplete {id:?}: {error:?}",);
+ }
+ }
+
/// <https://html.spec.whatwg.org/multipage/#update-the-rendering>
///
/// Attempt to update the rendering and then do a microtask checkpoint if rendering was actually
@@ -1272,6 +1289,10 @@ impl ScriptThread {
// TODO: Mark paint timing from https://w3c.github.io/paint-timing.
+ // Update the rendering of those does not require a reflow.
+ // e.g. animated images.
+ document.update_animating_images();
+
#[cfg(feature = "webgpu")]
document.update_rendering_of_webgpu_canvases();
@@ -3420,7 +3441,7 @@ impl ScriptThread {
// the pointer, when the user presses down and releases the primary pointer button"
// Servo-specific: Trigger if within 10px of the down point
- if let InputEvent::MouseButton(mouse_button_event) = event.event {
+ if let InputEvent::MouseButton(mouse_button_event) = &event.event {
if let MouseButton::Left = mouse_button_event.button {
match mouse_button_event.action {
MouseButtonAction::Up => {
@@ -3429,16 +3450,23 @@ impl ScriptThread {
let pixel_dist =
(pixel_dist.x * pixel_dist.x + pixel_dist.y * pixel_dist.y).sqrt();
if pixel_dist < 10.0 * document.window().device_pixel_ratio().get() {
- document.note_pending_input_event(event.clone());
+ // Pass webdriver_id to the newly generated click event
+ document.note_pending_input_event(ConstellationInputEvent {
+ hit_test_result: event.hit_test_result.clone(),
+ pressed_mouse_buttons: event.pressed_mouse_buttons,
+ active_keyboard_modifiers: event.active_keyboard_modifiers,
+ event: event.event.clone().with_webdriver_message_id(None),
+ });
document.note_pending_input_event(ConstellationInputEvent {
hit_test_result: event.hit_test_result,
pressed_mouse_buttons: event.pressed_mouse_buttons,
active_keyboard_modifiers: event.active_keyboard_modifiers,
- event: InputEvent::MouseButton(MouseButtonEvent {
- action: MouseButtonAction::Click,
- button: mouse_button_event.button,
- point: mouse_button_event.point,
- }),
+ event: InputEvent::MouseButton(MouseButtonEvent::new(
+ MouseButtonAction::Click,
+ mouse_button_event.button,
+ mouse_button_event.point,
+ ))
+ .with_webdriver_message_id(event.event.webdriver_message_id()),
});
return;
}
diff --git a/components/script/test.rs b/components/script/test.rs
index c5933696efc..35e05621125 100644
--- a/components/script/test.rs
+++ b/components/script/test.rs
@@ -7,7 +7,6 @@
pub use crate::dom::bindings::refcounted::TrustedPromise;
//pub use crate::dom::bindings::root::Dom;
pub use crate::dom::bindings::str::{ByteString, DOMString};
-pub use crate::dom::headers::normalize_value;
//pub use crate::dom::node::Node;
pub mod area {
diff --git a/components/script/timers.rs b/components/script/timers.rs
index 45574c5d717..0dc1397fbdd 100644
--- a/components/script/timers.rs
+++ b/components/script/timers.rs
@@ -24,7 +24,9 @@ use crate::dom::bindings::refcounted::Trusted;
use crate::dom::bindings::reflector::{DomGlobal, DomObject};
use crate::dom::bindings::root::Dom;
use crate::dom::bindings::str::DOMString;
-use crate::dom::document::{FakeRequestAnimationFrameCallback, RefreshRedirectDue};
+use crate::dom::document::{
+ FakeRequestAnimationFrameCallback, ImageAnimationUpdateCallback, RefreshRedirectDue,
+};
use crate::dom::eventsource::EventSourceTimeoutCallback;
use crate::dom::globalscope::GlobalScope;
#[cfg(feature = "testbinding")]
@@ -83,6 +85,7 @@ pub(crate) enum OneshotTimerCallback {
TestBindingCallback(TestBindingCallback),
FakeRequestAnimationFrame(FakeRequestAnimationFrameCallback),
RefreshRedirectDue(RefreshRedirectDue),
+ ImageAnimationUpdate(ImageAnimationUpdateCallback),
}
impl OneshotTimerCallback {
@@ -95,6 +98,7 @@ impl OneshotTimerCallback {
OneshotTimerCallback::TestBindingCallback(callback) => callback.invoke(),
OneshotTimerCallback::FakeRequestAnimationFrame(callback) => callback.invoke(can_gc),
OneshotTimerCallback::RefreshRedirectDue(callback) => callback.invoke(can_gc),
+ OneshotTimerCallback::ImageAnimationUpdate(callback) => callback.invoke(can_gc),
}
}
}