Merge branch 'main' into outreachy-intern

This commit is contained in:
Aniebiet Afia 2025-05-29 12:47:11 +01:00 committed by GitHub
commit 0a91273a97
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2099 changed files with 49743 additions and 390343 deletions

View file

@ -0,0 +1,71 @@
/* 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 dom_struct::dom_struct;
use js::jsapi::Heap;
use js::jsval::JSVal;
use js::rust::{HandleObject, MutableHandleValue};
use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
use crate::dom::bindings::reflector::reflect_dom_object_with_proto;
use crate::dom::bindings::root::DomRoot;
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::script_runtime::{CanGc, JSContext};
/// <https://dom.spec.whatwg.org/#abortsignal>
#[dom_struct]
pub(crate) struct AbortSignal {
eventtarget: EventTarget,
/// <https://dom.spec.whatwg.org/#abortsignal-abort-reason>
#[ignore_malloc_size_of = "mozjs"]
abort_reason: Heap<JSVal>,
}
impl AbortSignal {
#[allow(dead_code)]
fn new_inherited() -> AbortSignal {
AbortSignal {
eventtarget: EventTarget::new_inherited(),
abort_reason: Default::default(),
}
}
#[allow(dead_code)]
fn new_with_proto(
global: &GlobalScope,
proto: Option<HandleObject>,
can_gc: CanGc,
) -> DomRoot<AbortSignal> {
reflect_dom_object_with_proto(
Box::new(AbortSignal::new_inherited()),
global,
proto,
can_gc,
)
}
}
impl AbortSignalMethods<crate::DomTypeHolder> for AbortSignal {
/// <https://dom.spec.whatwg.org/#dom-abortsignal-aborted>
fn Aborted(&self) -> bool {
// TODO
false
}
/// <https://dom.spec.whatwg.org/#dom-abortsignal-reason>
fn Reason(&self, _: JSContext, _rval: MutableHandleValue) {
// TODO
}
/// <https://dom.spec.whatwg.org/#dom-abortsignal-throwifaborted>
#[allow(unsafe_code)]
fn ThrowIfAborted(&self) {
// TODO
}
// <https://dom.spec.whatwg.org/#dom-abortsignal-onabort>
event_handler!(abort, GetOnabort, SetOnabort);
}

View file

@ -9,10 +9,8 @@ use std::cell::{BorrowError, BorrowMutError};
pub(crate) use std::cell::{Ref, RefCell, RefMut};
#[cfg(feature = "refcell_backtrace")]
pub(crate) use accountable_refcell::{Ref, RefCell, RefMut, ref_filter_map};
pub(crate) use accountable_refcell::{Ref, RefCell, RefMut};
use malloc_size_of::{MallocConditionalSizeOf, MallocSizeOfOps};
#[cfg(not(feature = "refcell_backtrace"))]
pub(crate) use ref_filter_map::ref_filter_map;
use crate::dom::bindings::root::{assert_in_layout, assert_in_script};

View file

@ -11,7 +11,7 @@ use crate::DomTypes;
use crate::dom::bindings::conversions::DerivedFrom;
use crate::dom::bindings::root::DomRoot;
use crate::dom::globalscope::GlobalScope;
use crate::realms::InRealm;
use crate::realms::{InRealm, enter_realm};
use crate::script_runtime::CanGc;
/// Create the reflector for a new DOM object and yield ownership to the
@ -42,7 +42,16 @@ where
}
pub(crate) trait DomGlobal {
/// Returns the [relevant global] in whatever realm is currently active.
///
/// [relevant global]: https://html.spec.whatwg.org/multipage/#concept-relevant-global
fn global_(&self, realm: InRealm) -> DomRoot<GlobalScope>;
/// Returns the [relevant global] in the same realm as the callee object.
/// If you know the callee's realm is already the current realm, it is
/// more efficient to call [DomGlobal::global_] instead.
///
/// [relevant global]: https://html.spec.whatwg.org/multipage/#concept-relevant-global
fn global(&self) -> DomRoot<GlobalScope>;
}
@ -51,7 +60,8 @@ impl<T: DomGlobalGeneric<crate::DomTypeHolder>> DomGlobal for T {
<Self as DomGlobalGeneric<crate::DomTypeHolder>>::global_(self, realm)
}
fn global(&self) -> DomRoot<GlobalScope> {
<Self as DomGlobalGeneric<crate::DomTypeHolder>>::global(self)
let realm = enter_realm(self);
<Self as DomGlobalGeneric<crate::DomTypeHolder>>::global_(self, InRealm::entered(&realm))
}
}

View file

@ -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 {

View file

@ -12,11 +12,10 @@ use dom_struct::dom_struct;
use encoding_rs::UTF_8;
use js::jsapi::JSObject;
use js::rust::HandleObject;
use js::typedarray::Uint8;
use js::typedarray::{ArrayBufferU8, Uint8};
use net_traits::filemanager_thread::RelativePos;
use uuid::Uuid;
use crate::body::{FetchedData, run_array_buffer_data_algorithm};
use crate::dom::bindings::buffer_source::create_buffer_source;
use crate::dom::bindings::codegen::Bindings::BlobBinding;
use crate::dom::bindings::codegen::Bindings::BlobBinding::BlobMethods;
@ -226,7 +225,7 @@ impl BlobMethods<crate::DomTypeHolder> for Blob {
Blob::new(&global, blob_impl, can_gc)
}
// https://w3c.github.io/FileAPI/#text-method-algo
/// <https://w3c.github.io/FileAPI/#text-method-algo>
fn Text(&self, can_gc: CanGc) -> Rc<Promise> {
let global = self.global();
let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
@ -250,35 +249,51 @@ impl BlobMethods<crate::DomTypeHolder> for Blob {
}
// https://w3c.github.io/FileAPI/#arraybuffer-method-algo
fn ArrayBuffer(&self, can_gc: CanGc) -> Rc<Promise> {
let global = self.global();
let in_realm_proof = AlreadyInRealm::assert::<crate::DomTypeHolder>();
let p = Promise::new_in_current_realm(InRealm::Already(&in_realm_proof), can_gc);
fn ArrayBuffer(&self, in_realm: InRealm, can_gc: CanGc) -> Rc<Promise> {
let cx = GlobalScope::get_cx();
let global = GlobalScope::from_safe_context(cx, in_realm);
let promise = Promise::new_in_current_realm(in_realm, can_gc);
let id = self.get_blob_url_id();
// 1. Let stream be the result of calling get stream on this.
let stream = self.get_stream(can_gc);
global.read_file_async(
id,
p.clone(),
Box::new(|promise, bytes| {
match bytes {
Ok(b) => {
let cx = GlobalScope::get_cx();
let result = run_array_buffer_data_algorithm(cx, b, CanGc::note());
// 2. Let reader be the result of getting a reader from stream.
// If that threw an exception, return a new promise rejected with that exception.
let reader = match stream.and_then(|s| s.acquire_default_reader(can_gc)) {
Ok(reader) => reader,
Err(error) => {
promise.reject_error(error, can_gc);
return promise;
},
};
match result {
Ok(FetchedData::ArrayBuffer(a)) => {
promise.resolve_native(&a, CanGc::note())
},
Err(e) => promise.reject_error(e, CanGc::note()),
_ => panic!("Unexpected result from run_array_buffer_data_algorithm"),
}
},
Err(e) => promise.reject_error(e, CanGc::note()),
};
// 3. Let promise be the result of reading all bytes from stream with reader.
let success_promise = promise.clone();
let failure_promise = promise.clone();
reader.read_all_bytes(
cx,
&global,
Rc::new(move |bytes| {
rooted!(in(*cx) let mut js_object = ptr::null_mut::<JSObject>());
// 4. Return the result of transforming promise by a fulfillment handler that returns a new
// [ArrayBuffer]
let array_buffer = create_buffer_source::<ArrayBufferU8>(
cx,
bytes,
js_object.handle_mut(),
can_gc,
)
.expect("Converting input to ArrayBufferU8 should never fail");
success_promise.resolve_native(&array_buffer, can_gc);
}),
Rc::new(move |cx, value| {
failure_promise.reject(cx, value, can_gc);
}),
in_realm,
can_gc,
);
p
promise
}
/// <https://w3c.github.io/FileAPI/#dom-blob-bytes>

View file

@ -5,6 +5,7 @@
use canvas_traits::canvas::{Canvas2dMsg, CanvasId, CanvasMsg, FromScriptMsg};
use dom_struct::dom_struct;
use euclid::default::Size2D;
use ipc_channel::ipc::IpcSender;
use profile_traits::ipc;
use script_bindings::inheritance::Castable;
use servo_url::ServoUrl;
@ -36,27 +37,50 @@ use crate::dom::path2d::Path2D;
use crate::dom::textmetrics::TextMetrics;
use crate::script_runtime::CanGc;
#[derive(JSTraceable, MallocSizeOf)]
struct DroppableCanvasRenderingContext2D {
#[no_trace]
ipc_sender: IpcSender<CanvasMsg>,
#[no_trace]
canvas_id: CanvasId,
}
impl Drop for DroppableCanvasRenderingContext2D {
fn drop(&mut self) {
if let Err(err) = self.ipc_sender.send(CanvasMsg::Close(self.canvas_id)) {
warn!("Could not close canvas: {}", err)
}
}
}
// https://html.spec.whatwg.org/multipage/#canvasrenderingcontext2d
#[dom_struct]
pub(crate) struct CanvasRenderingContext2D {
reflector_: Reflector,
canvas: HTMLCanvasElementOrOffscreenCanvas,
canvas_state: CanvasState,
droppable: DroppableCanvasRenderingContext2D,
}
impl CanvasRenderingContext2D {
#[cfg_attr(crown, allow(crown::unrooted_must_root))]
pub(crate) fn new_inherited(
global: &GlobalScope,
canvas: HTMLCanvasElementOrOffscreenCanvas,
size: Size2D<u32>,
) -> CanvasRenderingContext2D {
let canvas_state =
CanvasState::new(global, Size2D::new(size.width as u64, size.height as u64));
let ipc_sender = canvas_state.get_ipc_renderer().clone();
let canvas_id = canvas_state.get_canvas_id();
CanvasRenderingContext2D {
reflector_: Reflector::new(),
canvas,
canvas_state: CanvasState::new(
global,
Size2D::new(size.width as u64, size.height as u64),
),
canvas_state,
droppable: DroppableCanvasRenderingContext2D {
ipc_sender,
canvas_id,
},
}
}
@ -689,15 +713,3 @@ impl CanvasRenderingContext2DMethods<crate::DomTypeHolder> for CanvasRenderingCo
.set_shadow_color(self.canvas.canvas(), value, can_gc)
}
}
impl Drop for CanvasRenderingContext2D {
fn drop(&mut self) {
if let Err(err) = self
.canvas_state
.get_ipc_renderer()
.send(CanvasMsg::Close(self.canvas_state.get_canvas_id()))
{
warn!("Could not close canvas: {}", err)
}
}
}

View file

@ -7,6 +7,7 @@ use std::rc::Rc;
use dom_struct::dom_struct;
use js::rust::{HandleObject, MutableHandleValue};
use net_traits::image_cache::Image;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::DataTransferBinding::DataTransferMethods;
@ -155,7 +156,10 @@ impl DataTransferMethods<crate::DomTypeHolder> for DataTransfer {
// Step 3
if let Some(image) = image.downcast::<HTMLImageElement>() {
data_store.set_bitmap(image.image_data(), x, y);
match image.image_data().as_ref().and_then(Image::as_raster_image) {
Some(image) => data_store.set_bitmap(Some(image), x, y),
None => warn!("Vector images are not yet supported in setDragImage"),
}
}
}

View file

@ -2384,6 +2384,9 @@ impl Document {
let mut cancel_state = event.get_cancel_state();
// https://w3c.github.io/uievents/#keys-cancelable-keys
// it MUST prevent the respective beforeinput and input
// (and keypress if supported) events from being generated
// TODO: keypress should be deprecated and superceded by beforeinput
if keyboard_event.state == KeyState::Down &&
is_character_value_key(&(keyboard_event.key)) &&
!keyboard_event.is_composing &&
@ -5074,6 +5077,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.
@ -6770,6 +6802,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 {

View file

@ -66,7 +66,7 @@ use xml5ever::serialize::TraversalScope::{
use crate::conversions::Convert;
use crate::dom::activation::Activatable;
use crate::dom::attr::{Attr, AttrHelpersForLayout, is_relevant_attribute};
use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut, ref_filter_map};
use crate::dom::bindings::cell::{DomRefCell, Ref, RefMut};
use crate::dom::bindings::codegen::Bindings::AttrBinding::AttrMethods;
use crate::dom::bindings::codegen::Bindings::DocumentBinding::DocumentMethods;
use crate::dom::bindings::codegen::Bindings::ElementBinding::{
@ -1480,7 +1480,7 @@ impl Element {
// is "xmlns", and local name is prefix, or if prefix is null and it has an attribute
// whose namespace is the XMLNS namespace, namespace prefix is null, and local name is
// "xmlns", then return its value if it is not the empty string, and null otherwise."
let attr = ref_filter_map(self.attrs(), |attrs| {
let attr = Ref::filter_map(self.attrs(), |attrs| {
attrs.iter().find(|attr| {
if attr.namespace() != &ns!(xmlns) {
return false;
@ -1493,7 +1493,8 @@ impl Element {
_ => false,
}
})
});
})
.ok();
if let Some(attr) = attr {
return (**attr.value()).into();

View file

@ -74,6 +74,7 @@ use super::bindings::codegen::Bindings::WebGPUBinding::GPUDeviceLostReason;
use super::bindings::error::Fallible;
use super::bindings::trace::{HashMapTracedValues, RootedTraceableBox};
use super::serviceworkerglobalscope::ServiceWorkerGlobalScope;
use super::transformstream::CrossRealmTransform;
use crate::dom::bindings::cell::{DomRefCell, RefMut};
use crate::dom::bindings::codegen::Bindings::BroadcastChannelBinding::BroadcastChannelMethods;
use crate::dom::bindings::codegen::Bindings::EventSourceBinding::EventSource_Binding::EventSourceMethods;
@ -458,13 +459,9 @@ pub(crate) struct ManagedMessagePort {
/// Whether the port has been closed by script in this global,
/// so it can be removed.
explicitly_closed: bool,
/// Note: it may seem strange to use a pair of options, versus for example an enum.
/// But it looks like tranform streams will require both of those in their transfer.
/// This will be resolved when we reach that point of the implementation.
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
cross_realm_transform_readable: Option<CrossRealmTransformReadable>,
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
cross_realm_transform_writable: Option<CrossRealmTransformWritable>,
/// The handler for `message` or `messageerror` used in the cross realm transform,
/// if any was setup with this port.
cross_realm_transform: Option<CrossRealmTransform>,
}
/// State representing whether this global is currently managing broadcast channels.
@ -1345,7 +1342,9 @@ impl GlobalScope {
unreachable!("Cross realm transform readable must match a managed port");
};
managed_port.cross_realm_transform_readable = Some(cross_realm_transform_readable.clone());
managed_port.cross_realm_transform = Some(CrossRealmTransform::Readable(
cross_realm_transform_readable.clone(),
));
}
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
@ -1368,7 +1367,9 @@ impl GlobalScope {
unreachable!("Cross realm transform writable must match a managed port");
};
managed_port.cross_realm_transform_writable = Some(cross_realm_transform_writable.clone());
managed_port.cross_realm_transform = Some(CrossRealmTransform::Writable(
cross_realm_transform_writable.clone(),
));
}
/// Custom routing logic, followed by the task steps of
@ -1380,8 +1381,7 @@ impl GlobalScope {
can_gc: CanGc,
) {
let cx = GlobalScope::get_cx();
rooted!(in(*cx) let mut cross_realm_transform_readable = None);
rooted!(in(*cx) let mut cross_realm_transform_writable = None);
rooted!(in(*cx) let mut cross_realm_transform = None);
let should_dispatch = if let MessagePortState::Managed(_id, message_ports) =
&mut *self.message_port_state.borrow_mut()
@ -1399,10 +1399,7 @@ impl GlobalScope {
let to_dispatch = port_impl.handle_incoming(task).map(|to_dispatch| {
(DomRoot::from_ref(&*managed_port.dom_port), to_dispatch)
});
cross_realm_transform_readable
.set(managed_port.cross_realm_transform_readable.clone());
cross_realm_transform_writable
.set(managed_port.cross_realm_transform_writable.clone());
cross_realm_transform.set(managed_port.cross_realm_transform.clone());
to_dispatch
} else {
panic!("managed-port has no port-impl.");
@ -1429,10 +1426,6 @@ impl GlobalScope {
// Re-ordered because we need to pass it to `structuredclone::read`.
rooted!(in(*cx) let mut message_clone = UndefinedValue());
// Note: if this port is used to transfer a stream, we handle the events in Rust.
let has_cross_realm_tansform = cross_realm_transform_readable.is_some() ||
cross_realm_transform_writable.is_some();
let realm = enter_realm(self);
let comp = InRealm::Entered(&realm);
@ -1447,26 +1440,28 @@ impl GlobalScope {
// if any, maintaining their relative order.
// Note: both done in `structuredclone::read`.
if let Ok(ports) = structuredclone::read(self, data, message_clone.handle_mut()) {
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
if let Some(transform) = cross_realm_transform_readable.as_ref() {
transform.handle_message(
cx,
self,
&dom_port,
message_clone.handle(),
comp,
can_gc,
);
}
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
if let Some(transform) = cross_realm_transform_writable.as_ref() {
transform.handle_message(cx, self, message_clone.handle(), comp, can_gc);
}
if !has_cross_realm_tansform {
// Note: if this port is used to transfer a stream, we handle the events in Rust.
if let Some(transform) = cross_realm_transform.as_ref() {
match transform {
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
CrossRealmTransform::Readable(readable) => {
readable.handle_message(
cx,
self,
&dom_port,
message_clone.handle(),
comp,
can_gc,
);
},
// Add a handler for ports message event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
CrossRealmTransform::Writable(writable) => {
writable.handle_message(cx, self, message_clone.handle(), comp, can_gc);
},
}
} else {
// Fire an event named message at messageEventTarget,
// using MessageEvent,
// with the data attribute initialized to messageClone
@ -1481,25 +1476,24 @@ impl GlobalScope {
can_gc,
);
}
} else if let Some(transform) = cross_realm_transform.as_ref() {
match transform {
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
CrossRealmTransform::Readable(readable) => {
readable.handle_error(cx, self, &dom_port, comp, can_gc);
},
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
CrossRealmTransform::Writable(writable) => {
writable.handle_error(cx, self, &dom_port, comp, can_gc);
},
}
} else {
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
if let Some(transform) = cross_realm_transform_readable.as_ref() {
transform.handle_error(cx, self, &dom_port, comp, can_gc);
}
// Add a handler for ports messageerror event with the following steps:
// from <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
if let Some(transform) = cross_realm_transform_writable.as_ref() {
transform.handle_error(cx, self, &dom_port, comp, can_gc);
}
if !has_cross_realm_tansform {
// If this throws an exception, catch it,
// fire an event named messageerror at messageEventTarget,
// using MessageEvent, and then return.
MessageEvent::dispatch_error(message_event_target, self, can_gc);
}
// If this throws an exception, catch it,
// fire an event named messageerror at messageEventTarget,
// using MessageEvent, and then return.
MessageEvent::dispatch_error(message_event_target, self, can_gc);
}
}
}
@ -1689,8 +1683,7 @@ impl GlobalScope {
dom_port: Dom::from_ref(dom_port),
pending: true,
explicitly_closed: false,
cross_realm_transform_readable: None,
cross_realm_transform_writable: None,
cross_realm_transform: None,
},
);
@ -1713,8 +1706,7 @@ impl GlobalScope {
dom_port: Dom::from_ref(dom_port),
pending: false,
explicitly_closed: false,
cross_realm_transform_readable: None,
cross_realm_transform_writable: None,
cross_realm_transform: None,
},
);
let _ = self.script_to_constellation_chan().send(
@ -2991,10 +2983,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();
image_bitmap.set_bitmap_data(snapshot.to_vec());
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
}
@ -3008,10 +2997,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();
image_bitmap.set_bitmap_data(snapshot.to_vec());
let image_bitmap = ImageBitmap::new(self, snapshot, can_gc);
image_bitmap.set_origin_clean(canvas.origin_is_clean());
p.resolve_native(&(image_bitmap), can_gc);
}

View file

@ -5,14 +5,15 @@
use std::cell::Cell;
use std::str::{self, FromStr};
use data_url::mime::Mime as DataUrlMime;
use dom_struct::dom_struct;
use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue};
use js::rust::HandleObject;
use net_traits::fetch::headers::{
get_decode_and_split_header_value, get_value_from_header_list, is_forbidden_method,
extract_mime_type, get_decode_and_split_header_value, get_value_from_header_list,
is_forbidden_method,
};
use net_traits::request::is_cors_safelisted_request_header;
use net_traits::trim_http_whitespace;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
@ -33,7 +34,7 @@ pub(crate) struct Headers {
header_list: DomRefCell<HyperHeaders>,
}
// https://fetch.spec.whatwg.org/#concept-headers-guard
/// <https://fetch.spec.whatwg.org/#concept-headers-guard>
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
pub(crate) enum Guard {
Immutable,
@ -66,7 +67,7 @@ impl Headers {
}
impl HeadersMethods<crate::DomTypeHolder> for Headers {
// https://fetch.spec.whatwg.org/#dom-headers
/// <https://fetch.spec.whatwg.org/#dom-headers>
fn Constructor(
global: &GlobalScope,
proto: Option<HandleObject>,
@ -78,47 +79,41 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
Ok(dom_headers_new)
}
// https://fetch.spec.whatwg.org/#concept-headers-append
/// <https://fetch.spec.whatwg.org/#concept-headers-append>
fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult {
// Step 1
let value = normalize_value(value);
// 1. Normalize value.
let value = trim_http_whitespace(&value);
// Step 2
// https://fetch.spec.whatwg.org/#headers-validate
let (mut valid_name, valid_value) = validate_name_and_value(name, value)?;
// 2. If validating (name, value) for headers returns false, then return.
let Some((mut valid_name, valid_value)) =
self.validate_name_and_value(name, ByteString::new(value.into()))?
else {
return Ok(());
};
valid_name = valid_name.to_lowercase();
if self.guard.get() == Guard::Immutable {
return Err(Error::Type("Guard is immutable".to_string()));
}
if self.guard.get() == Guard::Request &&
is_forbidden_request_header(&valid_name, &valid_value)
{
return Ok(());
}
if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
return Ok(());
}
// Step 3
// 3. If headerss guard is "request-no-cors":
if self.guard.get() == Guard::RequestNoCors {
// 3.1. Let temporaryValue be the result of getting name from headerss 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 headerss 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 headerss 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 thiss 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 thiss header list does not contain name, then return.
// 4. Delete name from thiss header list.
self.header_list.borrow_mut().remove(valid_name);
// 5. If thiss 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 thiss 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 thiss header list does not contain `Set-Cookie`, then return « ».
// 2. Return the values of all headers in thiss 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 thiss 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)?;
// 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(());
};
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)
{
return Ok(());
}
// Step 5
// 3. If thiss 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 thiss 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 thiss 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 headerss guard is "immutable", then throw a TypeError.
if self.guard.get() == Guard::Immutable {
return Err(Error::Type("Guard is immutable".to_string()));
}
// 3. If headerss 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 headerss guard is "response" and name is a forbidden response-header name, then return false.
if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
return Ok(None);
}
Ok(Some((valid_name, value)))
}
}
impl Iterable for Headers {
@ -391,6 +416,7 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
"keep-alive",
"origin",
"referer",
"set-cookie",
"te",
"trailer",
"transfer-encoding",
@ -448,26 +474,11 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
false
}
// https://fetch.spec.whatwg.org/#forbidden-response-header-name
/// <https://fetch.spec.whatwg.org/#forbidden-response-header-name>
fn is_forbidden_response_header(name: &str) -> bool {
matches!(name, "set-cookie" | "set-cookie2")
}
// There is some unresolved confusion over the definition of a name and a value.
//
// As of December 2019, WHATWG has no formal grammar production for value;
// https://fetch.spec.whatg.org/#concept-header-value just says not to have
// newlines, nulls, or leading/trailing whitespace. It even allows
// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this.
// The HeaderValue class does not fully reflect this, so headers
// containing bytes with values 1..31 or 127 can't be created, failing
// WPT tests but probably not affecting anything important on the real Internet.
fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec<u8>)> {
let valid_name = validate_name(name)?;
if !is_legal_header_value(&value) {
return Err(Error::Type("Header value is not valid".to_string()));
}
Ok((valid_name, value.into()))
// A forbidden response-header name is a header name that is a byte-case-insensitive match for one of
let name = name.to_ascii_lowercase();
matches!(name.as_str(), "set-cookie" | "set-cookie2")
}
fn validate_name(name: ByteString) -> Fallible<String> {
@ -480,47 +491,20 @@ fn validate_name(name: ByteString) -> Fallible<String> {
}
}
// Removes trailing and leading HTTP whitespace bytes.
// https://fetch.spec.whatwg.org/#concept-header-value-normalize
pub fn normalize_value(value: ByteString) -> ByteString {
match (
index_of_first_non_whitespace(&value),
index_of_last_non_whitespace(&value),
) {
(Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()),
_ => ByteString::new(vec![]),
}
}
fn is_http_whitespace(byte: u8) -> bool {
byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' '
}
fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> {
for (index, &byte) in value.iter().enumerate() {
if !is_http_whitespace(byte) {
return Some(index);
}
}
None
}
fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> {
for (index, &byte) in value.iter().enumerate().rev() {
if !is_http_whitespace(byte) {
return Some(index);
}
}
None
}
// http://tools.ietf.org/html/rfc7230#section-3.2
/// <http://tools.ietf.org/html/rfc7230#section-3.2>
fn is_field_name(name: &ByteString) -> bool {
is_token(name)
}
// https://fetch.spec.whatg.org/#concept-header-value
fn is_legal_header_value(value: &ByteString) -> bool {
// As of December 2019, WHATWG has no formal grammar production for value;
// https://fetch.spec.whatg.org/#concept-header-value just says not to have
// newlines, nulls, or leading/trailing whitespace. It even allows
// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this.
// The HeaderValue class does not fully reflect this, so headers
// containing bytes with values 1..31 or 127 can't be created, failing
// WPT tests but probably not affecting anything important on the real Internet.
/// <https://fetch.spec.whatg.org/#concept-header-value>
fn is_legal_header_value(value: &[u8]) -> bool {
let value_len = value.len();
if value_len == 0 {
return true;
@ -533,7 +517,7 @@ fn is_legal_header_value(value: &ByteString) -> bool {
b' ' | b'\t' => return false,
_ => {},
};
for &ch in &value[..] {
for &ch in value {
match ch {
b'\0' | b'\n' | b'\r' => return false,
_ => {},
@ -555,81 +539,12 @@ fn is_legal_header_value(value: &ByteString) -> bool {
// }
}
// https://tools.ietf.org/html/rfc5234#appendix-B.1
/// <https://tools.ietf.org/html/rfc5234#appendix-B.1>
pub(crate) fn is_vchar(x: u8) -> bool {
matches!(x, 0x21..=0x7E)
}
// http://tools.ietf.org/html/rfc7230#section-3.2.6
/// <http://tools.ietf.org/html/rfc7230#section-3.2.6>
pub(crate) fn is_obs_text(x: u8) -> bool {
matches!(x, 0x80..=0xFF)
}
// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
// This function uses data_url::Mime to parse the MIME Type because
// mime::Mime does not provide a parser following the Fetch spec
// see https://github.com/hyperium/mime/issues/106
pub(crate) fn extract_mime_type(headers: &HyperHeaders) -> Option<Vec<u8>> {
let mut charset: Option<String> = None;
let mut essence: String = "".to_string();
let mut mime_type: Option<DataUrlMime> = None;
// Step 4
let headers_values = headers.get_all(http::header::CONTENT_TYPE).iter();
// Step 5
if headers_values.size_hint() == (0, Some(0)) {
return None;
}
// Step 6
for header_value in headers_values {
// Step 6.1
match DataUrlMime::from_str(header_value.to_str().unwrap_or("")) {
// Step 6.2
Err(_) => continue,
Ok(temp_mime) => {
let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype);
// Step 6.2
if temp_essence == "*/*" {
continue;
}
let temp_charset = &temp_mime.get_parameter("charset");
// Step 6.3
mime_type = Some(DataUrlMime {
type_: temp_mime.type_.to_string(),
subtype: temp_mime.subtype.to_string(),
parameters: temp_mime.parameters.clone(),
});
// Step 6.4
if temp_essence != essence {
charset = temp_charset.map(|c| c.to_string());
temp_essence.clone_into(&mut essence);
} else {
// Step 6.5
if temp_charset.is_none() && charset.is_some() {
let DataUrlMime {
type_: t,
subtype: st,
parameters: p,
} = mime_type.unwrap();
let mut params = p;
params.push(("charset".to_string(), charset.clone().unwrap()));
mime_type = Some(DataUrlMime {
type_: t.to_string(),
subtype: st.to_string(),
parameters: params,
})
}
}
},
}
}
// Step 7, 8
mime_type.map(|m| format!("{}", m).into_bytes())
}

View file

@ -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
},
}
}

View file

@ -27,11 +27,12 @@ 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;
use crate::dom::bindings::callback::ExceptionHandling;
use crate::dom::bindings::cell::{DomRefCell, Ref, ref_filter_map};
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::HTMLCanvasElementBinding::{
BlobCallback, HTMLCanvasElementMethods, RenderingContext as RootedRenderingContext,
};
@ -225,7 +226,7 @@ impl LayoutHTMLCanvasElementHelpers for LayoutDom<'_, HTMLCanvasElement> {
impl HTMLCanvasElement {
pub(crate) fn context(&self) -> Option<Ref<RenderingContext>> {
ref_filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref())
Ref::filter_map(self.context_mode.borrow(), |ctx| ctx.as_ref()).ok()
}
fn get_or_init_2d_context(&self, can_gc: CanGc) -> Option<DomRoot<CanvasRenderingContext2D>> {
@ -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);
},
_ => {},
};
}

View file

@ -16,6 +16,7 @@ use embedder_traits::ViewportDetails;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use net_traits::ReferrerPolicy;
use net_traits::request::Destination;
use profile_traits::ipc as ProfiledIpc;
use script_traits::{NewLayoutInfo, UpdatePipelineIdReason};
use servo_url::ServoUrl;
@ -222,6 +223,7 @@ impl HTMLIFrameElement {
old_pipeline_id,
sandbox: sandboxed,
viewport_details,
theme: window.theme(),
};
window
.as_global_scope()
@ -237,6 +239,7 @@ impl HTMLIFrameElement {
opener: None,
load_data,
viewport_details,
theme: window.theme(),
};
self.pipeline_id.set(Some(new_pipeline_id));
@ -249,6 +252,7 @@ impl HTMLIFrameElement {
old_pipeline_id,
sandbox: sandboxed,
viewport_details,
theme: window.theme(),
};
window
.as_global_scope()
@ -282,6 +286,7 @@ impl HTMLIFrameElement {
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
load_data.destination = Destination::IFrame;
load_data.policy_container = Some(window.as_global_scope().policy_container());
let element = self.upcast::<Element>();
load_data.srcdoc = String::from(element.get_string_attribute(&local_name!("srcdoc")));
@ -375,6 +380,8 @@ impl HTMLIFrameElement {
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
load_data.destination = Destination::IFrame;
load_data.policy_container = Some(window.as_global_scope().policy_container());
let pipeline_id = self.pipeline_id();
// If the initial `about:blank` page is the current page, load with replacement enabled,
@ -382,10 +389,6 @@ impl HTMLIFrameElement {
let is_about_blank =
pipeline_id.is_some() && pipeline_id == self.about_blank_pipeline_id.get();
if is_about_blank {
load_data.policy_container = Some(window.as_global_scope().policy_container());
}
let history_handling = if is_about_blank {
NavigationHistoryBehavior::Replace
} else {
@ -425,6 +428,7 @@ impl HTMLIFrameElement {
Some(document.insecure_requests_policy()),
document.has_trustworthy_ancestor_or_current_origin(),
);
load_data.destination = Destination::IFrame;
load_data.policy_container = Some(window.as_global_scope().policy_container());
let browsing_context_id = BrowsingContextId::new();
let webview_id = window.window_proxy().webview_id();

View file

@ -20,8 +20,8 @@ use js::rust::HandleObject;
use mime::{self, Mime};
use net_traits::http_status::HttpStatus;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
PendingImageId, UsePlaceholder,
Image, ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable,
ImageResponse, PendingImageId, UsePlaceholder,
};
use net_traits::request::{Destination, Initiator, RequestId};
use net_traits::{
@ -29,7 +29,7 @@ use net_traits::{
ResourceFetchTiming, ResourceTimingType,
};
use num_traits::ToPrimitive;
use pixels::{CorsStatus, Image, ImageMetadata};
use pixels::{CorsStatus, ImageMetadata};
use servo_url::ServoUrl;
use servo_url::origin::MutableOrigin;
use style::attr::{AttrValue, LengthOrPercentageOrAuto, parse_integer, parse_length};
@ -146,9 +146,8 @@ struct ImageRequest {
parsed_url: Option<ServoUrl>,
source_url: Option<USVString>,
blocker: DomRefCell<Option<LoadBlocker>>,
#[conditional_malloc_size_of]
#[no_trace]
image: Option<Arc<Image>>,
image: Option<Image>,
#[no_trace]
metadata: Option<ImageMetadata>,
#[no_trace]
@ -177,7 +176,8 @@ impl HTMLImageElement {
pub(crate) fn is_usable(&self) -> Fallible<bool> {
// If image has an intrinsic width or intrinsic height (or both) equal to zero, then return bad.
if let Some(image) = &self.current_request.borrow().image {
if image.width == 0 || image.height == 0 {
let intrinsic_size = image.metadata();
if intrinsic_size.width == 0 || intrinsic_size.height == 0 {
return Ok(false);
}
}
@ -191,7 +191,7 @@ impl HTMLImageElement {
}
}
pub(crate) fn image_data(&self) -> Option<Arc<Image>> {
pub(crate) fn image_data(&self) -> Option<Image> {
self.current_request.borrow().image.clone()
}
}
@ -341,10 +341,12 @@ impl HTMLImageElement {
is_placeholder,
}) => {
if is_placeholder {
self.process_image_response(
ImageResponse::PlaceholderLoaded(image, url),
can_gc,
)
if let Some(raster_image) = image.as_raster_image() {
self.process_image_response(
ImageResponse::PlaceholderLoaded(raster_image, url),
can_gc,
)
}
} else {
self.process_image_response(ImageResponse::Loaded(image, url), can_gc)
}
@ -403,7 +405,7 @@ impl HTMLImageElement {
window
.image_cache()
.add_listener(ImageResponder::new(sender, window.pipeline_id(), id));
.add_listener(ImageLoadListener::new(sender, window.pipeline_id(), id));
}
fn fetch_request(&self, img_url: &ServoUrl, id: PendingImageId) {
@ -448,11 +450,8 @@ impl HTMLImageElement {
}
// Steps common to when an image has been loaded.
fn handle_loaded_image(&self, image: Arc<Image>, url: ServoUrl, can_gc: CanGc) {
self.current_request.borrow_mut().metadata = Some(ImageMetadata {
height: image.height,
width: image.width,
});
fn handle_loaded_image(&self, image: Image, url: ServoUrl, can_gc: CanGc) {
self.current_request.borrow_mut().metadata = Some(image.metadata());
self.current_request.borrow_mut().final_url = Some(url);
self.current_request.borrow_mut().image = Some(image);
self.current_request.borrow_mut().state = State::CompletelyAvailable;
@ -471,7 +470,7 @@ impl HTMLImageElement {
(true, false)
},
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Current) => {
self.handle_loaded_image(image, url, can_gc);
self.handle_loaded_image(Image::Raster(image), url, can_gc);
(false, true)
},
(ImageResponse::Loaded(image, url), ImageRequestPhase::Pending) => {
@ -483,7 +482,7 @@ impl HTMLImageElement {
(ImageResponse::PlaceholderLoaded(image, url), ImageRequestPhase::Pending) => {
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
self.image_request.set(ImageRequestPhase::Current);
self.handle_loaded_image(image, url, can_gc);
self.handle_loaded_image(Image::Raster(image), url, can_gc);
(false, true)
},
(ImageResponse::MetadataLoaded(meta), ImageRequestPhase::Current) => {
@ -536,11 +535,15 @@ impl HTMLImageElement {
can_gc: CanGc,
) {
match image {
ImageResponse::Loaded(image, url) | ImageResponse::PlaceholderLoaded(image, url) => {
self.pending_request.borrow_mut().metadata = Some(ImageMetadata {
height: image.height,
width: image.width,
});
ImageResponse::Loaded(image, url) => {
self.pending_request.borrow_mut().metadata = Some(image.metadata());
self.pending_request.borrow_mut().final_url = Some(url);
self.pending_request.borrow_mut().image = Some(image);
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
},
ImageResponse::PlaceholderLoaded(image, url) => {
let image = Image::Raster(image);
self.pending_request.borrow_mut().metadata = Some(image.metadata());
self.pending_request.borrow_mut().final_url = Some(url);
self.pending_request.borrow_mut().image = Some(image);
self.finish_reacting_to_environment_change(src, generation, selected_pixel_density);
@ -1020,10 +1023,7 @@ impl HTMLImageElement {
// set on this element.
self.generation.set(self.generation.get() + 1);
// Step 6.3
let metadata = ImageMetadata {
height: image.height,
width: image.width,
};
let metadata = image.metadata();
// Step 6.3.2 abort requests
self.abort_request(
State::CompletelyAvailable,
@ -1033,7 +1033,7 @@ impl HTMLImageElement {
self.abort_request(State::Unavailable, ImageRequestPhase::Pending, can_gc);
let mut current_request = self.current_request.borrow_mut();
current_request.final_url = Some(img_url.clone());
current_request.image = Some(image.clone());
current_request.image = Some(image);
current_request.metadata = Some(metadata);
// Step 6.3.6
current_request.current_pixel_density = pixel_density;
@ -1360,7 +1360,7 @@ impl HTMLImageElement {
pub(crate) fn same_origin(&self, origin: &MutableOrigin) -> bool {
if let Some(ref image) = self.current_request.borrow().image {
return image.cors_status == CorsStatus::Safe;
return image.cors_status() == CorsStatus::Safe;
}
self.current_request
@ -1432,7 +1432,7 @@ impl MicrotaskRunnable for ImageElementMicrotask {
pub(crate) trait LayoutHTMLImageElementHelpers {
fn image_url(self) -> Option<ServoUrl>;
fn image_density(self) -> Option<f64>;
fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>);
fn image_data(self) -> (Option<Image>, Option<ImageMetadata>);
fn get_width(self) -> LengthOrPercentageOrAuto;
fn get_height(self) -> LengthOrPercentageOrAuto;
}
@ -1449,12 +1449,9 @@ impl LayoutHTMLImageElementHelpers for LayoutDom<'_, HTMLImageElement> {
self.current_request().parsed_url.clone()
}
fn image_data(self) -> (Option<Arc<Image>>, Option<ImageMetadata>) {
fn image_data(self) -> (Option<Image>, Option<ImageMetadata>) {
let current_request = self.current_request();
(
current_request.image.clone(),
current_request.metadata.clone(),
)
(current_request.image.clone(), current_request.metadata)
}
fn image_density(self) -> Option<f64> {

View file

@ -12,9 +12,13 @@ use std::str::FromStr;
use std::{f64, ptr};
use dom_struct::dom_struct;
use embedder_traits::{FilterPattern, InputMethodType};
use embedder_traits::{
EmbedderMsg, FilterPattern, FormControl as EmbedderFormControl, InputMethodType, RgbColor,
};
use encoding_rs::Encoding;
use euclid::{Point2D, Rect, Size2D};
use html5ever::{LocalName, Prefix, local_name, ns};
use ipc_channel::ipc;
use js::jsapi::{
ClippedTime, DateGetMsecSinceEpoch, Handle, JS_ClearPendingException, JSObject, NewDateObject,
NewUCRegExpObject, ObjectIsDate, RegExpFlag_UnicodeSets, RegExpFlags,
@ -25,7 +29,9 @@ use js::rust::{HandleObject, MutableHandleObject};
use net_traits::blob_url_store::get_blob_origin;
use net_traits::filemanager_thread::FileManagerThreadMsg;
use net_traits::{CoreResourceMsg, IpcSend};
use profile_traits::ipc;
use script_bindings::codegen::GenericBindings::ShadowRootBinding::{
ShadowRootMode, SlotAssignmentMode,
};
use style::attr::AttrValue;
use style::str::{split_commas, str_join};
use stylo_atoms::Atom;
@ -33,12 +39,12 @@ use stylo_dom::ElementState;
use time::{Month, OffsetDateTime, Time};
use unicode_bidi::{BidiClass, bidi_class};
use url::Url;
use webrender_api::units::DeviceIntRect;
use super::bindings::str::{FromInputValueString, ToInputValueString};
use crate::clipboard_provider::EmbedderClipboardProvider;
use crate::dom::activation::Activatable;
use crate::dom::attr::Attr;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::ElementBinding::ElementMethods;
use crate::dom::bindings::codegen::Bindings::EventBinding::EventMethods;
use crate::dom::bindings::codegen::Bindings::FileListBinding::FileListMethods;
@ -48,30 +54,33 @@ use crate::dom::bindings::codegen::Bindings::NodeBinding::{GetRootNodeOptions, N
use crate::dom::bindings::error::{Error, ErrorResult};
use crate::dom::bindings::inheritance::Castable;
use crate::dom::bindings::reflector::DomGlobal;
use crate::dom::bindings::root::{DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::{DOMString, USVString};
use crate::dom::bindings::root::{Dom, DomRoot, LayoutDom, MutNullableDom};
use crate::dom::bindings::str::{DOMString, FromInputValueString, ToInputValueString, USVString};
use crate::dom::clipboardevent::ClipboardEvent;
use crate::dom::compositionevent::CompositionEvent;
use crate::dom::document::Document;
use crate::dom::element::{AttributeMutation, Element, LayoutElementHelpers};
use crate::dom::element::{AttributeMutation, Element, ElementCreator, LayoutElementHelpers};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::file::File;
use crate::dom::filelist::{FileList, LayoutFileListHelpers};
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmldatalistelement::HTMLDataListElement;
use crate::dom::htmldivelement::HTMLDivElement;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmlfieldsetelement::HTMLFieldSetElement;
use crate::dom::htmlformelement::{
FormControl, FormDatum, FormDatumValue, FormSubmitterElement, HTMLFormElement, ResetFrom,
SubmittedFrom,
};
use crate::dom::htmlstyleelement::HTMLStyleElement;
use crate::dom::keyboardevent::KeyboardEvent;
use crate::dom::mouseevent::MouseEvent;
use crate::dom::node::{
BindContext, CloneChildrenFlag, Node, NodeDamage, NodeTraits, ShadowIncluding, UnbindContext,
};
use crate::dom::nodelist::NodeList;
use crate::dom::shadowroot::{IsUserAgentWidget, ShadowRoot};
use crate::dom::textcontrol::{TextControlElement, TextControlSelection};
use crate::dom::validation::{Validatable, is_barred_by_datalist_ancestor};
use crate::dom::validitystate::{ValidationFlags, ValidityState};
@ -92,6 +101,34 @@ const DEFAULT_RESET_VALUE: &str = "Reset";
const PASSWORD_REPLACEMENT_CHAR: char = '●';
const DEFAULT_FILE_INPUT_VALUE: &str = "No file chosen";
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
/// Contains references to the elements in the shadow tree for `<input type=range>`.
///
/// The shadow tree consists of a single div with the currently selected color as
/// the background.
struct InputTypeColorShadowTree {
color_value: Dom<HTMLDivElement>,
}
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
#[non_exhaustive]
enum ShadowTree {
Color(InputTypeColorShadowTree),
// TODO: Add shadow trees for other input types (range etc) here
}
const COLOR_TREE_STYLE: &str = "
#color-value {
width: 100%;
height: 100%;
box-sizing: border-box;
border: 1px solid gray;
border-radius: 2px;
}
";
/// <https://html.spec.whatwg.org/multipage/#attr-input-type>
#[derive(Clone, Copy, Default, JSTraceable, PartialEq)]
#[allow(dead_code)]
@ -172,8 +209,7 @@ impl InputType {
fn is_textual(&self) -> bool {
matches!(
*self,
InputType::Color |
InputType::Date |
InputType::Date |
InputType::DatetimeLocal |
InputType::Email |
InputType::Hidden |
@ -277,9 +313,16 @@ impl From<&Atom> for InputType {
#[derive(Debug, PartialEq)]
enum ValueMode {
/// <https://html.spec.whatwg.org/multipage/#dom-input-value-value>
Value,
/// <https://html.spec.whatwg.org/multipage/#dom-input-value-default>
Default,
/// <https://html.spec.whatwg.org/multipage/#dom-input-value-default-on>
DefaultOn,
/// <https://html.spec.whatwg.org/multipage/#dom-input-value-filename>
Filename,
}
@ -314,6 +357,7 @@ pub(crate) struct HTMLInputElement {
form_owner: MutNullableDom<HTMLFormElement>,
labels_node_list: MutNullableDom<NodeList>,
validity_state: MutNullableDom<ValidityState>,
shadow_tree: DomRefCell<Option<ShadowTree>>,
}
#[derive(JSTraceable)]
@ -372,6 +416,7 @@ impl HTMLInputElement {
form_owner: Default::default(),
labels_node_list: MutNullableDom::new(None),
validity_state: Default::default(),
shadow_tree: Default::default(),
}
}
@ -475,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 {
@ -803,7 +849,7 @@ impl HTMLInputElement {
.map(DomRoot::from_ref)
}
// https://html.spec.whatwg.org/multipage/#suffering-from-being-missing
/// <https://html.spec.whatwg.org/multipage/#suffering-from-being-missing>
fn suffers_from_being_missing(&self, value: &DOMString) -> bool {
match self.input_type() {
// https://html.spec.whatwg.org/multipage/#checkbox-state-(type%3Dcheckbox)%3Asuffering-from-being-missing
@ -958,9 +1004,9 @@ impl HTMLInputElement {
failed_flags
}
// https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow
// https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow
// https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch
/// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-underflow>
/// * <https://html.spec.whatwg.org/multipage/#suffering-from-an-overflow>
/// * <https://html.spec.whatwg.org/multipage/#suffering-from-a-step-mismatch>
fn suffers_from_range_issues(&self, value: &DOMString) -> ValidationFlags {
if value.is_empty() || !self.does_value_as_number_apply() {
return ValidationFlags::empty();
@ -1014,9 +1060,109 @@ impl HTMLInputElement {
failed_flags
}
/// Return a reference to the ShadowRoot that this element is a host of,
/// or create one if none exists.
fn shadow_root(&self, can_gc: CanGc) -> DomRoot<ShadowRoot> {
self.upcast::<Element>().shadow_root().unwrap_or_else(|| {
self.upcast::<Element>()
.attach_shadow(
IsUserAgentWidget::Yes,
ShadowRootMode::Closed,
false,
false,
false,
SlotAssignmentMode::Manual,
can_gc,
)
.expect("Attaching UA shadow root failed")
})
}
fn create_color_shadow_tree(&self, can_gc: CanGc) {
let document = self.owner_document();
let shadow_root = self.shadow_root(can_gc);
Node::replace_all(None, shadow_root.upcast::<Node>(), can_gc);
let color_value = HTMLDivElement::new(local_name!("div"), None, &document, None, can_gc);
color_value
.upcast::<Element>()
.SetId(DOMString::from("color-value"), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(color_value.upcast::<Node>(), can_gc)
.unwrap();
let style = HTMLStyleElement::new(
local_name!("style"),
None,
&document,
None,
ElementCreator::ScriptCreated,
can_gc,
);
style
.upcast::<Node>()
.SetTextContent(Some(DOMString::from(COLOR_TREE_STYLE)), can_gc);
shadow_root
.upcast::<Node>()
.AppendChild(style.upcast::<Node>(), can_gc)
.unwrap();
let _ = self
.shadow_tree
.borrow_mut()
.insert(ShadowTree::Color(InputTypeColorShadowTree {
color_value: color_value.as_traced(),
}));
}
/// Get a handle to the shadow tree for this input, assuming it's [InputType] is `Color`.
///
/// If the input is not currently a shadow host, a new shadow tree will be created.
///
/// If the input is a shadow host for a different kind of shadow tree then the old
/// tree will be removed and a new one will be created.
fn color_shadow_tree(&self, can_gc: CanGc) -> Ref<InputTypeColorShadowTree> {
let has_color_shadow_tree = self
.shadow_tree
.borrow()
.as_ref()
.is_some_and(|shadow_tree| matches!(shadow_tree, ShadowTree::Color(_)));
if !has_color_shadow_tree {
self.create_color_shadow_tree(can_gc);
}
let shadow_tree = self.shadow_tree.borrow();
Ref::filter_map(shadow_tree, |shadow_tree| {
let shadow_tree = shadow_tree.as_ref()?;
let ShadowTree::Color(color_tree) = shadow_tree;
Some(color_tree)
})
.ok()
.expect("UA shadow tree was not created")
}
fn update_shadow_tree_if_needed(&self, can_gc: CanGc) {
if self.input_type() == InputType::Color {
let color_shadow_tree = self.color_shadow_tree(can_gc);
let mut value = self.Value();
if value.str().is_valid_simple_color_string() {
value.make_ascii_lowercase();
} else {
value = DOMString::from("#000000");
}
let style = format!("background-color: {value}");
color_shadow_tree
.color_value
.upcast::<Element>()
.set_string_attribute(&local_name!("style"), style.into(), can_gc);
}
}
}
pub(crate) trait LayoutHTMLInputElementHelpers<'dom> {
/// Return a string that represents the contents of the element for layout.
fn value_for_layout(self) -> Cow<'dom, str>;
fn size_for_layout(self) -> u32;
fn selection_for_layout(self) -> Option<Range<usize>>;
@ -1075,16 +1221,15 @@ impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElem
Some(filelist) => {
let length = filelist.len();
if length == 0 {
return DEFAULT_FILE_INPUT_VALUE.into();
}
if length == 1 {
DEFAULT_FILE_INPUT_VALUE.into()
} else if length == 1 {
match filelist.file_for_layout(0) {
Some(file) => return file.name().to_string().into(),
None => return DEFAULT_FILE_INPUT_VALUE.into(),
Some(file) => file.name().to_string().into(),
None => DEFAULT_FILE_INPUT_VALUE.into(),
}
} else {
format!("{} files", length).into()
}
format!("{} files", length).into()
},
None => DEFAULT_FILE_INPUT_VALUE.into(),
}
@ -1103,6 +1248,9 @@ impl<'dom> LayoutHTMLInputElementHelpers<'dom> for LayoutDom<'dom, HTMLInputElem
self.placeholder().into()
}
},
InputType::Color => {
unreachable!("Input type color is explicitly not rendered as text");
},
_ => {
let text = self.get_raw_textinput_value();
if !text.is_empty() {
@ -1178,11 +1326,11 @@ impl TextControlElement for HTMLInputElement {
InputType::Week |
InputType::Time |
InputType::DatetimeLocal |
InputType::Number |
InputType::Color => true,
InputType::Number => true,
InputType::Button |
InputType::Checkbox |
InputType::Color |
InputType::File |
InputType::Hidden |
InputType::Image |
@ -1257,7 +1405,7 @@ impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
// https://html.spec.whatwg.org/multipage/#dom-input-checked
fn SetChecked(&self, checked: bool) {
self.update_checked_state(checked, true);
update_related_validity_states(self, CanGc::note())
self.value_changed(CanGc::note());
}
// https://html.spec.whatwg.org/multipage/#dom-input-readonly
@ -1349,7 +1497,7 @@ impl HTMLInputElementMethods<crate::DomTypeHolder> for HTMLInputElement {
},
}
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
Ok(())
}
@ -1724,18 +1872,6 @@ fn perform_radio_group_validation(elem: &HTMLInputElement, group: Option<&Atom>,
}
}
fn update_related_validity_states(elem: &HTMLInputElement, can_gc: CanGc) {
match elem.input_type() {
InputType::Radio => {
perform_radio_group_validation(elem, elem.radio_group_name().as_ref(), can_gc)
},
_ => {
elem.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
},
}
}
// https://html.spec.whatwg.org/multipage/#radio-button-group
fn in_same_group(
other: &HTMLInputElement,
@ -1905,7 +2041,7 @@ impl HTMLInputElement {
InputType::Radio | InputType::Checkbox => {
self.update_checked_state(self.DefaultChecked(), false);
self.checked_changed.set(false);
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
},
InputType::Image => (),
_ => (),
@ -1949,8 +2085,9 @@ impl HTMLInputElement {
.collect()
});
let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone())
.expect("Error initializing channel");
let (chan, recv) =
profile_traits::ipc::channel(self.global().time_profiler_chan().clone())
.expect("Error initializing channel");
let msg =
FileManagerThreadMsg::SelectFiles(webview_id, filter, chan, origin, opt_test_paths);
resource_threads
@ -1977,8 +2114,9 @@ impl HTMLInputElement {
None => None,
};
let (chan, recv) = ipc::channel(self.global().time_profiler_chan().clone())
.expect("Error initializing channel");
let (chan, recv) =
profile_traits::ipc::channel(self.global().time_profiler_chan().clone())
.expect("Error initializing channel");
let msg =
FileManagerThreadMsg::SelectFile(webview_id, filter, chan, origin, opt_test_path);
resource_threads
@ -2348,6 +2486,70 @@ impl HTMLInputElement {
},
})
}
fn update_related_validity_states(&self, can_gc: CanGc) {
match self.input_type() {
InputType::Radio => {
perform_radio_group_validation(self, self.radio_group_name().as_ref(), can_gc)
},
_ => {
self.validity_state()
.perform_validation_and_update(ValidationFlags::all(), can_gc);
},
}
}
fn value_changed(&self, can_gc: CanGc) {
self.update_related_validity_states(can_gc);
self.update_shadow_tree_if_needed(can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#show-the-picker,-if-applicable>
fn show_the_picker_if_applicable(&self, can_gc: CanGc) {
// FIXME: Implement most of this algorithm
// Step 2. If element is not mutable, then return.
if !self.is_mutable() {
return;
}
// Step 6. Otherwise, the user agent should show the relevant user interface for selecting a value for element,
// in the way it normally would when the user interacts with the control.
if self.input_type() == InputType::Color {
let (ipc_sender, ipc_receiver) =
ipc::channel::<Option<RgbColor>>().expect("Failed to create IPC channel!");
let document = self.owner_document();
let rect = self.upcast::<Node>().bounding_content_box_or_zero(can_gc);
let rect = Rect::new(
Point2D::new(rect.origin.x.to_px(), rect.origin.y.to_px()),
Size2D::new(rect.size.width.to_px(), rect.size.height.to_px()),
);
let current_value = self.Value();
let current_color = RgbColor {
red: u8::from_str_radix(&current_value[1..3], 16).unwrap(),
green: u8::from_str_radix(&current_value[3..5], 16).unwrap(),
blue: u8::from_str_radix(&current_value[5..7], 16).unwrap(),
};
document.send_to_embedder(EmbedderMsg::ShowFormControl(
document.webview_id(),
DeviceIntRect::from_untyped(&rect.to_box2d()),
EmbedderFormControl::ColorPicker(current_color, ipc_sender),
));
let Ok(response) = ipc_receiver.recv() else {
log::error!("Failed to receive response");
return;
};
if let Some(selected_color) = response {
let formatted_color = format!(
"#{:0>2x}{:0>2x}{:0>2x}",
selected_color.red, selected_color.green, selected_color.blue
);
let _ = self.SetValue(formatted_color.into(), can_gc);
}
}
}
}
impl VirtualMethods for HTMLInputElement {
@ -2359,6 +2561,7 @@ impl VirtualMethods for HTMLInputElement {
self.super_type()
.unwrap()
.attribute_mutated(attr, mutation, can_gc);
match *attr.local_name() {
local_name!("disabled") => {
let disabled_state = match mutation {
@ -2466,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() {
@ -2493,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(
@ -2553,7 +2759,7 @@ impl VirtualMethods for HTMLInputElement {
_ => {},
}
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
}
fn parse_plain_attribute(&self, name: &LocalName, value: DOMString) -> AttrValue {
@ -2584,7 +2790,8 @@ impl VirtualMethods for HTMLInputElement {
if self.input_type() == InputType::Radio {
self.radio_group_updated(self.radio_group_name().as_ref());
}
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
}
fn unbind_from_tree(&self, context: &UnbindContext, can_gc: CanGc) {
@ -2676,6 +2883,17 @@ impl VirtualMethods for HTMLInputElement {
self.implicit_submission(can_gc);
},
DispatchInput => {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
self.value_dirty.set(true);
self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
@ -2692,17 +2910,9 @@ impl VirtualMethods for HTMLInputElement {
!event.DefaultPrevented() &&
self.input_type().is_textual_or_password()
{
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
// keypress should be deprecated and replaced by beforeinput.
// keypress was supposed to fire "blur" and "focus" events
// but already done in `document.rs`
} else if (event.type_() == atom!("compositionstart") ||
event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend")) &&
@ -2730,7 +2940,7 @@ impl VirtualMethods for HTMLInputElement {
}
}
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
}
// https://html.spec.whatwg.org/multipage/#the-input-element%3Aconcept-node-clone-ext
@ -2752,7 +2962,7 @@ impl VirtualMethods for HTMLInputElement {
elem.textinput
.borrow_mut()
.set_content(self.textinput.borrow().get_content());
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
}
}
@ -2861,7 +3071,8 @@ impl Activatable for HTMLInputElement {
},
// https://html.spec.whatwg.org/multipage/#checkbox-state-(type=checkbox):input-activation-behavior
// https://html.spec.whatwg.org/multipage/#radio-button-state-(type=radio):input-activation-behavior
InputType::Checkbox | InputType::Radio => true,
// https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior
InputType::Checkbox | InputType::Radio | InputType::Color => true,
_ => false,
}
}
@ -2907,7 +3118,7 @@ impl Activatable for HTMLInputElement {
};
if activation_state.is_some() {
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
}
activation_state
@ -2966,7 +3177,7 @@ impl Activatable for HTMLInputElement {
_ => (),
}
update_related_validity_states(self, can_gc);
self.value_changed(can_gc);
}
/// <https://html.spec.whatwg.org/multipage/#input-activation-behavior>
@ -3028,6 +3239,10 @@ impl Activatable for HTMLInputElement {
},
// https://html.spec.whatwg.org/multipage/#file-upload-state-(type=file):input-activation-behavior
InputType::File => self.select_files(None, can_gc),
// https://html.spec.whatwg.org/multipage/#color-state-(type=color):input-activation-behavior
InputType::Color => {
self.show_the_picker_if_applicable(can_gc);
},
_ => (),
}
}

View file

@ -24,10 +24,10 @@ use js::jsapi::JSAutoRealm;
use media::{GLPlayerMsg, GLPlayerMsgForward, WindowGLContext};
use net_traits::request::{Destination, RequestId};
use net_traits::{
FetchMetadata, FetchResponseListener, Metadata, NetworkError, ResourceFetchTiming,
ResourceTimingType,
FetchMetadata, FetchResponseListener, FilteredMetadata, Metadata, NetworkError,
ResourceFetchTiming, ResourceTimingType,
};
use pixels::Image;
use pixels::RasterImage;
use script_bindings::codegen::GenericBindings::TimeRangesBinding::TimeRangesMethods;
use script_bindings::codegen::InheritTypes::{
ElementTypeId, HTMLElementTypeId, HTMLMediaElementTypeId, NodeTypeId,
@ -186,12 +186,12 @@ impl MediaFrameRenderer {
}
}
fn render_poster_frame(&mut self, image: Arc<Image>) {
fn render_poster_frame(&mut self, image: Arc<RasterImage>) {
if let Some(image_key) = image.id {
self.current_frame = Some(MediaFrame {
image_key,
width: image.width as i32,
height: image.height as i32,
width: image.metadata.width as i32,
height: image.metadata.height as i32,
});
self.show_poster = true;
}
@ -1358,18 +1358,17 @@ impl HTMLMediaElement {
}
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
pub(crate) fn process_poster_image_loaded(&self, image: Arc<Image>) {
pub(crate) fn process_poster_image_loaded(&self, image: Arc<RasterImage>) {
if !self.show_poster.get() {
return;
}
// Step 6.
self.handle_resize(Some(image.width), Some(image.height));
self.handle_resize(Some(image.metadata.width), Some(image.metadata.height));
self.video_renderer
.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);
}
}
@ -2071,6 +2069,28 @@ impl HTMLMediaElement {
}
}
}
/// <https://html.spec.whatwg.org/multipage/#concept-media-load-resource>
pub(crate) fn origin_is_clean(&self) -> bool {
// Step 5.local (media provider object).
if self.src_object.borrow().is_some() {
// The resource described by the current media resource, if any,
// contains the media data. It is CORS-same-origin.
return true;
}
// Step 5.remote (URL record).
if self.resource_url.borrow().is_some() {
// Update the media data with the contents
// of response's unsafe response obtained in this fashion.
// Response can be CORS-same-origin or CORS-cross-origin;
if let Some(ref current_fetch_context) = *self.current_fetch_context.borrow() {
return current_fetch_context.origin_is_clean();
}
}
true
}
}
// XXX Placeholder for [https://github.com/servo/servo/issues/22293]
@ -2656,6 +2676,8 @@ pub(crate) struct HTMLMediaElementFetchContext {
cancel_reason: Option<CancelReason>,
/// Indicates whether the fetched stream is seekable.
is_seekable: bool,
/// Indicates whether the fetched stream is origin clean.
origin_clean: bool,
/// Fetch canceller. Allows cancelling the current fetch request by
/// manually calling its .cancel() method or automatically on Drop.
fetch_canceller: FetchCanceller,
@ -2666,6 +2688,7 @@ impl HTMLMediaElementFetchContext {
HTMLMediaElementFetchContext {
cancel_reason: None,
is_seekable: false,
origin_clean: true,
fetch_canceller: FetchCanceller::new(request_id),
}
}
@ -2678,6 +2701,14 @@ impl HTMLMediaElementFetchContext {
self.is_seekable = seekable;
}
pub(crate) fn origin_is_clean(&self) -> bool {
self.origin_clean
}
fn set_origin_unclean(&mut self) {
self.origin_clean = false;
}
fn cancel(&mut self, reason: CancelReason) {
if self.cancel_reason.is_some() {
return;
@ -2732,6 +2763,16 @@ impl FetchResponseListener for HTMLMediaElementFetchListener {
return;
}
if let Ok(FetchMetadata::Filtered {
filtered: FilteredMetadata::Opaque | FilteredMetadata::OpaqueRedirect(_),
..
}) = metadata
{
if let Some(ref mut current_fetch_context) = *elem.current_fetch_context.borrow_mut() {
current_fetch_context.set_origin_unclean();
}
}
self.metadata = metadata.ok().map(|m| match m {
FetchMetadata::Unfiltered(m) => m,
FetchMetadata::Filtered { unsafe_, .. } => unsafe_,

View file

@ -7,7 +7,7 @@ use std::default::Default;
use dom_struct::dom_struct;
use html5ever::{LocalName, Prefix, local_name, ns};
use js::rust::HandleObject;
use pixels::Image;
use pixels::RasterImage;
use servo_arc::Arc;
use crate::dom::attr::Attr;
@ -31,7 +31,7 @@ pub(crate) struct HTMLObjectElement {
htmlelement: HTMLElement,
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
image: DomRefCell<Option<Arc<Image>>>,
image: DomRefCell<Option<Arc<RasterImage>>>,
form_owner: MutNullableDom<HTMLFormElement>,
validity_state: MutNullableDom<ValidityState>,
}

View file

@ -152,25 +152,6 @@ impl HTMLOptionElement {
}
}
// FIXME(ajeffrey): Provide a way of buffering DOMStrings other than using Strings
fn collect_text(element: &Element, value: &mut String) {
let svg_script =
*element.namespace() == ns!(svg) && element.local_name() == &local_name!("script");
let html_script = element.is::<HTMLScriptElement>();
if svg_script || html_script {
return;
}
for child in element.upcast::<Node>().children() {
if child.is::<Text>() {
let characterdata = child.downcast::<CharacterData>().unwrap();
value.push_str(&characterdata.Data());
} else if let Some(element_child) = child.downcast() {
collect_text(element_child, value);
}
}
}
impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
/// <https://html.spec.whatwg.org/multipage/#dom-option>
fn Option(
@ -216,8 +197,28 @@ impl HTMLOptionElementMethods<crate::DomTypeHolder> for HTMLOptionElement {
/// <https://html.spec.whatwg.org/multipage/#dom-option-text>
fn Text(&self) -> DOMString {
let mut content = String::new();
collect_text(self.upcast(), &mut content);
let mut content = DOMString::new();
let mut iterator = self.upcast::<Node>().traverse_preorder(ShadowIncluding::No);
while let Some(node) = iterator.peek() {
if let Some(element) = node.downcast::<Element>() {
let html_script = element.is::<HTMLScriptElement>();
let svg_script = *element.namespace() == ns!(svg) &&
element.local_name() == &local_name!("script");
if html_script || svg_script {
iterator.next_skipping_children();
continue;
}
}
if node.is::<Text>() {
let characterdata = node.downcast::<CharacterData>().unwrap();
content.push_str(&characterdata.Data());
}
iterator.next();
}
DOMString::from(str_join(split_html_space_chars(&content), " "))
}

View file

@ -14,7 +14,7 @@ use style::attr::AttrValue;
use stylo_dom::ElementState;
use embedder_traits::{SelectElementOptionOrOptgroup, SelectElementOption};
use euclid::{Size2D, Point2D, Rect};
use embedder_traits::EmbedderMsg;
use embedder_traits::{FormControl as EmbedderFormControl, EmbedderMsg};
use crate::dom::bindings::codegen::GenericBindings::HTMLOptGroupElementBinding::HTMLOptGroupElement_Binding::HTMLOptGroupElementMethods;
use crate::dom::activation::Activatable;
@ -406,12 +406,10 @@ impl HTMLSelectElement {
let selected_index = self.list_of_options().position(|option| option.Selected());
let document = self.owner_document();
document.send_to_embedder(EmbedderMsg::ShowSelectElementMenu(
document.send_to_embedder(EmbedderMsg::ShowFormControl(
document.webview_id(),
options,
selected_index,
DeviceIntRect::from_untyped(&rect.to_box2d()),
ipc_sender,
EmbedderFormControl::SelectElement(options, selected_index, ipc_sender),
));
let Ok(response) = ipc_receiver.recv() else {

View file

@ -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") => {

View file

@ -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") => {

View file

@ -643,6 +643,17 @@ impl VirtualMethods for HTMLTextAreaElement {
match action {
KeyReaction::TriggerDefaultAction => (),
KeyReaction::DispatchInput => {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
self.value_dirty.set(true);
self.update_placeholder_shown_state();
self.upcast::<Node>().dirty(NodeDamage::OtherNodeDamage);
@ -656,17 +667,9 @@ impl VirtualMethods for HTMLTextAreaElement {
}
}
} else if event.type_() == atom!("keypress") && !event.DefaultPrevented() {
if event.IsTrusted() {
self.owner_global()
.task_manager()
.user_interaction_task_source()
.queue_event(
self.upcast(),
atom!("input"),
EventBubbles::Bubbles,
EventCancelable::NotCancelable,
);
}
// keypress should be deprecated and replaced by beforeinput.
// keypress was supposed to fire "blur" and "focus" events
// but already done in `document.rs`
} else if event.type_() == atom!("compositionstart") ||
event.type_() == atom!("compositionupdate") ||
event.type_() == atom!("compositionend")

View file

@ -9,10 +9,9 @@ use content_security_policy as csp;
use dom_struct::dom_struct;
use euclid::default::Size2D;
use html5ever::{LocalName, Prefix, local_name, ns};
use ipc_channel::ipc;
use js::rust::HandleObject;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
ImageCache, ImageCacheResult, ImageLoadListener, ImageOrMetadataAvailable, ImageResponse,
PendingImageId, UsePlaceholder,
};
use net_traits::request::{CredentialsMode, Destination, RequestBuilder, RequestId};
@ -23,6 +22,7 @@ use net_traits::{
use script_layout_interface::{HTMLMediaData, MediaMetadata};
use servo_media::player::video::VideoFrame;
use servo_url::ServoUrl;
use snapshot::Snapshot;
use style::attr::{AttrValue, LengthOrPercentageOrAuto};
use crate::document_loader::{LoadBlocker, LoadType};
@ -133,9 +133,7 @@ impl HTMLVideoElement {
sent_resize
}
pub(crate) fn get_current_frame_data(
&self,
) -> Option<(Option<ipc::IpcSharedMemory>, Size2D<u32>)> {
pub(crate) fn get_current_frame_data(&self) -> Option<Snapshot> {
let frame = self.htmlmediaelement.get_current_frame();
if frame.is_some() {
*self.last_frame.borrow_mut() = frame;
@ -145,11 +143,19 @@ impl HTMLVideoElement {
Some(frame) => {
let size = Size2D::new(frame.get_width() as u32, frame.get_height() as u32);
if !frame.is_gl_texture() {
let data = Some(ipc::IpcSharedMemory::from_bytes(&frame.get_data()));
Some((data, size))
let alpha_mode = snapshot::AlphaMode::Transparent {
premultiplied: false,
};
Some(Snapshot::from_vec(
size.cast(),
snapshot::PixelFormat::BGRA,
alpha_mode,
frame.get_data().to_vec(),
))
} else {
// XXX(victor): here we only have the GL texture ID.
Some((None, size))
Some(Snapshot::cleared(size.cast()))
}
},
None => None,
@ -217,7 +223,7 @@ impl HTMLVideoElement {
element.process_image_response(response.response, CanGc::note());
});
image_cache.add_listener(ImageResponder::new(sender, window.pipeline_id(), id));
image_cache.add_listener(ImageLoadListener::new(sender, window.pipeline_id(), id));
}
/// <https://html.spec.whatwg.org/multipage/#poster-frame>
@ -262,7 +268,10 @@ impl HTMLVideoElement {
match response {
ImageResponse::Loaded(image, url) => {
debug!("Loaded poster image for video element: {:?}", url);
self.htmlmediaelement.process_poster_image_loaded(image);
match image.as_raster_image() {
Some(image) => self.htmlmediaelement.process_poster_image_loaded(image),
None => warn!("Vector images are not yet supported in video poster"),
}
LoadBlocker::terminate(&self.load_blocker, can_gc);
},
ImageResponse::MetadataLoaded(..) => {},
@ -273,6 +282,18 @@ impl HTMLVideoElement {
},
}
}
/// <https://html.spec.whatwg.org/multipage/#check-the-usability-of-the-image-argument>
pub(crate) fn is_usable(&self) -> bool {
!matches!(
self.htmlmediaelement.get_ready_state(),
ReadyState::HaveNothing | ReadyState::HaveMetadata
)
}
pub(crate) fn origin_is_clean(&self) -> bool {
self.htmlmediaelement.origin_is_clean()
}
}
impl HTMLVideoElementMethods<crate::DomTypeHolder> for HTMLVideoElement {

View file

@ -2,58 +2,64 @@
* 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::vec::Vec;
use std::cell::{Cell, Ref};
use std::collections::HashMap;
use base::id::{ImageBitmapId, ImageBitmapIndex};
use constellation_traits::SerializableImageBitmap;
use dom_struct::dom_struct;
use snapshot::Snapshot;
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;
#[dom_struct]
pub(crate) struct ImageBitmap {
reflector_: Reflector,
width: u32,
height: u32,
/// The actual pixel data of the bitmap
///
/// If this is `None`, then the bitmap data has been released by calling
/// [`close`](https://html.spec.whatwg.org/multipage/#dom-imagebitmap-close)
bitmap_data: DomRefCell<Option<Vec<u8>>>,
#[no_trace]
bitmap_data: DomRefCell<Option<Snapshot>>,
origin_clean: Cell<bool>,
}
impl ImageBitmap {
fn new_inherited(width_arg: u32, height_arg: u32) -> ImageBitmap {
fn new_inherited(bitmap_data: Snapshot) -> ImageBitmap {
ImageBitmap {
reflector_: Reflector::new(),
width: width_arg,
height: height_arg,
bitmap_data: DomRefCell::new(Some(vec![])),
bitmap_data: DomRefCell::new(Some(bitmap_data)),
origin_clean: Cell::new(true),
}
}
#[allow(dead_code)]
pub(crate) fn new(
global: &GlobalScope,
width: u32,
height: u32,
bitmap_data: Snapshot,
can_gc: CanGc,
) -> Fallible<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))
) -> DomRoot<ImageBitmap> {
reflect_dom_object(
Box::new(ImageBitmap::new_inherited(bitmap_data)),
global,
can_gc,
)
}
pub(crate) fn set_bitmap_data(&self, data: Vec<u8>) {
*self.bitmap_data.borrow_mut() = Some(data);
#[allow(dead_code)]
pub(crate) fn bitmap_data(&self) -> Ref<Option<Snapshot>> {
self.bitmap_data.borrow()
}
pub(crate) fn origin_is_clean(&self) -> bool {
self.origin_clean.get()
}
pub(crate) fn set_origin_clean(&self, origin_is_clean: bool) {
@ -67,6 +73,108 @@ 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 {
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>, ()> {
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
Ok(ImageBitmap::new(owner, serialized.bitmap_data, can_gc))
}
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 {
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>, ()> {
// Step 1. Set value's bitmap data to serialized.[[BitmapData]].
Ok(ImageBitmap::new(
owner,
serialized.bitmap_data,
CanGc::note(),
))
}
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 {
@ -76,7 +184,13 @@ impl ImageBitmapMethods<crate::DomTypeHolder> for ImageBitmap {
}
// Step 2. Return this's height, in CSS pixels.
self.height
self.bitmap_data
.borrow()
.as_ref()
.unwrap()
.size()
.cast()
.height
}
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-width>
@ -87,7 +201,13 @@ impl ImageBitmapMethods<crate::DomTypeHolder> for ImageBitmap {
}
// Step 2. Return this's width, in CSS pixels.
self.width
self.bitmap_data
.borrow()
.as_ref()
.unwrap()
.size()
.cast()
.width
}
/// <https://html.spec.whatwg.org/multipage/#dom-imagebitmap-close>

View file

@ -211,6 +211,7 @@ pub(crate) mod types {
}
pub(crate) mod abortcontroller;
pub(crate) mod abortsignal;
#[allow(dead_code)]
pub(crate) mod abstractrange;
pub(crate) mod abstractworker;
@ -550,17 +551,29 @@ pub(crate) mod svgelement;
pub(crate) mod svggraphicselement;
pub(crate) mod svgimageelement;
pub(crate) mod svgsvgelement;
#[cfg(feature = "testbinding")]
pub(crate) mod testbinding;
#[cfg(feature = "testbinding")]
pub(crate) mod testbindingiterable;
#[cfg(feature = "testbinding")]
pub(crate) mod testbindingmaplikewithinterface;
#[cfg(feature = "testbinding")]
pub(crate) mod testbindingmaplikewithprimitive;
#[cfg(feature = "testbinding")]
pub(crate) mod testbindingpairiterable;
#[cfg(feature = "testbinding")]
pub(crate) mod testbindingproxy;
#[cfg(feature = "testbinding")]
pub(crate) mod testbindingsetlikewithinterface;
#[cfg(feature = "testbinding")]
pub(crate) mod testbindingsetlikewithprimitive;
#[cfg(feature = "testbinding")]
pub(crate) mod testns;
#[cfg(feature = "testbinding")]
pub(crate) mod testutils;
#[cfg(feature = "testbinding")]
pub(crate) mod testworklet;
#[cfg(feature = "testbinding")]
pub(crate) mod testworkletglobalscope;
pub(crate) mod text;
pub(crate) mod textcontrol;

View file

@ -10,7 +10,6 @@ use std::default::Default;
use std::f64::consts::PI;
use std::ops::Range;
use std::slice::from_ref;
use std::sync::Arc as StdArc;
use std::{cmp, fmt, iter};
use app_units::Au;
@ -26,7 +25,8 @@ use js::jsapi::JSObject;
use js::rust::HandleObject;
use libc::{self, c_void, uintptr_t};
use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
use pixels::{Image, ImageMetadata};
use net_traits::image_cache::Image;
use pixels::ImageMetadata;
use script_bindings::codegen::InheritTypes::DocumentFragmentTypeId;
use script_layout_interface::{
GenericLayoutData, HTMLCanvasData, HTMLMediaData, LayoutElementType, LayoutNodeType, QueryMsg,
@ -50,7 +50,6 @@ use style::stylesheets::{Stylesheet, UrlExtraData};
use uuid::Uuid;
use xml5ever::serialize as xml_serialize;
use super::globalscope::GlobalScope;
use crate::conversions::Convert;
use crate::document_loader::DocumentLoader;
use crate::dom::attr::Attr;
@ -92,13 +91,14 @@ use crate::dom::documenttype::DocumentType;
use crate::dom::element::{CustomElementCreationMode, Element, ElementCreator, SelectorWrapper};
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::htmlbodyelement::HTMLBodyElement;
use crate::dom::htmlcanvaselement::{HTMLCanvasElement, LayoutHTMLCanvasElementHelpers};
use crate::dom::htmlcollection::HTMLCollection;
use crate::dom::htmlelement::HTMLElement;
use crate::dom::htmliframeelement::{HTMLIFrameElement, HTMLIFrameElementLayoutMethods};
use crate::dom::htmlimageelement::{HTMLImageElement, LayoutHTMLImageElementHelpers};
use crate::dom::htmlinputelement::{HTMLInputElement, LayoutHTMLInputElementHelpers};
use crate::dom::htmlinputelement::{HTMLInputElement, InputType, LayoutHTMLInputElementHelpers};
use crate::dom::htmllinkelement::HTMLLinkElement;
use crate::dom::htmlslotelement::{HTMLSlotElement, Slottable};
use crate::dom::htmlstyleelement::HTMLStyleElement;
@ -1612,11 +1612,13 @@ pub(crate) trait LayoutNodeHelpers<'dom> {
/// attempting to read or modify the opaque layout data of this node.
unsafe fn clear_style_and_layout_data(self);
/// Whether this element is a `<input>` rendered as text or a `<textarea>`.
fn is_text_input(&self) -> bool;
fn text_content(self) -> Cow<'dom, str>;
fn selection(self) -> Option<Range<usize>>;
fn image_url(self) -> Option<ServoUrl>;
fn image_density(self) -> Option<f64>;
fn image_data(self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)>;
fn image_data(self) -> Option<(Option<Image>, Option<ImageMetadata>)>;
fn canvas_data(self) -> Option<HTMLCanvasData>;
fn media_data(self) -> Option<HTMLMediaData>;
fn svg_data(self) -> Option<SVGSVGData>;
@ -1776,6 +1778,25 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
self.unsafe_get().layout_data.borrow_mut_for_layout().take();
}
fn is_text_input(&self) -> bool {
let type_id = self.type_id_for_layout();
if type_id ==
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLInputElement,
))
{
let input = self.unsafe_get().downcast::<HTMLInputElement>().unwrap();
// FIXME: All the non-color input types currently render as text
input.input_type() != InputType::Color
} else {
type_id ==
NodeTypeId::Element(ElementTypeId::HTMLElement(
HTMLElementTypeId::HTMLTextAreaElement,
))
}
}
fn text_content(self) -> Cow<'dom, str> {
if let Some(text) = self.downcast::<Text>() {
return text.upcast().data_for_layout().into();
@ -1810,7 +1831,7 @@ impl<'dom> LayoutNodeHelpers<'dom> for LayoutDom<'dom, Node> {
.image_url()
}
fn image_data(self) -> Option<(Option<StdArc<Image>>, Option<ImageMetadata>)> {
fn image_data(self) -> Option<(Option<Image>, Option<ImageMetadata>)> {
self.downcast::<HTMLImageElement>().map(|e| e.image_data())
}
@ -2014,6 +2035,10 @@ impl TreeIterator {
self.current = None;
Some(current)
}
pub(crate) fn peek(&self) -> Option<&DomRoot<Node>> {
self.current.as_ref()
}
}
impl Iterator for TreeIterator {

View file

@ -21,15 +21,15 @@ use js::jsval::JSVal;
use js::rust::{HandleObject, MutableHandleValue};
use net_traits::http_status::HttpStatus;
use net_traits::image_cache::{
ImageCache, ImageCacheResult, ImageOrMetadataAvailable, ImageResponder, ImageResponse,
PendingImageId, PendingImageResponse, UsePlaceholder,
ImageCache, ImageCacheResponseMessage, ImageCacheResult, ImageLoadListener,
ImageOrMetadataAvailable, ImageResponse, PendingImageId, UsePlaceholder,
};
use net_traits::request::{RequestBuilder, RequestId};
use net_traits::{
FetchMetadata, FetchResponseListener, FetchResponseMsg, NetworkError, ResourceFetchTiming,
ResourceTimingType,
};
use pixels::Image;
use pixels::RasterImage;
use servo_url::{ImmutableOrigin, ServoUrl};
use uuid::Uuid;
@ -114,15 +114,15 @@ pub(crate) struct Notification {
/// <https://notifications.spec.whatwg.org/#image-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
image_resource: DomRefCell<Option<Arc<Image>>>,
image_resource: DomRefCell<Option<Arc<RasterImage>>>,
/// <https://notifications.spec.whatwg.org/#icon-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
icon_resource: DomRefCell<Option<Arc<Image>>>,
icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
/// <https://notifications.spec.whatwg.org/#badge-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
badge_resource: DomRefCell<Option<Arc<Image>>>,
badge_resource: DomRefCell<Option<Arc<RasterImage>>>,
}
impl Notification {
@ -561,7 +561,7 @@ struct Action {
/// <https://notifications.spec.whatwg.org/#action-icon-resource>
#[ignore_malloc_size_of = "Arc"]
#[no_trace]
icon_resource: DomRefCell<Option<Arc<Image>>>,
icon_resource: DomRefCell<Option<Arc<RasterImage>>>,
}
/// <https://notifications.spec.whatwg.org/#create-a-notification-with-a-settings-object>
@ -886,7 +886,11 @@ impl Notification {
ImageCacheResult::Available(ImageOrMetadataAvailable::ImageAvailable {
image, ..
}) => {
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
let image = image.as_raster_image();
if image.is_none() {
warn!("Vector images are not supported in notifications yet");
};
self.set_resource_and_show_when_ready(request_id, &resource_type, image);
},
ImageCacheResult::Available(ImageOrMetadataAvailable::MetadataAvailable(
_,
@ -925,8 +929,7 @@ impl Notification {
pending_image_id: PendingImageId,
resource_type: ResourceType,
) {
let (sender, receiver) =
ipc::channel::<PendingImageResponse>().expect("ipc channel failure");
let (sender, receiver) = ipc::channel().expect("ipc channel failure");
let global: &GlobalScope = &self.global();
@ -942,7 +945,11 @@ impl Notification {
task_source.queue(task!(handle_response: move || {
let this = trusted_this.root();
if let Ok(response) = response {
this.handle_image_cache_response(request_id, response.response, resource_type);
let ImageCacheResponseMessage::NotifyPendingImageLoadStatus(status) = response else {
warn!("Received unexpected message from image cache: {response:?}");
return;
};
this.handle_image_cache_response(request_id, status.response, resource_type);
} else {
this.handle_image_cache_response(request_id, ImageResponse::None, resource_type);
}
@ -950,7 +957,7 @@ impl Notification {
}),
);
global.image_cache().add_listener(ImageResponder::new(
global.image_cache().add_listener(ImageLoadListener::new(
sender,
global.pipeline_id(),
pending_image_id,
@ -965,7 +972,11 @@ impl Notification {
) {
match response {
ImageResponse::Loaded(image, _) => {
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
let image = image.as_raster_image();
if image.is_none() {
warn!("Vector images are not yet supported in notification attribute");
};
self.set_resource_and_show_when_ready(request_id, &resource_type, image);
},
ImageResponse::PlaceholderLoaded(image, _) => {
self.set_resource_and_show_when_ready(request_id, &resource_type, Some(image));
@ -981,7 +992,7 @@ impl Notification {
&self,
request_id: RequestId,
resource_type: &ResourceType,
image: Option<Arc<Image>>,
image: Option<Arc<RasterImage>>,
) {
match resource_type {
ResourceType::Image => {

View file

@ -10,7 +10,7 @@ use js::rust::{HandleObject, HandleValue};
use snapshot::Snapshot;
use crate::canvas_context::{CanvasContext, OffscreenRenderingContext};
use crate::dom::bindings::cell::{DomRefCell, Ref, ref_filter_map};
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::OffscreenCanvasBinding::{
OffscreenCanvasMethods, OffscreenRenderingContext as RootedOffscreenRenderingContext,
};
@ -84,7 +84,7 @@ impl OffscreenCanvas {
}
pub(crate) fn context(&self) -> Option<Ref<OffscreenRenderingContext>> {
ref_filter_map(self.context.borrow(), |ctx| ctx.as_ref())
Ref::filter_map(self.context.borrow(), |ctx| ctx.as_ref()).ok()
}
pub(crate) fn get_image_data(&self) -> Option<Snapshot> {

View file

@ -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)]

View file

@ -59,7 +59,9 @@ impl ServoInternalsMethods<crate::DomTypeHolder> for ServoInternals {
impl RoutedPromiseListener<MemoryReportResult> for ServoInternals {
fn handle_response(&self, response: MemoryReportResult, promise: &Rc<Promise>, can_gc: CanGc) {
promise.resolve_native(&response.content, can_gc);
let stringified = serde_json::to_string(&response.results)
.unwrap_or_else(|_| "{ error: \"failed to create memory report\"}".to_owned());
promise.resolve_native(&stringified, can_gc);
}
}

View file

@ -39,8 +39,8 @@ impl StyleSheet {
}
impl StyleSheetMethods<crate::DomTypeHolder> for StyleSheet {
// https://drafts.csswg.org/cssom/#dom-stylesheet-type
fn Type_(&self) -> DOMString {
/// <https://drafts.csswg.org/cssom/#dom-stylesheet-type>
fn Type(&self) -> DOMString {
self.type_.clone()
}

View file

@ -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};
@ -21,7 +21,9 @@ use super::bindings::structuredclone::StructuredData;
use super::bindings::transferable::Transferable;
use super::messageport::MessagePort;
use super::promisenativehandler::Callback;
use super::readablestream::CrossRealmTransformReadable;
use super::types::{TransformStreamDefaultController, WritableStream};
use super::writablestream::CrossRealmTransformWritable;
use crate::dom::bindings::cell::DomRefCell;
use crate::dom::bindings::codegen::Bindings::QueuingStrategyBinding::QueuingStrategy;
use crate::dom::bindings::codegen::Bindings::TransformStreamBinding::TransformStreamMethods;
@ -368,6 +370,19 @@ impl Callback for FlushPromiseRejection {
}
}
impl js::gc::Rootable for CrossRealmTransform {}
/// A wrapper to handle `message` and `messageerror` events
/// for the message port used by the transfered stream.
#[derive(Clone, JSTraceable, MallocSizeOf)]
#[cfg_attr(crown, crown::unrooted_must_root_lint::must_root)]
pub(crate) enum CrossRealmTransform {
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformreadable>
Readable(CrossRealmTransformReadable),
/// <https://streams.spec.whatwg.org/#abstract-opdef-setupcrossrealmtransformwritable>
Writable(CrossRealmTransformWritable),
}
/// <https://streams.spec.whatwg.org/#ts-class>
#[dom_struct]
pub struct TransformStream {
@ -1007,9 +1022,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 +1038,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 +1125,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,
}
}
}

View file

@ -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 thiss associated URL patterns protocol components 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)),
}
}
}

View file

@ -8,7 +8,7 @@ use canvas_traits::webgl::{
ActiveAttribInfo, WebGLCommand, WebGLError, WebGLResult, WebGLVersion, WebGLVertexArrayId,
};
use crate::dom::bindings::cell::{DomRefCell, Ref, ref_filter_map};
use crate::dom::bindings::cell::{DomRefCell, Ref};
use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::WebGL2RenderingContextConstants as constants2;
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::WebGLRenderingContextConstants as constants;
use crate::dom::bindings::root::{Dom, MutNullableDom};
@ -83,9 +83,10 @@ impl VertexArrayObject {
}
pub(crate) fn get_vertex_attrib(&self, index: u32) -> Option<Ref<VertexAttribData>> {
ref_filter_map(self.vertex_attribs.borrow(), |attribs| {
Ref::filter_map(self.vertex_attribs.borrow(), |attribs| {
attribs.get(index as usize)
})
.ok()
}
pub(crate) fn set_vertex_attrib_type(&self, index: u32, type_: u32) {

View file

@ -32,12 +32,11 @@ use crate::dom::bindings::codegen::Bindings::WebGL2RenderingContextBinding::{
WebGL2RenderingContextConstants as constants, WebGL2RenderingContextMethods,
};
use crate::dom::bindings::codegen::Bindings::WebGLRenderingContextBinding::{
WebGLContextAttributes, WebGLRenderingContextMethods,
TexImageSource, WebGLContextAttributes, WebGLRenderingContextMethods,
};
use crate::dom::bindings::codegen::UnionTypes::{
ArrayBufferViewOrArrayBuffer, Float32ArrayOrUnrestrictedFloatSequence,
HTMLCanvasElementOrOffscreenCanvas,
ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement, Int32ArrayOrLongSequence,
HTMLCanvasElementOrOffscreenCanvas, Int32ArrayOrLongSequence,
Uint32ArrayOrUnsignedLongSequence,
};
use crate::dom::bindings::error::{ErrorResult, Fallible};
@ -3023,7 +3022,7 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
internal_format: i32,
format: u32,
data_type: u32,
source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
source: TexImageSource,
) -> ErrorResult {
self.base
.TexImage2D_(target, level, internal_format, format, data_type, source)
@ -3118,7 +3117,7 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
border: i32,
format: u32,
type_: u32,
source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
source: TexImageSource,
) -> Fallible<()> {
if self.bound_pixel_unpack_buffer.get().is_some() {
self.base.webgl_error(InvalidOperation);
@ -3301,7 +3300,7 @@ impl WebGL2RenderingContextMethods<crate::DomTypeHolder> for WebGL2RenderingCont
yoffset: i32,
format: u32,
data_type: u32,
source: ImageDataOrHTMLImageElementOrHTMLCanvasElementOrHTMLVideoElement,
source: TexImageSource,
) -> ErrorResult {
self.base
.TexSubImage2D_(target, level, xoffset, yoffset, format, data_type, source)

View file

@ -609,17 +609,34 @@ impl WebGLRenderingContext {
};
let cors_setting = cors_setting_for_element(image.upcast());
let img =
match canvas_utils::request_image_from_cache(&window, img_url, cors_setting) {
ImageResponse::Loaded(img, _) => img,
ImageResponse::PlaceholderLoaded(_, _) |
ImageResponse::None |
ImageResponse::MetadataLoaded(_) => return Ok(None),
};
let img = match canvas_utils::request_image_from_cache(
&window,
img_url,
cors_setting,
) {
ImageResponse::Loaded(image, _) => {
match image.as_raster_image() {
Some(image) => image,
None => {
// Vector images are not currently supported here and there are some open questions
// in the specification about how to handle them:
// See https://github.com/KhronosGroup/WebGL/issues/1503.
warn!(
"Vector images as are not yet supported as WebGL texture source"
);
return Ok(None);
},
}
},
ImageResponse::PlaceholderLoaded(_, _) |
ImageResponse::None |
ImageResponse::MetadataLoaded(_) => return Ok(None),
};
let size = Size2D::new(img.width, img.height);
let size = Size2D::new(img.metadata.width, img.metadata.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
@ -641,14 +658,23 @@ impl WebGLRenderingContext {
return Ok(None);
}
},
TexImageSource::HTMLVideoElement(video) => match video.get_current_frame_data() {
Some((data, size)) => {
let data = data.unwrap_or_else(|| {
IpcSharedMemory::from_bytes(&vec![0; size.area() as usize * 4])
});
TexPixels::new(data, size, PixelFormat::BGRA8, false)
},
None => return Ok(None),
TexImageSource::HTMLVideoElement(video) => {
if !video.origin_is_clean() {
return Err(Error::Security);
}
let Some(snapshot) = video.get_current_frame_data() else {
return Ok(None);
};
let snapshot = snapshot.as_ipc();
let size = snapshot.size().cast();
let format: PixelFormat = match snapshot.format() {
snapshot::PixelFormat::RGBA => PixelFormat::RGBA8,
snapshot::PixelFormat::BGRA => PixelFormat::BGRA8,
};
let premultiply = snapshot.alpha_mode().is_premultiplied();
TexPixels::new(snapshot.to_ipc_shared_memory(), size, format, premultiply)
},
}))
}

View file

@ -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>

View file

@ -55,7 +55,8 @@ use malloc_size_of::MallocSizeOf;
use media::WindowGLContext;
use net_traits::ResourceThreads;
use net_traits::image_cache::{
ImageCache, ImageResponder, ImageResponse, PendingImageId, PendingImageResponse,
ImageCache, ImageCacheResponseMessage, ImageLoadListener, ImageResponse, PendingImageId,
PendingImageResponse, RasterizationCompleteResponse,
};
use net_traits::storage_thread::StorageType;
use num_traits::ToPrimitive;
@ -80,7 +81,6 @@ use style::dom::OpaqueNode;
use style::error_reporting::{ContextualParseError, ParseErrorReporter};
use style::properties::PropertyId;
use style::properties::style_structs::Font;
use style::queries::values::PrefersColorScheme;
use style::selector_parser::PseudoElement;
use style::str::HTML_SPACE_CHARACTERS;
use style::stylesheets::UrlExtraData;
@ -88,7 +88,7 @@ use style_traits::CSSPixel;
use stylo_atoms::Atom;
use url::Position;
use webrender_api::ExternalScrollId;
use webrender_api::units::{DevicePixel, LayoutPixel};
use webrender_api::units::{DeviceIntSize, DevicePixel, LayoutPixel};
use super::bindings::codegen::Bindings::MessagePortBinding::StructuredSerializeOptions;
use super::bindings::trace::HashMapTracedValues;
@ -219,6 +219,8 @@ impl LayoutBlocker {
}
}
type PendingImageRasterizationKey = (PendingImageId, DeviceIntSize);
#[dom_struct]
pub(crate) struct Window {
globalscope: GlobalScope,
@ -241,7 +243,7 @@ pub(crate) struct Window {
#[no_trace]
image_cache: Arc<dyn ImageCache>,
#[no_trace]
image_cache_sender: IpcSender<PendingImageResponse>,
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
window_proxy: MutNullableDom<WindowProxy>,
document: MutNullableDom<Document>,
location: MutNullableDom<Location>,
@ -269,7 +271,7 @@ pub(crate) struct Window {
/// Platform theme.
#[no_trace]
theme: Cell<PrefersColorScheme>,
theme: Cell<Theme>,
/// Parent id associated with this page, if any.
#[no_trace]
@ -344,11 +346,17 @@ pub(crate) struct Window {
pending_image_callbacks: DomRefCell<HashMap<PendingImageId, Vec<PendingImageCallback>>>,
/// All of the elements that have an outstanding image request that was
/// initiated by layout during a reflow. They are stored in the script thread
/// initiated by layout during a reflow. They are stored in the [`ScriptThread`]
/// to ensure that the element can be marked dirty when the image data becomes
/// available at some point in the future.
pending_layout_images: DomRefCell<HashMapTracedValues<PendingImageId, Vec<Dom<Node>>>>,
/// Vector images for which layout has intiated rasterization at a specific size
/// and whose results are not yet available. They are stored in the [`ScriptThread`]
/// so that the element can be marked dirty once the rasterization is completed.
pending_images_for_rasterization:
DomRefCell<HashMapTracedValues<PendingImageRasterizationKey, Vec<Dom<Node>>>>,
/// Directory to store unminified css for this window if unminify-css
/// opt is enabled.
unminified_css_dir: DomRefCell<Option<String>>,
@ -569,7 +577,7 @@ impl Window {
&self,
id: PendingImageId,
callback: impl Fn(PendingImageResponse) + 'static,
) -> IpcSender<PendingImageResponse> {
) -> IpcSender<ImageCacheResponseMessage> {
self.pending_image_callbacks
.borrow_mut()
.entry(id)
@ -598,6 +606,22 @@ impl Window {
}
}
pub(crate) fn handle_image_rasterization_complete_notification(
&self,
response: RasterizationCompleteResponse,
) {
let mut images = self.pending_images_for_rasterization.borrow_mut();
let nodes = images.entry((response.image_id, response.requested_size));
let nodes = match nodes {
Entry::Occupied(nodes) => nodes,
Entry::Vacant(_) => return,
};
for node in nodes.get() {
node.dirty(NodeDamage::OtherNodeDamage);
}
nodes.remove();
}
pub(crate) fn pending_image_notification(&self, response: PendingImageResponse) {
// We take the images here, in order to prevent maintaining a mutable borrow when
// image callbacks are called. These, in turn, can trigger garbage collection.
@ -2225,8 +2249,11 @@ impl Window {
.pending_layout_image_notification(response);
});
self.image_cache
.add_listener(ImageResponder::new(sender, self.pipeline_id(), id));
self.image_cache.add_listener(ImageLoadListener::new(
sender,
self.pipeline_id(),
id,
));
}
let nodes = images.entry(id).or_default();
@ -2235,6 +2262,25 @@ impl Window {
}
}
for image in results.pending_rasterization_images {
let node = unsafe { from_untrusted_node_address(image.node) };
let mut images = self.pending_images_for_rasterization.borrow_mut();
if !images.contains_key(&(image.id, image.size)) {
self.image_cache.add_rasterization_complete_listener(
pipeline_id,
image.id,
image.size,
self.image_cache_sender.clone(),
);
}
let nodes = images.entry((image.id, image.size)).or_default();
if !nodes.iter().any(|n| std::ptr::eq(&**n, &*node)) {
nodes.push(Dom::from_ref(&*node));
}
}
let size_messages = self
.Document()
.iframes_mut()
@ -2326,12 +2372,13 @@ impl Window {
});
let has_sent_idle_message = self.has_sent_idle_message.get();
let pending_images = !self.pending_layout_images.borrow().is_empty();
let no_pending_images = self.pending_layout_images.borrow().is_empty() &&
self.pending_images_for_rasterization.borrow().is_empty();
if !has_sent_idle_message &&
is_ready_state_complete &&
!reftest_wait &&
!pending_images &&
no_pending_images &&
!waiting_for_web_fonts_to_load
{
debug!(
@ -2739,13 +2786,13 @@ impl Window {
self.viewport_details.get()
}
/// Get the theme of this [`Window`].
pub(crate) fn theme(&self) -> Theme {
self.theme.get()
}
/// Handle a theme change request, triggering a reflow is any actual change occured.
pub(crate) fn handle_theme_change(&self, new_theme: Theme) {
let new_theme = match new_theme {
Theme::Light => PrefersColorScheme::Light,
Theme::Dark => PrefersColorScheme::Dark,
};
if self.theme.get() == new_theme {
return;
}
@ -3006,7 +3053,7 @@ impl Window {
script_chan: Sender<MainThreadScriptMsg>,
layout: Box<dyn Layout>,
font_context: Arc<FontContext>,
image_cache_sender: IpcSender<PendingImageResponse>,
image_cache_sender: IpcSender<ImageCacheResponseMessage>,
image_cache: Arc<dyn ImageCache>,
resource_threads: ResourceThreads,
#[cfg(feature = "bluetooth")] bluetooth_thread: IpcSender<BluetoothRequest>,
@ -3033,6 +3080,7 @@ impl Window {
player_context: WindowGLContext,
#[cfg(feature = "webgpu")] gpu_id_hub: Arc<IdentityHub>,
inherited_secure_context: Option<bool>,
theme: Theme,
) -> DomRoot<Self> {
let error_reporter = CSSErrorReporter {
pipelineid: pipeline_id,
@ -3104,6 +3152,7 @@ impl Window {
webxr_registry,
pending_image_callbacks: Default::default(),
pending_layout_images: Default::default(),
pending_images_for_rasterization: Default::default(),
unminified_css_dir: Default::default(),
local_script_source,
test_worklet: Default::default(),
@ -3118,7 +3167,7 @@ impl Window {
throttled: Cell::new(false),
layout_marker: DomRefCell::new(Rc::new(Cell::new(true))),
current_event: DomRefCell::new(None),
theme: Cell::new(PrefersColorScheme::Light),
theme: Cell::new(theme),
trusted_types: Default::default(),
});

View file

@ -328,6 +328,9 @@ impl WindowProxy {
opener: Some(self.browsing_context_id),
load_data,
viewport_details: window.viewport_details(),
// Use the current `WebView`'s theme initially, but the embedder may
// change this later.
theme: window.theme(),
};
ScriptThread::process_attach_layout(new_layout_info, document.origin().clone());
// TODO: if noopener is false, copy the sessionStorage storage area of the creator origin.

View file

@ -43,6 +43,7 @@ use crate::dom::bindings::str::USVString;
use crate::dom::bindings::trace::{CustomTraceable, JSTraceable, RootedTraceableBox};
use crate::dom::globalscope::GlobalScope;
use crate::dom::promise::Promise;
#[cfg(feature = "testbinding")]
use crate::dom::testworkletglobalscope::TestWorkletTask;
use crate::dom::window::Window;
use crate::dom::workletglobalscope::{
@ -354,6 +355,7 @@ impl WorkletThreadPool {
}
/// For testing.
#[cfg(feature = "testbinding")]
pub(crate) fn test_worklet_lookup(&self, id: WorkletId, key: String) -> Option<String> {
let (sender, receiver) = unbounded();
let msg = WorkletData::Task(id, WorkletTask::Test(TestWorkletTask::Lookup(key, sender)));

View file

@ -26,6 +26,7 @@ use crate::dom::bindings::trace::CustomTraceable;
use crate::dom::bindings::utils::define_all_exposed_interfaces;
use crate::dom::globalscope::GlobalScope;
use crate::dom::paintworkletglobalscope::{PaintWorkletGlobalScope, PaintWorkletTask};
#[cfg(feature = "testbinding")]
use crate::dom::testworkletglobalscope::{TestWorkletGlobalScope, TestWorkletTask};
#[cfg(feature = "webgpu")]
use crate::dom::webgpu::identityhub::IdentityHub;
@ -60,6 +61,7 @@ impl WorkletGlobalScope {
init: &WorkletGlobalScopeInit,
) -> DomRoot<WorkletGlobalScope> {
let scope: DomRoot<WorkletGlobalScope> = match scope_type {
#[cfg(feature = "testbinding")]
WorkletGlobalScopeType::Test => DomRoot::upcast(TestWorkletGlobalScope::new(
runtime,
pipeline_id,
@ -163,6 +165,7 @@ impl WorkletGlobalScope {
/// Perform a worklet task
pub(crate) fn perform_a_worklet_task(&self, task: WorkletTask) {
match task {
#[cfg(feature = "testbinding")]
WorkletTask::Test(task) => match self.downcast::<TestWorkletGlobalScope>() {
Some(global) => global.perform_a_worklet_task(task),
None => warn!("This is not a test worklet."),
@ -203,6 +206,7 @@ pub(crate) struct WorkletGlobalScopeInit {
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf)]
pub(crate) enum WorkletGlobalScopeType {
/// A servo-specific testing worklet
#[cfg(feature = "testbinding")]
Test,
/// A paint worklet
Paint,
@ -210,6 +214,7 @@ pub(crate) enum WorkletGlobalScopeType {
/// A task which can be performed in the context of a worklet global.
pub(crate) enum WorkletTask {
#[cfg(feature = "testbinding")]
Test(TestWorkletTask),
Paint(PaintWorkletTask),
}

View file

@ -26,6 +26,7 @@ use js::jsval::{JSVal, NullValue};
use js::rust::wrappers::JS_ParseJSON;
use js::rust::{HandleObject, MutableHandleValue};
use js::typedarray::{ArrayBuffer, ArrayBufferU8};
use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime;
use net_traits::http_status::HttpStatus;
use net_traits::request::{CredentialsMode, Referrer, RequestBuilder, RequestId, RequestMode};
use net_traits::{
@ -59,7 +60,7 @@ use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLD
use crate::dom::event::{Event, EventBubbles, EventCancelable};
use crate::dom::eventtarget::EventTarget;
use crate::dom::globalscope::GlobalScope;
use crate::dom::headers::{extract_mime_type, is_forbidden_request_header};
use crate::dom::headers::is_forbidden_request_header;
use crate::dom::node::Node;
use crate::dom::performanceresourcetiming::InitiatorType;
use crate::dom::progressevent::ProgressEvent;
@ -1324,11 +1325,7 @@ impl XMLHttpRequest {
return response;
}
// Step 2
let mime = self
.final_mime_type()
.as_ref()
.map(|m| normalize_type_string(&m.to_string()))
.unwrap_or("".to_owned());
let mime = normalize_type_string(&self.final_mime_type().to_string());
// Step 3, 4
let bytes = self.response.borrow().to_vec();
@ -1366,64 +1363,77 @@ impl XMLHttpRequest {
return response;
}
// Step 1
// Step 1: If xhrs responses body is null, then return.
if self.response_status.get().is_err() {
return None;
}
// Step 2
let mime_type = self.final_mime_type();
// Step 5.3, 7
let charset = self.final_charset().unwrap_or(UTF_8);
let temp_doc: DomRoot<Document>;
match mime_type {
Some(ref mime) if mime.matches(TEXT, HTML) => {
// Step 4
if self.response_type.get() == XMLHttpRequestResponseType::_empty {
return None;
} else {
// TODO Step 5.2 "If charset is null, prescan the first 1024 bytes of xhrs received bytes"
// Step 5
temp_doc = self.document_text_html(can_gc);
}
},
// Step 7
None => {
temp_doc = self.handle_xml(can_gc);
// Not sure it the parser should throw an error for this case
// The specification does not indicates this test,
// but for now we check the document has no child nodes
let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none();
if has_no_child_nodes {
return None;
}
},
Some(ref mime)
if mime.matches(TEXT, XML) ||
mime.matches(APPLICATION, XML) ||
mime.has_suffix(XML) =>
{
temp_doc = self.handle_xml(can_gc);
// Not sure it the parser should throw an error for this case
// The specification does not indicates this test,
// but for now we check the document has no child nodes
let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none();
if has_no_child_nodes {
return None;
}
},
// Step 3
_ => {
return None;
},
// Step 2: Let finalMIME be the result of get a final MIME type for xhr.
let final_mime = self.final_mime_type();
// Step 3: If finalMIME is not an HTML MIME type or an XML MIME type, then return.
let is_xml_mime_type = final_mime.matches(TEXT, XML) ||
final_mime.matches(APPLICATION, XML) ||
final_mime.has_suffix(XML);
if !final_mime.matches(TEXT, HTML) && !is_xml_mime_type {
return None;
}
// Step 8
// Step 4: If xhrs response type is the empty string and finalMIME is an HTML MIME
// type, then return.
let charset;
let temp_doc;
if final_mime.matches(TEXT, HTML) {
if self.response_type.get() == XMLHttpRequestResponseType::_empty {
return None;
}
// Step 5: If finalMIME is an HTML MIME type, then:
// Step 5.1: Let charset be the result of get a final encoding for xhr.
// Step 5.2: If charset is null, prescan the first 1024 bytes of xhrs received bytes
// and if that does not terminate unsuccessfully then let charset be the return value.
// TODO: This isn't happening right now.
// Step 5.3. If charset is null, then set charset to UTF-8.
charset = Some(self.final_charset().unwrap_or(UTF_8));
// Step 5.4: Let document be a document that represents the result parsing xhrs
// received bytes following the rules set forth in the HTML Standard for an HTML parser
// with scripting disabled and a known definite encoding charset. [HTML]
temp_doc = self.document_text_html(can_gc);
} else {
assert!(is_xml_mime_type);
// Step 6: Otherwise, let document be a document that represents the result of running
// the XML parser with XML scripting support disabled on xhrs received bytes. If that
// fails (unsupported character encoding, namespace well-formedness error, etc.), then
// return null. [HTML]
//
// TODO: The spec seems to suggest the charset should come from the XML parser here.
temp_doc = self.handle_xml(can_gc);
charset = self.final_charset();
// Not sure it the parser should throw an error for this case
// The specification does not indicates this test,
// but for now we check the document has no child nodes
let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none();
if has_no_child_nodes {
return None;
}
}
// Step 7: If charset is null, then set charset to UTF-8.
let charset = charset.unwrap_or(UTF_8);
// Step 8: Set documents encoding to charset.
temp_doc.set_encoding(charset);
// Step 9 to 11
// Done by handle_text_html and handle_xml
// Step 9: Set documents content type to finalMIME.
// Step 10: Set documents URL to xhrs responses URL.
// Step 11: Set documents origin to xhrs relevant settings objects origin.
//
// Done by `handle_text_html()` and `handle_xml()`.
// Step 12
// Step 12: Set xhrs response object to document.
self.response_xml.set(Some(&temp_doc));
self.response_xml.get()
}
@ -1507,7 +1517,7 @@ impl XMLHttpRequest {
Ok(parsed) => Some(parsed),
Err(_) => None, // Step 7
};
let content_type = self.final_mime_type();
let content_type = Some(self.final_mime_type());
Document::new(
win,
HasBrowsingContext::No,
@ -1598,14 +1608,16 @@ impl XMLHttpRequest {
// 3. If responseMIMEs parameters["charset"] exists, then set label to it.
let response_charset = self
.response_mime_type()
.and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string()));
.get_parameter(CHARSET)
.map(ToString::to_string);
// 4. If xhrs override MIME types parameters["charset"] exists, then set label to it.
let override_charset = self
.override_mime_type
.borrow()
.as_ref()
.and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string()));
.and_then(|mime| mime.get_parameter(CHARSET))
.map(ToString::to_string);
// 5. If label is null, then return null.
// 6. Let encoding be the result of getting an encoding from label.
@ -1617,23 +1629,22 @@ impl XMLHttpRequest {
}
/// <https://xhr.spec.whatwg.org/#response-mime-type>
fn response_mime_type(&self) -> Option<Mime> {
return extract_mime_type(&self.response_headers.borrow())
.and_then(|mime_as_bytes| {
String::from_utf8(mime_as_bytes)
.unwrap_or_default()
.parse()
.ok()
})
.or(Some(Mime::new(TEXT, XML)));
fn response_mime_type(&self) -> Mime {
// 1. Let mimeType be the result of extracting a MIME type from xhrs responses
// header list.
// 2. If mimeType is failure, then set mimeType to text/xml.
// 3. Return mimeType.
extract_mime_type_as_dataurl_mime(&self.response_headers.borrow())
.unwrap_or_else(|| Mime::new(TEXT, XML))
}
/// <https://xhr.spec.whatwg.org/#final-mime-type>
fn final_mime_type(&self) -> Option<Mime> {
match *self.override_mime_type.borrow() {
Some(ref override_mime) => Some(override_mime.clone()),
None => self.response_mime_type(),
}
fn final_mime_type(&self) -> Mime {
self.override_mime_type
.borrow()
.as_ref()
.map(MimeExt::clone)
.unwrap_or_else(|| self.response_mime_type())
}
}