mirror of
https://github.com/servo/servo.git
synced 2025-08-05 13:40:08 +01:00
Merge branch 'main' into outreachy-intern
This commit is contained in:
commit
0a91273a97
2099 changed files with 49743 additions and 390343 deletions
71
components/script/dom/abortsignal.rs
Normal file
71
components/script/dom/abortsignal.rs
Normal 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);
|
||||
}
|
|
@ -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};
|
||||
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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 port’s 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);
|
||||
}
|
||||
|
|
|
@ -5,14 +5,15 @@
|
|||
use std::cell::Cell;
|
||||
use std::str::{self, FromStr};
|
||||
|
||||
use data_url::mime::Mime as DataUrlMime;
|
||||
use dom_struct::dom_struct;
|
||||
use http::header::{HeaderMap as HyperHeaders, HeaderName, HeaderValue};
|
||||
use js::rust::HandleObject;
|
||||
use net_traits::fetch::headers::{
|
||||
get_decode_and_split_header_value, get_value_from_header_list, is_forbidden_method,
|
||||
extract_mime_type, get_decode_and_split_header_value, get_value_from_header_list,
|
||||
is_forbidden_method,
|
||||
};
|
||||
use net_traits::request::is_cors_safelisted_request_header;
|
||||
use net_traits::trim_http_whitespace;
|
||||
|
||||
use crate::dom::bindings::cell::DomRefCell;
|
||||
use crate::dom::bindings::codegen::Bindings::HeadersBinding::{HeadersInit, HeadersMethods};
|
||||
|
@ -33,7 +34,7 @@ pub(crate) struct Headers {
|
|||
header_list: DomRefCell<HyperHeaders>,
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-headers-guard
|
||||
/// <https://fetch.spec.whatwg.org/#concept-headers-guard>
|
||||
#[derive(Clone, Copy, Debug, JSTraceable, MallocSizeOf, PartialEq)]
|
||||
pub(crate) enum Guard {
|
||||
Immutable,
|
||||
|
@ -66,7 +67,7 @@ impl Headers {
|
|||
}
|
||||
|
||||
impl HeadersMethods<crate::DomTypeHolder> for Headers {
|
||||
// https://fetch.spec.whatwg.org/#dom-headers
|
||||
/// <https://fetch.spec.whatwg.org/#dom-headers>
|
||||
fn Constructor(
|
||||
global: &GlobalScope,
|
||||
proto: Option<HandleObject>,
|
||||
|
@ -78,47 +79,41 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
|
|||
Ok(dom_headers_new)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-headers-append
|
||||
/// <https://fetch.spec.whatwg.org/#concept-headers-append>
|
||||
fn Append(&self, name: ByteString, value: ByteString) -> ErrorResult {
|
||||
// Step 1
|
||||
let value = normalize_value(value);
|
||||
// 1. Normalize value.
|
||||
let value = trim_http_whitespace(&value);
|
||||
|
||||
// Step 2
|
||||
// https://fetch.spec.whatwg.org/#headers-validate
|
||||
let (mut valid_name, valid_value) = validate_name_and_value(name, value)?;
|
||||
// 2. If validating (name, value) for headers returns false, then return.
|
||||
let Some((mut valid_name, valid_value)) =
|
||||
self.validate_name_and_value(name, ByteString::new(value.into()))?
|
||||
else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
valid_name = valid_name.to_lowercase();
|
||||
|
||||
if self.guard.get() == Guard::Immutable {
|
||||
return Err(Error::Type("Guard is immutable".to_string()));
|
||||
}
|
||||
if self.guard.get() == Guard::Request &&
|
||||
is_forbidden_request_header(&valid_name, &valid_value)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
// Step 3
|
||||
// 3. If headers’s guard is "request-no-cors":
|
||||
if self.guard.get() == Guard::RequestNoCors {
|
||||
// 3.1. Let temporaryValue be the result of getting name from headers’s header list.
|
||||
let tmp_value = if let Some(mut value) =
|
||||
get_value_from_header_list(&valid_name, &self.header_list.borrow())
|
||||
{
|
||||
// 3.3. Otherwise, set temporaryValue to temporaryValue, followed by 0x2C 0x20, followed by value.
|
||||
value.extend(b", ");
|
||||
value.extend(valid_value.clone());
|
||||
value.extend(valid_value.to_vec());
|
||||
value
|
||||
} else {
|
||||
valid_value.clone()
|
||||
// 3.2. If temporaryValue is null, then set temporaryValue to value.
|
||||
valid_value.to_vec()
|
||||
};
|
||||
|
||||
// 3.4. If (name, temporaryValue) is not a no-CORS-safelisted request-header, then return.
|
||||
if !is_cors_safelisted_request_header(&valid_name, &tmp_value) {
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
|
||||
// Step 4
|
||||
// 4. Append (name, value) to headers’s header list.
|
||||
match HeaderValue::from_bytes(&valid_value) {
|
||||
Ok(value) => {
|
||||
self.header_list
|
||||
|
@ -134,7 +129,7 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
|
|||
},
|
||||
};
|
||||
|
||||
// Step 5
|
||||
// 5. If headers’s guard is "request-no-cors", then remove privileged no-CORS request-headers from headers.
|
||||
if self.guard.get() == Guard::RequestNoCors {
|
||||
self.remove_privileged_no_cors_request_headers();
|
||||
}
|
||||
|
@ -142,50 +137,53 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-delete
|
||||
/// <https://fetch.spec.whatwg.org/#dom-headers-delete>
|
||||
fn Delete(&self, name: ByteString) -> ErrorResult {
|
||||
// Step 1
|
||||
let (mut valid_name, valid_value) = validate_name_and_value(name, ByteString::new(vec![]))?;
|
||||
// Step 1 If validating (name, ``) for this returns false, then return.
|
||||
let name_and_value = self.validate_name_and_value(name, ByteString::new(vec![]))?;
|
||||
let Some((mut valid_name, _valid_value)) = name_and_value else {
|
||||
return Ok(());
|
||||
};
|
||||
|
||||
valid_name = valid_name.to_lowercase();
|
||||
|
||||
// Step 2
|
||||
if self.guard.get() == Guard::Immutable {
|
||||
return Err(Error::Type("Guard is immutable".to_string()));
|
||||
}
|
||||
// Step 3
|
||||
if self.guard.get() == Guard::Request &&
|
||||
is_forbidden_request_header(&valid_name, &valid_value)
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
// Step 4
|
||||
// Step 2 If this’s guard is "request-no-cors", name is not a no-CORS-safelisted request-header name,
|
||||
// and name is not a privileged no-CORS request-header name, then return.
|
||||
if self.guard.get() == Guard::RequestNoCors &&
|
||||
!is_cors_safelisted_request_header(&valid_name, &b"invalid".to_vec())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
// Step 5
|
||||
if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
|
||||
return Ok(());
|
||||
|
||||
// 3. If this’s header list does not contain name, then return.
|
||||
// 4. Delete name from this’s header list.
|
||||
self.header_list.borrow_mut().remove(valid_name);
|
||||
|
||||
// 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this.
|
||||
if self.guard.get() == Guard::RequestNoCors {
|
||||
self.remove_privileged_no_cors_request_headers();
|
||||
}
|
||||
// Step 6
|
||||
self.header_list.borrow_mut().remove(&valid_name);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-get
|
||||
/// <https://fetch.spec.whatwg.org/#dom-headers-get>
|
||||
fn Get(&self, name: ByteString) -> Fallible<Option<ByteString>> {
|
||||
// Step 1
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
let valid_name = validate_name(name)?;
|
||||
|
||||
// 2. Return the result of getting name from this’s header list.
|
||||
Ok(
|
||||
get_value_from_header_list(&valid_name, &self.header_list.borrow())
|
||||
.map(ByteString::new),
|
||||
)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-getsetcookie
|
||||
/// <https://fetch.spec.whatwg.org/#dom-headers-getsetcookie>
|
||||
fn GetSetCookie(&self) -> Vec<ByteString> {
|
||||
// 1. If this’s header list does not contain `Set-Cookie`, then return « ».
|
||||
// 2. Return the values of all headers in this’s header list whose name is a
|
||||
// byte-case-insensitive match for `Set-Cookie`, in order.
|
||||
self.header_list
|
||||
.borrow()
|
||||
.get_all("set-cookie")
|
||||
|
@ -194,42 +192,36 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
|
|||
.collect()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-has
|
||||
/// <https://fetch.spec.whatwg.org/#dom-headers-has>
|
||||
fn Has(&self, name: ByteString) -> Fallible<bool> {
|
||||
// Step 1
|
||||
// 1. If name is not a header name, then throw a TypeError.
|
||||
let valid_name = validate_name(name)?;
|
||||
// Step 2
|
||||
// 2. Return true if this’s header list contains name; otherwise false.
|
||||
Ok(self.header_list.borrow_mut().get(&valid_name).is_some())
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#dom-headers-set
|
||||
/// <https://fetch.spec.whatwg.org/#dom-headers-set>
|
||||
fn Set(&self, name: ByteString, value: ByteString) -> Fallible<()> {
|
||||
// Step 1
|
||||
let value = normalize_value(value);
|
||||
// Step 2
|
||||
let (mut valid_name, valid_value) = validate_name_and_value(name, value)?;
|
||||
// 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 this’s guard is "request-no-cors" and (name, value) is not a
|
||||
// no-CORS-safelisted request-header, then return.
|
||||
if self.guard.get() == Guard::RequestNoCors &&
|
||||
!is_cors_safelisted_request_header(&valid_name, &valid_value)
|
||||
!is_cors_safelisted_request_header(&valid_name, &valid_value.to_vec())
|
||||
{
|
||||
return Ok(());
|
||||
}
|
||||
// Step 6
|
||||
if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
|
||||
return Ok(());
|
||||
}
|
||||
// Step 7
|
||||
|
||||
// 4. Set (name, value) in this’s header list.
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-set
|
||||
match HeaderValue::from_bytes(&valid_value) {
|
||||
Ok(value) => {
|
||||
|
@ -245,6 +237,12 @@ impl HeadersMethods<crate::DomTypeHolder> for Headers {
|
|||
);
|
||||
},
|
||||
};
|
||||
|
||||
// 5. If this’s guard is "request-no-cors", then remove privileged no-CORS request-headers from this.
|
||||
if self.guard.get() == Guard::RequestNoCors {
|
||||
self.remove_privileged_no_cors_request_headers();
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
@ -260,7 +258,7 @@ impl Headers {
|
|||
Ok(())
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-headers-fill
|
||||
/// <https://fetch.spec.whatwg.org/#concept-headers-fill>
|
||||
pub(crate) fn fill(&self, filler: Option<HeadersInit>) -> ErrorResult {
|
||||
match filler {
|
||||
Some(HeadersInit::ByteStringSequenceSequence(v)) => {
|
||||
|
@ -316,12 +314,12 @@ impl Headers {
|
|||
self.header_list.borrow_mut().clone()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
|
||||
/// <https://fetch.spec.whatwg.org/#concept-header-extract-mime-type>
|
||||
pub(crate) fn extract_mime_type(&self) -> Vec<u8> {
|
||||
extract_mime_type(&self.header_list.borrow()).unwrap_or_default()
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine
|
||||
/// <https://fetch.spec.whatwg.org/#concept-header-list-sort-and-combine>
|
||||
pub(crate) fn sort_and_combine(&self) -> Vec<(String, Vec<u8>)> {
|
||||
let borrowed_header_list = self.header_list.borrow();
|
||||
let mut header_vec = vec![];
|
||||
|
@ -341,11 +339,38 @@ impl Headers {
|
|||
header_vec
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name
|
||||
/// <https://fetch.spec.whatwg.org/#ref-for-privileged-no-cors-request-header-name>
|
||||
pub(crate) fn remove_privileged_no_cors_request_headers(&self) {
|
||||
// https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name
|
||||
// <https://fetch.spec.whatwg.org/#privileged-no-cors-request-header-name>
|
||||
self.header_list.borrow_mut().remove("range");
|
||||
}
|
||||
|
||||
/// <https://fetch.spec.whatwg.org/#headers-validate>
|
||||
pub(crate) fn validate_name_and_value(
|
||||
&self,
|
||||
name: ByteString,
|
||||
value: ByteString,
|
||||
) -> Fallible<Option<(String, ByteString)>> {
|
||||
// 1. If name is not a header name or value is not a header value, then throw a TypeError.
|
||||
let valid_name = validate_name(name)?;
|
||||
if !is_legal_header_value(&value) {
|
||||
return Err(Error::Type("Header value is not valid".to_string()));
|
||||
}
|
||||
// 2. If headers’s guard is "immutable", then throw a TypeError.
|
||||
if self.guard.get() == Guard::Immutable {
|
||||
return Err(Error::Type("Guard is immutable".to_string()));
|
||||
}
|
||||
// 3. If headers’s guard is "request" and (name, value) is a forbidden request-header, then return false.
|
||||
if self.guard.get() == Guard::Request && is_forbidden_request_header(&valid_name, &value) {
|
||||
return Ok(None);
|
||||
}
|
||||
// 4. If headers’s guard is "response" and name is a forbidden response-header name, then return false.
|
||||
if self.guard.get() == Guard::Response && is_forbidden_response_header(&valid_name) {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
Ok(Some((valid_name, value)))
|
||||
}
|
||||
}
|
||||
|
||||
impl Iterable for Headers {
|
||||
|
@ -391,6 +416,7 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
|
|||
"keep-alive",
|
||||
"origin",
|
||||
"referer",
|
||||
"set-cookie",
|
||||
"te",
|
||||
"trailer",
|
||||
"transfer-encoding",
|
||||
|
@ -448,26 +474,11 @@ pub(crate) fn is_forbidden_request_header(name: &str, value: &[u8]) -> bool {
|
|||
false
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#forbidden-response-header-name
|
||||
/// <https://fetch.spec.whatwg.org/#forbidden-response-header-name>
|
||||
fn is_forbidden_response_header(name: &str) -> bool {
|
||||
matches!(name, "set-cookie" | "set-cookie2")
|
||||
}
|
||||
|
||||
// There is some unresolved confusion over the definition of a name and a value.
|
||||
//
|
||||
// As of December 2019, WHATWG has no formal grammar production for value;
|
||||
// https://fetch.spec.whatg.org/#concept-header-value just says not to have
|
||||
// newlines, nulls, or leading/trailing whitespace. It even allows
|
||||
// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this.
|
||||
// The HeaderValue class does not fully reflect this, so headers
|
||||
// containing bytes with values 1..31 or 127 can't be created, failing
|
||||
// WPT tests but probably not affecting anything important on the real Internet.
|
||||
fn validate_name_and_value(name: ByteString, value: ByteString) -> Fallible<(String, Vec<u8>)> {
|
||||
let valid_name = validate_name(name)?;
|
||||
if !is_legal_header_value(&value) {
|
||||
return Err(Error::Type("Header value is not valid".to_string()));
|
||||
}
|
||||
Ok((valid_name, value.into()))
|
||||
// A forbidden response-header name is a header name that is a byte-case-insensitive match for one of
|
||||
let name = name.to_ascii_lowercase();
|
||||
matches!(name.as_str(), "set-cookie" | "set-cookie2")
|
||||
}
|
||||
|
||||
fn validate_name(name: ByteString) -> Fallible<String> {
|
||||
|
@ -480,47 +491,20 @@ fn validate_name(name: ByteString) -> Fallible<String> {
|
|||
}
|
||||
}
|
||||
|
||||
// Removes trailing and leading HTTP whitespace bytes.
|
||||
// https://fetch.spec.whatwg.org/#concept-header-value-normalize
|
||||
pub fn normalize_value(value: ByteString) -> ByteString {
|
||||
match (
|
||||
index_of_first_non_whitespace(&value),
|
||||
index_of_last_non_whitespace(&value),
|
||||
) {
|
||||
(Some(begin), Some(end)) => ByteString::new(value[begin..end + 1].to_owned()),
|
||||
_ => ByteString::new(vec![]),
|
||||
}
|
||||
}
|
||||
|
||||
fn is_http_whitespace(byte: u8) -> bool {
|
||||
byte == b'\t' || byte == b'\n' || byte == b'\r' || byte == b' '
|
||||
}
|
||||
|
||||
fn index_of_first_non_whitespace(value: &ByteString) -> Option<usize> {
|
||||
for (index, &byte) in value.iter().enumerate() {
|
||||
if !is_http_whitespace(byte) {
|
||||
return Some(index);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn index_of_last_non_whitespace(value: &ByteString) -> Option<usize> {
|
||||
for (index, &byte) in value.iter().enumerate().rev() {
|
||||
if !is_http_whitespace(byte) {
|
||||
return Some(index);
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7230#section-3.2
|
||||
/// <http://tools.ietf.org/html/rfc7230#section-3.2>
|
||||
fn is_field_name(name: &ByteString) -> bool {
|
||||
is_token(name)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatg.org/#concept-header-value
|
||||
fn is_legal_header_value(value: &ByteString) -> bool {
|
||||
// As of December 2019, WHATWG has no formal grammar production for value;
|
||||
// https://fetch.spec.whatg.org/#concept-header-value just says not to have
|
||||
// newlines, nulls, or leading/trailing whitespace. It even allows
|
||||
// octets that aren't a valid UTF-8 encoding, and WPT tests reflect this.
|
||||
// The HeaderValue class does not fully reflect this, so headers
|
||||
// containing bytes with values 1..31 or 127 can't be created, failing
|
||||
// WPT tests but probably not affecting anything important on the real Internet.
|
||||
/// <https://fetch.spec.whatg.org/#concept-header-value>
|
||||
fn is_legal_header_value(value: &[u8]) -> bool {
|
||||
let value_len = value.len();
|
||||
if value_len == 0 {
|
||||
return true;
|
||||
|
@ -533,7 +517,7 @@ fn is_legal_header_value(value: &ByteString) -> bool {
|
|||
b' ' | b'\t' => return false,
|
||||
_ => {},
|
||||
};
|
||||
for &ch in &value[..] {
|
||||
for &ch in value {
|
||||
match ch {
|
||||
b'\0' | b'\n' | b'\r' => return false,
|
||||
_ => {},
|
||||
|
@ -555,81 +539,12 @@ fn is_legal_header_value(value: &ByteString) -> bool {
|
|||
// }
|
||||
}
|
||||
|
||||
// https://tools.ietf.org/html/rfc5234#appendix-B.1
|
||||
/// <https://tools.ietf.org/html/rfc5234#appendix-B.1>
|
||||
pub(crate) fn is_vchar(x: u8) -> bool {
|
||||
matches!(x, 0x21..=0x7E)
|
||||
}
|
||||
|
||||
// http://tools.ietf.org/html/rfc7230#section-3.2.6
|
||||
/// <http://tools.ietf.org/html/rfc7230#section-3.2.6>
|
||||
pub(crate) fn is_obs_text(x: u8) -> bool {
|
||||
matches!(x, 0x80..=0xFF)
|
||||
}
|
||||
|
||||
// https://fetch.spec.whatwg.org/#concept-header-extract-mime-type
|
||||
// This function uses data_url::Mime to parse the MIME Type because
|
||||
// mime::Mime does not provide a parser following the Fetch spec
|
||||
// see https://github.com/hyperium/mime/issues/106
|
||||
pub(crate) fn extract_mime_type(headers: &HyperHeaders) -> Option<Vec<u8>> {
|
||||
let mut charset: Option<String> = None;
|
||||
let mut essence: String = "".to_string();
|
||||
let mut mime_type: Option<DataUrlMime> = None;
|
||||
|
||||
// Step 4
|
||||
let headers_values = headers.get_all(http::header::CONTENT_TYPE).iter();
|
||||
|
||||
// Step 5
|
||||
if headers_values.size_hint() == (0, Some(0)) {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 6
|
||||
for header_value in headers_values {
|
||||
// Step 6.1
|
||||
match DataUrlMime::from_str(header_value.to_str().unwrap_or("")) {
|
||||
// Step 6.2
|
||||
Err(_) => continue,
|
||||
Ok(temp_mime) => {
|
||||
let temp_essence = format!("{}/{}", temp_mime.type_, temp_mime.subtype);
|
||||
|
||||
// Step 6.2
|
||||
if temp_essence == "*/*" {
|
||||
continue;
|
||||
}
|
||||
|
||||
let temp_charset = &temp_mime.get_parameter("charset");
|
||||
|
||||
// Step 6.3
|
||||
mime_type = Some(DataUrlMime {
|
||||
type_: temp_mime.type_.to_string(),
|
||||
subtype: temp_mime.subtype.to_string(),
|
||||
parameters: temp_mime.parameters.clone(),
|
||||
});
|
||||
|
||||
// Step 6.4
|
||||
if temp_essence != essence {
|
||||
charset = temp_charset.map(|c| c.to_string());
|
||||
temp_essence.clone_into(&mut essence);
|
||||
} else {
|
||||
// Step 6.5
|
||||
if temp_charset.is_none() && charset.is_some() {
|
||||
let DataUrlMime {
|
||||
type_: t,
|
||||
subtype: st,
|
||||
parameters: p,
|
||||
} = mime_type.unwrap();
|
||||
let mut params = p;
|
||||
params.push(("charset".to_string(), charset.clone().unwrap()));
|
||||
mime_type = Some(DataUrlMime {
|
||||
type_: t.to_string(),
|
||||
subtype: st.to_string(),
|
||||
parameters: params,
|
||||
})
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7, 8
|
||||
mime_type.map(|m| format!("{}", m).into_bytes())
|
||||
}
|
||||
|
|
|
@ -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
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
},
|
||||
_ => {},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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(¤t_value[1..3], 16).unwrap(),
|
||||
green: u8::from_str_radix(¤t_value[3..5], 16).unwrap(),
|
||||
blue: u8::from_str_radix(¤t_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);
|
||||
},
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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_,
|
||||
|
|
|
@ -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>,
|
||||
}
|
||||
|
|
|
@ -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), " "))
|
||||
}
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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") => {
|
||||
|
|
|
@ -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") => {
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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 => {
|
||||
|
|
|
@ -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> {
|
||||
|
|
|
@ -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)]
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
|
||||
use dom_struct::dom_struct;
|
||||
use js::rust::HandleObject;
|
||||
use script_bindings::codegen::GenericBindings::URLPatternBinding::URLPatternResult;
|
||||
use script_bindings::codegen::GenericUnionTypes::USVStringOrURLPatternInit;
|
||||
use script_bindings::error::{Error, Fallible};
|
||||
use script_bindings::reflector::Reflector;
|
||||
|
@ -46,7 +47,7 @@ impl URLPattern {
|
|||
) -> Fallible<DomRoot<URLPattern>> {
|
||||
// The section below converts from servos types to the types used in the urlpattern crate
|
||||
let base_url = base_url.map(|usv_string| usv_string.0);
|
||||
let input = bindings_to_third_party::map_urlpattern_input(input, base_url.clone());
|
||||
let input = bindings_to_third_party::map_urlpattern_input(input);
|
||||
let options = urlpattern::UrlPatternOptions {
|
||||
ignore_case: options.ignoreCase,
|
||||
};
|
||||
|
@ -94,6 +95,50 @@ impl URLPatternMethods<crate::DomTypeHolder> for URLPattern {
|
|||
URLPattern::initialize(global, proto, input, None, options, can_gc)
|
||||
}
|
||||
|
||||
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-test>
|
||||
fn Test(
|
||||
&self,
|
||||
input: USVStringOrURLPatternInit,
|
||||
base_url: Option<USVString>,
|
||||
) -> Fallible<bool> {
|
||||
let input = bindings_to_third_party::map_urlpattern_input(input);
|
||||
let inputs = urlpattern::quirks::process_match_input(input, base_url.as_deref())
|
||||
.map_err(|error| Error::Type(format!("{error}")))?;
|
||||
let Some((match_input, _)) = inputs else {
|
||||
return Ok(false);
|
||||
};
|
||||
|
||||
self.associated_url_pattern
|
||||
.test(match_input)
|
||||
.map_err(|error| Error::Type(format!("{error}")))
|
||||
}
|
||||
|
||||
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-exec>
|
||||
fn Exec(
|
||||
&self,
|
||||
input: USVStringOrURLPatternInit,
|
||||
base_url: Option<USVString>,
|
||||
) -> Fallible<Option<URLPatternResult>> {
|
||||
let input = bindings_to_third_party::map_urlpattern_input(input);
|
||||
let inputs = urlpattern::quirks::process_match_input(input, base_url.as_deref())
|
||||
.map_err(|error| Error::Type(format!("{error}")))?;
|
||||
let Some((match_input, inputs)) = inputs else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
let result = self
|
||||
.associated_url_pattern
|
||||
.exec(match_input)
|
||||
.map_err(|error| Error::Type(format!("{error}")))?;
|
||||
let Some(result) = result else {
|
||||
return Ok(None);
|
||||
};
|
||||
|
||||
Ok(Some(third_party_to_bindings::map_urlpattern_result(
|
||||
result, inputs,
|
||||
)))
|
||||
}
|
||||
|
||||
/// <https://urlpattern.spec.whatwg.org/#dom-urlpattern-protocol>
|
||||
fn Protocol(&self) -> USVString {
|
||||
// Step 1. Return this’s associated URL pattern’s protocol component’s pattern string.
|
||||
|
@ -151,54 +196,115 @@ impl URLPatternMethods<crate::DomTypeHolder> for URLPattern {
|
|||
}
|
||||
|
||||
mod bindings_to_third_party {
|
||||
use script_bindings::codegen::GenericBindings::URLPatternBinding::URLPatternInit;
|
||||
|
||||
use crate::dom::urlpattern::USVStringOrURLPatternInit;
|
||||
|
||||
fn map_urlpatterninit(pattern_init: URLPatternInit) -> urlpattern::quirks::UrlPatternInit {
|
||||
urlpattern::quirks::UrlPatternInit {
|
||||
protocol: pattern_init.protocol.map(|protocol| protocol.0),
|
||||
username: pattern_init.username.map(|username| username.0),
|
||||
password: pattern_init.password.map(|password| password.0),
|
||||
hostname: pattern_init.hostname.map(|hostname| hostname.0),
|
||||
port: pattern_init.port.map(|hash| hash.0),
|
||||
pathname: pattern_init
|
||||
.pathname
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
search: pattern_init.search.map(|search| search.0),
|
||||
hash: pattern_init.hash.map(|hash| hash.0),
|
||||
base_url: pattern_init.baseURL.map(|base_url| base_url.0),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn map_urlpattern_input(
|
||||
input: USVStringOrURLPatternInit,
|
||||
base_url: Option<String>,
|
||||
) -> urlpattern::quirks::StringOrInit {
|
||||
match input {
|
||||
USVStringOrURLPatternInit::USVString(usv_string) => {
|
||||
urlpattern::quirks::StringOrInit::String(usv_string.0)
|
||||
},
|
||||
USVStringOrURLPatternInit::URLPatternInit(pattern_init) => {
|
||||
let pattern_init = urlpattern::quirks::UrlPatternInit {
|
||||
protocol: pattern_init
|
||||
.protocol
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
username: pattern_init
|
||||
.username
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
password: pattern_init
|
||||
.password
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
hostname: pattern_init
|
||||
.hostname
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
port: pattern_init
|
||||
.port
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
pathname: pattern_init
|
||||
.pathname
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
search: pattern_init
|
||||
.search
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
hash: pattern_init
|
||||
.hash
|
||||
.as_ref()
|
||||
.map(|usv_string| usv_string.to_string()),
|
||||
base_url,
|
||||
};
|
||||
urlpattern::quirks::StringOrInit::Init(pattern_init)
|
||||
urlpattern::quirks::StringOrInit::Init(map_urlpatterninit(pattern_init))
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mod third_party_to_bindings {
|
||||
use script_bindings::codegen::GenericBindings::URLPatternBinding::{
|
||||
URLPatternComponentResult, URLPatternInit, URLPatternResult,
|
||||
};
|
||||
use script_bindings::codegen::GenericUnionTypes::USVStringOrUndefined;
|
||||
use script_bindings::record::Record;
|
||||
use script_bindings::str::USVString;
|
||||
|
||||
use crate::dom::bindings::codegen::UnionTypes::USVStringOrURLPatternInit;
|
||||
|
||||
// FIXME: For some reason codegen puts a lot of options into these types that don't make sense
|
||||
|
||||
fn map_component_result(
|
||||
component_result: urlpattern::UrlPatternComponentResult,
|
||||
) -> URLPatternComponentResult {
|
||||
let mut groups = Record::new();
|
||||
for (key, value) in component_result.groups.iter() {
|
||||
let value = match value {
|
||||
Some(value) => USVStringOrUndefined::USVString(USVString(value.to_owned())),
|
||||
None => USVStringOrUndefined::Undefined(()),
|
||||
};
|
||||
|
||||
groups.insert(USVString(key.to_owned()), value);
|
||||
}
|
||||
|
||||
URLPatternComponentResult {
|
||||
input: Some(component_result.input.into()),
|
||||
groups: Some(groups),
|
||||
}
|
||||
}
|
||||
|
||||
fn map_urlpatterninit(pattern_init: urlpattern::quirks::UrlPatternInit) -> URLPatternInit {
|
||||
URLPatternInit {
|
||||
baseURL: pattern_init.base_url.map(USVString),
|
||||
protocol: pattern_init.protocol.map(USVString),
|
||||
username: pattern_init.username.map(USVString),
|
||||
password: pattern_init.password.map(USVString),
|
||||
hostname: pattern_init.hostname.map(USVString),
|
||||
port: pattern_init.port.map(USVString),
|
||||
pathname: pattern_init.pathname.map(USVString),
|
||||
search: pattern_init.search.map(USVString),
|
||||
hash: pattern_init.hash.map(USVString),
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn map_urlpattern_result(
|
||||
result: urlpattern::UrlPatternResult,
|
||||
(string_or_init, base_url): urlpattern::quirks::Inputs,
|
||||
) -> URLPatternResult {
|
||||
let string_or_init = match string_or_init {
|
||||
urlpattern::quirks::StringOrInit::String(string) => {
|
||||
USVStringOrURLPatternInit::USVString(USVString(string))
|
||||
},
|
||||
urlpattern::quirks::StringOrInit::Init(pattern_init) => {
|
||||
USVStringOrURLPatternInit::URLPatternInit(map_urlpatterninit(pattern_init))
|
||||
},
|
||||
};
|
||||
|
||||
let mut inputs = vec![string_or_init];
|
||||
|
||||
if let Some(base_url) = base_url {
|
||||
inputs.push(USVStringOrURLPatternInit::USVString(USVString(base_url)));
|
||||
}
|
||||
|
||||
URLPatternResult {
|
||||
inputs: Some(inputs),
|
||||
protocol: Some(map_component_result(result.protocol)),
|
||||
username: Some(map_component_result(result.username)),
|
||||
password: Some(map_component_result(result.password)),
|
||||
hostname: Some(map_component_result(result.hostname)),
|
||||
port: Some(map_component_result(result.port)),
|
||||
pathname: Some(map_component_result(result.pathname)),
|
||||
search: Some(map_component_result(result.search)),
|
||||
hash: Some(map_component_result(result.hash)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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)
|
||||
},
|
||||
}))
|
||||
}
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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(),
|
||||
});
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)));
|
||||
|
|
|
@ -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),
|
||||
}
|
||||
|
|
|
@ -26,6 +26,7 @@ use js::jsval::{JSVal, NullValue};
|
|||
use js::rust::wrappers::JS_ParseJSON;
|
||||
use js::rust::{HandleObject, MutableHandleValue};
|
||||
use js::typedarray::{ArrayBuffer, ArrayBufferU8};
|
||||
use net_traits::fetch::headers::extract_mime_type_as_dataurl_mime;
|
||||
use net_traits::http_status::HttpStatus;
|
||||
use net_traits::request::{CredentialsMode, Referrer, RequestBuilder, RequestId, RequestMode};
|
||||
use net_traits::{
|
||||
|
@ -59,7 +60,7 @@ use crate::dom::document::{Document, DocumentSource, HasBrowsingContext, IsHTMLD
|
|||
use crate::dom::event::{Event, EventBubbles, EventCancelable};
|
||||
use crate::dom::eventtarget::EventTarget;
|
||||
use crate::dom::globalscope::GlobalScope;
|
||||
use crate::dom::headers::{extract_mime_type, is_forbidden_request_header};
|
||||
use crate::dom::headers::is_forbidden_request_header;
|
||||
use crate::dom::node::Node;
|
||||
use crate::dom::performanceresourcetiming::InitiatorType;
|
||||
use crate::dom::progressevent::ProgressEvent;
|
||||
|
@ -1324,11 +1325,7 @@ impl XMLHttpRequest {
|
|||
return response;
|
||||
}
|
||||
// Step 2
|
||||
let mime = self
|
||||
.final_mime_type()
|
||||
.as_ref()
|
||||
.map(|m| normalize_type_string(&m.to_string()))
|
||||
.unwrap_or("".to_owned());
|
||||
let mime = normalize_type_string(&self.final_mime_type().to_string());
|
||||
|
||||
// Step 3, 4
|
||||
let bytes = self.response.borrow().to_vec();
|
||||
|
@ -1366,64 +1363,77 @@ impl XMLHttpRequest {
|
|||
return response;
|
||||
}
|
||||
|
||||
// Step 1
|
||||
// Step 1: If xhr’s response’s body is null, then return.
|
||||
if self.response_status.get().is_err() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 2
|
||||
let mime_type = self.final_mime_type();
|
||||
// Step 5.3, 7
|
||||
let charset = self.final_charset().unwrap_or(UTF_8);
|
||||
let temp_doc: DomRoot<Document>;
|
||||
match mime_type {
|
||||
Some(ref mime) if mime.matches(TEXT, HTML) => {
|
||||
// Step 4
|
||||
if self.response_type.get() == XMLHttpRequestResponseType::_empty {
|
||||
return None;
|
||||
} else {
|
||||
// TODO Step 5.2 "If charset is null, prescan the first 1024 bytes of xhr’s received bytes"
|
||||
// Step 5
|
||||
temp_doc = self.document_text_html(can_gc);
|
||||
}
|
||||
},
|
||||
// Step 7
|
||||
None => {
|
||||
temp_doc = self.handle_xml(can_gc);
|
||||
// Not sure it the parser should throw an error for this case
|
||||
// The specification does not indicates this test,
|
||||
// but for now we check the document has no child nodes
|
||||
let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none();
|
||||
if has_no_child_nodes {
|
||||
return None;
|
||||
}
|
||||
},
|
||||
Some(ref mime)
|
||||
if mime.matches(TEXT, XML) ||
|
||||
mime.matches(APPLICATION, XML) ||
|
||||
mime.has_suffix(XML) =>
|
||||
{
|
||||
temp_doc = self.handle_xml(can_gc);
|
||||
// Not sure it the parser should throw an error for this case
|
||||
// The specification does not indicates this test,
|
||||
// but for now we check the document has no child nodes
|
||||
let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none();
|
||||
if has_no_child_nodes {
|
||||
return None;
|
||||
}
|
||||
},
|
||||
// Step 3
|
||||
_ => {
|
||||
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 xhr’s response type is the empty string and finalMIME is an HTML MIME
|
||||
// type, then return.
|
||||
let charset;
|
||||
let temp_doc;
|
||||
if final_mime.matches(TEXT, HTML) {
|
||||
if self.response_type.get() == XMLHttpRequestResponseType::_empty {
|
||||
return None;
|
||||
}
|
||||
|
||||
// Step 5: If finalMIME is an HTML MIME type, then:
|
||||
// Step 5.1: Let charset be the result of get a final encoding for xhr.
|
||||
// Step 5.2: If charset is null, prescan the first 1024 bytes of xhr’s received bytes
|
||||
// and if that does not terminate unsuccessfully then let charset be the return value.
|
||||
// TODO: This isn't happening right now.
|
||||
// Step 5.3. If charset is null, then set charset to UTF-8.
|
||||
charset = Some(self.final_charset().unwrap_or(UTF_8));
|
||||
|
||||
// Step 5.4: Let document be a document that represents the result parsing xhr’s
|
||||
// received bytes following the rules set forth in the HTML Standard for an HTML parser
|
||||
// with scripting disabled and a known definite encoding charset. [HTML]
|
||||
temp_doc = self.document_text_html(can_gc);
|
||||
} else {
|
||||
assert!(is_xml_mime_type);
|
||||
|
||||
// Step 6: Otherwise, let document be a document that represents the result of running
|
||||
// the XML parser with XML scripting support disabled on xhr’s received bytes. If that
|
||||
// fails (unsupported character encoding, namespace well-formedness error, etc.), then
|
||||
// return null. [HTML]
|
||||
//
|
||||
// TODO: The spec seems to suggest the charset should come from the XML parser here.
|
||||
temp_doc = self.handle_xml(can_gc);
|
||||
charset = self.final_charset();
|
||||
|
||||
// Not sure it the parser should throw an error for this case
|
||||
// The specification does not indicates this test,
|
||||
// but for now we check the document has no child nodes
|
||||
let has_no_child_nodes = temp_doc.upcast::<Node>().children().next().is_none();
|
||||
if has_no_child_nodes {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
|
||||
// Step 7: If charset is null, then set charset to UTF-8.
|
||||
let charset = charset.unwrap_or(UTF_8);
|
||||
|
||||
// Step 8: Set document’s encoding to charset.
|
||||
temp_doc.set_encoding(charset);
|
||||
|
||||
// Step 9 to 11
|
||||
// Done by handle_text_html and handle_xml
|
||||
// Step 9: Set document’s content type to finalMIME.
|
||||
// Step 10: Set document’s URL to xhr’s response’s URL.
|
||||
// Step 11: Set document’s origin to xhr’s relevant settings object’s origin.
|
||||
//
|
||||
// Done by `handle_text_html()` and `handle_xml()`.
|
||||
|
||||
// Step 12
|
||||
// Step 12: Set xhr’s response object to document.
|
||||
self.response_xml.set(Some(&temp_doc));
|
||||
self.response_xml.get()
|
||||
}
|
||||
|
@ -1507,7 +1517,7 @@ impl XMLHttpRequest {
|
|||
Ok(parsed) => Some(parsed),
|
||||
Err(_) => None, // Step 7
|
||||
};
|
||||
let content_type = self.final_mime_type();
|
||||
let content_type = Some(self.final_mime_type());
|
||||
Document::new(
|
||||
win,
|
||||
HasBrowsingContext::No,
|
||||
|
@ -1598,14 +1608,16 @@ impl XMLHttpRequest {
|
|||
// 3. If responseMIME’s parameters["charset"] exists, then set label to it.
|
||||
let response_charset = self
|
||||
.response_mime_type()
|
||||
.and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string()));
|
||||
.get_parameter(CHARSET)
|
||||
.map(ToString::to_string);
|
||||
|
||||
// 4. If xhr’s override MIME type’s parameters["charset"] exists, then set label to it.
|
||||
let override_charset = self
|
||||
.override_mime_type
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.and_then(|mime| mime.get_parameter(CHARSET).map(|c| c.to_string()));
|
||||
.and_then(|mime| mime.get_parameter(CHARSET))
|
||||
.map(ToString::to_string);
|
||||
|
||||
// 5. If label is null, then return null.
|
||||
// 6. Let encoding be the result of getting an encoding from label.
|
||||
|
@ -1617,23 +1629,22 @@ impl XMLHttpRequest {
|
|||
}
|
||||
|
||||
/// <https://xhr.spec.whatwg.org/#response-mime-type>
|
||||
fn response_mime_type(&self) -> Option<Mime> {
|
||||
return extract_mime_type(&self.response_headers.borrow())
|
||||
.and_then(|mime_as_bytes| {
|
||||
String::from_utf8(mime_as_bytes)
|
||||
.unwrap_or_default()
|
||||
.parse()
|
||||
.ok()
|
||||
})
|
||||
.or(Some(Mime::new(TEXT, XML)));
|
||||
fn response_mime_type(&self) -> Mime {
|
||||
// 1. Let mimeType be the result of extracting a MIME type from xhr’s response’s
|
||||
// header list.
|
||||
// 2. If mimeType is failure, then set mimeType to text/xml.
|
||||
// 3. Return mimeType.
|
||||
extract_mime_type_as_dataurl_mime(&self.response_headers.borrow())
|
||||
.unwrap_or_else(|| Mime::new(TEXT, XML))
|
||||
}
|
||||
|
||||
/// <https://xhr.spec.whatwg.org/#final-mime-type>
|
||||
fn final_mime_type(&self) -> Option<Mime> {
|
||||
match *self.override_mime_type.borrow() {
|
||||
Some(ref override_mime) => Some(override_mime.clone()),
|
||||
None => self.response_mime_type(),
|
||||
}
|
||||
fn final_mime_type(&self) -> Mime {
|
||||
self.override_mime_type
|
||||
.borrow()
|
||||
.as_ref()
|
||||
.map(MimeExt::clone)
|
||||
.unwrap_or_else(|| self.response_mime_type())
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue